Перейти к основному содержимому

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

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

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

Таким образом, если индексируется, коллекция изображений с помощью B-дерева PostgreSQL, необходимо выполнять только такие запросы, как «меньше ли изображение 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, эта функция определяет, является ли индексная запись «согласованной» с запросом; то есть, может ли предикат «индексированный_столбец индексируемый_оператор q» будет истинным для любой строки, представленной индексной записью? Для записи листового индекса это равносильно проверке условия индексируемости, а для внутреннего узла дерева это определяет, нужно ли сканировать поддерево индекса, представленное узлом дерева. Если результат равен true, то также должен быть возвращен флаг перепроверки. Он указывает, является ли предикат безусловно истинным или только возможно истинным. Если recheck = false, то индекс точно проверил условие предиката, в то время как если recheck = true, то строка является только кандидатом на совпадение. В этом случае система автоматически оценит индексируемый_оператор по фактическому значению строки, чтобы проверить, действительно ли это совпадение. Это соглашение позволяет 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 для аргумента 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 - это указатель на GistEntryVector. Второй аргумент - указатель на целочисленную переменную, которую можно игнорировать. (Раньше требовалось, чтобы функция объединения сохраняла в этой переменной размер значения своего результата, но теперь в этом нет необходимости).

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

Объявление функции в формате SQL должно выглядеть следующим образом:


CREATE OR REPLACE FUNCTION my_penalty(internal, internal, internal)
RETURNS internal
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT; -- in some cases penalty functions need not be strict

И соответствующий код в модуле C может следовать этому шаблону:

PG_FUNCTION_INFO_V1(my_penalty);

Datum
my_penalty(PG_FUNCTION_ARGS)
{
GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0);
GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1);
float *penalty = (float *) PG_GETARG_POINTER(2);
data_type *orig = DatumGetDataType(origentry->key);
data_type *new = DatumGetDataType(newentry->key);

*penalty = my_penalty_implementation(orig, new);
PG_RETURN_POINTER(penalty);
}

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

Функция penalty имеет решающее значение для хорошей работы индекса. Она будет использоваться во время вставки, чтобы определить, какой ветви следовать при выборе места для добавления новой записи в дереве. Во время запроса чем более сбалансирован индекс, тем быстрее поиск.

picksplit

Когда необходимо разделить индексную страницу, эта функция решает, какие записи на странице должны остаться на старой странице, а какие переместиться на новую.

Объявление функции в формате SQL должно выглядеть следующим образом:

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

И соответствующий код в модуле C может следовать этому шаблону:

PG_FUNCTION_INFO_V1(my_picksplit);

Datum
my_picksplit(PG_FUNCTION_ARGS)
{
GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1);
OffsetNumber maxoff = entryvec->n - 1;
GISTENTRY *ent = entryvec->vector;
int i,
nbytes;
OffsetNumber *left,
*right;
data_type *tmp_union;
data_type *unionL;
data_type *unionR;
GISTENTRY **raw_entryvec;

maxoff = entryvec->n - 1;
nbytes = (maxoff + 1) * sizeof(OffsetNumber);

v->spl_left = (OffsetNumber *) palloc(nbytes);
left = v->spl_left;
v->spl_nleft = 0;

v->spl_right = (OffsetNumber *) palloc(nbytes);
right = v->spl_right;
v->spl_nright = 0;

unionL = NULL;
unionR = NULL;

/* Initialize the raw entry vector. */
raw_entryvec = (GISTENTRY **) malloc(entryvec->n * sizeof(void *));
for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
raw_entryvec[i] = &(entryvec->vector[i]);

for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
{
int real_index = raw_entryvec[i] - entryvec->vector;

tmp_union = DatumGetDataType(entryvec->vector[real_index].key);
Assert(tmp_union != NULL);

/*
* Choose where to put the index entries and update unionL and unionR
* accordingly. Append the entries to either v->spl_left or
* v->spl_right, and care about the counters.
*/

if (my_choice_is_left(unionL, curl, unionR, curr))
{
if (unionL == NULL)
unionL = tmp_union;
else
unionL = my_union_implementation(unionL, tmp_union);

*left = real_index;
++left;
++(v->spl_nleft);
}
else
{
/*
* Same on the right
*/
}
}

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

Обратите внимание, что результат функции picksplit получается путем модификации переданной структуры v. Возвращаемое значение как таковое игнорируется, хотя принято передавать обратно адрес v.

Как и penalty, функция picksplit имеет решающее значение для хорошей производительности индекса. Разработка подходящих реализаций penalty и picksplit - вот где кроется проблема реализации хорошо работающих индексов GiST.

same

Возвращает true, если две записи индекса идентичны, false - в противном случае. (Запись индекса - это значение типа хранения индекса, не обязательно типа исходного индексируемого столбца).

Объявление функции в формате SQL должно выглядеть следующим образом:

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

И соответствующий код в модуле C может следовать этому шаблону:

PG_FUNCTION_INFO_V1(my_same);

Datum
my_same(PG_FUNCTION_ARGS)
{
prefix_range *v1 = PG_GETARG_PREFIX_RANGE_P(0);
prefix_range *v2 = PG_GETARG_PREFIX_RANGE_P(1);
bool *result = (bool *) PG_GETARG_POINTER(2);

*result = my_eq(v1, v2);
PG_RETURN_POINTER(result);
}

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

distance

Учитывая индексную запись p и значение запроса q, эта функция определяет «расстояние» индексной записи от значения запроса. Эта функция должна быть представлена, если класс операторов содержит какие-либо операторы упорядочивания. Запрос, использующий оператор упорядочивания, будет реализован путем возврата записей индекса с наименьшими значениями «расстояния», поэтому результаты должны соответствовать семантике оператора. Для листовой индексной записи результат просто представляет собой расстояние до индексной записи; для внутреннего узла дерева результат должен быть наименьшим расстоянием, которое может иметь любая дочерняя запись.

Объявление функции в формате SQL должно выглядеть следующим образом:


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

И соответствующий код в модуле C может следовать этому шаблону:

PG_FUNCTION_INFO_V1(my_distance);

Datum
my_distance(PG_FUNCTION_ARGS)
{
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
data_type *query = PG_GETARG_DATA_TYPE_P(1);
StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
/* Oid subtype = PG_GETARG_OID(3); */
/* bool *recheck = (bool *) PG_GETARG_POINTER(4); */
data_type *key = DatumGetDataType(entry->key);
double retval;

/*
* determine return value as a function of strategy, key and query.
*/

PG_RETURN_FLOAT8(retval);
}

Функции distance передаются те же аргументы, что и функции consistent.

При определении расстояния допускается некоторая аппроксимация, но результат не должен превышать фактическое расстояние до объекта. Так, например, в геометрических приложениях обычно достаточно расстояния до ограничительной рамки. Для узла внутреннего дерева возвращаемое расстояние не должно быть больше, чем расстояние до любого из дочерних узлов. Если возвращаемое расстояние не является точным, функция должна установить *recheck в true. (Это не обязательно для узлов внутреннего дерева; для них вычисление всегда предполагается неточным). В этом случае исполнитель вычислит точное расстояние после извлечения кортежа из кучи и при необходимости переупорядочит кортежи.

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

fetch

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

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


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

Аргументом является указатель на структуру GISTENTRY. При входе в нее ключевое поле содержит не-NULL данные листа в сжатом виде. Возвращаемое значение - другая структура GISTENTRY, ключевое поле которой содержит те же данные в исходном, несжатом виде. Если функция compress ничего не делает для записей листа, метод fetch может вернуть аргумент как есть. Либо, если класс операторов не имеет функции compress, метод fetch тоже может быть опущен, так как он в любом случае не должен ничего делать.

Соответствующий код в модуле C может следовать этому скелету:

PG_FUNCTION_INFO_V1(my_fetch);

Datum
my_fetch(PG_FUNCTION_ARGS)
{
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
input_data_type *in = DatumGetPointer(entry->key);
fetched_data_type *fetched_data;
GISTENTRY *retval;

retval = palloc(sizeof(GISTENTRY));
fetched_data = palloc(sizeof(fetched_data_type));

/*
* Convert 'fetched_data' into the a Datum of the original datatype.
*/

/* fill *retval from fetched_data. */
gistentryinit(*retval, PointerGetDatum(converted_datum),
entry->rel, entry->page, entry->offset, FALSE);

PG_RETURN_POINTER(retval);
}

Если метод сжатия для записей в листе имеет потери, класс оператора не может поддерживать сканирование только по индексу и не должен определять функцию 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. Компаратор принимает три аргумента: два Datum для сравнения и указатель на структуру SortSupport. Datum - это два индексированных значения в том формате, в котором они хранятся в индексе; то есть в формате, возвращаемом методом compress. Полный API определен в src/include/utils/sortsupport.h.

Соответствующий код в модуле C может следовать этому шаблону:


PG_FUNCTION_INFO_V1(my_sortsupport);

static int
my_fastcmp(Datum x, Datum y, SortSupport ssup)
{
/* establish order between x and y by computing some sorting value z */

int z1 = ComputeSpatialCode(x);
int z2 = ComputeSpatialCode(y);

return z1 == z2 ? 0 : z1 > z2 ? 1 : -1;
}

Datum
my_sortsupport(PG_FUNCTION_ARGS)
{
SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0);

ssup->comparator = my_fastcmp;
PG_RETURN_VOID();
}

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