既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
±-------±-------±--------±--------±---------------±-------±------±–+
| 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’,这两个文件会转换成表emps
和depts
。
表和视图
值得注意的是,我们在模型文件(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
)获取部分。(csvtablefactory
和csvschema
一样,都创建了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
)也是可扩展的,就像schemas
和tables
一样。所以如果你有一些存储下来的数据希望通过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
public void onmatch(reloptrulecall call) {
final project project = call.rel(0);
final csvtablescan scan = call.rel(1);
int[] fields = getprojectfields(project.getprojects());
if (fields == null) {
// project contains expressions more complex than just field references.
return;
}
call.transformto(
new csvtablescan(
scan.getcluster(),
scan.gettable(),
scan.csvtable,
fields));
}
private int[] getprojectfields(list<rexnode> exps) {
final int[] fields = new int[exps.size()];
for (int i = 0; i < exps.size(); i++) {
final rexnode exp = exps.get(i);
if (exp instanceof rexinputref) {
fields[i] = ((rexinputref) exp).getindex();
} else {
return null; // not a simple projection
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
ettable(),
scan.csvtable,
fields));
}
private int[] getprojectfields(list<rexnode> exps) {
final int[] fields = new int[exps.size()];
for (int i = 0; i < exps.size(); i++) {
final rexnode exp = exps.get(i);
if (exp instanceof rexinputref) {
fields[i] = ((rexinputref) exp).getindex();
} else {
return null; // not a simple projection
[外链图片转存中…(img-nm7kfq3w-1715312814344)]
[外链图片转存中…(img-lpaauzay-1715312814344)]
[外链图片转存中…(img-5yxi3qhj-1715312814344)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
发表评论