个人理解总结,错误欢迎指出
引言
这里的隐式类型转换指的是 SqlOperator 的隐式类型转换。举个例,MOD(expr$0, expr$1) 函数接受两个参数,表示两个数值表达式作除法运算后的余数,在 Calcite 1.17.0 版本中执行以下 SQL:select mod(cast(1.5 as DECIMAL), cast(6.0 as DOUBLE)); 会报错 Cannot apply 'MOD' to arguments of type 'MOD(<DECIMAL(19, 0)>, <DOUBLE>)',但是在 Calcite 1.30.0 版本中执行则可以正常返回结果。这是因为 MOD 函数在声明时只接受“确定精度“类型,Decimal 有默认的精度 Decimal(19, 0),但是 Double 是可变精度。在 1.30.0 版本中观察 SQL 校验后的关系代数发现此 SQL 已经被转换为select mod(cast(1.5 as DECIMAL), cast(cast(6.0 as DOUBLE)) as DECIMAL(30, 15));
问题
Calcite 实现了隐式类型转换机制,想要了解其原理还要确定一下问题:
-
Operator 的参数类型推断策略是什么?如何指定的?
-
如何判断是否要进行类型转换?
-
是否可以手动控制隐式转换行为?
Operator 初始化
Calcite 中 SqlOperator 是一种 SQL 解析数中的一种节点,SqlOperator 可以代表某种操作符、函数或者是某种语法结构(syntactic constructs)。
SqlOperandTypeInference
SqlOperator 的构造函数如下:
SqlOperandTypeInference 是参数推断策略,在org.apache.calcite.sql.type.InferTypes中一共定义了 5 种推断策略:
FIRST_KNOWN:未知的类型从第一个已知类型派生。
RETURN_TYPE:未知类型从 operator 返回值类型派生。
BOOLEAN:未知类型假定是 boolean。
VARCHAR_1024:未知类型假定是 VARCHAR(1024)。
ANY_NULLABLE:未知类型是可以为空的任意类型。
例如 Cast 函数的参数推断策略就是 FIRST_KNOWN,cast(6.0 as DOUBLE),6.0 这个字面量就会尝试转换为 Double 类型。
SqlOperandTypeChecker
SqlFunction 的构造函数以及 MOD(expr$0, expr$1) 的初始化如下:
SqlOperandTypeChecker 是函数校验规则,MOD 函数指定了 EXACT_NUMERIC_EXACT_NUMERIC,说明第一个参数和第二个参数必须都为 EXACT_NUMERIC,它包括 TINYINT, SMALLINT, INTEGER, BIGINT, DECIMAL。'%' 操作符的初始化和 MOD 函数一样,只不过 name 改成了 '%',声明在org.apache.calcite.sql.fun.SqlStdOperatorTable#PERCENT_REMAINDER。
隐式类型转换
高版本 Calcite 函数的类型转换主逻辑在org.apache.calcite.sql.validate.implicit.TypeCoercionImpl#builtinFunctionCoercion方法中,该方法一种三个参数:
binding 是 SqlOperator 与实际操作数的绑定,以及包含验证这些操作数所需的任何其他信息,例如 validator、typeFactory。
operandTypes 是函数参数的类型或者推断类型,这里为什么有可能是推断类型呢?因为 selectList 中所有的 item 初始化类型都是 unknown,除了表的字段可以从 Catalog Reader 中获取准确类型以外,其他的字面量都是推断出来的。具体代码在org.apache.calcite.sql.validate.SqlValidatorImpl#validateSelectList中,如下:
expectedFamilies 是当前函数期望的类型分类。那么在 MOD 函数验证过程中类型如下:
转换过程
转换过程主要分为两步:
-
尝试将实际类型向期望类型进行转换。
-
如果隐式转换后的类型和原来类型不一直将尝试应用这次转换。
步骤一:
org.apache.calcite.sql.validate.implicit.AbstractTypeCoercion#implicitCast,以 Double 为例,可变精度类型类型都会转换为 Decimal 类型。
步骤二:
org.apache.calcite.sql.validate.implicit.AbstractTypeCoercion#coerceOperandType,在应用 castTo() 函数之前会进行一系列校验,例如:动态类型不支持转换、JavaType 和 RelDataType 不能相互转换、Any 类型不能转换、没必要相互转换 char 和 varchar、tinyint 没必要转换为 int,int 也没必要转换为 bigint。如果验证都通过会执行 castTo 函数生成新的 SqlNode 并替换原来 SqlNode。
隐式类型转换开关
在初始化org.apache.calcite.sql.validate.SqlValidator.Config时设置 withTypeCoercionEnabled = false
核心流程
inferUnknownTypes 方法会进行 select list 中所有的 unknown 类型的表达式进行推断,例如 cast(6.0 as DOUBLE),6.0 字面量被推断为 Double 类型。
deriveType 中会进行表达式返回值类型的推到和表达式参数类型的校验。
总结
Operator 在初始化时会指定 SqlOperandTypeInference 用于 unknown 类型的表达式推断和 SqlOperandTypeChecker 用于约束参数的类型,在验证 SqlFunction 时会尝试进行隐式类型转换并使用 castTo 函数生成转换后的表达式替换原来的 SqlNode 节点。