阿卡姆大数据科普报告——Calcite(1)

0: jdbc:calcite:model=target/test-classes/mod> !describe

Usage: describe <table name>

0: jdbc:calcite:model=target/test-classes/mod> !describe DEPTS

±----------±------------±-----------±------------±----------±----------±------------±--------------±---------------±---------------±---------±--------±-----------±--------------±-----------------±------------------±-----------------±------------±--------------±-------------±------------+

| TABLE_CAT | TABLE_SCHEM | TABLE_NAME | COLUMN_NAME | DATA_TYPE | TYPE_NAME | COLUMN_SIZE | BUFFER_LENGTH | DECIMAL_DIGITS | NUM_PREC_RADIX | NULLABLE | REMARKS | COLUMN_DEF | SQL_DATA_TYPE | SQL_DATETIME_SUB | CHAR_OCTET_LENGTH | ORDINAL_POSITION | IS_NULLABLE | SCOPE_CATALOG | SCOPE_SCHEMA | SCOPE_TABLE |

±----------±------------±-----------±------------±----------±----------±------------±--------------±---------------±---------------±---------±--------±-----------±--------------±-----------------±------------------±-----------------±------------±--------------±-------------±------------+

|           | SALES       | DEPTS      | DEPTNO      | 4         | INTEGER   | -1          | null          | null           | 10             | 1        |         |            | null          | null             | -1                | 1                | YES         |               |              |             |

|           | SALES       | DEPTS      | NAME        | 12        | VARCHAR CHARACTER SET “ISO-8859-1” COLLATE “ISO-8859-1 e n U S en_US enUSprimary” | -1          | null          | null           | 10             | 1        |         |            | null          | null             | -1                | 2               |

±----------±------------±-----------±------------±----------±----------±------------±--------------±---------------±---------------±---------±--------±-----------±--------------±-----------------±------------------±-----------------±------------±--------------±-------------±------------+

你能看到,在执行!tables的时候有5个表,表EMPSDEPTSHOBBIESSALES库(schema)里,表COLUMNSTABLES在系统元数据库(system metadata schema)里。系统表总是在Calcite里显示,但其他表是由库(schema)的实现来指定的,在本例中,EMPSDEPTS表来源于target/test-classes路径下的EMPS.csvDEPTS.csv

让我们来执行一些查询,来展示Calcite的全SQL功能,首先表检索:

sqlline> SELECT * FROM emps;

±-------±-------±--------±--------±---------------±-------±------±–+

| EMPNO  |  NAME  | DEPTNO  | GENDER  |      CITY      | EMPID  |  AGE  | S |

±-------±-------±--------±--------±---------------±-------±------±–+

| 100    | Fred   | 10      |         |                | 30     | 25    | t |

| 110    | Eric   | 20      | M       | San Francisco  | 3      | 80    | n |

| 110    | John   | 40      | M       | Vancouver      | 2      | null  | f |

| 120    | Wilma  | 20      | F       |                | 1      | 5     | n |

| 130    | Alice  | 40      | F       | Vancouver      | 2      | null  | f |

±-------±-------±--------±--------±---------------±-------±------±–+

接下来是表连接和分组聚合查询:

sqlline> 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       |

±-----------±--------+

最后,一个计算操作返回一个单行记录,也可以通过这种简便的方法来测试表达式和SQL函数

sqlline> VALUES CHAR_LENGTH('Hello, ’ || ‘world!’);

±--------+

| EXPR$0  |

±--------+

| 13      |

±--------+

Calcite还包含很多SQL特性,这里就不一一列举了。

Schema探索


那么Calcite是如何发现表的呢?事实上Calcite的核心是并不能理解CSV文件的(作为一个“没有存储层的databse”,Calcite是了解任何文件格式),之所以Calcite能读取上文中的元数据,是因为在calcite-example-csv里我们撰写了相关代码。

在执行链里包含着很多步骤。首先我们定义一个可以被库工厂加载的模型文件(we define a schema based on a schema factory class in a model file.)。然后库工厂会加载成数据库并创建许多表,每一个表都需要知道自己如何加载CSV中的数据。最后Calcite解析完查询并将查询计划映射到这几个表上时,Calcite会在查询执行时触发这些表去读取数据。接下来我们更深入地解析其中的细节步骤。

举个栗子(a model in JSON format):

{

version: ‘1.0’,

defaultSchema: ‘SALES’,

schemas: [

{

name: ‘SALES’,

type: ‘custom’,

factory: ‘org.apache.calcite.adapter.csv.CsvSchemaFactory’,

operand: {

directory: ‘target/test-classes/sales’

}

}

]

}

这个模型文件定义了一个库(schema)叫SALES,这个库是由一个插件类(a plugin class)支持的,org.apache.calcite.adapter.csv.CsvSchemaFactory这个是calcite-example-csv工程里interface SchemaFactory的一个实现。它的create方法将一个schema实例化了,将model file中的directory作为参数传递过去了。

public Schema create(SchemaPlus parentSchema, String name,

Map<String, Object> operand) {

String directory = (String) operand.get(“directory”);

String flavorName = (String) operand.get(“flavor”);

CsvTable.Flavor flavor;

if (flavorName == null) {

flavor = CsvTable.Flavor.SCANNABLE;

} else {

flavor = CsvTable.Flavor.valueOf(flavorName.toUpperCase());

}

return new CsvSchema(

new File(directory),

flavor);

}

根据模型(model)描述,库工程(schema factory)实例化了一个名为’SALES’的简单库(schema)。这个库(schema)是org.apache.calcite.adapter.csv.CsvSchema的实例并且实现了Calcite里的接口Schema。

一个库(schema)的主要职责就是创建一个表(table)的列表(库的职责还包括子库列表、函数列表等,但是calcite-example-csv项目里并没有包含他们)。这些表实现了Calcite的Table接口。CsvSchema创建的表全部是CsvTable和他的子类的实例。

下面是CsvSchema的一些相关代码,对基类AbstractSchema中的getTableMap()方法进行了重载。

protected Map<String, Table> getTableMap() {

// Look for files in the directory ending in “.csv”, “.csv.gz”, “.json”,

// “.json.gz”.

File[] files = directoryFile.listFiles(

new FilenameFilter() {

public boolean accept(File dir, String name) {

final String nameSansGz = trim(name, “.gz”);

return nameSansGz.endsWith(“.csv”)

|| nameSansGz.endsWith(“.json”);

}

});

if (files == null) {

System.out.println(“directory " + directoryFile + " not found”);

files = new File[0];

}

// Build a map from table name to table; each file becomes a table.

final ImmutableMap.Builder<String, Table> builder = ImmutableMap.builder();

for (File file : files) {

String tableName = trim(file.getName(), “.gz”);

final String tableNameSansJson = trimOrNull(tableName, “.json”);

if (tableNameSansJson != null) {

JsonTable table = new JsonTable(file);

builder.put(tableNameSansJson, table);

continue;

}

tableName = trim(tableName, “.csv”);

final Table table = createTable(file);

builder.put(tableName, table);

}

return builder.build();

}

/** Creates different sub-type of table based on the “flavor” attribute. */

private Table createTable(File file) {

switch (flavor) {

case TRANSLATABLE:

return new CsvTranslatableTable(file, null);

case SCANNABLE:

return new CsvScannableTable(file, null);

case FILTERABLE:

return new CsvFilterableTable(file, null);

default:

throw new AssertionError("Unknown flavor " + flavor);

}

}

schema会扫描指定路径,找到所有以.csv/结尾的文件。在本例中,指定路径是 target/test-classes/sales,路径中包含文件’EMPS.csv’和’DEPTS.csv’,这两个文件会转换成表EMPSDEPTS

表和视图


值得注意的是,我们在模型文件(model)里并不需要定义任何表,schema会自动创建的。 你可以额外扩展一些表(tables),使用这个schema中其他表的属性。

让我们看看如何创建一个重要且常用的一种表——视图。

在写一个查询时,视图就相当于一个table,但它不存储数据。它通过执行查询来生成数据。在查询转换为执行计划时,视图会被展开,所以查询执行器可以执行一些优化策略,例如移除一些SELECT子句中存在但在最终结果中没有用到的表达式。

举个栗子:

{

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’’

}

]

}

]

}

栗子中type:view这一行将FEMALE_EMPS定义为一个视图,而不是常规表或者是自定义表。注意通常在JSON文件里,定义view的时候,需要对单引号进行转义。

用JSON来定义长字符串易用性不太高,因此Calcite支持了一种替代语法。如果你的视图定义中有长SQL语句,可以使用多行来定义一个长字符串:

{

name: ‘FEMALE_EMPS’,

type: ‘view’,

sql: [

‘SELECT * FROM emps’,

‘WHERE gender = ‘F’’

]

}

现在我们定义了一个视图(view),我们可以再查询中使用它就像使用普通表(table)一样:

sqlline> SELECT e.name, d.name FROM female_emps AS e JOIN depts AS d on e.deptno = d.deptno;

±-------±-----------+

|  NAME  |    NAME    |

±-------±-----------+

| Wilma  | Marketing  |

±-------±-----------+

自定义表


自定义表是由用户定义的代码来实现定义的,不需要额外自定义schema

继续举个栗子model-with-custom-table.json

{

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”

}

}

]

}

]

}

我们可以一样来查询表数据:

sqlline> !connect jdbc:calcite:model=target/test-classes/model-with-custom-table.json admin admin

sqlline> SELECT empno, name FROM custom_table.emps;

±-------±-------+

| EMPNO  |  NAME  |

±-------±-------+

| 100    | Fred   |

| 110    | Eric   |

| 110    | John   |

| 120    | Wilma  |

| 130    | Alice  |

±-------±-------+

上面的schema是通用格式,包含了一个自定义表org.apache.calcite.adapter.csv.CsvTableFactory,这个类实现了Calcite中的TableFactory接口。它在create方法里实例化了CsvScannableTable,将model文件中的file参数传递过去。

public CsvTable create(SchemaPlus schema, String name,

Map<String, Object> map, RelDataType rowType) {

String fileName = (String) map.get(“file”);

final File file = new File(fileName);

final RelProtoDataType protoRowType =

rowType != null ? RelDataTypeImpl.proto(rowType) : null;

return new CsvScannableTable(file, protoRowType);

}

通常做法是实现一个自定义表(a custom table)来替代实现一个自定义库(a custom schema)。两个方法最后都会创建一个Table接口的实例,但是自定义表无需重新实现元数据(metadata)获取部分。(CsvTableFactoryCsvSchema一样,都创建了CsvScannableTable,但是自定表实现就不需要实现在文件系统里检索.csv文件。)

自定义表(table)要求开发者在model上执有多操作(开发者需要在model文件中显式指定每一个table和它对应的文件),同时也提供给了开发者更多的控制选项(例如,为每一个table提供不同参数)。

模型中的注释


注释使用语法 /* ... */ 和 //:

{

version: ‘1.0’,

/* 多行

注释 */

defaultSchema: ‘CUSTOM_TABLE’,

// 单行注释

schemas: [

]

}

(注释不是标准JSON格式,但不会造成影响。)

使用查询计划来优化查询


目前来看表(table)实现和查询都没有问题,因为我们的表中并没有大量的数据。但如果你的自定义表(table)有,例如,有100列和100万行数据,你肯定希望用户在每次查询过程中不检索全量数据。你会希望Calcite通过适配器来进行衡量,并找到一个更有效的方法来访问数据。

这个衡量过程是一个简单的查询优化格式。Calcite是通过添加执行器规则(planner rules)来支持查询优化的。执行器规则(planner rules)通过在查询解析中寻找指定模式(patterns)(例如在某个项目中匹配到某种类型的table是生效),使用实现优化后的新节点替换寻找到节点。

执行器规则(planner rules)也是可扩展的,就像schemastables一样。所以如果你有一些存储下来的数据希望通过SQL访问它,首先需要定义一个自定义表或是schema,然后再去定义一些能使数据访问高效的规则。

为了查看效果,我们可以使用一个执行器规则(planner rules)来访问一个CSV文件中的某些子列集合。我们可以在两个相似的schema中执行同样的查询:

sqlline> !connect jdbc:calcite:model=target/test-classes/model.json admin admin

sqlline> explain plan for select name from emps;

±----------------------------------------------------+

| PLAN                                                |

±----------------------------------------------------+

| EnumerableCalcRel(expr#0…9=[{inputs}], NAME=[$t1]) |

|   EnumerableTableScan(table=[[SALES, EMPS]])        |

±----------------------------------------------------+

sqlline> !connect jdbc:calcite:model=target/test-classes/smart.json admin admin

sqlline> explain plan for select name from emps;

±----------------------------------------------------+

| PLAN                                                |

±----------------------------------------------------+

| EnumerableCalcRel(expr#0…9=[{inputs}], NAME=[$t1]) |

|   CsvTableScan(table=[[SALES, EMPS]])               |

±----------------------------------------------------+

这两个计划到底有什么不同呢?通过对比可以发现,在smart.json里只多了一行:

flavor: “translatable”

这会让CsvSchema携带参数参数falvor = TRANSLATABLE 参数进行创建,并且它的createTable方法会创建CsvTranslatableTable,而不是CsvScannableTable.

CsvTranslatableTable实现了TranslatableTable.toRel()方法来创建CsvTableScan. 扫描表(Table scan)操作是查询执行树中的叶子节点,默认实现方式是EnumerableTableScan,但我们构造了一种不同的的子类型来让规则生效。

下面是完整的代码:

public class CsvProjectTableScanRule extends RelOptRule {

public static final CsvProjectTableScanRule INSTANCE =

new CsvProjectTableScanRule();

private CsvProjectTableScanRule() {

super(

operand(Project.class,

operand(CsvTableScan.class, none())),

“CsvProjectTableScanRule”);

}

@Override

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

默认实现方式是EnumerableTableScan,但我们构造了一种不同的的子类型来让规则生效。

下面是完整的代码:

public class CsvProjectTableScanRule extends RelOptRule {

public static final CsvProjectTableScanRule INSTANCE =

new CsvProjectTableScanRule();

private CsvProjectTableScanRule() {

super(

operand(Project.class,

operand(CsvTableScan.class, none())),

“CsvProjectTableScanRule”);

}

@Override

[外链图片转存中…(img-jHveXRW2-1714550907534)]
[外链图片转存中…(img-MA8WuMQ6-1714550907534)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 22
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值