文章目录
- FLIP-70:Flink SQL计算列设计
- 状态
- 动机
- 公开接口
- 提议的变更
- 列计算和存储
- TableSchema变更
- TableSchema行类型接口
- 持久化
- 兼容性、弃用和迁移计划
- 实施计划
- 测试计划
FLIP-70:Flink SQL计算列设计
状态
当前状态: 已接受
讨论线索: http://apache-flink-mailing-list-archive.1008284.n3.nabble.com/DISCUSS-FLIP-70-Support-Computed-Column-for-Flink-SQL-td33126.htmlJIRA: FLINK-14386 - Support computed column for create table statement OPEN
Released: Flink Version
在邮件中进行讨论比在wiki上发表评论的方式更好
动机
在任务FLINK-10232中,我们在新模块flink-sql-parser中引入了CREATE TABLE
语法。在设计文档FLINK SQL DDL中,我们建议使用计算列来描述process time的时间属性,因此用户可以创建一个具有process time属性的表,如下所示:
create table T1(
a int,
b bigint,
c varchar,
d as PROCTIME,
) with (
'k1' = 'v1',
'k2' = 'v2'
);
d字段将成为T1表的一个process time属性
除此之外,计算列还有以下作用:
- 虚拟计算列可以用来简化和统一查询。复杂条件可以定义为计算列,并从表上的多个查询中引用,以确保所有查询都使用完全相同的条件。
- 存储计算列可以用作一个物化缓存,用于复杂的条件,这些条件在运行时计算成本很高。
- 计算列可以模拟函数索引:使用计算列定义函数表达式并为其编制索引。这对于处理无法直接索引的类型列(如JSON列)非常有用。
- 对于存储的计算列,这种方法的缺点是存储两次值;一次作为生成列的值,一次作为索引。
- 如果计算列已编入索引,优化器将识别与列定义匹配的查询表达式,并在查询执行期间酌情使用该列中的索引(目前尚不支持)。
MS-SQL-2016[1]、MYSQL-5.6[2]和ORACLE-11g[3]中引入了计算列
公开接口
计算列语法
结合MS-SQL-2017[1]和MYSQL-5.6[2]的语法,我们提出计算列语法如下:
col_name AS expr
[COMMENT 'string']
- 数据类型可以根据表达式来推断。
- 此列称为虚拟列,列值不被存储,但在读取数据时会被计算,当他输出时会触发计算,特别是proc time(我们可能会急切地进行一些具体化,请参阅RelTimeIndicatorConverter以了解详细信息)。请参阅“列计算/存储”部分,了解当表用作source/sink时如何持久化虚拟列。
- 如果将来需要,我们可能会支持STORED/VIRTUAL关键字。
- 如果用户没有更改来显式指定它,默认关键字将是VIRTUAL。
- 可空性由表达式决定,不能指定。
限制:
- 允许使用字面量、用户定义函数和内置函数。
- 不支持子查询。
- 它只能引用同一表中定义的列。
InitializerExpressionFactory
InitializerExpressionFactory定义计算列值的生成策略。它将为每个虚拟列生成一个RexNode,可以在逻辑计划中使用它来派生我们想要的值。下面的图表说明了它对select语句的工作原理:
TableColumn
TableColumn描述表的列定义,包括列名、列数据类型和列策略的定义。
TableSchema
TableSchema描述用于内部和当前连接器的表结构。
提议的变更
计划重写
假设我们有一个名为T2的表,其schema如下:
create table T2(
a int,
b as a + 1 virtual,
c bigint,
e varchar
) with (
k1=v1,
k2=v2
);
读取
如果T2表用作扫描表(source表),T2的列b将在扫描正上方的投影中计算。这意味着,我们将从扫描节点到扫描上方的投影进行等价转换。
我们将为所有虚拟计算列添加投影表达式,TableSource行类型应排除这些虚拟列。写入
如果T2表用作插入目标表(sink表),T2列b被完全忽略,因为它是虚拟的(不存储)。
我们将计算非虚拟列并将其插入到目标表中。TableSink行类型应排除这些虚拟列。
注意:这些计划重写发生在sql-to-rel转换阶段。
列计算和存储
对于scan,从表达式计算虚拟列;从实际表源查询存储列。
对于sink,虚拟列被忽略;存储的列在插入接收器时从表达式计算。
TableSchema变更
我们将扩展TableSchema以支持计算列(我们应该始终在代码中保留一个TableSchema,将来连接器只能看到行类型,而不能看到TableSchema,所以我们不应该担心这个问题)
我们将引入一个名为TableColumn的新结构来保存列名、列数据类型和列策略信息。TableSchema持有TableColumn,而不是原始字段名和字段数据类型。
TableSchema行类型接口
在TableSchema中,我们在推导行类型时总是忽略虚拟列。此行类型对应于source的输出物理类型和sink的输入物理类型。
持久化
我们将把TableSchema的TableColumn信息放入CatalogTable属性中,这些属性将用于持久化。对于每个列表达式,有三个项要持久化:
- 列名称
- 列类型
- SQL样式字符串的列表达式,与用户编写它们的方式类似
反序列化时,我们使用SqlParser将表达式字符串解析为SqlNode,然后将其转换为RexNode并应用投影。
兼容性、弃用和迁移计划
这是一个新功能,与旧版本的Flink兼容。我们可以扩展TableSourceTable以支持计算列接口,但这对用户是透明的。
实施计划
- 引入InitializerExpressionFactory来处理默认值的初始化和生成的列的计算表达式的生成。
- 使FlinkRelOptTable扩展接口InitializerExpressionFactory,因为它是Calcite schema查找的Flink外部表的抽象。
- 在TableSchema中引入TableColumn结构来描述DDL中声明的列的名称/类型/表达式。TableColumn应保持可序列化,以便在BaseCatalogTable中持久化到目录中。
- 扩展TableSchema以包含所有TableColumn的定义。
测试计划
对于每个新特性,都可以使用单元测试来测试实现。我们还可以为连接器添加集成测试,以验证它是否可以与现有的source实现兼容。
参考文献
[1] https://docs.microsoft.com/en-us/sql/relational-databases/tables/specify-computed-columns-in-a-table?view=sql-server-2016 [2] https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html [3] https://oracle-base.com/articles/11g/virtual-columns-11gr1