虽然之前也在Github上尝试提过一些PR,但都是一些doc、typo等类型的入门实践。真正算得上有一定实质性工作,要数最近在Apache IoTDB上提交的一个功能PR.如果大家对开源感兴趣的话,可以看我的一篇关于开源介绍的文章(开源介绍).如果大家对文中提到的比如Github、PR等词汇不熟悉建议可以先去学习一下Git,注册使用Github网站。
首先可以根据自身情况选择一个开源项目进行着手,熟悉项目,在社区学习,先做些简单issue,慢慢推进,默默发育,升级打怪,最后成为Committer甚至PMC,可以hold住功能模块开发,重要bug修复,架构设计等等~。由于本人对于Java语言相对熟悉,同时因为某些原因对于时序数据库有一定了解,经过一番搜索最终选择了Apache IoTDB.
好了~废话不多说,回归正传。
一般来说,一个质量比较好的开源项目,其代码仓库的README文件或者官方文档会有比较详细的开发指南。Apache IoTDB也不例外,下面是我列的几个在这当中对我帮助比较大的几个文档。
用户指南: 用户指南
代码贡献指南:代码贡献指南、如何提交PR
设计实现文档: 设计实现文档
我在Aapche IoTDB的JIRA上领取了issue,这个issue是关于一个语法功能上的扩展。
原先IoTDB在返回结果集上进行空值过滤仅支持比较粗粒度的without null all(即针对结果集中的如果所有列都为null则过滤该行)和without null any(即针对结果集中的如果所有列都为null则过滤该行)。
现在需要支持without null子句针对某几列生效,原先默认是对一行中所有列生效。
由于该功能总体上可以归结为查询功能的一个小模块,所以我首先先要对IoTDB在执行一条查询语句的总体流程有一个大致的全局概念,后面再慢慢深挖without null子句在其中的一些处理逻辑。其总体的处理逻辑如下:
本着以目标为导向,开始梳理原有代码中without null子句处理逻辑位置,从中获取需要改动的位置、边界以及如何改动的灵感。在原有代码中对without null语句的处理有四处,如下所示:
1. UDTFAlignByTimeDataSet类的fillBuffer()方法
public TSQueryDataSet fillBuffer(int fetchSize, WatermarkEncoder encoder) {
、、、
if (withoutAllNull || withoutAnyNull) {
int nullFieldsCnt = 0;
for (LayerPointReader reader : transformers) {
if (!reader.next() || reader.currentTime() != minTime || reader.isCurrentNull()) {
nullFieldsCnt++;
}
}
、、、
}
2. RawQueryDataSetWithoutValueFilter类的fillBuffer()方法
public TSQueryDataSet fillBuffer(int fetchSize, WatermarkEncoder encoder) {
、、、
if (withoutAnyNull && filterRowRecord(seriesNum, minTime)) {
continue;
}
、、、
}
3. QueryDataSetUtils类的convertQueryDataSetByFetchSize()方法
public static TSQueryDataSet convertQueryDataSetByFetchSize() {
、、、
// filter rows whose columns are null according to the rule
if ((queryDataSet.isWithoutAllNull() && rowRecord.isAllNull())
|| (queryDataSet.isWithoutAnyNull() && rowRecord.hasNullField())) {
// if the current RowRecord doesn't satisfy, we should also decrease AlreadyReturnedRowNum
queryDataSet.decreaseAlreadyReturnedRowNum();
i--;
continue;
}
、、、
}
4. QueryDataSet类的hasNext()方法
public boolean hasNext() throws IOException {
// proceed to the OFFSET row by skipping rows
while (rowOffset > 0) {
if (hasNextWithoutConstraint()) {
RowRecord rowRecord = nextWithoutConstraint(); // DO NOT use next()
// filter rows whose columns are null according to the rule
if ((withoutAllNull && rowRecord.isAllNull())
|| (withoutAnyNull && rowRecord.hasNullField())) {
continue;
}
rowOffset--;
} else {
return false;
}
}
// make sure within the LIMIT constraint if exists
if (rowLimit > 0 && alreadyReturnedRowNum >= rowLimit) {
return false;
}
return hasNextWithoutConstraint();
}
通过前面对于without null处理逻辑的梳理,不难发现都是在对结果集的每一行进行判断是不是所有列都为null或者存在null.
所以其实只要我们在这些位置如果将判断null的范围限定到without null指定的那些列,至于其他列直接忽略就行。于是一个很朴素的想法就出现了,由于它每一行是按数组索引的形式遍历每一列的值,有的不包括列名信息,如果我能找到without null指定的列名在遍历的结果集的每一行的与其相应的索引的映射关系就可以了。
所以在到达上述的四个位置时,我要维护得到一个WithoutNullColumnIndexSet集合,在遍历每一个RowRecord时去判断其为null列的索引是否在WithoutNullColumnIndexSet中即可。
1. select语句后面跟的列名与without null指定的列名并不是那么简单的字符串相等关系,而且还有可能有别名信息,以及一些整合名(与from后的路径拼接、group by level等);一个比较典型的例子就是select * from xxx without null (s1);
解决:从Antlr语法层面,select 后面跟的column是一个expression,而刚好without null后面跟的column也是一个expression。在原有逻辑中已经包含了对解析到的select的column的表达式的一些transform(比如去*、拼接路径等等),我再处理without null可以复用这段逻辑跟着进行变换。这样在其内部处理表示中就会是一个equals的关系,这样就可以很方便地进行元数据校验(without null里指定的列是否与select的column名相一致),以及填充WithoutNullColumnIndexSet,当然这里要注意别名的考虑。
2. 原有IoTDB的Query类型众多,而且处理逻辑并不是完全统一,要考虑一些特殊类型的查询,比如AlignByDevice、对齐时间序列等
在原有的正常的AlignByTime查询中,存在一个pathToIndex变量可以帮助我更快形成WithoutNullColumnIndexSet,但在AlignByDevice中这个pathToIndex是空的,它有另一套逻辑去维护结果集列名与设备名之间的关系。
而对齐时间序列的数据类型,并不想原始数据类型,它是一个类似数组的结构,在原有RowRecord中它只占一列但其实其内部会展开成多列,原来逻辑只会告诉你它内部包含的列是否全为null或者部分为null。如果without null指定其中的某几列就会出现不正确的问题。针对这种情况在维护index信息时就不能使用外部的那个只占一列信息,而应该去遍历其内部包含的列名,从而得到真正的正确的WithoutNullColumnIndexSet。
3. 原有代码中,without null存在一个bug
提bug issue,先把这个bug解决再继续接下来的开发。
在开发完成,编写自测通过、编写相应的用户使用文档后,根据上面的提交PR指南,提了PR,功能PR是有两位社区人员进行Review,根据他们提出的一些建议与问题,前前后后又改了几天,最后终于Merge了~。
本次的实践,算是对自己开源入门的一次实战。在这当中,自己熟悉了整个的一套开源开发流程,熟悉了Apache IoTDB的部分源码,同时对自己代码阅读能力有一定提升,促进了自己的代码规范,总而言之还是好处多多。当然这个Merge了不是说没有问题,他内部可能还会要进行测试,没准哪天就会通知我修bug了。好了,今天就说到这,希望自己可以坚持下去,持续做好开源贡献!