×
Мы обрабатываем cookies, чтобы сделать наш сайт удобнее и персонализированнее для вас. Подробнее: политика использования «cookies» и «политики конфиденциальности».

Для самостоятельной настройки ознакомьтесь с инструкцией

Дополнительные настройки cookies в браузерах

Файлы cookie автоматически загружаются в ваш браузер при посещении веб-сайта. У вас есть возможность управлять этими файлами. Если Вы не согласны с использованием файлов cookies, запретите их сохранение на своём устройстве, удалите уже имеющиеся файлы cookies через настройки браузера или прекратите использование сайта.

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

Инструкция по отключению cookies
Принять
Настроить
Отклонить

ДОКУМЕНТАЦИЯ

Выберите версию, форк и язык для СУБД Digital Q.DataBase, чтобы прочитать или скачать всю документацию.
Техподдержка
Документация
Диасофт
Авторские права © 2016–2025 ООО "Диасофт Экосистема"
Скачать всю документацию:

7.14.2. Индексы GiST

7.14.2.1. Введение
7.14.2.2. Встроенные классы операторов
7.14.2.3. Расширяемость
7.14.2.4. Реализация
7.14.2.5. Примеры

7.14.2.1. Введение #

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 в основном поддерживается Федором Сигаевым и Олегом Бартуновым; более подробная информация приведена на их веб-сайте.

7.14.2.2. Встроенные классы операторов #

В основной состав 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);

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

Традиционно реализация нового метода доступа к индексам требовала выполнения большого объема сложной работы. Необходимо было понимать внутренние механизмы базы данных, такие как менеджер блокировок и журнал упреждающей записи (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 значения, иначе утечка памяти будет накапливаться в течение всей операции.

7.14.2.4. Реализация #

7.14.2.4.1. Методы построения индекса GiST #

Самый простой способ построения индекса GiST заключается в последовательной вставке всех записей по одной. Для больших индексов этот процесс, как правило, протекает медленно, так как если кортежи индекса распределены по всему индексу и его размер превышает объем кэша, потребуется выполнение значительного объема операций произвольного ввода-вывода. Digital Q.DataBase поддерживает два альтернативных метода первоначального построения индекса GiST: с сортировкой и с буферизацией режимы.

Метод с сортировкой доступен только в том случае, если каждый из классов операторов, используемых индексом, предоставляет функцию sortsupport как описано в Раздел 7.14.2.3. В этом случае данный метод обычно является предпочтительным, поэтому он используется по умолчанию.

Метод с буферизацией работает без немедленной вставки кортежей в индекс. Это позволяет значительно сократить объем операций произвольного ввода-вывода, необходимых для неупорядоченных наборов данных. Для хорошо упорядоченных наборов данных преимущество будет незначительным или вовсе отсутствовать, так как в каждый момент времени новые кортежи записываются лишь в небольшое количество страниц, которые помещаются в кэш, даже если индекс в целом в него не умещается.

Методу с буферизацией требуется вызывать функцию penalty чаще, чем обычному методу, что влечет за собой дополнительные затраты ресурсов процессора. Кроме того, буферам требуется временное дисковое пространство объемом вплоть до размера результирующего индекса. Буферизация также может влиять на качество результирующего индекса как в положительную, так и в отрицательную сторону. Это влияние зависит от различных факторов, таких как распределение входных данных и реализация класса операторов.

Если сортировка невозможна, то по умолчанию построение индекса GiST переключается на метод буферизации, когда размер индекса достигает effective_cache_size. Буферизацию можно принудительно включить или отключить с помощью buffering параметра команды CREATE INDEX. Поведение по умолчанию оптимально для большинства случаев, но отключение буферизации может несколько ускорить построение, если входные данные упорядочены.

7.14.2.5. Примеры #

В Digital Q.DataBase составе исходного дистрибутива имеется несколько примеров методов доступа, реализованных с использованием GiST. Ядро системы в настоящее время обеспечивает поддержку полнотекстового поиска (индексирование для типов tsvector и tsquery), а также функциональность, эквивалентную R-дереву, для некоторых встроенных геометрических типов данных (см. src/backend/access/gist/gistproc.c). Следующие contrib модули также содержат GiST классы операторов:

btree_gist

Функциональность, эквивалентная B-дереву, для нескольких типов данных

cube

Индексирование многомерных кубов

hstore

Модуль для хранения пар (ключ, значение)

intarray

RD-дерево для одномерных массивов значений типа int4

ltree

Индексирование древовидных структур

pg_trgm

Определение сходства текста на основе сопоставления триграмм

seg

Индексирование «диапазонов чисел с плавающей запятой»

Наверх
свяжитесь
с нами
контакты
Для прямой связи с нами вы можете использовать контакты ниже, либо оставить заявку через форму обратной связи, и мы обязательно свяжемся с вами

*поля обязательные к заполнению