GiSTGiST
代表通用搜索樹(Generalized Search Tree)。它是一種平衡的樹結構訪問方法,用作實現任意索引方案的基礎模板。B 樹、R 樹和許多其他索引方案都可以在 GiST
中實現。GiST.
GiST
的一個優點是GiST它允許資料型別領域的專家(而不是資料庫專家)為自定義資料型別開發相應的訪問方法。
這裡的一些資訊來自加州大學伯克利分校的 GiST 索引專案網站和 Marcel Kornacker 的論文《下一代資料庫系統的訪問方法》。GiST
GiST在 PostgreSQL 中的實現主要由 Teodor Sigaev 和 Oleg Bartunov 維護,他們的網站上有更多資訊。
核心 PostgreSQL 發行版包含GiSTPostgreSQL 的核心發行版包括了 表 65.1 中所示的 GiST
運算子類。(附錄 F 中描述的一些可選模組提供了額外的 GiST
GiST運算子類。)
表 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)。GiST
GiST介面具有高度抽象性,要求訪問方法實現者只需實現被訪問資料型別的語義。該GiST介面層本身負責併發、日誌記錄和樹結構搜尋。
這種可擴充套件性不應與其他標準搜尋樹在處理資料方面的可擴充套件性相混淆。例如,PostgreSQL 支援可擴充套件的 B 樹和雜湊索引。這意味著你可以使用 PostgreSQL 為任何你想要的資料型別構建 B 樹或雜湊索引。但 B 樹只支援範圍謂詞(<
、=
、>
),而雜湊索引只支援等值查詢。
因此,如果你用 PostgreSQL 的 B 樹索引一個影像集合,你只能發出諸如“影像 x 等於影像 y 嗎”、“影像 x 小於影像 y 嗎”和“影像 x 大於影像 y 嗎”之類的查詢。根據你如何在此上下文中定義“等於”、“小於”和“大於”,這可能有用。然而,透過使用基於 GiST
的GiST索引,你可以建立方法來提出特定領域的問題,例如“找到所有馬的影像”或“找到所有過度曝光的影像”。
要使GiST要讓一個 GiST
訪問方法執行起來,需要實現幾個使用者定義的方法,這些方法定義了鍵在樹中的行為。當然,這些方法必須足夠巧妙才能支援巧妙的查詢,但對於所有標準查詢(B 樹、R 樹等),它們都相對直接。簡而言之,GiST
GiST結合了可擴充套件性、通用性、程式碼重用和清晰的介面。
GiST
的索引運算子類必須提供五種方法,另有七種是可選的。GiST索引的正確性透過正確實現 same
、consistent
和 union
方法來保證,而索引的效率(大小和速度)將取決於 penalty
和 picksplit
方法。兩個可選方法是 compress
和 decompress
,它們允許索引的內部樹資料型別與它索引的資料型別不同。葉子節點必須是索引的資料型別,而其他樹節點可以是任何 C 結構體(但你仍需遵守 PostgreSQL 的資料型別規則,參見關於可變大小資料的 varlena
)。如果樹的內部資料型別存在於 SQL 層面,則可以使用 CREATE OPERATOR CLASS
命令的 STORAGE
選項。第八個可選方法是 distance
,如果運算子類希望支援有序掃描(最近鄰搜尋),則需要此方法。第九個可選方法 fetch
,如果運算子類希望支援僅索引掃描,則需要此方法,除非省略了 compress
方法。第十個可選方法 options
,如果運算子類有使用者指定的引數,則需要此方法。第十一個可選方法 sortsupport
用於加速構建 GiST
GiST索引。第十二個可選方法 stratnum
用於將比較型別(來自 src/include/nodes/primnodes.h
)轉換為運算子類使用的策略號。這使得核心程式碼可以為時態約束索引查詢運算子。
consistent
給定一個索引項 p
和一個查詢值 q
,此函式確定該索引項是否與查詢“一致”;也就是說,謂詞“indexed_column
indexable_operator
q
”對於該索引項所代表的任何行是否可能為真?對於葉子索引項,這相當於測試可索引條件,而對於內部樹節點,這決定了是否有必要掃描該樹節點所代表的索引子樹。當結果為 true
時,還必須返回一個 recheck
標誌。這表示謂詞是確定為真還是僅可能為真。如果 recheck
= false
,則索引已精確測試了謂詞條件;而如果 recheck
= true
,則該行只是一個候選匹配。在這種情況下,系統將自動對實際行值評估 indexable_operator
,以檢視它是否真的是一個匹配項。這個約定允許 GiST
GiST支援無損和有損索引結構。
該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_type
PG_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)
。透過在這個 GiST
GiST支援方法中實現正確的聯合演算法,可以很容易地支援不符合這種情況的資料型別。
union
函式的結果必須是索引的儲存型別的值,無論該型別是什麼(它可能與索引列的型別不同,也可能相同)。union
函式應該返回一個指向新 palloc()
ed 記憶體的指標。你不能僅僅按原樣返回輸入值,即使沒有型別變化。
如上所示,union
函式的第一個 internal
引數實際上是一個 GistEntryVector
指標。第二個引數是一個指向整數變數的指標,可以忽略。(以前要求 union
函式將其結果值的大小儲存在該變數中,但現在不再需要了。)
compress
將資料項轉換為適合在索引頁中物理儲存的格式。如果省略 compress
方法,資料項將不加修改地儲存在索引中。CREATE OR REPLACE FUNCTION my_compress(internal) RETURNS internal
PG_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 internal
PG_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 internal
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); 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 internal
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); 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
實現是實現高效能 GiST
GiST索引的挑戰所在。
same
如果兩個索引條目相同則返回 true,否則返回 false。(“索引條目”是索引儲存型別的值,不一定是原始索引列的型別。)CREATE OR REPLACE FUNCTION my_same(storage_type, storage_type, internal) RETURNS internal
PG_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
函式。
options
options
允許定義使用者可見的引數,以控制運算子類的行為。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()
的示例。
sortsupport
sortsupport
返回一個比較器函式,以一種保留區域性性的方式對資料進行排序。它被 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
模組也包含 GiST
GiST運算子類:
如果您在文件中發現任何不正確、與您對特定功能的體驗不符或需要進一步澄清的內容,請使用此表單報告文件問題。