Chapter 18. 扩展索引接口

到目前为止我们描述的过程可以让你定义一个新类型,新函数和新操作符. 但是,我们还不能在一个新类型或它的操作符上面定义一个从属索引(象一个 B-treeR-tree 或 或哈希(hash,散列)访问方式).

回过头来看看 Figure 12-1。 右半部分显示了那些我们如果要告诉 Postgres 如何在索引上(例如, pg_am,pg_amop, pg_amproc,pg_operatorpg_opclass) 去使用用户定义类型和/或用户定义操作符时必须要修改的表. 不幸的是,我们还没有可以做这些事情的简单命令. 我们将通过实例来演示如何通过修改这些表来实现上面需求, 此例子是:一个新的用于 B-tree 访问模式的操作符表,它把复数以绝对值升序的顺序排列.

pg_am 表为每个用户定义的访问模式都保留一条记录. 对堆的访问模式的支持内建于 Postgres,但其他所有访问模式在这里都有描述. 表结构是

Table 18-1. 索引表结构

字段/属性描述
amname访问模式名称
amowner所有者标识
amstrategies此访问模式的(访问)策略数(见下面)
amsupport此访问模式支持的过程数(见下面)
amorderstrategy如果该索引没有提供排序顺序,为零, 否则是描述了排序顺序的策略操作符的策略数
amgettuple 
aminsert 
...该访问模式的接口过程的过程标识. 例如,regproc 表示打开,关闭,和从这里出现的访问模式中获取记录.

pg_am里的记录的对象标识object ID) 用做其他很多表的外部键值. 你用不着向这个表里面增加新记录;你要 关心的是你想要扩展的访问模式记录的对象标识object ID):

SELECT oid FROM pg_am WHERE amname = 'btree';

 oid
-----
 403
(1 row)
   
我们稍后将在一个 WHERE子句中使用这个 SELECT

amstrategies 字段的存在用于使数据类型之间的比较标准化. 例如,B-tree 对键字, 小于号到大于号施加了很严格的顺序. 因为 Postgres 允许用户定义操作符,所以 Postgres 不能只是看到操作符的名称 (如">" 或 "<")就认为是什么样的比较.实际上, 一些访问模式并不强加任何顺序要求. 例如, R-tree的表达式是长方形包含关系,而一个散列 (hash)数据结构表达式只是与散列(hash)函数的结果有一些位 (bitwise)相似. Postgres 需要某种连贯的方法从你的查询里取来一个资格(条件), 查看一下操作符然后马上决定是否有一个可用的索引存在. 这意味着 Postgres 需要知道类似 "<=" 和 ">" 操作符分割一个 B-tree这样的信息. Postgres 使用策略来表达这些操作符之间的关系以及它们可以用于扫描索引的方法.

定义一套新的策略超出了这个讨论的范畴,但是我们将分析 B-tree 策略如何工作, 因为你将需要了解这些来增加一个新的操作符表. 在pg_am 表里, amstrategies 字段是为这个访问模式定义的策略数量. 对于 B-tree,这个数量是 5.这些策略对应于

Table 18-2. B-tree 策略

操作索引
小于1
小于或等于2
等于3
大于或等于4
大于5

方法是你需要增加与上面对应的比较过程到 pg_amop 关系里去(见下面 ). 访问模式代码可以使用这些策略数(不管数据类型是什么), 来确定如何分割 B-tree,计算选择性等. 先不必关心增加过程的细节问题;只要明白我们必须有一套这样的过程用于 int2,int4,oid, 和所有其他 B-tree 可以操作的数据类型.

有时候,策略的信息还不足以让系统决定如何使用某个索引. 一些访问模式就需要其他的一些过程来保证能够工作. 例如,B-tree 访问模式必须能够比较两个键字以决定其中一个是大于,等于, 还是小于另外一个. 类似的,R-tree访问模式必须能够计算长方形的相交, 并,和大小等.这些操作不能在 SQL 查询里与用户的资格(条件)对应; 它们是被访问模式的管理性质的过程内部调用的过程.

为了管理所有 Postgres 的访问模式支持的各种各样的过程, pg_am 包含一个字段叫 amsupport .这个字段记录被某个访问模式支持的过程的个数. 对于 B-tree 这个数字是一 -一个接受两个键字并且根据第一个键字是否小于, 等于或大于第二个键字而返回 -1,0,或 +1 的过程.

注意: 严格的说,这个过程可以返回一个负数 (< 0),0,或一个非零正数(> 0).

pg_am 里的 amstrategies 条目只是正在讨论的访问模式定义的策略数.用于小于,小于等于等的过程不在 pg_am 里出现.类似的, amsupport 只是访问模式需要的支持过程个数.实际的过程在其他地方列出.

顺便说一句,amorderstrategy 字段报告此访问模式是否支持排序的扫描。 零意味着它不支持;如果该访问模式支持排序的扫描, amorderstrategy 就是对应排序操作符的策略过程的数目。例如, btree 的 amorderstrategy = 1 ,就是它的"小于"策略数目。

下一个让人感兴趣的表是 pg_opclass. 这个表的存在只是用于把一个名称 和一个缺省类型与一个对象标识(oid)联结起来. 一些现有的 opclasses 是 int2_ops, int4_ops,oid_ops。 你需要用你的 opclass 名称(例如, complex_abs_ops) )增加一条记录到 pg_opclass里面.这条记录的 oid 是其他表的外部键字,尤其是 pg_amop

INSERT INTO pg_opclass (opcname, opcdeftype)
    SELECT 'complex_abs_ops', oid FROM pg_type WHERE typname = 'complex';

SELECT oid, opcname, opcdeftype
    FROM pg_opclass
    WHERE opcname = 'complex_abs_ops';

  oid   |     opcname     | opcdeftype
--------+-----------------+------------
 277975 | complex_abs_ops |     277946
(1 row)
   
注意你的 pg_opclass 记录的对象标识(oid) 将会不一样!先不考虑这些。 我们将在稍后从系统获取这些数字,就象我们在这里获取该类型的 oid 一样。

上面的例子假设你想把这个新的 opclass 做为 complex 数据类型的缺省索引. 如果不是这样,只需要向 opcdeftype 插入零, 而不是插入该数据类型的 oid:

INSERT INTO pg_opclass (opcname, opcdeftype) VALUES ('complex_abs_ops', 0);
   

因此,现在我们有了一个访问方式和一个操作符表. 我们还需要一套操作符; 用于定义操作符的过程已经在这份手册的早先部分讨论过了. 对这个用于 Btrees 的 complex_abs_ops 操作符表, 我们需要的操作符是:

        absolute value less-than
        absolute value less-than-or-equal
        absolute value equal
        absolute value greater-than-or-equal
        absolute value greater-than
   

假设实现函数册代码放在文件 PGROOT/src/tutorial/complex.c

有一部分代码看起来象:(注意我们在余下的例子中将只展示等号操作符. 其他四种操作符都非常相似,请参考 complex.ccomplex.source 获取详细信息.)

#define Mag(c) ((c)->x*(c)->x + (c)->y*(c)->y)

         bool
         complex_abs_eq(Complex *a, Complex *b)
         {
             double amag = Mag(a), bmag = Mag(b);
             return (amag==bmag);
         }
   

我们用下面语句让 Postgres 这样识别这个函数:

CREATE FUNCTION complex_abs_eq(complex, complex)
              RETURNS bool
              AS 'PGROOT/tutorial/obj/complex.so'
              LANGUAGE 'c';
   

有几个很重要的问题要在这里指出.

首先,请注意定义了用于 complex 的小于,小于或等于,等于,大于或等于和大于操作符. 我们可以只有一个命名了的操作符名, (比如 =)并把类型 complex 做为其两个操作数.这种情况下我们没有其它用于 complex 的 = 操作符,但是如果我们要制作一个实用的数据类型, 我们可能需要 = 做为用于复数的通用等于操作的操作符. 这种情况,我们可能需要使用一些其他操作符名称来命名 complex_abs_eq.

第二,尽管 Postgres 可以处理同名操作符,只要它们的输入数据类型不同, 而 C 只能处理一个具有给出名称的全局过程. 因此我们不能把 C 函数命名为象 abs_eq 这样简单的名字. 通常在 C 函数名里面包含数据类型名称是一个好习惯, 这样就不会和用于其它数据类型的函数冲突.

第三,我们可以制作函数abs_eq的 Postgres 名称, 依靠 Postgres 通过输入数据类型的不同区分任何其他同名 Postgres 函数. 为了令例子简单,我们做的函数在 C 层次和 Postgres 层次都有相同的名称.

最后,请注意这些操作符函数返回布尔值.访问模式依靠这个特性. (另一方面,支持函数返回特定的访问模式的希望值 -- 在这种情况下是一个符号整数.) 文件里最终的过程是我们在讨论表 pg_am 里 amsupport 字段时提到过的"支持过程". 我们稍后将用到这个东西.目前我们暂时先忽略它.

现在可以定义使用它们的操作符了:

CREATE OPERATOR = (
     leftarg = complex, rightarg = complex,
     procedure = complex_abs_eq,
     restrict = eqsel, join = eqjoinsel
         )
   
里的重要问题是过程名称(就是上面定义的 C函数) 和这个关系和连接选择性函数. 你应该只使用例子里(参阅 complex.source )的选择性函数.请注意还要有这样的用于小于,等于和大于情况的函数. 你必须提供这些(函数),否则优化器将无法有效地使用该索引.

下一步是为这些操作符向 pg_amop 关系里面增加条目. 要做这些,我们将需要我们刚定义的这些操作符的 oid. 我们将从所有操作符中找出接受两个复数的操作符名称,并把它们选出来:

    SELECT o.oid AS opoid, o.oprname
     INTO TABLE complex_ops_tmp
     FROM pg_operator o, pg_type t
     WHERE o.oprleft = t.oid and o.oprright = t.oid
      and t.typname = 'complex';

 opoid  | oprname
--------+---------
 277963 | +
 277970 | <
 277971 | <=
 277972 | =
 277973 | >=
 277974 | >
(6 rows)
   
(同样,一些你的 oid (对象标识)将可以说肯定是不同的.) 我们感兴趣的操作符是那些 oid(对象标识)在 277970 到 277974 之间的.你得到的值将可能不同, 你应该用你得到的值代替上面的数值. 我们通过一条 select 语句实现这个目的。

现在我们已经准备好用我们新的操作符表更新 pg_amop 表.在整个讨论中最重要的是在 pg_amop里, 操作符是顺序排列的,从小于等于到大于等于. 我们用下面方法增加我们需要的行:

    INSERT INTO pg_amop (amopid, amopclaid, amopopr, amopstrategy)
        SELECT am.oid, opcl.oid, c.opoid, 1
        FROM pg_am am, pg_opclass opcl, complex_ops_tmp c
        WHERE amname = 'btree' AND
            opcname = 'complex_abs_ops' AND
            c.oprname = '<';
   
然后添加其他操作符,相应地替换上面第三行的 "1" 和最后一行的 "<"。 注意顺序: "小于" 是 1,"小于或等于" 是 2,"等于" 是 3,"大于或等于" 是 4, 而 "大于" 是 5。

下一步是注册我们前面在讨论 pg_am 时描述过的"支持过程". 支持过程的 oid (对象标识)存放在表 pg_amproc里, 用访问模式的 oid (对象标识)和操作符表 oid (对象标识)做关键字.首先,我们需要在 Postgres 里注册函数(还记得我们把实现这个过程的 C 代码放在了我们实现操作符过程的文件的底部吗?):

    CREATE FUNCTION complex_abs_cmp(complex, complex)
     RETURNS int4
     AS 'PGROOT/tutorial/obj/complex.so'
     LANGUAGE 'c';

    SELECT oid, proname FROM pg_proc
     WHERE proname = 'complex_abs_cmp';

  oid   |     proname
--------+-----------------
 277997 | complex_abs_cmp
(1 row)
   
(同样,你的 oid (对象标识)的数字将可能不同.) 我们可以用下面方法增加新的记录:
    INSERT INTO pg_amproc (amid, amopclaid, amproc, amprocnum)
        SELECT a.oid, b.oid, c.oid, 1
            FROM pg_am a, pg_opclass b, pg_proc c
            WHERE a.amname = 'btree' AND
                b.opcname = 'complex_abs_ops' AND
                c.proname = 'complex_abs_cmp';
   

这样我们就完成了!(乌拉.)现在我们可以在一个 复数(complex) 列上创建和使用 btree 索引了.