pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
到目前为止所描述的过程使我们能够定义新类型、新函数以及新操作符。然而,我们还不能在一个新类型或其操作符上定义一个二级索引(例如 B-tree、R-tree 或哈希访问方法)。
回头看图 12.1。右半部分显示了我们必须修改的目录,以便告诉 Postgres 如何在索引中使用用户定义的类型和/或用户定义的操作符 (即 pg_am, pg_amop, pg_amproc, pg_operator 和 pg_opclass)。 遗憾的是,没有一条简单的命令可以完成这件事。我们将通过一个贯穿始终的示例来演示如何修改这些目录:为 B-tree 访问方法定义一个新的操作符类,以便按绝对值升序存储和排序复数。
pg_am 表为每个用户定义的访问方法保存一行。堆访问方法的支持内置于 Postgres 中,但每个其他访问方法都在这里描述。其模式是
表 18.1. 索引模式
| 列 | 描述 |
|---|---|
| amname | 访问方法的名称 |
| amowner | 属主的用户 id |
| amstrategies | 该访问方法的策略数目(见下文) |
| amsupport | 该访问方法的支持例程数目(见下文) |
| amorderstrategy | 如果索引不提供排序顺序则为零,否则是描述该排序顺序的策略操作符的策略号 |
| amgettuple | |
| aminsert | |
| ... | 访问方法接口例程的过程标识符。例如,打开、关闭访问方法以及从中获取行的 regproc id 就出现在这里。 |
pg_am 中该行的对象 ID 被用作许多其他表中的外键。你不需要向这个表添加新行;你所关心的只是你想扩展的访问方法行的对象 ID:
SELECT oid FROM pg_am WHERE amname = 'btree'; oid ----- 403 (1 row)
我们稍后会在 WHERE 子句中使用那条 SELECT。
amstrategies 列的存在是为了标准化跨数据类型的比较。例如,B-tree 对键施加了严格的从小到大的顺序。由于 Postgres 允许用户定义操作符,Postgres 不能仅凭操作符名称(例如 ">" 或 "<")就判断它是哪一类比较。事实上,某些访问方法根本不施加任何排序。例如,R-tree 表达一种矩形包含关系,而哈希数据结构只表达基于哈希函数值的按位相似性。Postgres 需要某种一致的方式,来接受你查询中的条件、查看操作符,然后判断是否存在可用的索引。这蕴含着 Postgres 需要知道,例如,"<=" 和 ">" 操作符划分一个 B-tree。Postgres 使用策略来表达操作符与它们可用于扫描索引的方式之间的这些关系。
定义一组新的策略超出了本讨论的范围,但我们将解释 B-tree 策略如何工作,因为你要添加一个新的操作符类就需要了解它。在 pg_am 表中,amstrategies 列是该访问方法所定义的策略数目。对 B-tree 来说,这个数是 5。这些策略对应于
表 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 相关联。一些现有的操作符类是 int2_ops, int4_ops, 和 oid_ops。你需要把带有你的操作符类名(例如 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 一样从系统中取得这个数。
上述示例假定你希望把这个新操作符类设为 complex 数据类型的默认索引操作符类。如果不是这样,只需向 opcdeftype 中插入零,而不是插入该数据类型的 oid:
INSERT INTO pg_opclass (opcname, opcdeftype) VALUES ('complex_abs_ops', 0);
现在我们有了一个访问方法和一个操作符类。我们还需要一组操作符。定义操作符的过程已在本手册前面讨论过。对于 Btree 上的 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 中
C 代码的一部分如下所示:(注意,在余下的示例中我们只展示相等操作符。其他四个操作符非常类似。细节请参阅 complex.c 或 complex.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 函数名中包含数据类型名称是个好习惯,这样就不会与其他数据类型的函数发生冲突。
第三,我们本可以把该函数的 Postgres 名称取为 abs_eq,并依靠 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。我们将查找所有取两个 complex 类型操作数的操作符的名称,并挑出我们的那些:
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 索引了。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。