GiST означает Generalized Search Tree (обобщенное дерево поиска). Это сбалансированный метод доступа с древовидной структурой, выступающий в качестве базового шаблона для реализации произвольных схем индексирования. B-деревья, R-деревья и многие другие схемы индексирования могут быть реализованы в GiST.
Одним из преимуществ GiST является то, что он позволяет специалисту в предметной области типа данных, а не эксперту по базам данных, разрабатывать пользовательские типы данных с соответствующими методами доступа.
Часть представленной здесь информации заимствована из проекта GiST Indexing Project Калифорнийского университета в Беркли веб-сайте и диссертации Марселя Корнакера (Marcel Kornacker), Access Methods for Next-Generation Database Systems. The GiST Реализация в Digital Q.DataBase в основном поддерживается Федором Сигаевым и Олегом Бартуновым; более подробная информация приведена на их веб-сайте.
В основной состав Digital Q.DataBase дистрибутива входят GiST классы операторов, указанные в Таблица 7.14.1. (Некоторые дополнительные модули, описанные в Приложение 8.6 предоставляют дополнительные GiST классы операторов.)
Таблица 7.14.1. Встроенные GiST классы операторов
| Имя | Индексируемые операторы | Операторы упорядочивания |
|---|---|---|
box_ops | << (box, box) | <-> (box, point) |
&< (box, box) | ||
&& (box, box) | ||
&> (box, box) | ||
>> (box, box) | ||
~= (box, box) | ||
@> (box, box) | ||
<@ (box, box) | ||
&<| (box, box) | ||
<<| (box, box) | ||
|>> (box, box) | ||
|&> (box, box) | ||
circle_ops | << (circle, circle) | <-> (circle, point) |
&< (circle, circle) | ||
&> (circle, circle) | ||
>> (circle, circle) | ||
<@ (circle, circle) | ||
@> (circle, circle) | ||
~= (circle, circle) | ||
&& (circle, circle) | ||
|>> (circle, circle) | ||
<<| (circle, circle) | ||
&<| (circle, circle) | ||
|&> (circle, circle) | ||
inet_ops | << (inet, inet) | |
<<= (inet, inet) | ||
>> (inet, inet) | ||
>>= (inet, inet) | ||
= (inet, inet) | ||
<> (inet, inet) | ||
< (inet, inet) | ||
<= (inet, inet) | ||
> (inet, inet) | ||
>= (inet, inet) | ||
&& (inet, inet) | ||
multirange_ops | = (anymultirange, anymultirange) | |
&& (anymultirange, anymultirange) | ||
&& (anymultirange, anyrange) | ||
@> (anymultirange, anyelement) | ||
@> (anymultirange, anymultirange) | ||
@> (anymultirange, anyrange) | ||
<@ (anymultirange, anymultirange) | ||
<@ (anymultirange, anyrange) | ||
<< (anymultirange, anymultirange) | ||
<< (anymultirange, anyrange) | ||
>> (anymultirange, anymultirange) | ||
>> (anymultirange, anyrange) | ||
&< (anymultirange, anymultirange) | ||
&< (anymultirange, anyrange) | ||
&> (anymultirange, anymultirange) | ||
&> (anymultirange, anyrange) | ||
-|- (anymultirange, anymultirange) | ||
-|- (anymultirange, anyrange) | ||
point_ops | |>> (point, point) | <-> (point, point) |
<< (point, point) | ||
>> (point, point) | ||
<<| (point, point) | ||
~= (point, point) | ||
<@ (point, box) | ||
<@ (point, polygon) | ||
<@ (point, circle) | ||
poly_ops | << (polygon, polygon) | <-> (polygon, point) |
&< (polygon, polygon) | ||
&> (polygon, polygon) | ||
>> (polygon, polygon) | ||
<@ (polygon, polygon) | ||
@> (polygon, polygon) | ||
~= (polygon, polygon) | ||
&& (polygon, polygon) | ||
<<| (polygon, polygon) | ||
&<| (polygon, polygon) | ||
|&> (polygon, polygon) | ||
|>> (polygon, polygon) | ||
range_ops | = (anyrange, anyrange) | |
&& (anyrange, anyrange) | ||
&& (anyrange, anymultirange) | ||
@> (anyrange, anyelement) | ||
@> (anyrange, anyrange) | ||
@> (anyrange, anymultirange) | ||
<@ (anyrange, anyrange) | ||
<@ (anyrange, anymultirange) | ||
<< (anyrange, anyrange) | ||
<< (anyrange, anymultirange) | ||
>> (anyrange, anyrange) | ||
>> (anyrange, anymultirange) | ||
&< (anyrange, anyrange) | ||
&< (anyrange, anymultirange) | ||
&> (anyrange, anyrange) | ||
&> (anyrange, anymultirange) | ||
-|- (anyrange, anyrange) | ||
-|- (anyrange, anymultirange) | ||
tsquery_ops | <@ (tsquery, tsquery) | |
@> (tsquery, tsquery) | ||
tsvector_ops | @@ (tsvector, tsquery) |
По историческим причинам inet_ops класс операторов не является классом по умолчанию для типов данных inet и cidr.
Чтобы использовать его, укажите имя класса операторов в команде CREATE INDEX,
например:
CREATE INDEX ON my_table USING GIST (my_inet_column inet_ops);
Традиционно реализация нового метода доступа к индексам требовала выполнения большого объема сложной работы. Необходимо было понимать внутренние механизмы базы данных, такие как менеджер блокировок и журнал упреждающей записи (Write-Ahead Log). GiST интерфейс обладает высоким уровнем абстракции, требуя от разработчика метода доступа реализации только семантики обрабатываемого типа данных. GiST уровень сам по себе берет на себя вопросы параллельного доступа, логирования и поиска по древовидной структуре.
Эту расширяемость не следует путать с расширяемостью других стандартных деревьев поиска в контексте типов данных, которые они могут обрабатывать. Например, Digital Q.DataBase поддерживает расширяемые B-деревья
и хеш-индексы. Это означает, что вы можете использовать
Digital Q.DataBase для построения B-дерева или хеш-индекса над любым типом данных. Но B-деревья поддерживают только предикаты диапазона
(<, =, >),
а хеш-индексы — только запросы на равенство.
Так, если вы индексируете, скажем, коллекцию изображений с помощью Digital Q.DataBase B-дерева, вы сможете выполнять только такие запросы, как «равно ли изображение imagex изображению imagey», «меньше ли изображение imagex, чем imagey» и «больше ли изображение imagex, чем imagey». В зависимости от того, как вы определите понятия ««равно»», ««меньше чем»» и ««больше чем»» в данном контексте, это может быть полезно. Однако, используя индекс на основе GiST , вы могли бы реализовать способы формирования специфических для предметной области запросов, например, «найти все изображения лошадей» или «найти все переэкспонированные изображения».
Все, что требуется для запуска GiST метода доступа в рабочем состоянии заключается в реализации нескольких пользовательских функций, определяющих поведение ключей в дереве. Безусловно, эти методы должны быть достаточно сложными для поддержки специальных запросов, однако для всех стандартных случаев (B-деревья, R-деревья и т. д.) они относительно просты. Вкратце, GiST сочетает в себе расширяемость, универсальность, возможность повторного использования кода и чистый интерфейс.
Существует пять методов, которые класс операторов индекса для
GiST необходимо предоставить, и шесть необязательных. Корректность индекса обеспечивается надлежащей реализацией same, consistent
и union , в то время как эффективность (размер и скорость работы) индекса будет зависеть от методов penalty и picksplit
.
Двумя необязательными методами являются compress и
decompress, которые позволяют хранить во внутренних узлах дерева данные типа, отличного от типа индексируемых данных. Листовые узлы должны иметь тип индексируемых данных, тогда как другие узлы дерева могут представлять собой любую C-структуру (однако здесь все равно необходимо соблюдать Digital Q.DataBase правила для типов данных,
см. сведения о varlena для данных переменного размера). Если внутренний тип данных дерева существует на уровне SQL, может быть использован STORAGE параметр
команды CREATE OPERATOR CLASS .
Необязательный восьмой метод — distance, который необходим, если класс операторов должен поддерживать упорядоченное сканирование (поиск ближайших соседей). Необязательный девятый метод fetch необходим, если класс операторов должен поддерживать сканирование только по индексу, за исключением случаев, когда
compress метод STORAGE опущен. Необязательный десятый метод
options необходим, если класс операторов имеет
определяемые пользователем параметры.
Необязательный одиннадцатый метод sortsupport используется для
ускорения построения GiST индекса.
consistent
Для заданной индексной записи p и значения запроса q,
эта функция определяет, является ли индексная запись
«consistent» согласованной с запросом; то есть может ли предикат
«indexed_column
indexable_operator q» быть истинным для
любой строки, представленной данной индексной записью? Для листовой индексной записи это
эквивалентно проверке индексируемого условия, тогда как для внутреннего
узла дерева это определяет, необходимо ли сканировать поддерево
индекса, представленное этим узлом дерева. Если результат —
true, recheck также должен возвращаться флаг.
Он указывает на то, является ли предикат достоверно истинным или только вероятно
истинным. Если значение равно recheck = false , то это означает, что индекс
выполнил точную проверку условия предиката, тогда как если recheck
= true , то строка является лишь кандидатом на совпадение. В этом случае
система автоматически выполнит проверку
indexable_operator на соответствие фактическому значению строки, чтобы убедиться,
действительно ли это совпадение. Такое соглашение позволяет
GiST поддерживать как точные (lossless), так и избыточные (lossy) индексные
структуры.
Команда 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;
/*
* определение возвращаемого значения в зависимости от стратегии, ключа и запроса.
*
* Используйте GIST_LEAF(entry), чтобы определить место вызова в дереве индекса,
* что полезно, например, при поддержке оператора = (можно
* проверить наличие непустого объединения union() в промежуточных узлах и
* равенство в листовых узлах).
*/
*recheck = true; /* или false, если проверка является точной */
PG_RETURN_BOOL(retval);
}
Здесь key представляет собой элемент в индексе, а query
— значение, искомое в индексе. Параметр тип данных StrategyNumber
StrategyNumber указывает, какой именно оператор вашего класса операторов
применяется — он соответствует одному из номеров операторов в команде
CREATE OPERATOR CLASS CREATE OPERATOR CLASS.
В зависимости от того, какие операторы включены в класс, тип данных
параметра query 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()ed
памяти. Нельзя просто возвращать входное значение в исходном виде, даже если
изменение типа отсутствует.
Как показано выше, union функции
первый внутренний аргумент фактически представляет собой
указатель на GistEntryVector pointer. Второй аргумент — это
указатель на целочисленную переменную, который можно игнорировать. (Ранее
требовалось, чтобы 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)
{
/* замена entry->key сжатой версией */
compressed_data_type *compressed_data = palloc(sizeof(compressed_data_type));
/* заполнение *compressed_data из entry->key ... */
retval = palloc(sizeof(GISTENTRY));
gistentryinit(*retval, PointerGetDatum(compressed_data),
entry->rel, entry->page, entry->offset, FALSE);
}
else
{
/* обычно для нелистовых записей никаких действий не требуется */
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; -- в некоторых случаях функции штрафа не обязательно должны быть строгими (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;
/* Инициализация вектора исходных записей. */
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);
/*
* Выбор места размещения записей индекса и обновление unionL и unionR
* соответствующим образом. Добавление записей либо в v->spl_left, либо
* в v->spl_right с обновлением счетчиков.
*/
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 результат функции возвращается
посредством модификации переданной 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,
эта функция определяет расстояние от записи индекса
«distance» до значения в запросе. Данная функция должна быть
определена, если класс операторов содержит операторы упорядочивания.
Запрос с использованием оператора упорядочивания будет выполняться путем возврата
сначала тех записей индекса, которые имеют наименьшие «distance» значения,
таким образом, результаты должны соответствовать семантике оператора.
Для записи в листовом узле индекса результат просто представляет собой расстояние до
этой записи; для внутреннего узла дерева результатом должно быть
минимальное расстояние, которое может иметь любая дочерняя запись.
Команда 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
величины, если относительный порядок результатов соответствует
порядку, возвращаемому оператором упорядочивания. (Бесконечность и минус бесконечность
используются во внутреннем представлении для обработки таких случаев, как значения NULL, поэтому не
рекомендуется, чтобы distance функции возвращают эти значения.)
fetchПреобразует сжатое индексное представление элемента данных в исходный тип данных для сканирования только по индексу. Возвращаемые данные должны представлять собой точную копию исходного проиндексированного значения без потерь.
Команда SQL для объявления функции должна выглядеть следующим образом:
CREATE OR REPLACE FUNCTION my_fetch(internal) RETURNS internal AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
Аргумент является указателем на GISTENTRY структуру. При
вызове функции её key поле содержит ненулевое листовое значение в
сжатом виде. Возвращаемым значением является другая GISTENTRY
структура, в key поле которой содержатся те же данные в их
исходном несжатом виде. Если функция compress класса операторов не выполняет
никаких действий для листовых записей, то fetch метод может возвращать
аргумент в исходном виде. Кроме того, если класс операторов не имеет функции compress,
функции fetch данный метод также может быть опущен, так как он
неизбежно будет представлять собой пустую операцию (no-op).
Соответствующий код в модуле на языке 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);
}
Если метод compress является методом с потерями для листовых записей, класс операторов
не может поддерживать сканирование только по индексу и не должен определять
указатель на 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 (не изменять напрямую!) */
int int_param; /* целочисленный параметр */
double real_param; /* параметр real_param */
MyEnumType enum_param; /* перечисляемый параметр */
int str_param; /* параметр str_param */
} MyOptionsStruct;
/* Строковое представление значений перечисления */
static relopt_enum_elt_def myEnumValues[] =
{
{"on", MY_ENUM_ON},
{"off", MY_ENUM_OFF},
{"auto", MY_ENUM_AUTO},
{(const char *) NULL} /* терминатор списка */
};
static char *str_param_default = "default";
/*
* Пример валидатора: проверяет, что строка не длиннее 8 байт.
*/
static void
validate_my_string_relopt(const char *value)
{
if (strlen(value) > 8)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("параметр str_param должен содержать не более 8 байт")));
}
/*
* Пример заполнения: преобразует символы в нижний регистр.
*/
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", "целочисленный параметр",
100, 0, 1000000,
offsetof(MyOptionsStruct, int_param));
add_local_real_reloption(relopts, "real_param", "вещественный параметр",
1.0, 0.0, 1000000.0,
offsetof(структура MyOptionsStruct, параметр real_param));
add_local_enum_reloption(relopts, "enum_param", "enum parameter",
myEnumValues, MY_ENUM_ON,
"Допустимые значения: \"on\", \"off\" и \"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;
/*
* Обычно, когда класс операторов содержит метод «options», параметры всегда
* передаются вспомогательным функциям. Однако при добавлении метода «options» в
* существующий класс операторов у ранее созданных индексов параметры отсутствуют,
* поэтому требуется проверка.
*/
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 commands. Качество создаваемого индекса
зависит от того, насколько эффективно порядок сортировки, определяемый функцией сравнения,
сохраняет локальность входных данных.
Команда 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)
{
/* установить порядок между x и y путем вычисления некоторого значения сортировки 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, вызывалась функция pfree. Однако в ряде случаев для вспомогательного метода целесообразно кэшировать данные между повторными вызовами. Для этого следует выделить
память для долгоживущих данных в fcinfo->flinfo->fn_mcxt, и
сохранить указатель на них в fcinfo->flinfo->fn_extra. Такие данные сохраняются в течение всего времени выполнения операции с индексом (например, одного сканирования индекса GiST, построения индекса или вставки кортежа индекса). При замене значения следует соблюдать осторожность и вызывать функцию pfree для освобождения предыдущего fn_extra значения, иначе утечка памяти
будет накапливаться в течение всей операции.
Самый простой способ построения индекса GiST заключается в последовательной вставке всех записей по одной. Для больших индексов этот процесс, как правило, протекает медленно, так как если кортежи индекса распределены по всему индексу и его размер превышает объем кэша, потребуется выполнение значительного объема операций произвольного ввода-вывода. Digital Q.DataBase поддерживает два альтернативных метода первоначального построения индекса GiST: с сортировкой и с буферизацией режимы.
Метод с сортировкой доступен только в том случае, если каждый из классов операторов, используемых индексом, предоставляет функцию sortsupport как описано
в Раздел 7.14.2.3. В этом случае данный метод обычно является предпочтительным, поэтому он используется по умолчанию.
Метод с буферизацией работает без немедленной вставки кортежей в индекс. Это позволяет значительно сократить объем операций произвольного ввода-вывода, необходимых для неупорядоченных наборов данных. Для хорошо упорядоченных наборов данных преимущество будет незначительным или вовсе отсутствовать, так как в каждый момент времени новые кортежи записываются лишь в небольшое количество страниц, которые помещаются в кэш, даже если индекс в целом в него не умещается.
Методу с буферизацией требуется вызывать функцию penalty
чаще, чем обычному методу, что влечет за собой дополнительные затраты ресурсов процессора. Кроме того, буферам требуется временное дисковое пространство объемом вплоть до размера результирующего индекса. Буферизация также может влиять на качество результирующего индекса как в положительную, так и в отрицательную сторону. Это влияние зависит от различных факторов, таких как распределение входных данных и реализация класса операторов.
Если сортировка невозможна, то по умолчанию построение индекса GiST переключается на метод буферизации, когда размер индекса достигает
effective_cache_size. Буферизацию можно принудительно
включить или отключить с помощью buffering параметра команды
CREATE INDEX. Поведение по умолчанию оптимально для большинства случаев, но отключение буферизации может несколько ускорить построение, если входные данные упорядочены.
В Digital Q.DataBase составе исходного дистрибутива имеется
несколько примеров методов доступа, реализованных с использованием
GiST. Ядро системы в настоящее время обеспечивает поддержку полнотекстового поиска
(индексирование для типов tsvector и tsquery), а также функциональность, эквивалентную R-дереву, для некоторых встроенных геометрических типов данных (см. src/backend/access/gist/gistproc.c). Следующие
contrib модули также содержат GiST
классы операторов:
btree_gistФункциональность, эквивалентная B-дереву, для нескольких типов данных
cubeИндексирование многомерных кубов
hstoreМодуль для хранения пар (ключ, значение)
intarrayRD-дерево для одномерных массивов значений типа int4
ltreeИндексирование древовидных структур
pg_trgmОпределение сходства текста на основе сопоставления триграмм
segИндексирование «диапазонов чисел с плавающей запятой»