GiSTGiST 代表通用搜索树(Generalized Search Tree)。它是一种平衡的树结构访问方法,用作实现任意索引方案的基础模板。B 树、R 树和许多其他索引方案都可以在 GiST 中实现。GiST.
GiST 的一个优点是GiST它允许数据类型领域的专家(而不是数据库专家)为自定义数据类型开发相应的访问方法。
这里的一些信息来自加州大学伯克利分校的 GiST 索引项目网站和 Marcel Kornacker 的论文《下一代数据库系统的访问方法》。GiSTGiST在 PostgreSQL 中的实现主要由 Teodor Sigaev 和 Oleg Bartunov 维护,他们的网站上有更多信息。
核心 PostgreSQL 发行版包含GiSTPostgreSQL 的核心发行版包括了 表 65.1 中所示的 GiST 操作符类。(附录 F 中描述的一些可选模块提供了额外的 GiSTGiST操作符类。)
表 65.1. 内置 GiST 操作符类GiST操作符类
| 名称 | 可索引操作符 | 排序操作符 |
|---|---|---|
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);
CREATE INDEX ON my_table USING GIST (my_inet_column inet_ops);
传统上,实现一种新的索引访问方法意味着大量困难的工作。必须了解数据库的内部工作原理,例如锁管理器和预写日志(Write-Ahead Log)。GiSTGiST接口具有高度抽象性,要求访问方法实现者只需实现被访问数据类型的语义。该GiST接口层本身负责并发、日志记录和树结构搜索。
这种可扩展性不应与其他标准搜索树在处理数据方面的可扩展性相混淆。例如,PostgreSQL 支持可扩展的 B 树和哈希索引。这意味着你可以使用 PostgreSQL 为任何你想要的数据类型构建 B 树或哈希索引。但 B 树只支持范围谓词(<、=、>),而哈希索引只支持等值查询。
因此,如果你用 PostgreSQL 的 B 树索引一个图像集合,你只能发出诸如“图像 x 等于图像 y 吗”、“图像 x 小于图像 y 吗”和“图像 x 大于图像 y 吗”之类的查询。根据你如何在此上下文中定义“等于”、“小于”和“大于”,这可能有用。然而,通过使用基于 GiST 的GiST索引,你可以创建方法来提出特定领域的问题,例如“找到所有马的图像”或“找到所有过度曝光的图像”。
要使GiST要让一个 GiST 访问方法运行起来,需要实现几个用户定义的方法,这些方法定义了键在树中的行为。当然,这些方法必须足够巧妙才能支持巧妙的查询,但对于所有标准查询(B 树、R 树等),它们都相对直接。简而言之,GiSTGiST结合了可扩展性、通用性、代码重用和清晰的接口。
GiST 的索引操作符类必须提供五种方法,另有七种是可选的。GiST索引的正确性通过正确实现 same、consistent 和 union 方法来保证,而索引的效率(大小和速度)将取决于 penalty 和 picksplit 方法。两个可选方法是 compress 和 decompress,它们允许索引的内部树数据类型与它索引的数据类型不同。叶子节点必须是索引的数据类型,而其他树节点可以是任何 C 结构体(但你仍需遵守 PostgreSQL 的数据类型规则,参见关于可变大小数据的 varlena)。如果树的内部数据类型存在于 SQL 层面,则可以使用 CREATE OPERATOR CLASS 命令的 STORAGE 选项。第八个可选方法是 distance,如果操作符类希望支持有序扫描(最近邻搜索),则需要此方法。第九个可选方法 fetch,如果操作符类希望支持仅索引扫描,则需要此方法,除非省略了 compress 方法。第十个可选方法 options,如果操作符类有用户指定的参数,则需要此方法。第十一个可选方法 sortsupport 用于加速构建 GiSTGiST索引。第十二个可选方法 stratnum 用于将比较类型(来自 src/include/nodes/primnodes.h)转换为操作符类使用的策略号。这使得核心代码可以为时态约束索引查找操作符。
consistent给定一个索引项 p 和一个查询值 q,此函数确定该索引项是否与查询“一致”;也就是说,谓词“indexed_column indexable_operator q”对于该索引项所代表的任何行是否可能为真?对于叶子索引项,这相当于测试可索引条件,而对于内部树节点,这决定了是否有必要扫描该树节点所代表的索引子树。当结果为 true 时,还必须返回一个 recheck 标志。这表示谓词是确定为真还是仅可能为真。如果 recheck = false,则索引已精确测试了谓词条件;而如果 recheck = true,则该行只是一个候选匹配。在这种情况下,系统将自动对实际行值评估 indexable_operator,以查看它是否真的是一个匹配项。这个约定允许 GiSTGiST支持无损和有损索引结构。
该SQL函数的 SQL 声明必须如下所示:CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal) RETURNS bool
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 = (data_type *) PG_GETARG_POINTER(1); StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); /* Oid subtype = PG_GETARG_OID(3); */ /* not used */ bool *recheck = (bool *) PG_GETARG_POINTER(4); data_type *key = DatumGetDataType(entry->key); bool retval; /* * set *recheck to false if the operator is exact for this data type, * true if it might be inexact. */ *recheck = true; /* * determine return value... */ retval = true; PG_RETURN_BOOL(retval); }
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此方法合并树中的信息。给定一组条目,此函数会生成一个新的索引条目,该条目代表所有给定的条目。CREATE OR REPLACE FUNCTION my_union(internal, internal) RETURNS storage_typePG_FUNCTION_INFO_V1(my_union); Datum my_union(PG_FUNCTION_ARGS) { GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); /* int *v_siz = (int *) PG_GETARG_POINTER(1); */ storage_type *out, *tmp; int i; out = palloc(sizeof(storage_type)); memcpy(out, DatumGetPointer(entryvec->vector[0].key), sizeof(storage_type)); for(i=1; i < entryvec->n; i++) { tmp = DatumGetPointer(entryvec->vector[i].key); /* a_union_of(out, tmp) */ memcpy(out, tmp, sizeof(storage_type)); } /* *v_siz = sizeof(storage_type); */ PG_RETURN_POINTER(out); }
该SQL函数的 SQL 声明必须如下所示:CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal) RETURNS bool
CREATE OR REPLACE FUNCTION my_union(internal, internal) RETURNS storage_type 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 = (data_type *) PG_GETARG_POINTER(1); StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); /* Oid subtype = PG_GETARG_OID(3); */ /* not used */ bool *recheck = (bool *) PG_GETARG_POINTER(4); data_type *key = DatumGetDataType(entry->key); bool retval; /* * set *recheck to false if the operator is exact for this data type, * true if it might be inexact. */ *recheck = true; /* * determine return value... */ retval = true; PG_RETURN_BOOL(retval); }
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)。通过在这个 GiSTGiST支持方法中实现正确的联合算法,可以很容易地支持不符合这种情况的数据类型。
union 函数的结果必须是索引的存储类型的值,无论该类型是什么(它可能与索引列的类型不同,也可能相同)。union 函数应该返回一个指向新 palloc()ed 内存的指针。你不能仅仅按原样返回输入值,即使没有类型变化。
如上所示,union 函数的第一个 internal 参数实际上是一个 GistEntryVector 指针。第二个参数是一个指向整数变量的指针,可以忽略。(以前要求 union 函数将其结果值的大小存储在该变量中,但现在不再需要了。)
compress将数据项转换为适合在索引页中物理存储的格式。如果省略 compress 方法,数据项将不加修改地存储在索引中。CREATE OR REPLACE FUNCTION my_compress(internal) RETURNS internalPG_FUNCTION_INFO_V1(my_compress); Datum my_compress(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); if (entry->leafkey) { /* leaf node */ GISTENTRY *retval; compressed_data_type *compressed_data = palloc(sizeof(compressed_data_type)); data_type *leaf_data = DatumGetPointer(entry->key); /* * convert leaf_data to compressed_data format... */ retval = palloc(sizeof(GISTENTRY)); gistentryinit(*retval, PointerGetDatum(compressed_data), entry->rel, entry->page, entry->offset, FALSE); PG_RETURN_POINTER(retval); } else { /* internal node */ PG_RETURN_POINTER(entry); } }
该SQL函数的 SQL 声明必须如下所示:CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal) RETURNS bool
CREATE OR REPLACE FUNCTION my_compress(internal) RETURNS internal 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 = (data_type *) PG_GETARG_POINTER(1); StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); /* Oid subtype = PG_GETARG_OID(3); */ /* not used */ bool *recheck = (bool *) PG_GETARG_POINTER(4); data_type *key = DatumGetDataType(entry->key); bool retval; /* * set *recheck to false if the operator is exact for this data type, * true if it might be inexact. */ *recheck = true; /* * determine return value... */ retval = true; PG_RETURN_BOOL(retval); }
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 方法可能不需要完全重建数据。)CREATE OR REPLACE FUNCTION my_decompress(internal) RETURNS internalPG_FUNCTION_INFO_V1(my_decompress); Datum my_decompress(PG_FUNCTION_ARGS) { PG_RETURN_POINTER(PG_GETARG_POINTER(0)); }
该SQL函数的 SQL 声明必须如下所示:CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal) RETURNS bool
CREATE OR REPLACE FUNCTION my_decompress(internal) RETURNS internal 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 = (data_type *) PG_GETARG_POINTER(1); StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); /* Oid subtype = PG_GETARG_OID(3); */ /* not used */ bool *recheck = (bool *) PG_GETARG_POINTER(4); data_type *key = DatumGetDataType(entry->key); bool retval; /* * set *recheck to false if the operator is exact for this data type, * true if it might be inexact. */ *recheck = true; /* * determine return value... */ retval = true; PG_RETURN_BOOL(retval); }
PG_FUNCTION_INFO_V1(my_decompress);
Datum
my_decompress(PG_FUNCTION_ARGS)
{
PG_RETURN_POINTER(PG_GETARG_POINTER(0));
}
上面的骨架适用于不需要解压缩的情况。(但是,当然,完全省略该方法更容易,并且在这种情况下推荐这样做。)
penalty返回一个值,表示将新条目插入树的特定分支的“成本”。项目将沿着树中 penalty 最小的路径插入。由 penalty 返回的值应为非负数。如果返回负值,它将被视为零。CREATE OR REPLACE FUNCTION my_penalty(internal, internal, internal) RETURNS internalPG_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); storage_type *orig = DatumGetPointer(origentry->key); storage_type *new = DatumGetPointer(newentry->key); /* * calculate penalty value, and store it in *penalty. */ *penalty = 0.0f; PG_RETURN_POINTER(penalty); }
该SQL函数的 SQL 声明必须如下所示:CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal) RETURNS bool
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_consistent); Datum my_consistent(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); data_type *query = (data_type *) PG_GETARG_POINTER(1); StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); /* Oid subtype = PG_GETARG_OID(3); */ /* not used */ bool *recheck = (bool *) PG_GETARG_POINTER(4); data_type *key = DatumGetDataType(entry->key); bool retval; /* * set *recheck to false if the operator is exact for this data type, * true if it might be inexact. */ *recheck = true; /* * determine return value... */ retval = true; PG_RETURN_BOOL(retval); }
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 当需要进行索引页拆分时,此函数决定页面上的哪些条目将保留在旧页面上,哪些将移动到新页面上。CREATE OR REPLACE FUNCTION my_picksplit(internal, internal) RETURNS internalPG_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); int i; OffsetNumber maxoff; int nbytes; OffsetNumber *left, *right; data_type *tmp_union; data_type *left_union, *right_union; GISTENTRY *ent; 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; left_union = NULL; right_union = NULL; for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) { ent = &entryvec->vector[i]; tmp_union = DatumGetPointer(ent->key); /* * choose where to put the given entry, and update left_union and * right_union to include it. */ } v->spl_ldatum = PointerGetDatum(left_union); v->spl_rdatum = PointerGetDatum(right_union); PG_RETURN_POINTER(v); }
该SQL函数的 SQL 声明必须如下所示:CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal) RETURNS bool
CREATE OR REPLACE FUNCTION my_picksplit(internal, internal) RETURNS internal 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 = (data_type *) PG_GETARG_POINTER(1); StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); /* Oid subtype = PG_GETARG_OID(3); */ /* not used */ bool *recheck = (bool *) PG_GETARG_POINTER(4); data_type *key = DatumGetDataType(entry->key); bool retval; /* * set *recheck to false if the operator is exact for this data type, * true if it might be inexact. */ *recheck = true; /* * determine return value... */ retval = true; PG_RETURN_BOOL(retval); }
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 实现是实现高性能 GiSTGiST索引的挑战所在。
same如果两个索引条目相同则返回 true,否则返回 false。(“索引条目”是索引存储类型的值,不一定是原始索引列的类型。)CREATE OR REPLACE FUNCTION my_same(storage_type, storage_type, internal) RETURNS internalPG_FUNCTION_INFO_V1(my_same); Datum my_same(PG_FUNCTION_ARGS) { storage_type *b1 = (storage_type *) PG_GETARG_POINTER(0); storage_type *b2 = (storage_type *) PG_GETARG_POINTER(1); bool *result = (bool *) PG_GETARG_POINTER(2); /* * compare b1 and b2, and set *result */ *result = false; PG_RETURN_POINTER(result); }
该SQL函数的 SQL 声明必须如下所示:CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal) RETURNS bool
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_consistent); Datum my_consistent(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); data_type *query = (data_type *) PG_GETARG_POINTER(1); StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); /* Oid subtype = PG_GETARG_OID(3); */ /* not used */ bool *recheck = (bool *) PG_GETARG_POINTER(4); data_type *key = DatumGetDataType(entry->key); bool retval; /* * set *recheck to false if the operator is exact for this data type, * true if it might be inexact. */ *recheck = true; /* * determine return value... */ retval = true; PG_RETURN_BOOL(retval); }
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,此函数确定该索引项与查询值的“距离”。如果操作符类包含任何排序操作符,则必须提供此函数。使用排序操作符的查询将通过首先返回“距离”值最小的索引项来实现,因此结果必须与操作符的语义一致。对于叶子索引项,结果仅表示到该索引项的距离;对于内部树节点,结果必须是任何子项可能具有的最小距离。CREATE OR REPLACE FUNCTION my_distance(internal, data_type, smallint, oid, internal) RETURNS float8
该SQL函数的 SQL 声明必须如下所示:CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal) RETURNS bool
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_consistent); Datum my_consistent(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); data_type *query = (data_type *) PG_GETARG_POINTER(1); StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); /* Oid subtype = PG_GETARG_OID(3); */ /* not used */ bool *recheck = (bool *) PG_GETARG_POINTER(4); data_type *key = DatumGetDataType(entry->key); bool retval; /* * set *recheck to false if the operator is exact for this data type, * true if it might be inexact. */ *recheck = true; /* * determine return value... */ retval = true; PG_RETURN_BOOL(retval); }
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 值,只要结果值的相对顺序与排序操作符返回的顺序匹配。(无穷大和负无穷大在内部用于处理诸如 null 的情况,因此不建议 distance 函数返回这些值。)
fetch为仅索引扫描将数据项的压缩索引表示转换为原始数据类型。返回的数据必须是原始索引值的精确、无损副本。
该SQL函数的 SQL 声明必须如下所示:CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal) RETURNS bool
CREATE OR REPLACE FUNCTION my_fetch(internal) RETURNS internal AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
参数是指向 GISTENTRY 结构体的指针。在进入时,其 key 字段包含一个非 NULL 的压缩形式的叶子数据。返回值是另一个 GISTENTRY 结构体,其 key 字段包含原始、未压缩形式的相同数据。如果操作符类的压缩函数对叶子条目不做任何操作,则 fetch 方法可以按原样返回参数。或者,如果操作符类没有压缩函数,fetch 方法也可以省略,因为它必然是无操作的。CREATE OR REPLACE FUNCTION my_fetch(internal) RETURNS internal
然后,C 模块中匹配的代码可以遵循这个骨架:PG_FUNCTION_INFO_V1(my_fetch); Datum my_fetch(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); if (entry->leafkey) { GISTENTRY *retval; data_type *uncompressed_data = palloc(sizeof(data_type)); compressed_data_type *leaf_data = DatumGetPointer(entry->key); /* * convert leaf_data to uncompressed_data format... */ retval = palloc(sizeof(GISTENTRY)); gistentryinit(*retval, PointerGetDatum(uncompressed_data), entry->rel, entry->page, entry->offset, FALSE); PG_RETURN_POINTER(retval); } else { PG_RETURN_POINTER(entry); } }
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);
}
如果压缩方法对叶子条目是有损的,则操作符类不能支持仅索引扫描,并且不得定义 fetch 函数。
optionsoptions 允许定义用户可见的参数,以控制操作符类的行为。CREATE OR REPLACE FUNCTION my_options(internal) RETURNS void
该SQL函数的 SQL 声明必须如下所示:CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal) RETURNS bool
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 struct { int32 vl_len_; /* varlena header */ int siglen; } MyOptions; ... PG_FUNCTION_INFO_V1(my_options); Datum my_options(PG_FUNCTION_ARGS) { local_relopts *relopts = (local_relopts *) PG_GETARG_POINTER(0); int siglen = 128; MyOptions *options; /* Get integer parameter siglen from user. */ get_reloptions_int("siglen", &siglen, relopts, 1, 2048, 10); options = (MyOptions *) palloc(sizeof(MyOptions)); SET_VARSIZE(options, sizeof(MyOptions)); options->siglen = siglen; PG_RETURN_POINTER(options); } ... /* In other support functions */ MyOptions *options = (MyOptions *) PG_GET_OPCLASS_OPTIONS(fcinfo->flinfo);
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 中键的表示是灵活的,GiST它可能依赖于用户指定的参数。例如,可以指定键签名的长度。参见 gtsvector_options() 的示例。
sortsupportsortsupport 返回一个比较器函数,以一种保留局部性的方式对数据进行排序。它被 CREATE INDEX 和 REINDEX 命令使用。创建的索引的质量取决于比较器函数确定的排序顺序保留输入局部性的程度。CREATE OR REPLACE FUNCTION my_sortsupport(internal) RETURNS internal
sortsupport 方法是可选的。如果没有提供,CREATE INDEX 将通过使用 penalty 和 picksplit 函数将每个元组插入到树中来构建索引,这要慢得多。
该SQL函数的 SQL 声明必须如下所示:CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal) RETURNS bool
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_fetch); Datum my_fetch(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); if (entry->leafkey) { GISTENTRY *retval; data_type *uncompressed_data = palloc(sizeof(data_type)); compressed_data_type *leaf_data = DatumGetPointer(entry->key); /* * convert leaf_data to uncompressed_data format... */ retval = palloc(sizeof(GISTENTRY)); gistentryinit(*retval, PointerGetDatum(uncompressed_data), entry->rel, entry->page, entry->offset, FALSE); PG_RETURN_POINTER(retval); } else { PG_RETURN_POINTER(entry); } }
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/nodes/primnodes.h 的 CompareType 值,返回此操作符类用于匹配功能的策略号。如果操作符类没有匹配的策略,函数应返回 InvalidStrategy。CREATE OR REPLACE FUNCTION my_translate_cmptype(int) RETURNS smallint
这用于时态索引约束(即 PRIMARY KEY 和 UNIQUE)。如果操作符类提供了此函数并且它为 COMPARE_EQ 返回结果,则它可以在索引约束的非 WITHOUT OVERLAPS 部分中使用。
此支持函数对应于索引访问方法回调函数 amtranslatecmptype(参见 第 63.2 节)。GiST 索引的 amtranslatecmptype 回调函数仅调用相应操作符族的 translate_cmptype 支持函数,因为 GiST 索引访问方法本身没有固定的策略号。
该SQL函数的 SQL 声明必须如下所示:CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal) RETURNS bool
CREATE OR REPLACE FUNCTION my_translate_cmptype(integer) RETURNS smallint AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
并且操作符族注册必须如下所示:OPERATOR 1 =(any, any), ... , FUNCTION 12 my_translate_cmptype (int4)
ALTER OPERATOR FAMILY my_opfamily USING gist ADD
FUNCTION 12 ("any", "any") my_translate_cmptype(int);
然后,C 模块中匹配的代码可以遵循这个骨架:PG_FUNCTION_INFO_V1(my_fetch); Datum my_fetch(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); if (entry->leafkey) { GISTENTRY *retval; data_type *uncompressed_data = palloc(sizeof(data_type)); compressed_data_type *leaf_data = DatumGetPointer(entry->key); /* * convert leaf_data to uncompressed_data format... */ retval = palloc(sizeof(GISTENTRY)); gistentryinit(*retval, PointerGetDatum(uncompressed_data), entry->rel, entry->page, entry->offset, FALSE); PG_RETURN_POINTER(retval); } else { PG_RETURN_POINTER(entry); } }
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 将在处理每个元组后被重置。因此,不必太担心 pfree 你 palloc 的所有东西。然而,在某些情况下,支持方法在重复调用之间缓存数据是很有用的。为此,请在 fcinfo->flinfo->fn_mcxt 中分配更长生命周期的数据,并在 fcinfo->flinfo->fn_extra 中保留指向它的指针。这些数据将在索引操作的生命周期内(例如,单个 GiST 索引扫描、索引构建或索引元组插入)存在。在替换 fn_extra 值时,要小心 pfree 先前的值,否则泄漏将在操作期间累积。
构建 GiST 索引的最简单方法就是逐一插入所有条目。对于大型索引来说,这往往很慢,因为如果索引元组分散在索引中,并且索引大到无法容纳在缓存中,将需要大量的随机 I/O。PostgreSQL 支持两种替代方法来初始构建 GiST 索引:排序模式和缓冲模式。
排序方法仅在索引使用的每个操作符类都提供了 sortsupport 函数时才可用,如 第 65.2.3 节 中所述。如果它们提供了,这种方法通常是最好的,因此默认使用它。
缓冲方法的工作原理是,不是立即将元组直接插入到索引中。它可以显著减少非有序数据集所需的随机 I/O 量。对于有序良好的数据集,好处较小或不存在,因为一次只有少数页面接收新元组,并且即使整个索引不适合缓存,这些页面也适合缓存。
缓冲方法比简单方法需要更频繁地调用 penalty 函数,这会消耗一些额外的 CPU 资源。此外,缓冲区需要临时磁盘空间,最多可达最终索引的大小。缓冲也可能影响最终索引的质量,既有积极的也有消极的。这种影响取决于多种因素,如输入数据的分布和操作符类的实现。
如果无法排序,那么默认情况下,当索引大小达到 effective_cache_size 时,GiST 索引构建会切换到缓冲方法。缓冲可以通过 CREATE INDEX 命令的 buffering 参数手动强制或阻止。默认行为对大多数情况都很好,但如果输入数据是有序的,关闭缓冲可能会稍微加快构建速度。
PostgreSQL 源代码发行版包含几个使用 GiST 实现的索引方法示例。GiST核心系统目前提供文本搜索支持(为 tsvector 和 tsquery 进行索引)以及为一些内置几何数据类型提供 R-Tree 等效功能(参见 src/backend/access/gist/gistproc.c)。以下 contrib 模块也包含 GiSTGiST操作符类: