lightdb 创建自定义类型

lightdb 创建自定义基本类型

本文基于 lightdb 22.3(pg13) 创建一种新的基本类型, 这要求使用低级语言(通常是 C)实现在该类型上操作的函数。如下是各个步骤:

  1. 定义类型 存取数据
  2. 定义类型转换cast 与其他类型建立联系
  3. 定义操作符 比较/操作此类型数据 用于查找, 比如where 条件
  4. 定义操作符类 在类型上使用索引

1. 定义类型 create type

creat type 语法如下

CREATE TYPE name (
    INPUT = input_function,
    OUTPUT = output_function
    [ , RECEIVE = receive_function ]
    [ , SEND = send_function ]
    [ , TYPMOD_IN = type_modifier_input_function ]
    [ , TYPMOD_OUT = type_modifier_output_function ]
    [ , ANALYZE = analyze_function ]
    [ , INTERNALLENGTH = { internallength | VARIABLE } ]
    [ , PASSEDBYVALUE ]
    [ , ALIGNMENT = alignment ]
    [ , STORAGE = storage ]
    [ , LIKE = like_type ]
    [ , CATEGORY = category ]
    [ , PREFERRED = preferred ]
    [ , DEFAULT = default ]
    [ , ELEMENT = element ]
    [ , DELIMITER = delimiter ]
    [ , COLLATABLE = collatable ]
)

其中输入函数(INPUT)和输出函数(OUTPUT)是必须的,这两个函数决定了该类型的值如何表示及存储,输入函数把一个字符串(外部文本表示)转换为内部使用(内存存储)格式,输出函数则把内部表示转换为字符串(用户可见的外部表示)。

  • RECEIVE/SEND 用来定义二进制输入输出,类同INPUT/OUTPUT
  • TYPMOD_IN/TYPMOD_OUT 用于转换类型修饰符,在需要类型修饰符时需定义,如char(5) , 需要通过修饰符5来定义存储大小。TYPMOD_IN 转换为内部使用值存放在pg_type 表的typtypmod列中,具体使用请参考官方文档
  • ANALYZE 用于类型相关的统计信息收集
  • INTERNALLENGTH 类型的内部存储长度, 定长直接正整数, 变长设为VARIABLE
  • PASSEDBYVALUE 设置此值表示这种数据类型只能通过值传递,不能传引用,需为定长
  • ALIGNMENT 存储对齐
  • STORAGE 存储格式,用于为变长数据类型选择存储方式
  • LIKE 指定此数据类型的一些属性与其他的类型一致:internallengthpassedbyvalue、 *alignment*和 storage
  • CATEGORY/PREFERRED 用于隐式类型转换
  • DEFAULT 默认值, 可以被建表时的default子句覆盖
  • ELEMENT 用于数组类型,用于创建的类型包含子元素,如point 类型,除了可以整体操作,还可以通过point[0],point[1] 访问内部值
  • DELIMITER 指定分隔符, 默认逗号 作为数组外部表达时使用
  • COLLATABLE 为true 表示 类型作为字段定义时可以使用COLLATE子句携带排序规则信息

note

  • 只要一种自定义类型被创建,LightDB 会自动创建一种相关的数组类型。
  • 如果数据类型存储可变, 建议指定内部长度为variable并且选择某个不是plain的适当的存储选项,已使用toast
  • 通过pg_type 表可以查询类型相关属性

2. 定义cast

定义了数据类型后需要定义此类型与其他数据类型的转换, 不然只能在相同类型间进行操作。

定义了cast 有时也可以免去定义操作符和操作符族。

create cast语法

CREATE CAST (source_type AS target_type)
    WITH FUNCTION function_name [ (argument_type [, ...]) ]
    [ AS ASSIGNMENT | AS IMPLICIT ]

CREATE CAST (source_type AS target_type)
    WITHOUT FUNCTION
    [ AS ASSIGNMENT | AS IMPLICIT ]

CREATE CAST (source_type AS target_type)
    WITH INOUT
    [ AS ASSIGNMENT | AS IMPLICIT ]
  • 分为三类, 通过函数转换,直接转换(表示该转换可以被“免费”执行而不用调用任何函数。这要求相应的值使用同样的内部表示,二进制兼容),使用类型的输入输出函数转换。

  • 默认情况下,只有一次显式造型请求才会调用造型, 形式是CAST(x AS typename) or x::typename

  • 如果使用了AS ASSIGNMENT, 则在为列赋值时会进行隐式转换。

  • 如果标记为AS IMPLICIT, 则表示在任何情况下都可以隐式转换。定义隐式类型转换需要保证谨慎,在执行的时候可能导致多条路径可选,导致报错。

3. 定义操作符 CREATE OPERATOR

定义操作符不是必须的,在需要使用操作符时,如果早不到完全匹配的操作符,会对类型进行隐式转换,来查找可用的操作符,如果找到即可使用(在src\backend\parser\parse_oper.c下的oper函数中处理, 具体转换规则可以参考官方文档的类型转换-操作符一节1)。如果没有可以使用的则必须定义,不然不能通过条件查找表。

操作符可以包含如下字符:

\+ - * / < > = ~ ! @ # % ^ & | ` ?

一般使用到的有+,-,*,/ 四类运算符,和< ,>,=,<=,>=,!=, ~~ (like), !~ ~(not like)等比较符.

语法

CREATE OPERATOR name (
    {FUNCTION|PROCEDURE} = function_name
    [, LEFTARG = left_type ] [, RIGHTARG = right_type ]
    [, COMMUTATOR = com_op ] [, NEGATOR = neg_op ]
    [, RESTRICT = res_proc ] [, JOIN = join_proc ]
    [, HASHES ] [, MERGES ]
)
  • FUNCTION|PROCEDURE 指定处理函数或存储过程
  • LEFTARG/RIGHTARG, 忽略其中一个可以表示一元操作符

下述选项用于提供优化信息,供优化器使用

  • COMMUTATOR 指定交换子, 如果(a op1 b)等于(b op2 a),则op1, op2 互为交换子
  • NEGATOR 求反器,及反向操作符, 如 = 的反向即为!=, 即(a op1 b)等于not(a op1 b)
  • RESTRICT 指定限制选择度估计函数,用于估计如column OP constant的where 子句能够选出多少行,一般使用内置的估算函数即可。具体参考RESTRICT
  • JOIN 类同RESTRICT 估算join操作会选出多少行 具体参考join
  • HASHES 表明可以基于这个操作符使用hash 连接,只对返回boolean类型的二元操作符有用,并且需要表示某些数据类型的相等。如: ‘=’,具体参考官方文档
  • MERGES 表明可以基于这个操作符使用merge 连接,需要表示某些数据类型的相等,具体参考官方文档

note

  • 通过pg_operator 获取操作符信息

4. 定义操作符类 create operator class

要在类型上使用索引,需要有可用的操作符类。

与操作符相同操作符类不是必须定义的,只要有可以通过隐式转换使用的操作符类即可。具体参见源码分析

LightDB中内建了对表常规访问的支持,但是所有的索引方法都是在pg_am中描述。可以通过编写必要的代码并且在pg_am中创建一项来增加一种新的索引访问方法。

一个索引方法的例程并不直接了解它将要操作的数据类型。而是由一个操作符类标识索引方法用来操作一种特定数据类型的一组操作。之所以被称为操作符类是因为它们指定的一件事情就是可以被用于一个索引的WHERE子句操作符集合(即,能被转换成一个索引扫描条件)。一个操作符类也能指定一些索引方法内部操作所需的支持函数,这些过程不能直接对应于能用于索引的任何WHERE子句操作符。

可以为相同的数据类型和索引方法定义多个操作符类。通过这种方式,可以为一种数据类型定义多个索引语义集合。例如,一个B-树索引要求在它要操作的每一种数据类型上都定义一个排序顺序。对一种复数数据类型来说,拥有一个可以根据复数绝对值排序的 B-树操作符类和另一个可以根据实数部分排序的操作符类可能会有用。典型地,其中一个操作符类将被认为是最常用的并且将被标记为那种数据类型和索引方法的默认操作符类。

相同的操作符类名称可以被用于多个不同的索引方法(例如,B-树和哈希索引方法都有名为int4_ops的操作符类)。但是每一个这样的类都是一个独立实体并且必须被单独定义。

与一个操作符类关联的操作符通过“策略号”标识,它被用来标识每个操作符在其操作符类中的语义。

对于系统来说只有策略信息通常不足以断定如何使用一种索引。实际上,为了能工作,索引方法还要求额外的支持例程。例如,B-树索引方法必须能比较两个键并且决定其中一个是否大于、等于或小于另一个。类似地,哈希索引方法必须能够为键值计算哈希码。这些操作并不对应在 SQL 命令的条件中使用的操作符。它们是索引方法在内部使用的管理例程。

与策略一样,操作符类会标识哪些函数应该为一种给定的数据类型扮演这些角色以及相应的语义解释。索引方法定义它需要的函数集合,而操作符类则会通过为函数分配由索引方法说明的“支持函数号”来标识正确的函数。

此外,一些 opclass 允许用户指定控制其行为的参数。每个内置索引访问方法都有一个可选的 options支持函数,它定义了一组特定于 opclass 的参数。

操作符类用来指明索引方法进行where条件查找时需要的操作符2,以及支持索引正常工作或更优的一些支持函数3

下面介绍操作符类的定义:

语法4

CREATE OPERATOR CLASS name [ DEFAULT ] FOR TYPE data_type
  USING index_method [ FAMILY family_name ] AS
  {  OPERATOR strategy_number operator_name [ ( op_type, op_type ) ] [ FOR SEARCH | FOR ORDER BY sort_family_name ]
   | FUNCTION support_number [ ( op_type [ , op_type ] ) ] function_name ( argument_type [, ...] )
   | STORAGE storage_type
  } [, ... ]
  • 一个操作符类定义一种特殊的数据类型如何被用于一个索引
  • DEFAULT 指定为数据类型的默认操作符
  • data_type 数据类型
  • index_method 索引方法
  • FAMILY family_name 指定FAMILY 则表示把操作符类加入到一个现有的族中, 没有指定则放到一个同名的族中(如果操作符族不存在则创建)
  • strategy_number 索引方法策略号,参见Index Method Strategies
  • operator_name 一个与该操作符类相关联的操作符的名称(可以被模式限定)
  • op_type 在一个OPERATOR子句中,这表示该操作符的操作数的数据类型,或者用NONE来表示一个左一元或者右一元操作符。在操作数的数据类型与该操作符的数据类型相同的一般情况下,操作数的数据类型可以被省略。

更复杂的用法请参考官方文档

Example

CREATE OPERATOR CLASS int8_ops
DEFAULT FOR TYPE int8 USING btree FAMILY integer_ops AS
  -- 标准 int8 比较
  OPERATOR 1 < ,  -- <(int8,int8)
  OPERATOR 2 <= ,
  OPERATOR 3 = ,
  OPERATOR 4 >= ,
  OPERATOR 5 > ,
  FUNCTION 1 btint8cmp(int8, int8) ,
  FUNCTION 2 btint8sortsupport(internal) ,
  FUNCTION 3 in_range(int8, int8, int8, boolean, boolean) ,
  FUNCTION 4 btequalimage(oid) ;

索引使用操作符类源码分析

索引在创建的时候会查找索引字段的数据类型对应的操作符类。

DefineIndex
	ComputeIndexAttrs
		ResolveOpClass
			GetDefaultOpClass
			
/*
从pg_opclass 中查找所有使用了此索引方法的opclass(只查找操作符族中的默认操作符类),如果找到数据类型一直的直接返回,没有则查找数据类型是BinaryCoercible的操作符类(通过调用IsBinaryCoercible),能获取唯一一个则返回(操作符类的数据类型为字段类型所属分类中标记为preferred的,且只有一个 ncompatiblepreferred == 1 或者 都不为preferred且只有一个(ncompatiblepreferred == 0 && ncompatible == 1)),没有则失败
*/
GetDefaultOpClass(Oid type_id, Oid am_id)
{

	/* If it's a domain, look at the base type instead */
	type_id = getBaseType(type_id);

	tcategory = TypeCategory(type_id);

	/*
	 * We scan through all the opclasses available for the access method,
	 * looking for one that is marked default and matches the target type
	 * (either exactly or binary-compatibly, but prefer an exact match).
	 *
	 * We could find more than one binary-compatible match.  If just one is
	 * for a preferred type, use that one; otherwise we fail, forcing the user
	 * to specify which one he wants.  (The preferred-type special case is a
	 * kluge for varchar: it's binary-compatible to both text and bpchar, so
	 * we need a tiebreaker.)  If we find more than one exact match, then
	 * someone put bogus entries in pg_opclass.
	 */
	rel = table_open(OperatorClassRelationId, AccessShareLock);

	ScanKeyInit(&skey[0],
				Anum_pg_opclass_opcmethod,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(am_id));

	scan = systable_beginscan(rel, OpclassAmNameNspIndexId, true,
							  NULL, 1, skey);

	while (HeapTupleIsValid(tup = systable_getnext(scan)))
	{
		Form_pg_opclass opclass = (Form_pg_opclass) GETSTRUCT(tup);

		/* ignore altogether if not a default opclass */
		if (!opclass->opcdefault)
			continue;
		if (opclass->opcintype == type_id)
		{
			nexact++;
			result = opclass->oid;
		}
		else if (nexact == 0 &&
				 IsBinaryCoercible(type_id, opclass->opcintype))
		{
			if (IsPreferredType(tcategory, opclass->opcintype))
			{
				ncompatiblepreferred++;
				result = opclass->oid;
			}
			else if (ncompatiblepreferred == 0)
			{
				ncompatible++;
				result = opclass->oid;
			}
		}
	}

	systable_endscan(scan);

	table_close(rel, AccessShareLock);

	/* raise error if pg_opclass contains inconsistent data */
	if (nexact > 1)
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("there are multiple default operator classes for data type %s",
						format_type_be(type_id))));

	if (nexact == 1 ||
		ncompatiblepreferred == 1 ||
		(ncompatiblepreferred == 0 && ncompatible == 1))
		return result;

	return InvalidOid;
}

note

pg_opclass定义索引访问方法的操作符类

5. 操作符族 Operator Families

如上的操作符类只处理一种数据类型,通过定义操作符族也可以支持索引列与其他数据类型比较。操作符族也可以把一些相关的操作符类放在一起分组,有助于优化器进行SQL查询优化。

操作符类必然属于一个操作符族。

一个操作符类应该包含一个索引在特定数据类型上正常工作所需要的最小操作符和函数集合,而相关但不关键的操作符可以作为操作符族的松散成员被加入。

具体可以参考LightDB内建的 B-树操作符族integer_ops:

CREATE OPERATOR FAMILY integer_ops USING btree;

CREATE OPERATOR CLASS int8_ops
DEFAULT FOR TYPE int8 USING btree FAMILY integer_ops AS
  -- 标准 int8 比较
  OPERATOR 1 < ,
  OPERATOR 2 <= ,
  OPERATOR 3 = ,
  OPERATOR 4 >= ,
  OPERATOR 5 > ,
  FUNCTION 1 btint8cmp(int8, int8) ,
  FUNCTION 2 btint8sortsupport(internal) ,
  FUNCTION 3 in_range(int8, int8, int8, boolean, boolean) ,
  FUNCTION 4 btequalimage(oid) ;

CREATE OPERATOR CLASS int4_ops
DEFAULT FOR TYPE int4 USING btree FAMILY integer_ops AS
  -- 标准 int4 比较
  OPERATOR 1 < ,
  OPERATOR 2 <= ,
  OPERATOR 3 = ,
  OPERATOR 4 >= ,
  OPERATOR 5 > ,
  FUNCTION 1 btint4cmp(int4, int4) ,
  FUNCTION 2 btint4sortsupport(internal) ,
  FUNCTION 3 in_range(int4, int4, int4, boolean, boolean) ,
  FUNCTION 4 btequalimage(oid) ;

CREATE OPERATOR CLASS int2_ops
DEFAULT FOR TYPE int2 USING btree FAMILY integer_ops AS
  -- 标准 int2 比较
  OPERATOR 1 < ,
  OPERATOR 2 <= ,
  OPERATOR 3 = ,
  OPERATOR 4 >= ,
  OPERATOR 5 > ,
  FUNCTION 1 btint2cmp(int2, int2) ,
  FUNCTION 2 btint2sortsupport(internal) ,
  FUNCTION 3 in_range(int2, int2, int2, boolean, boolean) ,
  FUNCTION 4 btequalimage(oid) ;

ALTER OPERATOR FAMILY integer_ops USING btree ADD
  -- 跨类型比较 int8 vs int2
  OPERATOR 1 < (int8, int2) ,
  OPERATOR 2 <= (int8, int2) ,
  OPERATOR 3 = (int8, int2) ,
  OPERATOR 4 >= (int8, int2) ,
  OPERATOR 5 > (int8, int2) ,
  FUNCTION 1 btint82cmp(int8, int2) ,

  -- 跨类型比较 int8 vs int4
  OPERATOR 1 < (int8, int4) ,
  OPERATOR 2 <= (int8, int4) ,
  OPERATOR 3 = (int8, int4) ,
  OPERATOR 4 >= (int8, int4) ,
  OPERATOR 5 > (int8, int4) ,
  FUNCTION 1 btint84cmp(int8, int4) ,

  -- 跨类型比较 int4 vs int2
  OPERATOR 1 < (int4, int2) ,
  OPERATOR 2 <= (int4, int2) ,
  OPERATOR 3 = (int4, int2) ,
  OPERATOR 4 >= (int4, int2) ,
  OPERATOR 5 > (int4, int2) ,
  FUNCTION 1 btint42cmp(int4, int2) ,

  -- 跨类型比较 int4 vs int8
  OPERATOR 1 < (int4, int8) ,
  OPERATOR 2 <= (int4, int8) ,
  OPERATOR 3 = (int4, int8) ,
  OPERATOR 4 >= (int4, int8) ,
  OPERATOR 5 > (int4, int8) ,
  FUNCTION 1 btint48cmp(int4, int8) ,

  -- 跨类型比较 int2 vs int8
  OPERATOR 1 < (int2, int8) ,
  OPERATOR 2 <= (int2, int8) ,
  OPERATOR 3 = (int2, int8) ,
  OPERATOR 4 >= (int2, int8) ,
  OPERATOR 5 > (int2, int8) ,
  FUNCTION 1 btint28cmp(int2, int8) ,

  -- 跨类型比较 int2 vs int4
  OPERATOR 1 < (int2, int4) ,
  OPERATOR 2 <= (int2, int4) ,
  OPERATOR 3 = (int2, int4) ,
  OPERATOR 4 >= (int2, int4) ,
  OPERATOR 5 > (int2, int4) ,
  FUNCTION 1 btint24cmp(int2, int4) ,

  -- 跨类型的in_range函数
  FUNCTION 3 in_range(int4, int4, int8, boolean, boolean) ,
  FUNCTION 3 in_range(int4, int4, int2, boolean, boolean) ,
  FUNCTION 3 in_range(int2, int2, int8, boolean, boolean) ,
  FUNCTION 3 in_range(int2, int2, int4, boolean, boolean) ;

note

pg_amop包含操作符族中的操作符,对于一个操作符族中的每一个成员即操作符都在pg_amop中有一行。一个成员可以是一个搜索操作符或者一个排序操作符。

pg_amproc 包含操作符族中的支持函数,属于一个操作符族的每一个支持函数在pg_amproc中都有一行。

pg_opfamily定义了操作符族, 包含操作符族的信息。

参考文档


  1. http://www.light-pg.com/docs/lightdb/13.3-22.2/typeconv-oper.html ↩︎

  2. http://www.light-pg.com/docs/lightdb/13.3-22.2/xindex.html#XINDEX-STRATEGIES ↩︎

  3. http://www.light-pg.com/docs/lightdb/13.3-22.2/xindex.html#XINDEX-SUPPORT ↩︎

  4. http://www.light-pg.com/docs/lightdb/13.3-22.2/sql-createopclass.html ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

紫无之紫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值