36.15. Интерфейсные расширения для индексов#

36.15. Интерфейсные расширения для индексов

36.15. Интерфейсные расширения для индексов

Все до сих пор описанные процедуры позволяют вам определить новые типы, новые функции и новые операторы. Однако мы пока не можем определить индекс на столбце нового типа данных. Для этого мы должны определить класс операторов для нового типа данных. Позже в этом разделе мы проиллюстрируем эту концепцию на примере: новый класс операторов для метода индексирования B-дерева, который хранит и сортирует комплексные числа в порядке возрастания абсолютного значения.

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

36.15.1. Методы индексирования и классы операторов

Таблица pg_am содержит одну строку для каждого метода индекса (внутренне известного как метод доступа). Поддержка обычного доступа к таблицам встроена в Tantor SE, но все методы индексов описаны в pg_am. Возможно добавить новый метод доступа к индексу, написав необходимый код, а затем создав запись в pg_am — но это выходит за рамки данной главы (см. Глава 62).

процедуры для метода индекса напрямую не знают ничего о типах данных, на которых будет работать метод индекса. Вместо этого, класс операторов определяет набор операций, которые метод индекса должен использовать для работы с определенным типом данных. Операторные классы называются так, потому что они определяют набор операторов WHERE-предложения, которые могут быть использованы с индексом (т.е. могут быть преобразованы в квалификацию индексного сканирования). Операторный класс также может указывать некоторые функции поддержки, которые необходимы для внутренних операций метода индекса, но не соответствуют непосредственно какому-либо оператору предложения WHERE, который может быть использован с индексом.

Возможно определить несколько классов операторов для одного и того же типа данных и метода индексирования. Это позволяет определить несколько наборов семантики индексирования для одного типа данных. Например, для индекса B-дерева требуется определить порядок сортировки для каждого типа данных, с которым он работает. Может быть полезно для типа данных комплексного числа иметь один класс операторов B-дерева, который сортирует данные по абсолютному значению комплексного числа, другой - по действительной части и так далее. Обычно один из классов операторов считается наиболее полезным и помечается как класс операторов по умолчанию для этого типа данных и метода индексирования.

То же имя класса оператора может использоваться для нескольких различных методов индексации (например, как для методов индексации B-дерева, так и для методов индексации хеша есть классы операторов с именем int4_ops), но каждый такой класс является независимой сущностью и должен быть определен отдельно.

36.15.2. Стратегии индексного метода

Операторы, связанные с классом операторов, идентифицируются с помощью номеров стратегий, которые служат для определения семантики каждого оператора в контексте его класса операторов. Например, B-деревья налагают строгий порядок на ключи, от меньшего к большему, поэтому операторы, такие как меньше и больше или равно, интересны с точки зрения B-дерева. Поскольку Tantor SE позволяет пользователю определять операторы, Tantor SE не может посмотреть на имя оператора (например, < или >=) и сказать, какого рода сравнение это. Вместо этого метод индекса определяет набор стратегий, которые можно рассматривать как обобщенные операторы. Каждый класс операторов указывает, какой конкретный оператор соответствует каждой стратегии для определенного типа данных и интерпретации семантики индекса.

Метод индекса B-дерева определяет пять стратегий, показанных в Таблица 36.2.

Таблица 36.2. Стратегии B-дерева

ОперацияНомер стратегии
меньше1
меньше или равно2
равно3
больше или равно4
больше чем5

Хеш-индексы поддерживают только сравнения на равенство, поэтому они используют только одну стратегию, показанную в Таблица 36.3.

Таблица 36.3. Стратегии хеширования

ОперацияНомер стратегии
равно1

GiST индексы более гибкие: у них вообще нет фиксированного набора стратегий. Вместо этого, опорная функция согласованности каждого конкретного класса операторов GiST интерпретирует номера стратегий так, как ей нравится. В качестве примера, несколько встроенных классов операторов GiST индексируют двумерные геометрические объекты, предоставляя стратегии R-tree, показанные в Таблица 36.4. Четыре из них являются trueи двумерными тестами (перекрываются, одинаковые, содержат, содержатся); четыре из них учитывают только направление X; и другие четыре предоставляют те же тесты в направлении Y.

Таблица 36.4. Стратегии двумерного GiST R-tree

ОперацияНомер стратегии
строго слева от1
не расширяется вправо от2
перекрывает3
не распространяется налево от4
строго справа от5
такой же6
содержит7
содержится в8
не расширяется выше9
строго ниже10
строго выше11
не расширяется ниже12

Индексы SP-GiST похожи на индексы GiST в гибкости: у них нет фиксированного набора стратегий. Вместо этого вспомогательные функции каждого класса операторов интерпретируют номера стратегий в соответствии с определением класса операторов. В качестве примера, номера стратегий, используемые встроенными классами операторов для точек, показаны в Таблица 36.5.

Таблица 36.5. Стратегии точек SP-GiST

ОперацияНомер стратегии
строго слева от1
строго справа от5
такой же6
содержится в8
строго ниже10
строго выше11

Все индексы GIN похожи на индексы GiST и SP-GiST в том смысле, что у них также нет фиксированного набора стратегий. Вместо этого, вспомогательные функции каждого класса операторов интерпретируют номера стратегий в соответствии с определением класса операторов. Например, номера стратегий, используемые встроенным классом операторов для массивов, показаны в Таблица 36.6.

Таблица 36.6. GIN Стратегии Массива

ОперацияНомер стратегии
перекрытие1
содержит2
содержится в3
равно4

Все индексы BRIN похожи на индексы GiST, SP-GiST и GIN в том смысле, что у них также нет фиксированного набора стратегий. Вместо этого, вспомогательные функции каждого класса операторов интерпретируют номера стратегий в соответствии с определением класса операторов. Например, номера стратегий, используемые встроенными классами операторов Minmax, показаны в таблице Таблица 36.7.

Таблица 36.7. BRIN Минимакс стратегии

ОперацияНомер стратегии
меньше1
меньше или равно2
равно3
больше или равно4
больше чем5

Обратите внимание, что все перечисленные выше операторы возвращают логические значения. На практике все операторы, определенные как операторы поиска метода индекса, должны возвращать тип boolean, поскольку они должны появляться на верхнем уровне предложения WHERE, чтобы использоваться с индексом. (Некоторые методы доступа к индексу также поддерживают операторы упорядочивания, которые обычно не возвращают логические значения; об этой функции рассказывается в разделе Раздел 36.15.7).

36.15.3. Поддержка методов индексации

Стратегии обычно не являются достаточной информацией для системы, чтобы определить, как использовать индекс. На практике методы индексации требуют дополнительных вспомогательных процедур для работы. Например, метод индексации B-дерева должен иметь возможность сравнивать два ключа и определять, является ли один больше, равным или меньшим другого. Аналогично, метод индексации хеша должен иметь возможность вычислять хеш-коды для значений ключей. Эти операции не соответствуют операторам, используемым в квалификациях в SQL-командах; они являются административными процедурами, используемыми внутри методов индексации.

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

Дополнительно, некоторые opclasses позволяют пользователям указывать параметры, которые контролируют их поведение. У каждого встроенного метода доступа к индексу есть необязательная Опорная функция options, которая определяет набор параметров, специфичных для opclass.

B-деревья требуют функции поддержки сравнения и позволяют добавить еще четыре функции поддержки по выбору автора класса операторов, как показано в Таблица 36.8. Требования к этим функциям поддержки подробно объясняются в Раздел 65.3.

Таблица 36.8. Функции поддержки B-дерева

ФункцияНомер опорной функциии
Сравнить два ключа и вернуть целое число меньше нуля, ноль или больше нуля, указывающее, является ли первый ключ меньше, равным или больше второго 1
Возвращает адреса функций сортировки, доступных для вызова из C (необязательно) 2
Сравнивает тестовое значение с базовым значением плюс/минус смещение и возвращает true или false в зависимости от результата сравнения (необязательно) 3
Определяет, безопасно ли применять оптимизацию дедупликации b-дерева для индексов, использующих класс операторов (необязательно) 4
Определяет параметры, специфичные для данного класса операторов (необязательно) 5

Хеш-индексы требуют одной вспомогательной функции и позволяют добавить две дополнительные функции по выбору автора класса операторов, как показано в Таблица 36.9.

Таблица 36.9. Функции поддержки хеширования

ФункцияНомер опорной функциии
Вычислить 32-битное значение хеша для ключа1
Вычисляет 64-битное значение хеша для ключа с заданной 64-битной солью; если соль равна 0, то нижние 32 бита результата должны совпадать со значением, которое было бы вычислено функцией 1 (необязательно) 2
Определяет параметры, специфичные для данного класса операторов (необязательно) 5

В GiST-индексах есть одиннадцать вспомогательных функций, из которых шесть являются необязательными, как показано в Таблица 36.10. (Дополнительную информацию см. в разделе Глава 66).

Таблица 36.10. Функции поддержки GiST

ФункцияОписаниеНомер опорной функциии
consistentопределяет, удовлетворяет ли ключ условию запроса1
unionвычисляет объединение набора ключей2
compressвычисляет сжатое представление ключа или значения, которое будет проиндексировано (необязательно)3
decompressвычисляет разжатое представление сжатого ключа (необязательно)4
штрафвычисляет штраф за вставку нового ключа в поддерево с заданным ключом поддерева5
picksplitопределяет, какие записи страницы должны быть перемещены на новую страницу и вычислить объединенные ключи для результирующих страниц6
sameсравнивает два ключа и вернуть true, если они равны7
distanceопределяет расстояние от ключа до значения запроса (необязательно)8
fetchвычисляет исходное представление сжатого ключа для индексных сканирований только по индексу (необязательно)9
optionsопределяют параметры, специфичные для данного класса операторов (необязательно)10
sortsupportпредоставляет сортировочный компаратор, который может использоваться при быстрой построении индекса (необязательно)11

SP-GiST индексы имеют шесть вспомогательных функций, одна из которых является необязательной, как показано в Таблица 36.11. (Дополнительную информацию см. в Глава 67).

Таблица 36.11. Функции поддержки SP-GiST

ФункцияОписаниеНомер опорной функциии
configпредоставляет основную информацию об операторном классе1
chooseопределяет, как вставить новое значение во внутренний кортеж2
picksplitопределяет, как разделить набор значений3
inner_consistentопределяет, какие подсекции нужно искать для запроса4
leaf_consistentопределяет, удовлетворяет ли ключ условию запроса5
optionsопределяет параметры, специфичные для данного класса операторов (необязательно)6

Все индексы GIN имеют семь вспомогательных функций, из которых четыре являются необязательными, как показано в Таблица 36.12. (Дополнительную информацию см. в Глава 68).

Таблица 36.12. Функции поддержки GIN

ФункцияОписаниеНомер опорной функциии
compare сравнивает два ключа и вернуть целое число меньше нуля, ноль или больше нуля, указывающее, является ли первый ключ меньше, равным или больше второго 1
extractValueизвлекает ключи из значения для индексации2
extractQueryизвлекает ключи из условия запроса3
consistent определяет, соответствует ли значение условию запроса (вариант Boolean) (необязательно, если присутствует Опорная функция 6) 4
comparePartial сравнивает частичный ключ из запроса и ключ из индекса и возвращает целое число меньше нуля, ноль или больше нуля, указывающее, должен ли GIN игнорировать эту запись индекса, считать запись совпадением или остановить сканирование индекса (необязательно) 5
triConsistent определяет, соответствует ли значение условию запроса (тернарный вариант) (необязательно, если присутствует Опорная функция 4) 6
options определяет параметры, специфичные для данного класса операторов (необязательно) 7

Все индексы BRIN имеют пять основных вспомогательных функций, одна из которых является необязательной, как показано в Таблица 36.13. Некоторые версии основных функций требуют дополнительных вспомогательных функций. (Дополнительную информацию см. в Раздел 69.3).

Таблица 36.13. Функции поддержки BRIN

ФункцияОписаниеНомер опорной функциии
opcInfo возвращает внутреннюю информацию, описывающую сводные данные индексированных столбцов 1
add_valueдобавить новое значение к существующей кортежу сводного индекса2
consistentопределить, соответствует ли значение условию запроса3
union вычислить объединение двух сводных кортежей 4
options определить параметры, специфичные для данного класса операторов (необязательно) 5

В отличие от операторов поиска, функции поддержки возвращают данные того типа, который ожидает конкретный метод индекса; например, в случае функции сравнения для B-деревьев - это знаковое целое число. Количество и типы аргументов для каждой функции поддержки также зависят от метода индекса. Для B-деревьев и хешей функции сравнения и хеширования принимают те же типы входных данных, что и операторы, включенные в класс операторов, но это не относится к большинству функций поддержки GiST, SP-GiST, GIN и BRIN.

36.15.4. Пример

Теперь, когда мы рассмотрели идеи, вот обещанный пример создания нового класса операторов. (Вы можете найти рабочую копию этого примера в файле src/tutorial/complex.c и src/tutorial/complex.sql в исходном распределении). Класс операторов инкапсулирует операторы, которые сортируют комплексные числа по абсолютному значению, поэтому мы выбираем имя complex_abs_ops. Сначала нам нужен набор операторов. Процедура определения операторов была обсуждена в Раздел 36.13. Для класса операторов на B-деревьях нам требуются следующие операторы:

  • абсолютное-значение меньше-чем (стратегия 1)
  • абсолютное-значение меньше-или-равно (стратегия 2)
  • абсолютное-значение равно (стратегия 3)
  • абсолютное-значение больше-или-равно (стратегия 4)
  • абсолютное-значение больше-чем (стратегия 5)

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

#define Mag(c)  ((c)->x*(c)->x + (c)->y*(c)->y)

static int
complex_abs_cmp_internal(Complex *a, Complex *b)
{
    double      amag = Mag(a),
                bmag = Mag(b);

    if (amag < bmag)
        return -1;
    if (amag > bmag)
        return 1;
    return 0;
}

Теперь функция меньше выглядит так:

PG_FUNCTION_INFO_V1(complex_abs_lt);

Datum
complex_abs_lt(PG_FUNCTION_ARGS)
{
    Complex    *a = (Complex *) PG_GETARG_POINTER(0);
    Complex    *b = (Complex *) PG_GETARG_POINTER(1);

    PG_RETURN_BOOL(complex_abs_cmp_internal(a, b) < 0);
}

Другие четыре функции отличаются только тем, как они сравнивают результат внутренней функции с нулем.

Далее мы объявляем функции и операторы на основе функций в SQL:

CREATE FUNCTION complex_abs_lt(complex, complex) RETURNS bool
    AS 'filename', 'complex_abs_lt'
    LANGUAGE C IMMUTABLE STRICT;

CREATE OPERATOR < (
   leftarg = complex, rightarg = complex, procedure = complex_abs_lt,
   commutator = > , negator = >= ,
   restrict = scalarltsel, join = scalarltjoinsel
);

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

Здесь происходят и другие важные вещи:

  • Возможно только один оператор с именем, скажем, = и принимающий тип complex для обоих операндов. В этом случае у нас нет другого оператора = для complex, но если бы мы создавали практический тип данных, мы, вероятно, хотели бы, чтобы = был обычной операцией равенства для комплексных чисел (а не равенством абсолютных значений). В этом случае нам потребуется использовать другое имя оператора для complex_abs_eq.

  • Хотя Tantor SE может справиться с функциями, имеющими одно и то же SQL-имя, если у них разные типы аргументов, C может справиться только с одной глобальной функцией с заданным именем. Поэтому мы не должны называть C-функцию чем-то простым, например, abs_eq. Обычно хорошей практикой является включение имени типа данных в имя C-функции, чтобы избежать конфликта с функциями для других типов данных.

  • Мы могли бы назвать функцию SQL abs_eq, полагаясь на Tantor SE, чтобы отличить ее по типам аргументов от любой другой SQL-функции с таким же именем. Чтобы пример был простым, мы называем функцию одинаково на уровне C и SQL.

Следующим шагом является регистрация необходимой вспомогательной функции для B-деревьев. Пример кода на языке C, реализующий это, находится в том же файле, что и функции операторов. Вот как мы объявляем функцию:

CREATE FUNCTION complex_abs_cmp(complex, complex)
    RETURNS integer
    AS 'filename'
    LANGUAGE C IMMUTABLE STRICT;

Теперь, когда у нас есть необходимые операторы и вспомогательные функции, мы наконец можем создать класс операторов:

CREATE OPERATOR CLASS complex_abs_ops
    DEFAULT FOR TYPE complex USING btree AS
        OPERATOR        1       < ,
        OPERATOR        2       <= ,
        OPERATOR        3       = ,
        OPERATOR        4       >= ,
        OPERATOR        5       > ,
        FUNCTION        1       complex_abs_cmp(complex, complex);

И мы закончили! Теперь должна быть возможность создавать и использовать индексы B-дерева на столбцах complex.

Мы могли бы написать записи операторов более подробно, как в следующем примере:

        OPERATOR        1       < (complex, complex) ,

но нет необходимости делать это, когда операторы принимают тот же тип данных, для которого мы определяем класс операторов.

В приведенном выше примере предполагается, что вы хотите сделать этот новый класс операторов классом операторов B-дерева по умолчанию для типа данных complex. Если вы этого не хотите, просто опустите слово DEFAULT.

36.15.5. Классы операторов и семейства операторов

До сих пор мы неявно предполагали, что класс операторов работает только с одним типом данных. Хотя в определенном столбце индекса может быть только один тип данных, часто бывает полезно индексировать операции, которые сравнивают столбец индекса с значением другого типа данных. Кроме того, если существует необходимость в операторе между разными типами данных в связи с классом операторов, часто бывает так, что у другого типа данных есть свой собственный класс операторов. Полезно явно указывать связи между связанными классами, потому что это может помочь планировщику оптимизировать SQL-запросы (особенно для классов операторов B-дерева, поскольку планировщик содержит большое количество знаний о том, как с ними работать).

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

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

В качестве примера, Tantor SE имеет встроенную семейство операторов B-дерева integer_ops, которое включает операторы классов int8_ops, int4_ops и int2_ops для индексов на столбцах типа bigint (int8), integer (int4) и smallint (int2) соответственно. Семейство также содержит операторы сравнения между различными типами данных, позволяющие сравнивать любые два из этих типов, так что индекс на одном из этих типов может быть найден с использованием значения сравнения другого типа. Семейство может быть дублировано с помощью следующих определений:

CREATE OPERATOR FAMILY integer_ops USING btree;

CREATE OPERATOR CLASS int8_ops
DEFAULT FOR TYPE int8 USING btree FAMILY integer_ops AS
  -- standard int8 comparisons
  OPERATOR 1 < ,
  OPERATOR 2 <= ,
  OPERATOR 3 = ,
  OPERATOR 4 >= ,
  OPERATOR 5 > ,
  FUNCTION 1 btint8cmp(int8, int8) ,
  FUNCTION 2 btint8sortsupport(internal) ,
  FUNCTION 3 in_range(int8, int8, int8, boolean, boolean) ,
  FUNCTION 4 btequalimage(oid) ;

CREATE OPERATOR CLASS int4_ops
DEFAULT FOR TYPE int4 USING btree FAMILY integer_ops AS
  -- standard int4 comparisons
  OPERATOR 1 < ,
  OPERATOR 2 <= ,
  OPERATOR 3 = ,
  OPERATOR 4 >= ,
  OPERATOR 5 > ,
  FUNCTION 1 btint4cmp(int4, int4) ,
  FUNCTION 2 btint4sortsupport(internal) ,
  FUNCTION 3 in_range(int4, int4, int4, boolean, boolean) ,
  FUNCTION 4 btequalimage(oid) ;

CREATE OPERATOR CLASS int2_ops
DEFAULT FOR TYPE int2 USING btree FAMILY integer_ops AS
  -- standard int2 comparisons
  OPERATOR 1 < ,
  OPERATOR 2 <= ,
  OPERATOR 3 = ,
  OPERATOR 4 >= ,
  OPERATOR 5 > ,
  FUNCTION 1 btint2cmp(int2, int2) ,
  FUNCTION 2 btint2sortsupport(internal) ,
  FUNCTION 3 in_range(int2, int2, int2, boolean, boolean) ,
  FUNCTION 4 btequalimage(oid) ;

ALTER OPERATOR FAMILY integer_ops USING btree ADD
  -- cross-type comparisons int8 vs int2
  OPERATOR 1 < (int8, int2) ,
  OPERATOR 2 <= (int8, int2) ,
  OPERATOR 3 = (int8, int2) ,
  OPERATOR 4 >= (int8, int2) ,
  OPERATOR 5 > (int8, int2) ,
  FUNCTION 1 btint82cmp(int8, int2) ,

  -- cross-type comparisons int8 vs int4
  OPERATOR 1 < (int8, int4) ,
  OPERATOR 2 <= (int8, int4) ,
  OPERATOR 3 = (int8, int4) ,
  OPERATOR 4 >= (int8, int4) ,
  OPERATOR 5 > (int8, int4) ,
  FUNCTION 1 btint84cmp(int8, int4) ,

  -- cross-type comparisons int4 vs int2
  OPERATOR 1 < (int4, int2) ,
  OPERATOR 2 <= (int4, int2) ,
  OPERATOR 3 = (int4, int2) ,
  OPERATOR 4 >= (int4, int2) ,
  OPERATOR 5 > (int4, int2) ,
  FUNCTION 1 btint42cmp(int4, int2) ,

  -- cross-type comparisons int4 vs int8
  OPERATOR 1 < (int4, int8) ,
  OPERATOR 2 <= (int4, int8) ,
  OPERATOR 3 = (int4, int8) ,
  OPERATOR 4 >= (int4, int8) ,
  OPERATOR 5 > (int4, int8) ,
  FUNCTION 1 btint48cmp(int4, int8) ,

  -- cross-type comparisons int2 vs int8
  OPERATOR 1 < (int2, int8) ,
  OPERATOR 2 <= (int2, int8) ,
  OPERATOR 3 = (int2, int8) ,
  OPERATOR 4 >= (int2, int8) ,
  OPERATOR 5 > (int2, int8) ,
  FUNCTION 1 btint28cmp(int2, int8) ,

  -- cross-type comparisons int2 vs int4
  OPERATOR 1 < (int2, int4) ,
  OPERATOR 2 <= (int2, int4) ,
  OPERATOR 3 = (int2, int4) ,
  OPERATOR 4 >= (int2, int4) ,
  OPERATOR 5 > (int2, int4) ,
  FUNCTION 1 btint24cmp(int2, int4) ,

  -- cross-type in_range functions
  FUNCTION 3 in_range(int4, int4, int8, boolean, boolean) ,
  FUNCTION 3 in_range(int4, int4, int2, boolean, boolean) ,
  FUNCTION 3 in_range(int2, int2, int8, boolean, boolean) ,
  FUNCTION 3 in_range(int2, int2, int4, boolean, boolean) ;

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

В семействе операторов B-дерева все операторы должны быть совместимыми по сортировке, как указано подробно в Раздел 65.2. Для каждого оператора в семействе должна быть Опорная функция с теми же двумя типами данных ввода, что и оператор. Рекомендуется, чтобы семейство было полным, то есть для каждой комбинации типов данных включены все операторы. Каждый класс операторов должен включать только операторы и функции поддержки для своего типа данных.

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

Индексы GiST, SP-GiST и GIN не имеют явного понятия о перекрестных операциях между типами данных. Набор поддерживаемых операторов определяется только тем, что функции основной поддержки для данного класса операторов могут обработать.

В BRIN требования зависят от фреймворка, который предоставляет классы операторов. Для классов операторов, основанных на minmax, требуется такое же поведение, как и для семейств операторов B-дерева: все операторы в семействе должны сортироваться совместимо, и приведения типов не должны изменять связанный порядок сортировки.

Примечание

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

36.15.6. Системные зависимости от классов операторов

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

В частности, существуют SQL-функции, такие как ORDER BY и DISTINCT, которые требуют сравнения и сортировки значений. Для реализации этих функций на пользовательском типе данных, Tantor SE ищет класс операторов B-дерева по умолчанию для данного типа данных. Equals член этого класса операторов определяет системное представление равенства значений для GROUP BY и DISTINCT, а порядок сортировки, наложенный классом операторов, определяет порядок сортировки по умолчанию для ORDER BY.

Если для типа данных нет класса операторов B-дерева по умолчанию, система будет искать класс операторов хеша по умолчанию. Но поскольку такой класс операторов обеспечивает только равенство, он может поддерживать только группировку, но не сортировку.

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

Примечание

В версиях Tantor SE до 7.4 сортировка и группировка операций неявно использовали операторы с именами =, < и >. Новое поведение, основанное на использовании классов операторов по умолчанию, позволяет избежать предположений о поведении операторов с определенными именами.

Сортировка по нестандартному классу операторов B-дерева возможна путем указания оператора "меньше" класса в опции USING, например:

SELECT * FROM mytable ORDER BY somecol USING ~<~;

В качестве альтернативы, указание оператора больше для класса внутри тега USING выбирает сортировку в порядке убывания.

Сравнение массивов пользовательского типа также зависит от семантики, определенной классом операторов B-дерева по умолчанию для этого типа. Если нет класса операторов B-дерева по умолчанию, но есть класс операторов хеширования по умолчанию, то поддерживается равенство массивов, но не сравнение порядка.

Другая функция SQL, которая требует еще больше знаний о типах данных, это опция RANGE offset PRECEDING/FOLLOWING для оконных функций (см. Раздел 4.2.8). Для запроса, например,

SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING)
  FROM mytable;

Недостаточно знать, как упорядочить по x; база данных также должна понимать, как вычесть 5 или прибавить 10 к текущему значению строки x для определения границ текущего окна. Сравнение полученных границ с значениями других строк x возможно с использованием операторов сравнения, предоставляемых оператором класса B-дерева, который определяет порядок ORDER BY - но операторы сложения и вычитания не являются частью класса операторов, поэтому какие из них следует использовать? Жесткое закрепление этого выбора было бы нежелательным, потому что разные порядки сортировки (разные классы операторов B-дерева) могут требовать разного поведения. Поэтому класс операторов B-дерева может указать функцию поддержки in_range, которая инкапсулирует поведение сложения и вычитания, имеющее смысл для его порядка сортировки. Он может даже предоставить несколько функций поддержки in_range, на случай, если есть более одного типа данных, имеющего смысл использовать в качестве смещения в предложениях RANGE. Если класс операторов B-дерева, связанный с предложением ORDER BY окна, не имеет соответствующей функции поддержки in_range, опция RANGE offset PRECEDING/FOLLOWING не поддерживается.

Еще один важный момент заключается в том, что оператор равенства, который присутствует в семействе хеш-операторов, является кандидатом для соединений по хешу, агрегации по хешу и связанных оптимизаций. Семейство хеш-операторов здесь является неотъемлемым, поскольку оно определяет используемую хеш-функцию(и).

36.15.7. Операторы сортировки

Некоторые методы доступа к индексам (в настоящее время, только GiST и SP-GiST) поддерживают концепцию операторов упорядочивания. До сих пор мы обсуждали только операторы поиска. Оператор поиска - это оператор, для которого индекс может быть просканирован для поиска всех строк, удовлетворяющих условию WHERE indexed_column operator constant. Обратите внимание, что ничего не гарантируется относительно порядка, в котором будут возвращены соответствующие строки. В отличие от этого, оператор упорядочивания не ограничивает набор строк, которые могут быть возвращены, а определяет их порядок. Оператор упорядочивания - это оператор, для которого индекс может быть просканирован для возврата строк в порядке, представленном в ORDER BY indexed_column operator constant. Причина определения операторов упорядочивания таким образом заключается в том, что это поддерживает поиск ближайших соседей, если оператор измеряет расстояние. Например, запрос вроде

SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;

находит десять ближайших мест к заданной целевой точке. Индекс GiST на столбце location может выполнять это эффективно, потому что <-> является оператором сортировки.

В то время как операторы поиска должны возвращать логические результаты, операторы сортировки обычно возвращают другой тип данных, такой как float или numeric для расстояний. Этот тип данных обычно не совпадает с типом данных, по которому создан индекс. Чтобы избежать жесткой привязки к предположениям о поведении различных типов данных, определение оператора сортировки должно указывать семейство операторов B-tree, которое определяет порядок сортировки результата. Как было сказано в предыдущем разделе, семейства операторов B-tree определяют представление порядка в Tantor SE, поэтому это естественное представление. Поскольку оператор точка <-> возвращает float8, его можно указать в команде создания класса операторов, например, так:

OPERATOR 15    <-> (point, point) FOR ORDER BY float_ops

где float_ops - встроенная семейство операторов, которое включает операции над float8. Это объявление указывает, что индекс способен возвращать строки в порядке возрастания значений оператора <->.

36.15.8. Особенности специальных классов операторов

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

Обычно объявление оператора как члена класса операторов (или семейства) означает, что метод индекса может извлечь точно тот набор строк, который удовлетворяет условию WHERE с использованием оператора. Например:

SELECT * FROM table WHERE integer_column < 4;

может быть удовлетворен точно индексом B-дерева на столбце целых чисел. Но есть случаи, когда индекс полезен как приближенное руководство к соответствующим строкам. Например, если индекс GiST хранит только ограничивающие прямоугольники для геометрических объектов, то он не может точно удовлетворить условие WHERE, которое проверяет перекрытие между не прямоугольными объектами, такими как полигоны. Однако мы можем использовать индекс для поиска объектов, чьи ограничивающие прямоугольники перекрываются с ограничивающим прямоугольником целевого объекта, а затем выполнить точное тестирование перекрытия только на объектах, найденных с помощью индекса. Если это сценарий применим, то говорят, что индекс является потерянным для оператора. Потерянные поиски по индексу реализуются путем возврата индексным методом флага recheck, когда строка может или не может действительно удовлетворять условию запроса. Ядро системы затем проверяет исходное условие запроса на полученной строке, чтобы увидеть, должна ли она быть возвращена как допустимое совпадение. Этот подход работает, если индекс гарантирует возвращение всех необходимых строк, плюс, возможно, некоторые дополнительные строки, которые могут быть исключены путем выполнения исходного вызова оператора. Индексные методы, которые поддерживают потерянные поиски (в настоящее время GiST, SP-GiST и GIN), позволяют функциям поддержки отдельных классов операторов устанавливать флаг перепроверки, и поэтому это в основном функция класса оператора.

Рассмотрим снова ситуацию, когда мы храним в индексе только ограничивающий прямоугольник сложного объекта, такого как полигон. В этом случае нет особой ценности в хранении всего полигона в записи индекса - мы могли бы хранить только более простой объект типа box. Эта ситуация выражается с помощью опции STORAGE в CREATE OPERATOR CLASS: мы бы написали что-то вроде:

CREATE OPERATOR CLASS polygon_ops
    DEFAULT FOR TYPE polygon USING gist AS
        ...
        STORAGE box;

В настоящее время только методы индексирования GiST, SP-GiST, GIN и BRIN поддерживают тип STORAGE, отличный от типа данных столбца. Функции сжатия и разжатия GiST compress и decompress должны обрабатывать преобразование типов данных при использовании STORAGE. SP-GiST также требует функции поддержки compress для преобразования в тип хранения, если он отличается; если opclass SP-GiST также поддерживает извлечение данных, обратное преобразование должно выполняться функцией consistent. В GIN тип STORAGE определяет тип значений ключей, который обычно отличается от типа индексируемого столбца - например, класс операторов для столбцов с целочисленными массивами может иметь ключи, которые являются просто целыми числами. Функции поддержки extractValue и extractQuery GIN отвечают за извлечение ключей из индексируемых значений. BRIN аналогичен GIN: тип STORAGE определяет тип хранимых значений сводки, а процедуры поддержки классов операторов отвечают за правильную интерпретацию значений сводки.