1 下载代码并build
需要 java8以上 环境
$ git clone https://github.com/apache/calcite.git
$ cd calcite
$ ./gradlew build
$ cd example/csv
几种build方式,具体可以上官网查询(https://calcite.apache.org/docs/howto.html#running-tests):
$ ./gradlew assemble # build the artifacts
$ ./gradlew build -x test # build the artifacts, verify code style, skip tests
$ ./gradlew check # verify code style, execute tests
$ ./gradlew test # execute tests
$ ./gradlew style # update code formatting (for auto-correctable cases) and verify style
$ ./gradlew spotlessCheck checkstyleAll # report code style violations
如果下载的源码里有pom文件,也可以用maven编译:
mvn install -DskipTests -Dcheckstyle.skip=true
2 查询
官方文档通过sqlline查询的案例,通过执行sqlline命令实现对csv文件的查询,详情见官网:
https://calcite.apache.org/docs/tutorial.html
在官网的例子中,利用sqlline指定json文件设置数据库的元数据:
$ ./sqlline
sqlline> !connect jdbc:calcite:model=src/test/resources/model.json admin admin
通过 !tables 可以显示当前的所有表信息:
> !table
+-----------+-------------+------------+------------+---------+----------+------------+-----------+---------------------------+----------------+
| TABLE_CAT | TABLE_SCHEM | TABLE_NAME | TABLE_TYPE | REMARKS | TYPE_CAT | TYPE_SCHEM | TYPE_NAME | SELF_REFERENCING_COL_NAME | REF_GENERATION |
+-----------+-------------+------------+------------+---------+----------+------------+-----------+---------------------------+----------------+
| | SALES | DEPTS | TABLE | | | | | | |
| | SALES | EMPS | TABLE | | | | | | |
| | SALES | SDEPTS | TABLE | | | | | | |
| | metadata | COLUMNS | SYSTEM TABLE | | | | | | |
| | metadata | TABLES | SYSTEM TABLE | | | | | | |
+-----------+-------------+------------+------------+---------+----------+------------+-----------+---------------------------+----------------+
可以发现有5个表,COLUMNS
和TABLES
两个表可以不用考虑,我们看到在schema SALES
中,有三张表分别是:DEPTS,EMPS,SDEPTS
。
我们执行一个复杂点的查询语句试试:
> SELECT d.name, COUNT(*)
> FROM emps AS e JOIN depts AS d ON e.deptno = d.deptno
> GROUP BY d.name;
+------+---------------------+
| NAME | EXPR$1 |
+------+---------------------+
| Sales | 1 |
| Marketing | 2 |
+------+---------------------+
3 自定义schema
那么calcite是如何发现这些table的呢?
我们先看我们指定的model.json里有什么:
{
"version": "1.0",
"defaultSchema": "SALES",
"schemas": [
{
"name": "SALES",
"type": "custom",
"factory": "org.apache.calcite.adapter.csv.CsvSchemaFactory",
"operand": {
"directory": "sales"
}
}
]
}
里面声明了schema的名字SCALES
,声明了创建schema的工厂类org.apache.calcite.adapter.csv.CsvSchemaFactory
,operand
里面是一些自定义参数,在这里指定了csv文件的路径。
有了配置文件,还需要对配置文件进行解析,有兴趣的可以看一下这部分的源码,可以从CsvSchemaFactory
类里一层层往下看。
配置文件中还可以通过tables
的关键字指定自定义的table:
{
version: '1.0',
defaultSchema: 'SALES',
schemas: [
{
name: 'SALES',
type: 'custom',
factory: 'org.apache.calcite.adapter.csv.CsvSchemaFactory',
operand: {
directory: 'target/test-classes/sales'
},
tables: [
{
name: 'FEMALE_EMPS',
type: 'view',
sql: 'SELECT * FROM emps WHERE gender = \'F\''
}
]
}
]
}
以上定义了一个view,view没有物化的数据,可以通过执行sql得到view。
看了这么多我们接下来再回顾一下这几个重要的概念:
- Schema,是table和function的名称空间,它是一个可嵌套的结构,Schema还可以有subSchema,理论上可以无限嵌套,但一般不会这么做。Schema可以理解成Database,Database下面有table,这样就和传统数据库的概念联系起来了,在Calcite中,顶层的Schema是root,自定义的Schema是root的subSchema,同时还可以设置defaultSchema,类似我们使用数据库时,使用use database命令以后就不用再输入database名字前缀。
- Table,就很好理解了,就是数据库中的表。在table描述了字段名以及相应的类型、表的统计信息,例如表有多少条记录等等,这里先不展开讲。另外重要的是数据文件的存储以及如何扫描读取数据文件。
再来看上面的model文件,就比较清晰了。它描述了在数据库中有多少个Schema、每个Schema如何创建以及默认的Schema,这里的Schema可以理解成database。defaultSchema属性设置默认Schema,schemas是数组类型,每一项代表一个Schema描述信息,在描述信息中有一个关键的属性factory,它是创建Schema的工厂类,在这个例子中factory是org.apache.calcite.adapter.csv.CsvSchemaFactory,它实现了SchemaFactory接口。
要自实现具有全表扫描功能的简单数据库需要做如下几步:
- 自定义 SchemaFactory
- 自定义 Schema
- 自定义 Table
- 自定义 Enumerator
3 自定义table
上面章节介绍的是利用自定义的schema工厂创建table的方式,另外还有一种自定义table工厂的方式。
{
version: '1.0',
defaultSchema: 'CUSTOM_TABLE',
schemas: [
{
name: 'CUSTOM_TABLE',
tables: [
{
name: 'EMPS',
type: 'custom',
factory: 'org.apache.calcite.adapter.csv.CsvTableFactory',
operand: {
file: 'target/test-classes/sales/EMPS.csv.gz',
flavor: "scannable"
}
}
]
}
]
}
这种方式直接调用json中指定的CsvTableFactory创建一个table,不管是自定义schema还是自定义table,最终都会实现table接口。但是不同的是自定义table不需要去scan数据从而获取数据的字段信息。(CsvTableFactory creates a CsvScannableTable, just as CsvSchema does, but the table implementation does not scan the filesystem for .csv files.)
自定义table需要使用者做更多的工作,因为需要针对每个数据表进行配置,但是功能相对自定义schema更加强大,可以针对每个表做个性化配置。
4 定义rules
calcite 通过添加 rule 的方式对查询优化进行扩展, rule 通过指定在查询树中寻找特定的 pattern (比如: 一个挂在 table 之上的 projection), 并将匹配的节点转换为一组全新的节点, 来尝试优化.
calcite 中的 rule 也完全可扩展的, 通过定义规则就像前面定义 schema/table 一样. 直接看 demo 中的例子:
https://calcite.apache.org/docs/tutorial.html
https://zhuanlan.zhihu.com/p/53725382
5 几种table接口
5.1 ScannableTable
a simple implementation of Table, using the ScannableTable interface, that enumerates all rows directly
这种方式基本不会用,原因是查询数据库的时候没有任何条件限制,默认会先把全部数据拉到内存,然后再根据filter条件在内存中过滤。
使用方式:实现Enumerable scan(DataContext root);
,该函数返回Enumerable对象,通过该对象可以一行行的获取这个Table的全部数据。
5.2 FilterableTable
a more advanced implementation that implements FilterableTable, and can filter out rows according to simple predicates
初级用法,我们能拿到filter条件,即能再查询底层DB时进行一部分的数据过滤,一般开始介入calcite可以用这种方式(translatable方式学习成本较高)。
使用方式:实现Enumerable scan(DataContext root, List filters )
。
如果当前类型的“表”能够支持我们自己写代码优化这个过滤器,那么执行完自定义优化器,可以把该过滤条件从集合中移除,否则,就让calcite来过滤,简言之就是,如果我们不处理List filters
,Calcite也会根据自己的规则在内存中过滤,无非就是对于查询引擎来说查的数据多了,但如果我们可以写查询引擎支持的过滤器(比如写一些hbase、es的filter),这样在查的时候引擎本身就能先过滤掉多余数据,更加优化。提示,即使走了我们的查询过滤条件,可以再让calcite帮我们过滤一次,比较灵活。
5.3 TranslatableTable
advanced implementation of Table, using TranslatableTable, that translates to relational operators using planner rules.
高阶用法,有些查询用上面的方式都支持不了或支持的不好,比如join、聚合、或对于select的字段筛选等,需要用这种方式来支持,好处是可以支持更全的功能,代价是所有的解析都要自己写,“承上启下”,上面解析sql的各个部件,下面要根据不同的DB(es\mysql\drudi..)来写不同的语法查询。
当使用ScannableTable的时候,我们只需要实现函数Enumerable scan(DataContext root);
,该函数返回Enumerable对象,通过该对象可以一行行的获取这个Table的全部数据(也就意味着每次的查询都是扫描这个表的数据,我们干涉不了任何执行过程);当使用FilterableTable的时候,我们需要实现函数Enumerable scan(DataContext root, List filters );
参数中多了filters数组,这个数据包含了针对这个表的过滤条件,这样我们根据过滤条件只返回过滤之后的行,减少上层进行其它运算的数据集;当使用TranslatableTable的时候,我们需要实现RelNode toRel( RelOptTable.ToRelContext context, RelOptTable relOptTable);
,该函数可以让我们根据上下文自己定义表扫描的物理执行计划,至于为什么不在返回一个Enumerable对象了,因为上面两种其实使用的是默认的执行计划,转换成EnumerableTableAccessRel算子,通过TranslatableTable我们可以实现自定义的算子,以及执行一些其他的rule,Kylin就是使用这个类型的Table实现查询。
参考资料:
https://www.jianshu.com/p/c698673e2c84
https://www.infoq.cn/article/new-big-data-hadoop-query-engine-apache-calcite/
https://calcite.apache.org/docs/tutorial.html
https://zhuanlan.zhihu.com/p/53725382
http://www.programmersought.com/article/9029515743/
https://www.cnblogs.com/wcgstudy/p/11795952.html
网友评论