FlinkSQL类型系统
类型有什么作用, 类型可以提供编译期检查, 避免到运行期才报错.
类型
首先Flink中自己定义了一套类型, 有LogicalType和DataType两个表示
LogicalType
LogicalType表示的逻辑类型, 并不涉及类型的物理表示, 会包含nullable属性.
还有两个衍生的概念 LogicalTypeFamily和LogicalTypeRoot
LogicalTypeRoot表示的LogicalType的元类型, 例如TIMSTAMP类型可以包含多种精度, 但是多种精度的TIMESTAMP(3)``TIMSTAMP(6)都属于 TIMESTAMP_WITHOUT_TIME_ZONE root类型. 一个LogicalType只属于一个LogicalTypeRoot
LogicalTypeFamily表示的是LogicalTypeRoot的归类, 一个LogicalTypeRoot可以同时属于多个LogicalTypeFamily
例如 这就表示 DECIMAL属于 PREDEFINED, NUMERIC, EXACT_NUMERIC的分类.
DECIMAL(
LogicalTypeFamily.PREDEFINED,
LogicalTypeFamily.NUMERIC,
LogicalTypeFamily.EXACT_NUMERIC),
DataType
DataType就在LogicalType之上添加了类型的物理表示, 可以看到他的成员变量就是由 logicalType + conversionClass组成, conversionClass表示Flink该怎么去读写这个类型.
他主要用在和外部系统交互的时候, 比如我定义一个TIMSTAMP类型, 我可以用java.sql.Timestamp表示, 也可以用 java.time.LocalDateTime来表示, 这时候就可以通过显示的指定 conversionClass来实现, 如果不指定, 就使用LogicalType中的默认的conversionClass.
protected final LogicalType logicalType;
protected final Class<?> conversionClass;
例如在表示Row类型时, Flink内部都是表示为RowData, 并且内部的String都是表示为StringData, 那么通过LogicalTypeUtils#toInternalConversionClass就可以将相应的conversionClass转化成内部的表示. 并 DataType#bridgeTo用来修改此DataType的物理类型表示.
当然并不是随便bridge一个类型就能生效, 他会通过LogicalType supportsInputConversion和 supportsOutputConversion来校验你设置的conversionClass在不在这个范围之内
比如 VarcharType所支持的输入输出类型是
String.class.getName(), byte[].class.getName(), StringData.class.getName()
主要需要校验的点就是Source/LookupJoin/Sink/UDF 这些和外部用户定义类型需要交互的地方.
在 DynamicTableSource.Context中的 createDataStructureConverter就是将外部系统中的数据转化成内部的数据, 而这里首先就需要表示这些外部数据类型的conversionClass是满足supportsInputConversion的 然后通过DataStructureConverters.getConverter(producedDataType)创建外部Object到内部类型的映射, 这样外部Java类型系统就和SQL中的RowData关联上了.
举个例子
putConverter(
LogicalTypeRoot.VARCHAR, String.class, constructor(StringStringConverter::new));
putConverter(
LogicalTypeRoot.VARCHAR, byte[].class, constructor(StringByteArrayConverter::new));
putConverter(LogicalTypeRoot.VARCHAR, StringData.class, identity());
这里对于VARCHAR类型, 如果你在插件端中的实际Java object类型是String.class那么就通过StringStringConverter转化成内部的StringData类型. 如果是byte[].class那通过StringByteArrayConverter来转化, 输出侧也是一样.
对于UDF呢也是如此, 不过是通过Codegen的方式来生成converter, 将用户UDF中的外部类型, 转化成内部类型. 如果用户类型是非标准SQL的类型, 也支持指定成RAW来表示成 RawValueData
RowData
RowData是Flink内部的数据类型, 可以看到上面, toInternal转化的时候就是要将外部的ROW 转成RowData, 他和SQL类型的映射关系, 这可以理解成数据类型的内部物理表示层. 内部SQL算子, Codegen代码都是面向RowData编程, 而RowData 也有多种实现有基于Object的GenericRowData, 有基于二进制表示的BinaryRowData, 也有基于列式视图的 ColumnarRowData
* +--------------------------------+-----------------------------------------+
* | SQL Data Types | Internal Data Structures |
* +--------------------------------+-----------------------------------------+
* | BOOLEAN | boolean |
* +--------------------------------+-----------------------------------------+
* | CHAR / VARCHAR / STRING | {@link StringData} |
* +--------------------------------+-----------------------------------------+
* | BINARY / VARBINARY / BYTES | byte[] |
* +--------------------------------+-----------------------------------------+
* | DECIMAL | {@link DecimalData} |
* +--------------------------------+-----------------------------------------+
* | TINYINT | byte |
* +--------------------------------+-----------------------------------------+
* | SMALLINT | short |
* +--------------------------------+-----------------------------------------+
* | INT | int |
* +--------------------------------+-----------------------------------------+
* | BIGINT | long |
* +--------------------------------+-----------------------------------------+
* | FLOAT | float |
* +--------------------------------+-----------------------------------------+
* | DOUBLE | double |
* +--------------------------------+-----------------------------------------+
* | DATE | int (number of days since epoch) |
* +--------------------------------+-----------------------------------------+
* | TIME | int (number of milliseconds of the day) |
* +--------------------------------+-----------------------------------------+
* | TIMESTAMP | {@link TimestampData} |
* +--------------------------------+-----------------------------------------+
* | TIMESTAMP WITH LOCAL TIME ZONE | {@link TimestampData} |
* +--------------------------------+-----------------------------------------+
* | INTERVAL YEAR TO MONTH | int (number of months) |
* +--------------------------------+-----------------------------------------+
* | INTERVAL DAY TO MONTH | long (number of milliseconds) |
* +--------------------------------+-----------------------------------------+
* | ROW / structured types | {@link RowData} |
* +--------------------------------+-----------------------------------------+
* | ARRAY | {@link ArrayData} |
* +--------------------------------+-----------------------------------------+
* | MAP / MULTISET | {@link MapData} |
* +--------------------------------+-----------------------------------------+
* | RAW | {@link RawValueData} |
* +--------------------------------+-----------------------------------------+
TypeInformation
Flink还有一套比较早的类型系统 基于TypeInfomation, 用于兼容较早的Source/Sink 以及UDF的类型接口, 通过LegacyTypeInfoDataTypeConverter将TypeInfomation转化成新的DataType来使用.
RelDataType
以上这三种类型都是Flink中的类型体系, 最终Flink还是通过Calcite来进行sql validate, 而Calcite中的类型是RelDataType, 所以需要有这样的一个映射关系. 通过FlinkTypeFactory#createFieldTypeFromLogicalType可以将LogicalType转化成RelDataType
Validate
首先在validate阶段, calcite系统就会去推断各个字段, 函数入参, 返回值的类型, 来进行语法的校验.
表的schema类型
flink中的catalog和Calcite的CalciteSchema通过CatalogManagerCalciteSchema来打通, 将CatalogManager封装到SimpleCalciteSchema中, 所以最后的表的查找都会通过 FlinkCalciteCatalogReader#getTable路由到
CatalogManagerCalciteSchema -> CatalogCalciteSchema -> DatabaseCalciteSchema 最终是从Catalog中查找到相应的表(CatalogSchemaTable) 这个是Flink Catalog table 和Calcite Table的中间桥接.
而此时就需要将Flink Catalog table 的表类型 (DataType) 转化成 Calcite中的类型RelDataType. 在CatalogSchemaTable#getRowType中就会完成这一转化, 最终源表产出的是一个ROW struct type.
final List<String> fieldNames = schema.getColumnNames();
final List<LogicalType> fieldTypes =
schema.getColumnDataTypes().stream()
.map(DataType::getLogicalType)
.map(PlannerTypeUtils::removeLegacyTypes)
.collect(Collectors.toList());
return flinkTypeFactory.buildRelNodeRowType(fieldNames, fieldTypes);
函数定义
基于Calcite SqlFunction的定义
首先Flink从Calcite中集成了很多函数定义例如在 FlinkSqlOperatorTable中
public static final SqlOperator AND = SqlStdOperatorTable.AND;
public static final SqlOperator AS = SqlStdOperatorTable.AS;
SqlOperandTypeInference推断入参的类型 他的入参是 SqlCallBinding, RelDataType returnType, RelDataType[] operandTypes (这个入参都是unknown, 需要实现者去填充). InferTypes内置入参类型推断
SqlReturnTypeInference推断函数的返回类型. 入参是SqlOperatorBindingbinding实际上就是把校验时的上下文, 如入参类型, 入参个数, 入参的常量提供给你, 方便你去做类型推断.Calcite ReturnTypes和 FlinkReturnTypes提供了很多预置的返回类型推断的实现
SqlOperandTypeChecker 检查入参类型 个数等等是否合法
例如我写一个 SELECT A and B from myTable 测试a和b不是boolean类型. 首先在validate的过程中需要推断SELECT列表的类型, A and B 是一个 SqlOperator 首先会调用上面的SqlOperandTypeInference推导参数类型. 对于AND操作, 推导入参都是boolean
然后会根据每一个SqlNode#deriveType 推导其返回类型. 而递归查找的过程中最后会查找到source table schema 中找到相应的字段推断出A和B分别是什么类型. 所以感觉第一步的SqlOperandTypeInference并没有什么用 ?
最后推导出入参类型之后会通过SqlOperandTypeChecker校验入参类型是否符合预期.
因此当A和B类型不是Boolean时, 通过Checker就会校验报错
基于BuiltInSqlFunction接口
里面所使用的接口其实还是前面 Calcite里面定义的几种推导和校验的接口. BuiltInSqlFunction的接口的主要作用是承接一些 BuiltInFunctionDefinition 不支持的定义方式. 作者希望的主要内置函数的入口还是第三种
基于BuiltInFunctionDefinition
这个就是Flink自己完全新定义了一套类型的dsl. 如 InputTypeStrategy, OutputTypeStrategy. 这个构建出来的Function并不是直接的Calcite的SqlFunction. 最终这些函数都会注册到FunctionCatalogOperatorTable中. 而默认的Calcite初始化的OperatorTable就是 FunctionCatalogOperatorTable和 FlinkSqlOperatorTable
return SqlOperatorTables.chain(
new FunctionCatalogOperatorTable(
context.getFunctionCatalog(),
context.getCatalogManager().getDataTypeFactory(),
typeFactory),
FlinkSqlOperatorTable.instance());
所以查找函数的时候会首先去FunctionCatalogOperatorTable new stack中查找. 查找到的BuiltInFunctionDefinition会通过convertToSqlFunction转化成Calcite的接口. 最终转化成为BridgingSqlFunction, BridgingSqlAggFunction
基于CallExpression
- CallExpresssion的定义包含了函数的主体
FunctionDefinition, 参数ResolvedExpression- 这一套接口主要可以透传到插件端, 比如Filter下推时下推的就是
ResolvedExpression, 以及Watermark表达式 - 以及在Java scala的table api [[ImplicitExpressionOperations]]中提供dsl api
- 这一套接口主要可以透传到插件端, 比如Filter下推时下推的就是
- 最后这套CallExpression会通过一系列的转化规则
FunctionDefinitionConvertRuleDirectConvertRule等等转化成Calcite的RexNode, 用于在Plan阶段将这些边缘节点(用户交互界面)的Expression编译成RexNode. 里面的转化逻辑实际上和上面的三种基本映射
自定义函数
- 注册用户自定义函数的入口是
TableEnvironmentImpl#createSystemFunction最终实例化成FunctionDefinition - 类型校验则是通过
ScalarFunction#getTypeInference来完成的, 用户实际上也可以覆写这个方法来完成类型的校验, 他也提供了一个默认的实现. 这个默认的实现就是一套基于函数hint的类型推导.FunctionMappingExtractor完成对自定义函数的分析, 如入参类型, 返回值, 函数名称等等. 这里面用户就可以通过DataTypeHint 来对参数和返回值进行标记. 完成提取后, 用户参数的校验逻辑就和前面的BuiltInFunctionDefinition一致了
函数实现
- 函数实现分为两种Old stack中, 对于Calcite中定义的函数, Flink提供运行时实现基本是通过Codegen代码来生成的, 所以之前新增一个函数比较繁琐, 需要修改Codegen的逻辑, 主要涉及的地方就是 ExprCodeGenerator
- 而针对New stack的函数, 一般只需要开发者提供一个runtime class, 通过 BridgingSqlFunctionCallGen 完成函数实现的注入.
时间属性类型
- 时间属性主要是用作窗口时间的特殊标记, 标记是proctime或者eventime. 所以有一种特殊的类型叫做
TimeIndicatorRelDataType这里主要是在源表定义的地方会对时间属性字段做特殊处理插入相应的TimeKind. - 需要注意的一点是针对时间属性字段需要做一些物化处理. 在
RelTimeIndicatorConverter完成 例如- 聚合参数或者group维度 中的时间属性字段
- Sink节点下发前
- Calc计算节点
FlinkSQL类型系统的更多相关文章
- javascript中15种原生对象类型系统综述
前面的话 在编程语言中,能够表示并操作的值的类型称做数据类型,编程语言最基本的特性就是能够支持多种数据类型.javascript拥有强大的类型系统,主要包括原生对象.宿主对象和浏览器拓展对象,本文主要 ...
- TypeScript - 基本类型系统
对于程序来说我们需要基本的数据单元,如:numbers, strings, structures, boolean 等数据结构.在TypeScript中我们支持很多你所期望在JavaScript中所拥 ...
- 基于类型系统的面向对象编程语言Go
(整理自网络) 面向对象编程 Go语言的面向对象编程(OOP)非常简洁而优雅.说它简洁,在于它没有了OOP中很多概念,比如:继承.虚函数.构造函数和析构函数.隐藏的this指针等等.说它优雅,是它的面 ...
- 04.C#类型系统、值类型和引用类型(二章2.2-2.3)
今天要写的东西都是书中一些概念性的东西,就当抄笔记,以提问对话的方式将其写出来吧,说不定以后面试能有点谈资~~~ Q1.C#1系统类型包含哪三点特性? A1.C#1类型系统是静态的.显式的和安全的. ...
- javascript类型系统之Array
原文:javascript类型系统之Array 目录 [1]数组创建 [2]数组操作 [3]继承的方法 [4]实例方法 数组转换 数组检测 栈和队列 排序方法 操作方法 位置方法 前面的话 数组是一组 ...
- .NET Framework 中的类型系统的两个基本点
它支持继承原则. 类型可从称为基类型的其他类型派生. 派生类型继承基类型的方法.属性和其他成员(存在一些限制). 之后,基类型可从某些其他类型派生,这种情况下,派生类型继承其层次结构中这两个基类型的成 ...
- 《InsideUE4》UObject(三)类型系统设定和结构
垃圾分类,从我做起! 引言 上篇我们谈到了为何设计一个Object系统要从类型系统开始做起,并探讨了C#的实现,以及C++中各种方案的对比,最后得到的结论是UE采用UHT的方式搜集并生成反射所需代码. ...
- c#1所搭建的核心基础之类型系统的特征
类型系统的特征简介 几乎每种编程语言都有某种形式的一个类型系统.类型系统大致被分为:强/弱,安全/不安全,静态/动态,显式/隐式等类型. c#在类型系统世界中的位置 c#1的类型系统是静态的.显式的和 ...
- Haskell 笔记(三)类型系统
类型 (Type) Haskell的类型系统式静态类型系统,在编译的时候就知道数据类型,所以不同类型的值运算在编译的时候就会报错,比如用布尔值和整数运算,在C语言中这种运算就不会报错. Haskell ...
- 《InsideUE4》UObject(四)类型系统代码生成
你想要啊?想要你就说出来嘛,你不说我怎么知道你想要呢? 引言 上文讲到了UE的类型系统结构,以及UHT分析源码的一些宏标记设定.在已经进行了类型系统整体的设计之后,本文将开始讨论接下来的步骤.暂时不讨 ...
随机推荐
- #Python基础 pandas索引设置
一:XMIND 二:设置索引 示例数据,假设我们有一个DataFrame对象,如下: import pandas as pd df = pd.DataFrame({ "name": ...
- 数据库定时备份linux篇
目录 1 序言 2 crond 相关知识点 2.1 crond 是什么? 2.2 crontab 进行定时任务设置 2.2.1 crontab 指令选项说明 2.2.2 crontab 指令使用格式 ...
- 2023-02-16:两种颜色的球,蓝色和红色,都按1~n编号,共计2n个, 为方便放在一个数组中,红球编号取负,篮球不变,并打乱顺序, 要求同一种颜色的球按编号升序排列,可以进行如下操作: 交换相邻
2023-02-16:两种颜色的球,蓝色和红色,都按1-n编号,共计2n个, 为方便放在一个数组中,红球编号取负,篮球不变,并打乱顺序, 要求同一种颜色的球按编号升序排列,可以进行如下操作: 交换相邻 ...
- 2022-05-23:给定一个数组arr,你可以随意挑选其中的数字, 但是你挑选的数中,任何两个数a和b,必须Math.abs(a - b) > 1。 返回你最多能挑选几个数。 来自美团。
2022-05-23:给定一个数组arr,你可以随意挑选其中的数字, 但是你挑选的数中,任何两个数a和b,必须Math.abs(a - b) > 1. 返回你最多能挑选几个数. 来自美团. 答案 ...
- 2022-05-11:k8s安装easydarwin流媒体服务器,yaml如何写?
2022-05-11:k8s安装easydarwin流媒体服务器,yaml如何写? 答案2022-05-11: yaml如下: apiVersion: apps/v1 kind: Deployment ...
- 创建对象create()、批量创建bulk_create()、创建或更新update_or_create()、更新对象update()、更新或创建update_or_create()、删除对象delete()使用filter过滤、判断是否存在exists()、统计个数count()、聚合aggregate()
创建对象create().批量创建bulk_create().创建或更新update_or_create().更新对象update().更新或创建update_or_create().删除对象dele ...
- Vulnhub-Durian
Durian 一.靶机信息 # 靶机地址 http://www.vulnhub.com/entry/durian-1,553/ s #参考 https://blog.csdn.net/LYJ20010 ...
- 分布式机器学习(Parameter Server)
分布式机器学习中,参数服务器(Parameter Server)用于管理和共享模型参数,其基本思想是将模型参数存储在一个或多个中央服务器上,并通过网络将这些参数共享给参与训练的各个计算节点.每个计算节 ...
- Java方法的调用以及方法参数传递、方法的递归调用
一.方法的调用以及方法参数传递 1.方法的定义: 访问修饰符 返回值类型 方法名 ([参数列表]){ 方法体 } 如果方法体中需要一些未知的数据作为执行条件,那么这些数据可以作为参数. 如果方 ...
- 基于ggplot2的解剖图和组织模块可视化
摘要 将数据显示到解剖结构上,是一种可以快速观察组织相关信息的便捷技术.然而,绘制组织是一项复杂的任务(a complex task),需要解剖学和艺术方面的专业知识.虽然已经存在可用于在解剖图上显示 ...