Overview
SQL内常量的数据类型在梧桐数据库中需要经过两个阶段才可以确定,第一阶段是词法解析,此时只能确定常量的大致范围(整形/字符串/浮点数等),接着在transform阶段才可以准确的确定常量的数据类型(int/float/text/char/varchar这些数据库已经创建的数据类型)。
梧桐数据库提供了CREATE CAST语法,支持创建不同数据类型间的转换。但隐式类型转换涉及范围较广,存在很多潜在的风险。比如函数的选择逻辑就会受到影响,部分基础操作符不再唯一。因此建议使用时按需创建操作符和函数,而不是添加隐式类型转换。
1. 解析规则
语法解析通过lex和yacc完成,简单来说字符串常量解析为SCONST,纯数字解析为ICONST,数字+dot解析为FCONST。但是此时无法确定部分常量的具体类型,因为不同数据类型之间的比较规则不一样,比如char和varchar/text的比较规则就不同,如果提前确定了常量数据类型就有可能会导致选错操作符从而导致结果集错误。
2. 转换规则
转换规则是根据pg_cast这张系统表确定的,初始化之后系统已经内置了很多数据类型间的转换规则,可以通过CREATE CAST来创建新的转换规则。
- IMPLICT/ASSIGNMENT:说明转换规则是隐式/赋值时调用
- INOUT/FUNCTION:inout是通过类型的out函数+in函数方式读取,function则是通过类型转换函数实现。without function说明两个类型存储的二进制是兼容的,直接relabel就可以读取。
举例说明一个转换规则在transform的时候是怎么应用的,又为什么会引起报错
CREATE CAST (INTEGER AS TEXT) WITH INOUT AS IMPLICIT;
SELECT 1::int = '1'::text; -- 添加前不支持,添加后语句正确执行
SELECT 1||'1'; -- 添加前语句正确执行,添加后语句执行报错
解析阶段内核首先根据操作符/函数的名字和参数类型进行精准匹配(函数名和入参的个数与类型都必须完全相同),如果匹配到了则选取该函数并转换为query树中的节点。否则需要用到隐式转换转换规则,将所有满足隐式转换的函数作为备选,最后根据匹配到的参数个数确定最优的备选并作为最终输出。
添加了隐式转换后,等值条件匹配操作符的时候通过pg_cast匹配到了对应的函数:
select oid,format_type(oprleft, null),format_type(oprright, null) from pg_operator where oprname='=' and (oprleft = 23 or oprright=25);
oid | format_type | format_type
--------+-------------+-------------
15 | integer | bigint
96 | integer | integer
98 | text | text
254 | name | text
533 | integer | smallint
(5 rows)
拼接sql的报错也是和支持的操作符有关,添加隐式转换后textanycat/textcat/anytextcat都可以作为备选,导致transform无法选出一个最匹配的结果而报错。
select oid,format_type(oprleft, null),format_type(oprright, null),oprcode from pg_operator where oprname='||';
oid | format_type | format_type | oprcode
------+-------------+-------------+-----------------
349 | anyarray | anyelement | array_append
374 | anyelement | anyarray | array_prepend
375 | anyarray | anyarray | array_cat
654 | text | text | textcat
1797 | bit varying | bit varying | bitcat
2018 | bytea | bytea | byteacat
2779 | text | anynonarray | textanycat
2780 | anynonarray | text | anytextcat
3633 | tsvector | tsvector | tsvector_concat
3681 | tsquery | tsquery | tsquery_or
3284 | jsonb | jsonb | jsonb_concat
(11 rows)
3. 添加操作符和重载函数
-- 添加操作符示例,添加后可直接进行int与text的比较
CREATE FUNCTION public.int4_text_eq(integer, text)
RETURNS boolean
AS 'SELECT $1::text=$2'
LANGUAGE SQL IMMUTABLE STRICT;
-- immutable说明同样的入参结果是不是总是一致的,可以按需改成volatile或者stable
-- strict说明函数是不是严格的,输入null结果是null的函数就是严格的
CREATE OPERATOR public.= (
leftarg = integer,
rightarg = text,
procedure = int4_text_eq,
commutator = =
);
-- 两侧都要加
CREATE FUNCTION public.text_int4_eq(text, int4)
RETURNS boolean
AS 'SELECT $1=$2::text'
LANGUAGE SQL IMMUTABLE STRICT;
CREATE OPERATOR public.= (
leftarg = text,
rightarg = integer,
procedure = text_int4_eq,
commutator = =
);
-- 重载函数
-- 假设已有函数func1(text) returns text
-- 可以通过重载函数使预期入参可以是text也可以是int
-- 尽量不要使用anyelement类型入参,anyelement需要显示指定数据类型,而且重载可能导致死循环。
CREATE OR REPLACE FUNCTION func1(int)
RETURNS text
AS '
SELECT func1($1::text);
'
LANGUAGE 'sql' IMMUTABLE STRICT;




