PostgreSQL 可擴展性

2021-09-15 11:40 更新

在傳統(tǒng)上,實現(xiàn)一種新的索引訪問方法意味著很多困難的工作。開發(fā)者必須要理解數(shù)據(jù)庫的內(nèi)部工作,例如鎖管理器和預(yù)寫式日志。GiST接口有一個高層的抽象,要求訪問方法實現(xiàn)者只實現(xiàn)要被訪問的數(shù)據(jù)類型的語義。GiST層本身會處理并發(fā)、日志和對樹結(jié)構(gòu)的搜索。

這種可擴展性不應(yīng)該與其他標準搜索樹對于它們所處理的數(shù)據(jù)上的可擴展性混淆。例如,PostgreSQL支持可擴展的 B 樹和哈希索引。也就是說你可以用PostgreSQL在任何你想要的數(shù)據(jù)類型上構(gòu)建一個 B 樹或哈希。但是 B 樹只支持范圍謂詞(<、=、 >),而哈希索引支持等值查詢。

這樣如果你用一個PostgreSQL的 B 樹索引一個圖像集合,你只能發(fā)出例如imagex 等于 imagey 嗎imagex 小于 imagey 嗎以及imagex 大于 imagey 嗎的查詢。取決于你如何在這種上下文中定義 等于、小于大于,這可能會有用。但是,通過使用一個基于GiST的索引,你可以創(chuàng)建提問領(lǐng)域相關(guān)問題的方法,可能是找所有馬的圖片或者 找所有曝光過度的圖片。

建立一個GiST訪問方法并讓其運行的所有工作是實現(xiàn)幾個用戶定義的方法,它們定義了樹中鍵的行為。當然這些方法必須相當特別來支持特別的查詢,但是對于所有標準查詢(B 樹、R 樹等)它們相對直接。簡而言之,GiST在可擴展性之上結(jié)合了通用型、代碼重用和一個干凈的接口。

一個用于GiST的索引操作符類必須提供五種方法,并且還有五種可選的方法。 索引的正確性由正確實現(xiàn)的same、consistentunion方法保證,而索引的效率(尺寸和速度)將依賴于penaltypicksplit方法。 兩種可選的方法是compressdecompress,它們允許一個索引能對內(nèi)部數(shù)據(jù)使用一種不同于被其索引的數(shù)據(jù)的類型。 葉子是被索引的數(shù)據(jù)類型,而其他樹結(jié)點可以是任何 C 結(jié)構(gòu)(但是你仍必須遵循PostgreSQL的數(shù)據(jù)類型規(guī)則,見用于可變尺寸數(shù)據(jù)的varlena)。 如果樹的內(nèi)部數(shù)據(jù)類型在 SQL 層上存在,可以使用CREATE OPERATOR CLASS命令的STORAGE選項。 可選的第八個方法是distance,如果操作符類希望支持有序掃描(最近鄰搜索)就需要它。 如果該操作符希望支持只用索引的掃描,則需要可選的第九個方法fetch,但 compress方法被省略時不需要。 如果操作符類提供了用戶指定的參數(shù),則需要第10個options方法選項。

consistent

給定一個索引項p和一個查詢值q,這個函數(shù)決定該索引項是否與該查詢一致,就是說:是否該索引項表示的行使得謂詞indexed_columnindexable_operator q為真?對于一個葉子索引項,這等效于測試索引條件;而對于一個內(nèi)部樹結(jié)點,這會決定是否需要掃描由該樹結(jié)點表示的索引子樹。當結(jié)果為 true時,還必須返回一個recheck標志。這指示該謂詞一定為真或者只是可能為真。如果recheck = false那么該索引已經(jīng)完全測試過該謂詞條件,而如果recheck = true則該行只是一個候選匹配。在那種情況下,系統(tǒng)將根據(jù)實際的行值自動評估 indexable_operator 來看它是否真的是一個匹配。這允許GiST同時支持有損和無損的索引結(jié)構(gòu)。

該函數(shù)的SQL聲明必須看起來像這樣:

CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal)
RETURNS bool
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

在 C 模塊中匹配的代碼則應(yīng)該遵循這樣的框架:

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;

    /*
     * 根據(jù)策略、鍵和查詢確定返回值。
     *
     * 使用 GIST_LEAF(entry) 可以了解當前函數(shù)是在索引樹的哪里被調(diào)用,
     * 這在支持例如 = 操作符時很方便(可以在非葉子節(jié)點中檢查非空 union()
     * 以及在葉子節(jié)點中檢查等值)。
     */

    *recheck = true;        /* 如果檢查是準確的則返回 false */

    PG_RETURN_BOOL(retval);
}

這里,key是該索引中的一個元素而query是在該索引中查找的值。StrategyNumber參數(shù)指示在你的操作符類中哪個操作符被應(yīng)用 — 它匹配CREATE OPERATOR CLASS命令中的操作符編號之一。

取決于在操作符類中包含著哪些操作符,query的數(shù)據(jù)類型可能隨著操作符而變化,因為它可能是該操作符右手邊的任何類型,而這種類型可能和出現(xiàn)在其左手邊的被索引數(shù)據(jù)類型不同(上面的代碼框架假定只有一種類型;如果不是這樣,取query參數(shù)值的方式可能必須取決于操作符)。我們推薦讓consistent函數(shù)的 SQL 聲明對 query參數(shù)使用操作符類的被索引數(shù)據(jù)類型,即便實際類型可能是其他依賴于操作符的類型也是如此。

union

這個方法聯(lián)合樹中的信息。給定一組項,這個函數(shù)產(chǎn)生一個新的索引項,它表示所有給定的項。

該函數(shù)的SQL聲明必須看起來像這樣:

CREATE OR REPLACE FUNCTION my_union(internal, internal)
RETURNS storage_type
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

在 C 模塊中匹配的代碼則應(yīng)該遵循這樣的框架:

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);
}

如你所見,在這個框架中我們處理一種數(shù)據(jù)類型union(X, Y, Z) = union(union(X, Y), Z)。通過在這個GiST支持方法中實現(xiàn)正確的聯(lián)合算法,支持不是這種情況的數(shù)據(jù)類型足夠簡單。

union函數(shù)的結(jié)果必須是該索引的存儲類型的一個值,它可能與被索引列的類型不同,也可能相同。union函數(shù)應(yīng)該返回一個指針指向新palloc()的內(nèi)存。不能照原樣返回輸入值,即使沒有類型改變也不能。

如上所示,union函數(shù)的第一個internal參數(shù)實際上是一個GistEntryVector指針。第二個參數(shù)是一個指向整數(shù)變量的指針,它可以被忽略(過去要求union函數(shù)將其結(jié)果值的尺寸存儲在這個變量中,但現(xiàn)在這已不再必要)。

compress

把數(shù)據(jù)項轉(zhuǎn)換成適合于一個索引頁面中物理存儲的格式。如果compress方法被省略,數(shù)據(jù)項會被不加修改地存儲在索引中。

該函數(shù)的SQL聲明必須看起來像這樣:

CREATE OR REPLACE FUNCTION my_compress(internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

在 C 模塊中匹配的代碼則應(yīng)該遵循這樣的框架:

PG_FUNCTION_INFO_V1(my_compress);

Datum
my_compress(PG_FUNCTION_ARGS)
{
    GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
    GISTENTRY  *retval;

    if (entry->leafkey)
    {
        /* 用一個壓縮版本替換 entry->key */
        compressed_data_type *compressed_data = palloc(sizeof(compressed_data_type));

        /* 從 entry->key ... 填充 *compressed_data */

        retval = palloc(sizeof(GISTENTRY));
        gistentryinit(*retval, PointerGetDatum(compressed_data),
                      entry->rel, entry->page, entry->offset, FALSE);
    }
    else
    {
        /* 通常我們不需要對非葉子項做任何事情 */
        retval = entry;
    }

    PG_RETURN_POINTER(retval);
}

當然,為了壓縮你的葉結(jié)點,你必須把compressed_data_type改編成你正在轉(zhuǎn)換到的指定類型。

decompress

將一個數(shù)據(jù)項的存儲表達轉(zhuǎn)換成該操作符類中其他GiST方法能夠操縱的格式。如果decompress方法被省略,則假設(shè)其他GiST方法能夠直接在存儲的數(shù)據(jù)格式上工作(decompress不一定要是compress方法的逆操作,特別是如果compress是有損的,那么 decompress是不可能準確地重構(gòu)原始數(shù)據(jù)的。decompress也不一定與fetch等效,因為其他GiST方法可能不需要數(shù)據(jù)的完整重構(gòu))。

該函數(shù)的SQL聲明必須看起來像這樣:

CREATE OR REPLACE FUNCTION my_decompress(internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

在 C 模塊中匹配的代碼則應(yīng)該遵循這樣的框架:

PG_FUNCTION_INFO_V1(my_decompress);

Datum
my_decompress(PG_FUNCTION_ARGS)
{
    PG_RETURN_POINTER(PG_GETARG_POINTER(0));
}

上述框架適合于不需要解壓的情況(但是,將該方法一并省去當然更加容易,并且在這種情況下推薦這樣做)。

penalty

返回一個值,它指示在樹的一個特定分支插入新項的代價。項將被插入到樹中具有最小penalty的路徑中。penalty返回的值應(yīng)該為非負。如果一個賦值被返回,它將被當作零來處理。

該函數(shù)的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 模塊中匹配的代碼則應(yīng)該遵循這樣的框架:

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函數(shù)不只是返回一個float結(jié)果,而是必須把該值存儲在由第三個參數(shù)指定的位置。雖然傳回該參數(shù)的地址符合慣例,但返回值本身可以被忽略。

penalty函數(shù)對于索引的好性能是至關(guān)重要的。在插入時,當要選擇在樹中的哪個位置加入新項時,這個函數(shù)有助于決定應(yīng)該順著哪個分支進行。在查詢時,索引越平衡,查找越快速。

picksplit

當需要一次索引頁面分裂時,這個函數(shù)決定在該頁面上哪些項會留在舊頁面上,以及哪些項會移動到新頁面上。

該函數(shù)的SQL聲明必須看起來像這樣:

CREATE OR REPLACE FUNCTION my_picksplit(internal, internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

在 C 模塊中匹配的代碼則應(yīng)該遵循這樣的框架:

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;

    /* 初始化裸的項向量。 */
    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);

        /*
         * 選擇在哪里放置索引項并且相應(yīng)地更新 unionL 和 unionR。
         * 把項追加到 v->spl_left 或者 v->spl_right,并且設(shè)置好計數(shù)器。
         */

        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
        {
            /*
             * 和在右邊的過程相同
             */
        }
    }

    v->spl_ldatum = DataTypeGetDatum(unionL);
    v->spl_rdatum = DataTypeGetDatum(unionR);
    PG_RETURN_POINTER(v);
}

注意picksplit函數(shù)的結(jié)果通過修改傳入的v結(jié)構(gòu)來傳遞。盡管傳回v的地址符合慣例,但返回值本身可以被忽略。

penalty一樣,picksplit函數(shù)對于索引的好性能至關(guān)重要。設(shè)計合適的penaltypicksplit是實現(xiàn)一個好的GiST索引中最大的挑戰(zhàn)。

same

如果兩個索引項相同則返回真,否則返回假(一個索引項是該索引的存儲類型的一個值,而不一定是原始被索引列類型的值)。

該函數(shù)的SQL聲明必須看起來像這樣:

CREATE OR REPLACE FUNCTION my_same(storage_type, storage_type, internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

在 C 模塊中匹配的代碼則應(yīng)該遵循這樣的框架:

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函數(shù)不只返回一個布爾結(jié)果。相反它必須把該標志存儲在第三個參數(shù)指示的位置。盡管傳回該參數(shù)的地址符合慣例,但返回值本身可以被忽略。

distance

給定一個索引項p和一個查詢值q,這個函數(shù)決定兩者之間的距離。如果操作符類包含任何排序操作符,就必須提供這個函數(shù)。一個使用排序操作符的查詢將首先返回具有最小距離值的索引項,因此結(jié)果必須與操作符的語義一致。對于一個頁索引項,結(jié)果只表示到索引項的距離;對于一個內(nèi)部樹結(jié)點,結(jié)果必須是到任何子項的最小距離。

該函數(shù)的SQL聲明必須看起來像這樣:

CREATE OR REPLACE FUNCTION my_distance(internal, data_type, smallint, oid, internal)
RETURNS float8
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

在 C 模塊中匹配的代碼則應(yīng)該遵循這樣的框架:

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函數(shù)的參數(shù)和consistent函數(shù)的相同。

在決定距離時允許有某種近似,只要結(jié)果不要超過該項的實際距離即可。因此,例如在幾何應(yīng)用中到一個外包盒的距離就足夠了。對于一個內(nèi)部樹節(jié)點,返回的距離不能超過到其任意一個子節(jié)點的距離。如果返回的距離不準確,該函數(shù)必須設(shè)置*recheck為真(這對于內(nèi)部樹節(jié)點是不必要的,對于它們,計算總是被假定為不準確)。在這種情況下,執(zhí)行器將在從堆中取出元組后計算精確的距離,并且在必要時記錄這些元組。

如果距離函數(shù)對任意葉子節(jié)點都返回*recheck = true,初始的排序操作符的返回類型必須是float8或者float4,并且距離函數(shù)的結(jié)果值必須能和初始排序操作符的結(jié)果進行比較,因為執(zhí)行器將使用距離函數(shù)結(jié)果和重新計算的排序操作符結(jié)果進行排序。否則,該距離函數(shù)的結(jié)果值可以是任意有限的float8值,只要這些結(jié)果值的相對順序匹配該排序操作符返回的順序(在內(nèi)部會使用無窮以及負無窮來處理空值等情況,因此我們不推薦 distance函數(shù)返回這些值)。

fetch

為只用索引的掃描將一個數(shù)據(jù)項壓縮過的索引表達轉(zhuǎn)換成原始的數(shù)據(jù)類型。被返回的數(shù)據(jù)必須是原始被索引值的一份準確的、非有損的拷貝。

該函數(shù)的SQL聲明必須看起來像這樣:

CREATE OR REPLACE FUNCTION my_fetch(internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

參數(shù)是一個指向GISTENTRY結(jié)構(gòu)的指針。在項上,它的key域包含一個壓縮形式的非-NULL 葉子數(shù)據(jù)。返回值是另一個GISTENTRY結(jié)構(gòu),其key域包含同一數(shù)據(jù)的原始的未壓縮形式。如果操作符類的壓縮函數(shù)不對葉子項做任何事情, fetch方法可以原樣返回參數(shù)?;蛘撸绻搊pclass沒有一個壓縮函數(shù),則fetch方法也可以被省略,因為它必須是一個空操作。

C 模塊中相應(yīng)的代碼可能會遵循下面的框架:

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));

    /*
     * 將 'fetched_data' 轉(zhuǎn)換成原始數(shù)據(jù)類型的一個 Datum。
     */

    /* 從 fetched_data. 填充 *retval。 */
    gistentryinit(*retval, PointerGetDatum(converted_datum),
                  entry->rel, entry->page, entry->offset, FALSE);

    PG_RETURN_POINTER(retval);
}

如果該壓縮方法對于葉子項是有損的,操作符類就不能支持只用索引的掃描,并且不能定義fetch函數(shù)。

選項

允許定義控制操作符類行為的用戶可見參數(shù)。

該函數(shù)的SQL聲明必須看起來如下所示:

CREATE OR REPLACE FUNCTION my_options(internal)
RETURNS void
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;

向函數(shù)傳遞一個指向local_relopts結(jié)構(gòu)體的指針,該結(jié)構(gòu)體需要用一組特定于操作符類的選項來填充。 可以使用PG_HAS_OPCLASS_OPTIONS()PG_GET_OPCLASS_OPTIONS()宏從其他支持函數(shù)訪問這些選項。

下面給出了my_options()和其他支持函數(shù)參數(shù)的示例實現(xiàn):

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((char *) 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中的鍵表示是彈性的,它可能依賴于用戶指定的參數(shù)。 例如,可以指定密鑰簽名的長度。示例可參見gtsvector_options()。

所有的 GiST 支持方法通常都在一個短暫存在的內(nèi)存上下文中被調(diào)用;就是說,每個元組被處理之后CurrentMemoryContext將被重置。因此沒有必要操心釋放你 palloc 的所有東西。但是,在某些情況下,一個支持方法在重復(fù)調(diào)用之間緩存數(shù)據(jù)是有用的。要這樣做,將這些長期生存的數(shù)據(jù)分配在fcinfo->flinfo->fn_mcxt中,并且在fcinfo->flinfo->fn_extra中保持一個到它的指針。這種數(shù)據(jù)將在索引操作期間都存在(例如一次 GiST 索引掃描、索引構(gòu)建或索引元組插入)。注意當替換一個fn_extra值時要釋放之前的值,否則在操作期間該泄露會累積。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號