GiST 是 Generalized Search Tree(广义搜索树)的 缩写。它是一种平衡的、树状结构的访问方法,充当实现任意索引方案的 基础模板。B-tree、R-tree 以及许多其他索引方案都可以在 GiST 中实现。
GiST 的一个优点是:它允许由该数据类型领域的专家 (而非数据库专家)来开发带有相应访问方法的自定义数据类型。
这里的一些信息源自加州大学伯克利分校的 GiST Indexing Project 网站以及 Marcel Kornacker 的论文 Access Methods for Next-Generation Database Systems。 PostgreSQL 中的 GiST 实现主要由 Teodor Sigaev 和 Oleg Bartunov 维护,更多信息见他们的 网站。
PostgreSQL 核心发行版包含 Table 65.1 所示的 GiST 操作符类。 (Appendix F 中描述的一些可选模块提供了额外的 GiST 操作符类。)
Table 65.1. 内置 GiST 操作符类
| Name | Indexable Operators | Ordering Operators |
|---|---|---|
box_ops | << (box, box) | <-> (box, point) |
&< (box, box) | ||
&& (box, box) | ||
&> (box, box) | ||
>> (box, box) | ||
~= (box, box) | ||
@> (box, box) | ||
<@ (box, box) | ||
&<| (box, box) | ||
<<| (box, box) | ||
|>> (box, box) | ||
|&> (box, box) | ||
circle_ops | << (circle, circle) | <-> (circle, point) |
&< (circle, circle) | ||
&> (circle, circle) | ||
>> (circle, circle) | ||
<@ (circle, circle) | ||
@> (circle, circle) | ||
~= (circle, circle) | ||
&& (circle, circle) | ||
|>> (circle, circle) | ||
<<| (circle, circle) | ||
&<| (circle, circle) | ||
|&> (circle, circle) | ||
inet_ops | << (inet, inet) | |
<<= (inet, inet) | ||
>> (inet, inet) | ||
>>= (inet, inet) | ||
= (inet, inet) | ||
<> (inet, inet) | ||
< (inet, inet) | ||
<= (inet, inet) | ||
> (inet, inet) | ||
>= (inet, inet) | ||
&& (inet, inet) | ||
multirange_ops | = (anymultirange, anymultirange) | |
&& (anymultirange, anymultirange) | ||
&& (anymultirange, anyrange) | ||
@> (anymultirange, anyelement) | ||
@> (anymultirange, anymultirange) | ||
@> (anymultirange, anyrange) | ||
<@ (anymultirange, anymultirange) | ||
<@ (anymultirange, anyrange) | ||
<< (anymultirange, anymultirange) | ||
<< (anymultirange, anyrange) | ||
>> (anymultirange, anymultirange) | ||
>> (anymultirange, anyrange) | ||
&< (anymultirange, anymultirange) | ||
&< (anymultirange, anyrange) | ||
&> (anymultirange, anymultirange) | ||
&> (anymultirange, anyrange) | ||
-|- (anymultirange, anymultirange) | ||
-|- (anymultirange, anyrange) | ||
point_ops | |>> (point, point) | <-> (point, point) |
<< (point, point) | ||
>> (point, point) | ||
<<| (point, point) | ||
~= (point, point) | ||
<@ (point, box) | ||
<@ (point, polygon) | ||
<@ (point, circle) | ||
poly_ops | << (polygon, polygon) | <-> (polygon, point) |
&< (polygon, polygon) | ||
&> (polygon, polygon) | ||
>> (polygon, polygon) | ||
<@ (polygon, polygon) | ||
@> (polygon, polygon) | ||
~= (polygon, polygon) | ||
&& (polygon, polygon) | ||
<<| (polygon, polygon) | ||
&<| (polygon, polygon) | ||
|&> (polygon, polygon) | ||
|>> (polygon, polygon) | ||
range_ops | = (anyrange, anyrange) | |
&& (anyrange, anyrange) | ||
&& (anyrange, anymultirange) | ||
@> (anyrange, anyelement) | ||
@> (anyrange, anyrange) | ||
@> (anyrange, anymultirange) | ||
<@ (anyrange, anyrange) | ||
<@ (anyrange, anymultirange) | ||
<< (anyrange, anyrange) | ||
<< (anyrange, anymultirange) | ||
>> (anyrange, anyrange) | ||
>> (anyrange, anymultirange) | ||
&< (anyrange, anyrange) | ||
&< (anyrange, anymultirange) | ||
&> (anyrange, anyrange) | ||
&> (anyrange, anymultirange) | ||
-|- (anyrange, anyrange) | ||
-|- (anyrange, anymultirange) | ||
tsquery_ops | <@ (tsquery, tsquery) | |
@> (tsquery, tsquery) | ||
tsvector_ops | @@ (tsvector, tsquery) |
出于历史原因,inet_ops 操作符类不是
inet 和 cidr 类型的默认类。
要使用它,请在 CREATE INDEX 中提及类名,例如
CREATE INDEX ON my_table USING GIST (my_inet_column inet_ops);
按照传统做法,实现一个新的索引访问方法意味着大量困难的工作。 必须了解数据库的内部机制,例如锁管理器和预写式日志。 GiST 接口具有很高的抽象层次,只要求访问方法的 实现者实现被访问数据类型的语义。GiST 层本身负责 并发、日志记录以及树结构的搜索。
这种可扩展性不应与将讨论的其他标准搜索树在所能处理数据方面的
可扩展性相混淆。例如,PostgreSQL 支持可
扩展的 B-tree 和哈希索引。这意味着您可以用
PostgreSQL 对任何想要的数据类型构建
B-tree 或哈希索引。但 B-tree 只支持范围谓词
(<、=、>),
哈希索引只支持等值查询。
因此,如果用一个 PostgreSQL B-tree 为 图像集合建立索引,您只能发出诸如“imagex 是否等于 imagey”、“imagex 是否小于 imagey” 和 “imagex 是否大于 imagey” 这样的查询。取决于您在这个 上下文中如何定义“等于”、“小于”和 “大于”,这可能有用。 然而,通过使用基于 GiST 的索引,您可以创建提出 领域特定问题的方式,例如“找出所有马的图像”或 “找出所有过曝的图像”。
要让一个 GiST 访问方法运转起来,只需实现若干 用户定义的方法,这些方法定义了树中键的行为。当然,要支持花哨的 查询,这些方法也得相当精巧,但对于所有标准查询(B-tree、R-tree 等)而言它们相对直白。简而言之, GiST 把可扩展性与通用性、代码复用以及简洁的 接口结合在了一起。
GiST 的索引操作符类必须提供五个方法,另有七个
可选方法。
索引的正确性由 same、
consistent 和 union
方法的正确实现来保证,而索引的效率(大小和速度)则取决于
penalty 和 picksplit
方法。
两个可选方法是 compress 和
decompress,它们允许索引拥有与被索引数据不同
类型的内部树数据。叶子应当是被索引的数据类型,而其他树节点可以是
任何 C 结构(但您仍然必须遵守 PostgreSQL
的数据类型规则,可变大小数据参见 varlena)。
如果树的内部数据类型存在于 SQL 层面,可以使用
CREATE OPERATOR CLASS 命令的
STORAGE 选项。
可选的第八个方法是 distance,如果操作符类希望
支持有序扫描(最近邻搜索)则需要它。如果操作符类希望支持仅索引
扫描,则需要可选的第九个方法 fetch,除非省略了
compress 方法。如果操作符类有用户指定的参数,
则需要可选的第十个方法 options。
可选的第十一个方法 sortsupport 用于加速构建
GiST 索引。
可选的第十二个方法 stratnum 用于把比较类型
(来自
src/include/access/cmptype.h)转换为操作符类
使用的策略号。这让核心代码能够为时间约束索引查找操作符。
consistent
给定一个索引条目 p 和一个查询值
q,此函数判断该索引条目是否与查询
“一致”;也就是说,谓词
“indexed_column
indexable_operator q”
是否可能对该索引条目所代表的某一行成立?对于叶索引条目,这等价于
测试可索引条件;而对于内部树节点,它决定是否需要扫描该树节点所
代表的索引子树。当结果为 true 时,还必须返回
一个 recheck 标志。
它指示该谓词是确定为真还是只是可能为真。
如果 recheck = false,则索引
已经精确地测试了谓词条件;而如果 recheck
= true,该行只是一个候选匹配。这种情况下,
系统会自动针对实际行值求值
indexable_operator,以判断它是否真正
匹配。这一约定使 GiST 能够同时支持无损和有损
的索引结构。
该函数的 SQL 声明必须形如:
CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal) RETURNS bool AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
C 模块中匹配的代码可以遵循这个骨架:
PG_FUNCTION_INFO_V1(my_consistent);
Datum
my_consistent(PG_FUNCTION_ARGS)
{
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
data_type *query = PG_GETARG_DATA_TYPE_P(1);
StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
/* Oid subtype = PG_GETARG_OID(3); */
bool *recheck = (bool *) PG_GETARG_POINTER(4);
data_type *key = DatumGetDataType(entry->key);
bool retval;
/*
* determine return value as a function of strategy, key and query.
*
* Use GIST_LEAF(entry) to know where you're called in the index tree,
* which comes handy when supporting the = operator for example (you could
* check for non empty union() in non-leaf nodes and equality in leaf
* nodes).
*/
*recheck = true; /* or false if check is exact */
PG_RETURN_BOOL(retval);
}
这里,key 是索引中的一个元素,
query 是正在索引中查找的值。
StrategyNumber
参数指示您的操作符类的哪个操作符正在被应用 — 它与
CREATE OPERATOR CLASS 命令中的某个操作符编号
相匹配。
取决于类中包含了哪些操作符,query 的数据类型
可能随操作符而变化,因为它将是操作符右侧的任何类型,可能与左侧
出现的被索引数据类型不同。
(上面的代码骨架假定只有一种可能的类型;如果不是,取
query 参数值时就必须依赖操作符。)建议
consistent 函数的 SQL 声明对
query 参数使用操作符类的被索引数据类型,即使
实际类型可能因操作符而异。
union此方法合并树中的信息。给定一组条目,此函数生成一个代表所有给定 条目的新索引条目。
该函数的 SQL 声明必须形如:
CREATE OR REPLACE FUNCTION my_union(internal, internal) RETURNS storage_type AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
C 模块中匹配的代码可以遵循这个骨架:
PG_FUNCTION_INFO_V1(my_union);
Datum
my_union(PG_FUNCTION_ARGS)
{
GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
GISTENTRY *ent = entryvec->vector;
data_type *out,
*tmp,
*old;
int numranges,
i = 0;
numranges = entryvec->n;
tmp = DatumGetDataType(ent[0].key);
out = tmp;
if (numranges == 1)
{
out = data_type_deep_copy(tmp);
PG_RETURN_DATA_TYPE_P(out);
}
for (i = 1; i < numranges; i++)
{
old = out;
tmp = DatumGetDataType(ent[i].key);
out = my_union_implementation(out, tmp);
}
PG_RETURN_DATA_TYPE_P(out);
}
正如所见,在这个骨架中我们处理的是满足
union(X, Y, Z) = union(union(X, Y), Z) 的数据
类型。要支持不满足这一性质的数据类型也不难,只需在这个
GiST 支持方法中实现适当的 union 算法即可。
union 函数的结果必须是索引存储类型的一个
值,无论那是什么类型(它可能与被索引列的类型相同或不同)。
union 函数应返回一个指向新
palloc() 出的内存的指针。即使没有类型
变化,也不能按原样返回输入值。
如上所示,union 函数的第一个
internal 参数实际上是一个
GistEntryVector 指针。第二个参数是一个
指向整型变量的指针,可以忽略。
(过去要求 union 函数把其结果值的大小存入
该变量,但现在不再需要了。)
compress
把数据项转换为适合在索引页面中物理存储的格式。
如果省略了 compress 方法,数据项将不经修改
地存入索引。
该函数的 SQL 声明必须形如:
CREATE OR REPLACE FUNCTION my_compress(internal) RETURNS internal AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
C 模块中匹配的代码可以遵循这个骨架:
PG_FUNCTION_INFO_V1(my_compress);
Datum
my_compress(PG_FUNCTION_ARGS)
{
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
GISTENTRY *retval;
if (entry->leafkey)
{
/* replace entry->key with a compressed version */
compressed_data_type *compressed_data = palloc(sizeof(compressed_data_type));
/* fill *compressed_data from entry->key ... */
retval = palloc(sizeof(GISTENTRY));
gistentryinit(*retval, PointerGetDatum(compressed_data),
entry->rel, entry->page, entry->offset, FALSE);
}
else
{
/* typically we needn't do anything with non-leaf entries */
retval = entry;
}
PG_RETURN_POINTER(retval);
}
当然,您需要把 compressed_data_type
调整为您要转换到的具体类型,以压缩您的叶节点。
decompress
把数据项的存储表示转换为操作符类中其他 GiST 方法可以操纵的
格式。
如果省略了 decompress 方法,则假定其他 GiST
方法可以直接在存储的数据格式上工作。
(decompress 不一定是
compress 方法的逆过程;特别地,如果
compress 是有损的,那么
decompress 就不可能精确地重建原始数据。
decompress 也不一定等价于
fetch,因为其他 GiST 方法可能不需要对数据
做完整的重建。)
该函数的 SQL 声明必须形如:
CREATE OR REPLACE FUNCTION my_decompress(internal) RETURNS internal AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
C 模块中匹配的代码可以遵循这个骨架:
PG_FUNCTION_INFO_V1(my_decompress);
Datum
my_decompress(PG_FUNCTION_ARGS)
{
PG_RETURN_POINTER(PG_GETARG_POINTER(0));
}
上面的骨架适用于无需解压的情形。 (当然,直接省略整个方法更容易,也推荐这样做。)
penalty
返回一个值,表示把新条目插入树的某个特定分支的
“代价”。条目将沿着树中 penalty
最小的路径插入。
penalty 返回的值应当是非负的。
如果返回了负值,它将被当作零。
该函数的 SQL 声明必须形如:
CREATE OR REPLACE FUNCTION my_penalty(internal, internal, internal) RETURNS internal AS 'MODULE_PATHNAME' LANGUAGE C STRICT; -- in some cases penalty functions need not be strict
C 模块中匹配的代码可以遵循这个骨架:
PG_FUNCTION_INFO_V1(my_penalty);
Datum
my_penalty(PG_FUNCTION_ARGS)
{
GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0);
GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1);
float *penalty = (float *) PG_GETARG_POINTER(2);
data_type *orig = DatumGetDataType(origentry->key);
data_type *new = DatumGetDataType(newentry->key);
*penalty = my_penalty_implementation(orig, new);
PG_RETURN_POINTER(penalty);
}
出于历史原因,penalty 函数并非只返回一个
float 结果;它必须把值存储在第三个参数指示的
位置。返回值本身会被忽略,不过习惯上会把该参数的地址传回。
penalty 函数对索引的良好性能至关重要。
在插入时,它会用于确定在树中选择把新条目加到哪里时沿哪个分支
下降。在查询时,索引越平衡,查找越快。
picksplit当需要拆分索引页面时,此函数决定页面上的哪些条目留在旧页面, 哪些条目移到新页面。
该函数的 SQL 声明必须形如:
CREATE OR REPLACE FUNCTION my_picksplit(internal, internal) RETURNS internal AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
C 模块中匹配的代码可以遵循这个骨架:
PG_FUNCTION_INFO_V1(my_picksplit);
Datum
my_picksplit(PG_FUNCTION_ARGS)
{
GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1);
OffsetNumber maxoff = entryvec->n - 1;
GISTENTRY *ent = entryvec->vector;
int i,
nbytes;
OffsetNumber *left,
*right;
data_type *tmp_union;
data_type *unionL;
data_type *unionR;
GISTENTRY **raw_entryvec;
maxoff = entryvec->n - 1;
nbytes = (maxoff + 1) * sizeof(OffsetNumber);
v->spl_left = (OffsetNumber *) palloc(nbytes);
left = v->spl_left;
v->spl_nleft = 0;
v->spl_right = (OffsetNumber *) palloc(nbytes);
right = v->spl_right;
v->spl_nright = 0;
unionL = NULL;
unionR = NULL;
/* Initialize the raw entry vector. */
raw_entryvec = (GISTENTRY **) malloc(entryvec->n * sizeof(void *));
for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
raw_entryvec[i] = &(entryvec->vector[i]);
for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
{
int real_index = raw_entryvec[i] - entryvec->vector;
tmp_union = DatumGetDataType(entryvec->vector[real_index].key);
Assert(tmp_union != NULL);
/*
* Choose where to put the index entries and update unionL and unionR
* accordingly. Append the entries to either v->spl_left or
* v->spl_right, and care about the counters.
*/
if (my_choice_is_left(unionL, curl, unionR, curr))
{
if (unionL == NULL)
unionL = tmp_union;
else
unionL = my_union_implementation(unionL, tmp_union);
*left = real_index;
++left;
++(v->spl_nleft);
}
else
{
/*
* Same on the right
*/
}
}
v->spl_ldatum = DataTypeGetDatum(unionL);
v->spl_rdatum = DataTypeGetDatum(unionR);
PG_RETURN_POINTER(v);
}
注意,picksplit 函数的结果是通过修改传入的
v 结构来传递的。返回值本身会被忽略,
不过习惯上会把 v 的地址传回。
与 penalty 一样,picksplit
函数对索引的良好性能至关重要。设计合适的
penalty 和 picksplit
实现正是实现高性能 GiST 索引的挑战所在。
same如果两个索引条目完全相同则返回 true,否则返回 false。 (“索引条目”是索引存储类型的一个值,不一定是原始 被索引列的类型。)
该函数的 SQL 声明必须形如:
CREATE OR REPLACE FUNCTION my_same(storage_type, storage_type, internal) RETURNS internal AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
C 模块中匹配的代码可以遵循这个骨架:
PG_FUNCTION_INFO_V1(my_same);
Datum
my_same(PG_FUNCTION_ARGS)
{
prefix_range *v1 = PG_GETARG_PREFIX_RANGE_P(0);
prefix_range *v2 = PG_GETARG_PREFIX_RANGE_P(1);
bool *result = (bool *) PG_GETARG_POINTER(2);
*result = my_eq(v1, v2);
PG_RETURN_POINTER(result);
}
出于历史原因,same 函数并非只返回一个布尔
结果;它必须把标志存储在第三个参数指示的位置。返回值本身会被
忽略,不过习惯上会把该参数的地址传回。
distance
给定一个索引条目 p 和一个查询值
q,此函数确定该索引条目与查询值的
“距离”。如果操作符类包含任何排序操作符,则必须提供
此函数。使用排序操作符的查询将通过首先返回具有最小
“距离”值的索引条目来实现,因此结果必须与操作符的
语义一致。对于叶索引条目,结果只表示到该索引条目的距离;对于
内部树节点,结果必须是任何子条目可能具有的最小距离。
该函数的 SQL 声明必须形如:
CREATE OR REPLACE FUNCTION my_distance(internal, data_type, smallint, oid, internal) RETURNS float8 AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
C 模块中匹配的代码可以遵循这个骨架:
PG_FUNCTION_INFO_V1(my_distance);
Datum
my_distance(PG_FUNCTION_ARGS)
{
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
data_type *query = PG_GETARG_DATA_TYPE_P(1);
StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
/* Oid subtype = PG_GETARG_OID(3); */
/* bool *recheck = (bool *) PG_GETARG_POINTER(4); */
data_type *key = DatumGetDataType(entry->key);
double retval;
/*
* determine return value as a function of strategy, key and query.
*/
PG_RETURN_FLOAT8(retval);
}
distance 函数的参数与
consistent 函数的参数完全相同。
在确定距离时允许一定的近似,只要结果绝不大于该条目的实际距离。
因此,例如在几何应用中,到外接框的距离通常就足够了。
对于内部树节点,返回的距离不得大于到任何子节点的距离。
如果返回的距离不精确,函数必须把 *recheck 设为
true。(对内部树节点无需如此;对它们,计算总是被假定为不精确
的。)这种情况下,执行器会在从堆中取回元组之后计算精确的距离,
并在必要时对元组重新排序。
如果距离函数对任何叶节点返回 *recheck = true,
那么原始排序操作符的返回类型必须是 float8 或
float4,并且距离函数的结果值必须可与原始排序操作符
的结果值比较,因为执行器将同时使用距离函数结果和重新计算的排序
操作符结果进行排序。否则,距离函数的结果值可以是任何有限的
float8 值,只要结果值的相对顺序与排序操作符返回的
顺序一致。(无穷和负无穷在内部被用于处理空值等情况,因此不建议
distance 函数返回这些值。)
fetch把数据项的压缩索引表示转换为原始数据类型,用于仅索引扫描。 返回的数据必须是原始被索引值的精确、无损的副本。
该函数的 SQL 声明必须形如:
CREATE OR REPLACE FUNCTION my_fetch(internal) RETURNS internal AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
参数是一个指向 GISTENTRY 结构的指针。
进入时,其 key 字段包含一个非空的
压缩形式的叶 datum。返回值是另一个
GISTENTRY 结构,其
key 字段包含同一个 datum 的原始未
压缩形式。如果操作符类的 compress 函数对叶条目不做任何处理,
fetch 方法可以按原样返回参数。或者,如果
操作符类没有 compress 函数,fetch 方法也
可以省略,因为它必然是空操作。
C 模块中匹配的代码可以遵循这个骨架:
PG_FUNCTION_INFO_V1(my_fetch);
Datum
my_fetch(PG_FUNCTION_ARGS)
{
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
input_data_type *in = DatumGetPointer(entry->key);
fetched_data_type *fetched_data;
GISTENTRY *retval;
retval = palloc(sizeof(GISTENTRY));
fetched_data = palloc(sizeof(fetched_data_type));
/*
* Convert 'fetched_data' into the a Datum of the original datatype.
*/
/* fill *retval from fetched_data. */
gistentryinit(*retval, PointerGetDatum(converted_datum),
entry->rel, entry->page, entry->offset, FALSE);
PG_RETURN_POINTER(retval);
}
如果 compress 方法对叶条目是有损的,操作符类就不能支持仅索引
扫描,并且不得定义 fetch 函数。
options允许定义控制操作符类行为的用户可见参数。
该函数的 SQL 声明必须形如:
CREATE OR REPLACE FUNCTION my_options(internal) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
该函数会接收一个指向 local_relopts
结构的指针,该结构需要被填充为一组操作符类特定的选项。其他支持
函数可以使用 PG_HAS_OPCLASS_OPTIONS() 和
PG_GET_OPCLASS_OPTIONS() 宏来访问这些选项。
下面给出了 my_options() 的一个示例实现,以及其他支持函数使用 这些参数的方式:
typedef enum MyEnumType
{
MY_ENUM_ON,
MY_ENUM_OFF,
MY_ENUM_AUTO
} MyEnumType;
typedef struct
{
int32 vl_len_; /* varlena header (do not touch directly!) */
int int_param; /* integer parameter */
double real_param; /* real parameter */
MyEnumType enum_param; /* enum parameter */
int str_param; /* string parameter */
} MyOptionsStruct;
/* String representation of enum values */
static relopt_enum_elt_def myEnumValues[] =
{
{"on", MY_ENUM_ON},
{"off", MY_ENUM_OFF},
{"auto", MY_ENUM_AUTO},
{(const char *) NULL} /* list terminator */
};
static char *str_param_default = "default";
/*
* Sample validator: checks that string is not longer than 8 bytes.
*/
static void
validate_my_string_relopt(const char *value)
{
if (strlen(value) > 8)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("str_param must be at most 8 bytes")));
}
/*
* Sample filler: switches characters to lower case.
*/
static Size
fill_my_string_relopt(const char *value, void *ptr)
{
char *tmp = str_tolower(value, strlen(value), DEFAULT_COLLATION_OID);
int len = strlen(tmp);
if (ptr)
strcpy(ptr, tmp);
pfree(tmp);
return len + 1;
}
PG_FUNCTION_INFO_V1(my_options);
Datum
my_options(PG_FUNCTION_ARGS)
{
local_relopts *relopts = (local_relopts *) PG_GETARG_POINTER(0);
init_local_reloptions(relopts, sizeof(MyOptionsStruct));
add_local_int_reloption(relopts, "int_param", "integer parameter",
100, 0, 1000000,
offsetof(MyOptionsStruct, int_param));
add_local_real_reloption(relopts, "real_param", "real parameter",
1.0, 0.0, 1000000.0,
offsetof(MyOptionsStruct, real_param));
add_local_enum_reloption(relopts, "enum_param", "enum parameter",
myEnumValues, MY_ENUM_ON,
"Valid values are: \"on\", \"off\" and \"auto\".",
offsetof(MyOptionsStruct, enum_param));
add_local_string_reloption(relopts, "str_param", "string parameter",
str_param_default,
&validate_my_string_relopt,
&fill_my_string_relopt,
offsetof(MyOptionsStruct, str_param));
PG_RETURN_VOID();
}
PG_FUNCTION_INFO_V1(my_compress);
Datum
my_compress(PG_FUNCTION_ARGS)
{
int int_param = 100;
double real_param = 1.0;
MyEnumType enum_param = MY_ENUM_ON;
char *str_param = str_param_default;
/*
* Normally, when opclass contains 'options' method, then options are always
* passed to support functions. However, if you add 'options' method to
* existing opclass, previously defined indexes have no options, so the
* check is required.
*/
if (PG_HAS_OPCLASS_OPTIONS())
{
MyOptionsStruct *options = (MyOptionsStruct *) PG_GET_OPCLASS_OPTIONS();
int_param = options->int_param;
real_param = options->real_param;
enum_param = options->enum_param;
str_param = GET_STRING_RELOPTION(options, str_param);
}
/* the rest implementation of support function */
}
由于键在 GiST 中的表示方式是灵活的,它可能
依赖于用户指定的参数。例如,可以指定键签名的长度。
示例参见 gtsvector_options()。
sortsupport
返回一个比较器函数,用于以保持局部性的方式对数据排序。它被
CREATE INDEX 和
REINDEX 命令使用。所创建索引的质量取决于
比较器函数确定的排序顺序在多大程度上保持了输入的局部性。
sortsupport 方法是可选的。如果不提供它,
CREATE INDEX 会使用
penalty 和 picksplit
函数把每个元组逐个插入树中来构建索引,这要慢得多。
该函数的 SQL 声明必须形如 :
CREATE OR REPLACE FUNCTION my_sortsupport(internal) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
参数是一个指向 SortSupport
结构的指针。至少,函数必须填写其比较器字段。
比较器接收三个参数:两个要比较的 Datum,以及一个指向
SortSupport 结构的指针。
这些 Datum 是两个被索引值,采用它们在索引中的存储格式;也就是
compress 方法返回的格式。
完整的 API 定义在
src/include/utils/sortsupport.h 中。
C 模块中匹配的代码可以遵循这个骨架:
PG_FUNCTION_INFO_V1(my_sortsupport);
static int
my_fastcmp(Datum x, Datum y, SortSupport ssup)
{
/* establish order between x and y by computing some sorting value z */
int z1 = ComputeSpatialCode(x);
int z2 = ComputeSpatialCode(y);
return z1 == z2 ? 0 : z1 > z2 ? 1 : -1;
}
Datum
my_sortsupport(PG_FUNCTION_ARGS)
{
SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0);
ssup->comparator = my_fastcmp;
PG_RETURN_VOID();
}
translate_cmptype
给定一个来自
src/include/access/cmptype.h 的
CompareType 值,返回本操作符类用于匹配该功能
的策略号。如果操作符类没有匹配的策略,函数应返回
InvalidStrategy。
它用于时间索引约束(即 PRIMARY
KEY 和 UNIQUE)。如果操作符类
提供此函数,并且它能对 COMPARE_EQ 返回
结果,那么它就可以被用于索引约束的非
WITHOUT OVERLAPS 部分。
这个支持函数对应于索引访问方法的回调函数
amtranslatecmptype(见 Section 63.2)。GiST 索引的
amtranslatecmptype 回调函数只是向下
调用相应操作符族的
translate_cmptype 支持函数,因为 GiST 索引
访问方法本身没有固定的策略号。
该函数的 SQL 声明必须形如 :
CREATE OR REPLACE FUNCTION my_translate_cmptype(integer) RETURNS smallint AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
操作符族的注册则必须形如:
ALTER OPERATOR FAMILY my_opfamily USING gist ADD
FUNCTION 12 ("any", "any") my_translate_cmptype(int);
C 模块中匹配的代码可以遵循这个骨架:
PG_FUNCTION_INFO_V1(my_translate_cmptype);
Datum
my_translate_cmptype(PG_FUNCTION_ARGS)
{
CompareType cmptype = PG_GETARG_INT32(0);
StrategyNumber ret = InvalidStrategy;
switch (cmptype)
{
case COMPARE_EQ:
ret = BTEqualStrategyNumber;
}
PG_RETURN_UINT16(ret);
}
PostgreSQL 提供了一个转换函数:
gist_translate_cmptype_common,供使用
RT*StrategyNumber 常量的操作符类使用。
btree_gist
扩展定义了第二个转换函数
gist_translate_cmptype_btree,供使用
BT*StrategyNumber 常量的操作符类使用。
所有 GiST 支持方法通常都在短寿命的内存上下文中调用;也就是说,
每处理完一个元组,CurrentMemoryContext 都会被
重置。因此不必太担心对 palloc 出来的东西进行 pfree。
不过,在某些情况下,让支持方法在多次调用之间缓存数据是有用的。
要做到这一点,可以把寿命更长的数据分配在
fcinfo->flinfo->fn_mcxt 中,并在
fcinfo->flinfo->fn_extra 中保存指向它的
指针。这样的数据会在索引操作的整个生命期内存活(例如一次 GiST
索引扫描、一次索引构建或一次索引元组插入)。
替换 fn_extra 值时要注意 pfree 掉旧值,否则泄漏
会在操作的持续期间不断累积。
构建 GiST 索引最简单的方法就是把所有条目逐个插入。 对大索引来说这往往很慢,因为如果索引元组分散在索引各处,而且索引 大到无法放入缓存,就需要大量的随机 I/O。 PostgreSQL 支持两种替代的 GiST 索引初始 构建方法:排序模式和 缓冲模式。
只有当索引使用的每个操作符类都提供了
sortsupport 函数(如
Section 65.2.3 中所述)时,才能使用排序
方法。如果是,这种方法通常最好,因此默认使用它。
缓冲方法的工作方式是不立即把元组直接插入索引。 对于无序数据集,它可以极大地减少所需的随机 I/O 量。对于有序性好的 数据集,收益较小或不存在,因为一次只有少量页面接收新元组,而这些 页面即使整个索引放不进缓存,它们自己也能放进缓存。
缓冲方法比简单方法更频繁地调用 penalty
函数,这会消耗一些额外的 CPU 资源。此外,缓冲区需要临时磁盘空间,
最大可达最终索引的大小。缓冲还会影响最终索引的质量,既有正面影响
也有负面影响。这种影响取决于多种因素,例如输入数据的分布和操作符类
的实现。
如果无法排序,那么默认情况下,当索引大小达到
effective_cache_size 时,GiST 索引构建会切换
到缓冲方法。可以使用 CREATE INDEX 命令的
buffering 参数手动强制或阻止缓冲。默认行为对
大多数情况都是合适的,但如果输入数据是有序的,关闭缓冲可能会在一
定程度上加快构建。
PostgreSQL 源码发行版包含若干使用
GiST 实现的索引方法示例。
核心系统目前提供文本搜索支持(为 tsvector 和
tsquery 建立索引),以及为一些内建几何数据类型提供的
R-tree 等价功能(见
src/backend/access/gist/gistproc.c)。以下
contrib 模块也包含
GiST 操作符类:
btree_gist多种数据类型的 B-tree 等价功能
cube多维立方体的索引
hstore存储 (键, 值) 对的模块
intarray用于 int4 值一维数组的 RD-Tree
ltree树形结构的索引
pg_trgm基于三元组匹配的文本相似度
seg“浮点区间”的索引