1. 类型系统¶
在PostgreSQL中,支持了丰富的数据类型,函数,操作符,这些功能对类型系统有很大影响。
1.1. 常量类型¶
在SQL语句中包含的常量是什么类型呢?
例如: SELECT 1 这里数字 1 是smallint,integer还是bigint?
对于数字类型的常量类型是按照它的值来决定的,在语法解析后,依次判断integer, bigint, numeric是否能容纳这个数字,
所以 SELECT 1 的类型是integer 但 SELECT 1111111111111 的类型是bigint, 如果是更大的数字,则类型是numeric,
而浮点数始终是numeric。
这个特性会影响到重载函数的识别, 例如:
-- 有如下函数定义
create function tf1(numeric) ...
create function tf1(float4) ...
create function tf1(integer) ...
create function tf1(bigint) ...
select tf1(1.0); -- 调用numeric版本
select tf1(1); -- 调用integer版本
select tf1(111111111111111); -- 调用bigint版本
select tf1(1111111111111111111111111111111); -- 调用numeric版本
1.2. unknow类型¶
unknow类型是PostgreSQL类型中最容易误解的一部分,因为它的定义看起来像字符串,而且在大部分时候,它也是作为字符串来处理的。
事实上,在PostgreSQL的语法解析层面并没有字符串类型的语法单元, 所有以单引号包起来的字符串都是未知类型,在语法解析层面不能确定其真正类型,需要在语法解析完成后, 根据上下文的语义来判断出真正的类型。 它的存在是PostgreSQL类型系统的复杂和一些奇怪行为的根源。
create function tf2(text) ...
create function tf2(integer) ...
-- 在语法层面,'1'的类型是未知的
-- 所以不能很容易确定调用哪个版本
select tf2('1');
为了解决这个问题,PostgreSQL定义了一系列规则来实现尽可能看起来合理的行为, 比如上面的例子中,实际上是会调用text版本的函数,因为unknow类型首先看起像字符串, 所以在规则设计中,赋予了字符串类型较高的优先级,尽量符合大家对unknow类型理解为字符串的一个预期。
如果没有text类型的重载版本,则很可能会产生一个错误,例如:
create function tf3(bigint) ...
create function tf3(integer) ...
select tf3('1');
其产生的错误信息如下:
ERROR: function tf3(unknown) is not unique
LINE 1: select tf3('1');
^
HINT: Could not choose a best candidate function. You might need to add explicit type casts.
1.3. JDBC中对于unknow类型的处理¶
在PostgreSQL的JDBC驱动中,默认情况下没有unknow类型,所有的String类型参数会被明确指定为varchar类型。 这种情况下,PostgreSQL认为这个类型是明确的,不会触发unknow类型的识别机制。 例如:
create function tf4(integer) ...
select tf4('1'); -- OK
因为tf4是唯一的,所以PostgreSQL会尝试将unknow类型识别为integer类型,但是在java中则会报错
String sql = " SELECT tf4(?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "1");
ps.execute();
ERROR: function tf4(character varying) does not exist
这个错误和直接执行 select tf4('1'::varchar); 的错误是一样的。
在JDBC中提供了stringtype选项,如果指定stringtype=unspecified则会将string类型处理成unknow类型, 这样就可以享受到PostgreSQL的unknow类型识别机制,上面的代码就不会报错。 但是这样也带来了unknow类型的不确定性。
1.4. PostgreSQL函数和操作符¶
从类型的角度上看,函数和操作符并没有太大差别,在代码实现上相同的。
函数比操作符稍微复杂些,函数需要考虑变长参数(VARIADIC),默认参数的情况。
1.4.1. VARIADIC参数展开¶
如果有函数是变长参数,则会被展开为相应个数的参数列表,参与匹配。
create function tf5(VARIADIC integer[]) ...
-- tf5会被当成tf5(integer, integer)来处理
select tf5(1,1);
变长参数函数的优先级低于普通函数,所以可以用普通函数来覆盖变长参数函数。
create function tf5(VARIADIC integer[]) ...
create function tf5(integer) ...
create function tf5(integer, integer) ...
select tf5(1) -- 调用 integer版本
select tf5(1,1) -- 调用 integer,integer版本
select tf5(1,1,1) -- 调用VARIADIC版本
1.4.2. 默认参数¶
对于函数默认参数的设计需要注意的是,在创建函数的时候并没有过多的校验, 所以像下面的函数集是可以创建成功的,但是在调用时候就会出错。
create function tf6(integer) ...
create function tf6(integer, integer = 5) ...
select tf6(1); -- 错误
ERROR: function tf6(integer) is not unique
所以对于非默认参数列表相同的重载函数,是会有冲突的,并且在运行时才能检查出来。
1.5. 重载和类型识别规则¶
下面详细讲述这个规则(在官方文档Chapter 10. Type Conversion有相关描述), 在这里需要解决的问题是根据用户输入的参数列表,从多个同名重载函数或者操作符中选择最合适的一个。 选择成功后,便可依据被选中的函数或者操作符的参数列表,确定unknow的具体类型,并为所有需要隐式转换的参数增加 隐式类型转换动作。
在实参类型有unknow类型的时候,unknow类型不能给重载函数的识别带来有效的帮助。所以需要 依赖一些特殊的规则来减少unknow和重载的矛盾。
实际匹配流程是按如下方法顺序进行的,只要某个步骤能在找到唯一的匹配,则就成功了。否则继续往下按其他方案匹配, 直到按照某个步骤找到唯一匹配。 如果下面所有步骤走完,还是没有找到唯一的匹配,则会报错。
下面的步骤在官方文档也有描述,但是比较粗略,不容易理解,我在这里尝试用更易懂的措辞来描述, 为每个步骤取了个较为明确的名字,并且都提供代码样例。
1.5.1. 精确匹配¶
精确匹配肯定是最优先的,如果实参列表不包含unknow类型,且类型和某个函数形参类型列表完全一致, 那肯定就它没错。
create function tf7(int2) ... create function tf7(int4) ... select tf7('1'::int2); -- 调用int2版本 select tf7('1'::int4); -- 调用int4版本 select tf7(1); -- 调用int4版本如果输入有unknow类型,则这一步肯定找不到。
1.5.2. 隐式转换¶
如果能通过隐式转换找到唯一的函数,则匹配成功
create function tf8(int4) ... create function tf8(text) ... -- int2存在到int4的隐式转换 select tf8(1::int2); -- OK, 调用int4版本 -- 内置操作符|/的形参类型是float8,而实参20的类型是integer -- 但是存在integer到float8的隐式转换,所以是ok的 select |/20; -- OK, 调用float8版本如果通过隐式转换找不到唯一的一个,则这一步会匹配失败
create function tf9(int4) ... create function tf9(int8) ... -- int2同时存在到int4,int8的隐式转换 select tf9(1::int2); ERROR: function tf9(smallint) is not unique在这一步同时会尝试识别unknow类型,如果unknow类型可以匹配任何类型,能找到唯一的一个匹配的话,也匹配成功。
create function tf10(int4, int4) ... select tf10(1, '1'); -- OK select tf10(1, 'AB'); ERROR: invalid input syntax for type integer: "AB"从上面的错误提示中,也可以看到,已经在尝试把unknow类型AB转换成数字了,说明函数匹配已经成功了。
1.5.3. 类型提升¶
如果上面隐式转换还不能唯一识别的话,会对所有domain类型的实参做类型提升,再尝试匹配。
create domain mytext as varchar; create function tf11(varchar) ... create function tf11(text) ... -- mytext类型会被提升为varchar类型 select tf11('11'::mytext); -- OK,调用varchar版本在这里做类型提升的策略,对domain和unknow类型混用的情况不是太友好。
create domain mytext as varchar; create function tf31(mytext, text) ... create function tf31(varchar, text) ... select tf31('1'::mytext, '1'); -- OK, 调用(varchar, text)版本在这里,更准确的选择应该是(mytext, text)版本。 但是因为这个语句包含有unknow类型,所以第一步精确匹配失败, 因为mytext可以转换成varchar,所以在第二步隐式转换时,两个重载版本同时匹配,不唯一,所以第二步匹配失败。
到现在这一步,直接把mytext提升为varchar类型,导致(mytext,text)版本失去了匹配的机会。
1.5.4. Preferred type优先¶
这里有一个新概念: Preferred type,PostgreSQL把数据类型分为几大类, 其中,数字类型(Numeric types)包括了int2,int4,float4等所有数字类型, 而字符串类型(String types)包括了text,varchar,char等所有字符串类型。
对于每个大类,可以定义对应的Preferred type。默认情况下, 数字类型的Preferred type是float8, 字符串类型的Preferred type是text, Prefered type在对应的大类中具有优先权。
create function tf12(bigint) ... create function tf12(float8) ... select tf12(1); -- OK, 调用float8版本 create function tf13(text) ... create function tf13(varchar) ... select tf13('1'::char(1)); -- OK, 调用text版本如果不知道Preferred type概念,就无法理解上面例子中的行为。
1.5.5. unknow类型匹配¶
到这一步需要尝试利用unknow类型的灵活性做选择,即基于实际情况,让unknow优先匹配哪种类型, PostgreSQL基于两个原则: 字符串优先和preferred type优先。
因为unknow类型看起来更像是字符串,所以如果重载函数对应位置的参数是字符串类型的话,会优先匹配。
create function tf14(text) ... create function tf14(float8) ... select tf14('1'); -- OK, 调用text版本 create function tf15(bool) ... create function tf15(float8) ... select tf15('1'); -- ERROR ERROR: function tf15(unknown) is not unique如果没有字符串类型,但所有的重载函数参数都同属一个大类的话,会优先选择这个大类中的preferred type类型。
create function tf16(int4) ... create function tf16(float8) ... select tf16('1'); -- OK,调用float8版本如果有不属于同一个大类的,则会报错
create function tf17(int4) ... create function tf17(float8) ... create function tf17(bool) ... select tf17('1'); ERROR: function tf17(unknown) is not unique下面在举两个操作符的例子,说明同样的问题。
对于取绝对值的@操作符,有多个重载版本:
ltsql> \do @ Schema | Name | Right arg type | Result type -----------+------+------------------+------------------ pg_catalog | @ | bigint | bigint pg_catalog | @ | double precision | double precision pg_catalog | @ | integer | integer pg_catalog | @ | numeric | numeric pg_catalog | @ | real | real pg_catalog | @ | smallint | smallint我们看到@操作符的所有重载版本,都是数字类型,并且有float8(double precision)的重载版本, 所以对于unknow类型,会选择float8版本
select @ '1' -- OK, 调用float8版本 select @ 1 -- OK, 调用integer版本而对于~操作符,它的重载版本包含多个类型大类
ltsql> \do ~ Schema | Name | Right arg type | Result type -----------+------+----------------+------------- pg_catalog | ~ | bigint | bigint pg_catalog | ~ | bit | bit pg_catalog | ~ | inet | inet pg_catalog | ~ | integer | integer pg_catalog | ~ | macaddr | macaddr pg_catalog | ~ | macaddr8 | macaddr8 pg_catalog | ~ | smallint | smallint所以它不支持unknow类型的调用,因为它没有text版本,重载类型包含多个大类,所以无法唯一确定。
select ~ '1'; ERROR: operator is not unique: ~ unknown select ~ 1; -- OK, 调用integer版本
1.5.6. 已知类型优先¶
如果同时存在已知类型和未知类型,且已知类型的所有实参类型一样,则假设所有的unknow类型也是这个类型, 如果能找到唯一的一个匹配的话,就OK.
create function tf29(int4, int4) ... create function tf29(int4, bool) ... select tf29(1, '1'); -- OK, 调用(int4, int4)版本 create function tf30(bool, int4) ... create function tf30(bool, bool) ... select tf30(true, '1'); -- OK, 调用(bool, bool)版本如果经过以上步骤还是不能找到唯一的匹配,则会报错。
1.6. 泛型¶
PostgreSQL定义了一系列称为Pseudo-types的类型集合,包括anyelement, anyarray, anynonarray等, 这种类型不能用来建表,只能用来定义函数或操作符的参数类型或返回值类型。
但是他的定义也很容易让人误解。假设有如下定义
create function tf19(anyelement, anyelement)
有了这个函数,不代表你就可以为所欲为了。它有个严格的限制: 两个参数的类型必须完全一样。 甚至不支持隐式转换。
select tf19(1::int2, 1::int4);
ERROR: function tf19(smallint, integer) does not exist
select tf19('1'::varchar, '1'::text);
ERROR: function tf19(character varying, text) does not exist
select tf19(1::int2, 1::int2); -- OK
select tf19('1'::varchar, '1'::varchar); -- OK
所以从语义上看,这个更像是其他语言中的泛型 void tf19(T a, T b)
在TA的迁移中,用户使用了orafce插件中提供的decode函数,这个函数的前两个参数的类型就是 anyelement, 它对类型一致性的严格限制,导致迁移过程中的一些困扰。
对于不同Pseudo-types类型混用的情况同样存在限制,例如
create function tf20(anyelement, anyarray) ...
select tf20(1, ARRAY[1]); -- OK
select tf20('1'::varchar, ARRAY['1'::varchar]); -- OK
select tf20(1::int2, ARRAY[1]);
ERROR: function tf20(smallint, integer[]) does not exist
select tf20('1'::varchar, ARRAY[1]);
ERROR: function tf20(character varying, integer[]) does not exist
tf20函数要求数组成员的类型和第一个参数类型一致,
它的语义有点像其他语言中的定义 void tf20(T a, T[] b)
在Pseudo-types类型中还有一系列compatible类型,它对类型一致性的限制稍微宽容些,可以支持隐式转换。
create function tf21(anycompatible, anycompatible) ...
select tf21(1::int2, 1::int4); -- OK
select tf21(1::int2, '1'::varchar);
ERROR: function tf21(smallint, character varying) does not exist
Pseudo-types类型的返回值同样受到约束
create function tf22(anyelement, anyelement) returns anyelement
as
$$ select 'ab' $$
language sql;
select tf22(1,1);
ERROR: return type mismatch in function declared to return integer
select tf22('1'::text, '1'::text); -- OK
因为要确定参数类型,所以实参中,Pseudo-types位置的参数不能全部是unknow,否则无法判断泛型类型,从而报错。
create function tf23(anyelement) ...
select tf23('1');
ERROR: could not determine polymorphic type because input has type unknown
1.6.1. 泛型约束规则¶
所有ANYELEMENT的参数,传入类型必须严格一致
1.7. any类型(未完)¶
any类型提供了绕过泛型约束的方法,可以传入任意类型, 比如系统内置的concat,format,num_nulls等函数,都是any类型。
它在使用绑定变量执行(parse,bind)时和直接执行语句(exec)时行为有差异。
只能在内置函数使用这个类型(pg_proc.dat文件)