最新阿卡姆大数据科普报告——Calcite(2)

CSV适配器可以作为抛砖引玉的模板套用到其他数据格式上。尽管他代码量不多,但是麻雀虽小五脏俱全,重要原理都包含其中:

  1. 使用SchemaFactorySchema interfaces来自定义schema

  2. 使用固定格式的JSON文件来(a model JSON file模型文件)声明数据库(schemas)

  3. 使用固定格式的JSON文件来(a model JSON file模型文件)声明视图(views)

  4. 使用Table interface来自定义表(Table)

  5. 确定表格的记录类型

  6. 使用ScannableTable interface来实现一个简单的表(Table),来枚举所有行(rows)

  7. 进阶实现FilterableTable,可以根据条件(simple predicates)来过滤数据

  8. 表的进阶实现TranslatableTable,将执行计划翻译成关系运算(translates to relational operators using planner rules)

下载和编译


需要Java环境(1.7及以上版本,推荐1.8),git以及maven(3.2.1及以上版本)

$ git clone https://github.com/apache/calcite.git

$ cd calcite

$ mvn install -DskipTests -Dcheckstyle.skip=true

$ cd example/csv

第一个查询


现在让我们来使用sqlline来连接Calcitesqlline是一个包含在整个Calcite项目里的SQL的命令行工具。

$ ./sqlline

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

(如果是windows操作系统,使用sqlline.bat)

执行一个元数据查询:

sqlline> !tables

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

| TABLE_CAT  | TABLE_SCHEM  | TABLE_NAME  |  TABLE_TYPE   | REMARKS  | TYPE |

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

| null       | SALES        | DEPTS       | TABLE         | null     | null |

| null       | SALES        | EMPS        | TABLE         | null     | null |

| null       | SALES        | HOBBIES     | TABLE         | null     | null |

| null       | metadata     | COLUMNS     | SYSTEM_TABLE  | null     | null |

| null       | metadata     | TABLES      | SYSTEM_TABLE  | null     | null |

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

(*译者注:上面案例里使用的!tables命令查询元数据,但是译者在使用的时候发现这个命令不好使)

0: jdbc:calcite:model=target/test-classes/mod> !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 |         |          |            |           |                           |                |

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

(JDBC提示: 在sqlline!tables命令只是执行了DatabaseMetaData.getTables()方法,还有其他的获取元数据命令如:!columns,!describe)

(译者注:!describe需要加表名)

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文件。)

img
img

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

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

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

le = 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文件。)

[外链图片转存中…(img-msE0nJdy-1714831184657)]
[外链图片转存中…(img-LVisHJ97-1714831184658)]

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

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

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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值