65.3. Расширяемость#

65.3. Расширяемость

65.3. Расширяемость #

Традиционно, реализация нового метода доступа к индексу требовала много сложной работы. Необходимо было понимать внутреннее устройство базы данных, такое как менеджер блокировок и журнал предварительной записи. Интерфейс GiST имеет высокий уровень абстракции, требуя от реализатора метода доступа только реализации семантики типа данных, к которым осуществляется доступ. Сам слой GiST заботится о параллельности, журналировании и поиске в структуре дерева.

Эта расширяемость не следует путать с расширяемостью других стандартных деревьев поиска в терминах данных, с которыми они могут работать. Например, PostgreSQL поддерживает расширяемые B-деревья и хеш-индексы. Это означает, что вы можете использовать PostgreSQL для создания B-дерева или хеша для любого типа данных, который вам нужен. Но B-деревья поддерживают только диапазонные предикаты (<, =, >), а хеш-индексы поддерживают только запросы на равенство.

Таким образом, если вы индексируете, скажем, коллекцию изображений с помощью B-дерева PostgreSQL, вы можете выполнять только запросы вроде равно ли изображениеX изображениюY, меньше ли изображениеX, чем изображениеY и больше ли изображениеX, чем изображениеY. В зависимости от того, как вы определяете равенство, меньше и больше в данном контексте, это может быть полезно. Однако, используя индекс на основе GiST, вы можете создавать способы задавать вопросы, специфичные для домена, например, найти все изображения лошадей или найти все переэкспонированные изображения.

Все, что нужно сделать, чтобы запустить метод доступа GiST, - это реализовать несколько пользовательских методов, которые определяют поведение ключей в дереве. Конечно, эти методы должны быть довольно сложными, чтобы поддерживать сложные запросы, но для всех стандартных запросов (B-деревья, R-деревья и т. д.) они относительно просты. Вкратце, GiST объединяет расширяемость с общностью, повторное использование кода и чистый интерфейс.

Есть пять методов, которые операторный класс индекса для GiST должен предоставить, и шесть, которые являются необязательными. Правильность индекса обеспечивается правильной реализацией методов same, consistent и union, в то время как эффективность (размер и скорость) индекса будет зависеть от методов penalty и picksplit. Два необязательных метода - compress и decompress, позволяют индексу иметь внутренние данные дерева другого типа, чем данные, которые он индексирует. Листья должны быть типа индексируемых данных, в то время как другие узлы дерева могут быть любой структурой C (но все же нужно следовать правилам типов данных PostgreSQL, см. о varlena для данных переменного размера). Если внутренний тип данных дерева существует на уровне SQL, можно использовать опцию STORAGE команды CREATE OPERATOR CLASS. Необязательный восьмой метод - distance, который необходим, если операторный класс хочет поддерживать упорядоченные сканирования (поиск ближайших соседей). Необязательный девятый метод fetch необходим, если операторный класс хочет поддерживать сканирование только индекса, за исключением случаев, когда метод compress не указан. Необязательный десятый метод options необходим, если операторный класс имеет пользовательские параметры. Необязательный одиннадцатый метод sortsupport используется для ускорения построения индекса GiST.

consistent

Данная функция определяет, соответствует ли запись индекса p значению запроса q. То есть, может ли предикат indexed_column indexable_operator q быть true для любой строки, представленной записью индекса? Для записи листа индекса это эквивалентно проверке условия индексируемости, а для внутреннего узла дерева определяется, нужно ли сканировать поддерево индекса, представленное узлом дерева. Когда результат равен true, также должен быть возвращен флаг recheck. Это указывает, является ли предикат точно true или только возможно true. Если 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 должно зависеть от оператора). Рекомендуется, чтобы SQL-объявление функции consistent использовало индексированный тип данных opclass для аргумента 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 должна возвращать указатель на вновь выделенную память с помощью palloc(). Вы не можете просто вернуть входное значение как есть, даже если нет изменения типа.

Согласно приведенному выше примеру, первый аргумент функции union на самом деле является указателем на тип internal, который представляет собой указатель на структуру GistEntryVector. Второй аргумент является указателем на переменную типа integer, которую можно игнорировать. (Ранее требовалось, чтобы функция 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 также является ключевой для хорошей производительности индекса. Основной сложностью при реализации эффективных индексов GiST является разработка подходящих реализаций функций penalty и picksplit.

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. (Это необходимо только для внутренних узлов дерева; для них всегда предполагается, что вычисление не точное). В этом случае исполнитель вычислит точное расстояние после получения кортежа из кучи и, при необходимости, переупорядочит кортежи.

Если функция distance возвращает *recheck = true для любого листового узла, то исходный оператор сортировки должен иметь тип возвращаемого значения float8 или float4, а значения результатов функции distance должны быть сравнимы с значениями исходного оператора сортировки, поскольку исполнитель будет сортировать, используя как результаты функции distance, так и пересчитанные результаты оператора сортировки. В противном случае, значения результатов функции distance могут быть любыми конечными значениями float8, при условии, что относительный порядок значений результатов соответствует порядку, возвращаемому оператором сортировки. (Бесконечность и минус бесконечность используются внутренне для обработки случаев, таких как null, поэтому не рекомендуется, чтобы функции distance возвращали эти значения).

fetch

Преобразует сжатое представление индекса элемента данных в исходный тип данных для сканирования только индекса. Возвращаемые данные должны быть точной, без потерь копией исходного индексированного значения.

Декларация функции SQL должна выглядеть так:

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

Аргументом является указатель на структуру GISTENTRY. При входе, поле key содержит некоторые данные листа в сжатом виде. Возвращаемое значение - это другая структура GISTENTRY, поле key которой содержит те же данные в исходном, несжатом виде. Если функция сжатия opclass не выполняет никаких действий для листовых записей, метод fetch может вернуть аргумент без изменений. Или, если у opclass нет функции сжатия, метод 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);
}

Если метод сжатия является потерянным для листовых записей, класс операторов не может поддерживать индексные только-сканы и не должен определять функцию 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((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 является гибким, оно может зависеть от параметров, указанных пользователем. Например, можно указать длину сигнатуры ключа. См. 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. Как минимум, функция должна заполнить поле comparator. Компаратор принимает три аргумента: два значения Datums для сравнения и указатель на структуру SortSupport. Значения Datums - это два индексированных значения в формате, в котором они хранятся в индексе; то есть в формате, возвращаемом методом 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();
}

Все методы поддержки GiST обычно вызываются в контекстах кратковременной памяти; то есть, CurrentMemoryContext будет сброшен после обработки каждой кортежа. Поэтому не очень важно беспокоиться о pfree'ing всего, что вы palloc. Однако в некоторых случаях полезно, чтобы метод поддержки кешировал данные между повторными вызовами. Для этого выделяйте данные с более длительным сроком службы в fcinfo->flinfo->fn_mcxt и сохраняйте указатель на них в fcinfo->flinfo->fn_extra. Такие данные будут существовать в течение жизни операции индекса (например, одного сканирования индекса GiST, построения индекса или вставки кортежа индекса). Будьте осторожны при освобождении предыдущего значения при замене значения fn_extra, иначе утечка будет накапливаться в течение операции.