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
, иначе утечка будет накапливаться в течение операции.