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

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

примечание

Эта страница переведена при помощи нейросети GigaChat.

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

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

Методы индексации и классы операторов

Классы операторов связаны с методом доступа к индексу, таким как B-дерево или GIN. Пользовательский метод доступа к индексу может быть определен с помощью команды CREATE ACCESS METHOD. См. раздел «Определение интерфейса для индексных методов доступа» для получения подробной информации.

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

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

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

Стратегии методов индексации

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

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

Стратегии B-дерева:

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

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

Стратегии хеширования:

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

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

Двумерные стратегии «R-дерева» GiST:

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

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

Стратегии SP-GiST для точек:

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

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

Стратегии массива GIN:

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

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

Стратегии BRIN Minmax:

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

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

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

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

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

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

Для работы с B-деревом требуется функция сравнения, а также допускается использование четырех дополнительных функций поддержки, предоставляемых автором класса операторов, как показано в таблице «Функции поддержки B-дерева». Требования к этим функциям поддержки описаны более подробно в разделе «Функции поддержки B-дерева».

Функции поддержки B-дерева:

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

Хеш-индексы требуют одной функции поддержки и допускают предоставление двух дополнительных функций по усмотрению автора класса операторов, как показано в таблице «Функции поддержки хеширования».

Функции поддержки хеширования:

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

Индексы GiST имеют одиннадцать функций поддержки, шесть из которых являются необязательными, как показано в таблице «Функции поддержки GiST». Для получения дополнительной информации см. раздел «Индексы GiST».

Функции поддержки GiST:

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

Индексы SP-GiST имеют шесть функций поддержки, одна из которых является необязательной, как показано в таблице «Функции поддержки SP-GiST» (для получения дополнительной информации см. раздел «Индексы SP-GiST»).

Функции поддержки SP-GiST:

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

Индексы GIN имеют семь функций поддержки, четыре из которых являются необязательными, как показано в таблице «Функции поддержки GIN» (для получения дополнительной информации см. раздел «Индексы GIN»).

Функции поддержки GIN:

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

Индексы BRIN имеют пять основных функций поддержки, одна из которых является необязательной, как показано в таблице «Функции поддержки BRIN». Некоторые версии базовых функций требуют предоставления дополнительных функций поддержки (для получения дополнительной информации см. раздел «Расширяемость»).

Функции поддержки BRIN:

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

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

Пример

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

  • абсолютное значение меньше чем (стратегия 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.
  • Хотя PostgreSQL может работать с функциями, имеющими одно и то же имя SQL при условии, что они имеют разные типы аргументов данных, C может работать только с одной глобальной функцией с заданным именем. Поэтому не надо называть функцию C чем-то простым вроде abs_eq. Обычно хорошей практикой является включение имени типа данных в имя функции C, чтобы избежать конфликтов с функциями для других типов данных.
  • Можно было бы использовать имя SQL-функции abs_eq, рассчитывая на то, что PostgreSQL отличит ее от любых других одноименных функций 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.

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

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

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

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

В качестве примера, PostgreSQL имеет встроенное семейство операторов 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-дерева все операторы в семействе должны сортироваться совместимым образом, как подробно указано в разделе 67.2. Для каждого оператора в семействе должна быть поддержка функции с теми же двумя типами входных данных, что и оператор. Рекомендуется, чтобы семейство было полным, т.е. для каждой комбинации типов данных включены все операторы. В классы операторов следует включать только однотиповые операторы и опорные функции для определенного типа данных.

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

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

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

Примечание

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

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

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

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

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

Если для типа не определен класс операторов по умолчанию, попытавшись использовать эти конструкции SQL с данным типом, то получите ошибку вида «не удалось найти оператор сортировки».

Примечание

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

Сортировка по классу операторов B-дерева, отличному от класса по умолчанию, возможна путем указания оператора «меньше чем» этого класса в опции USING, например:

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

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

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

Еще одной функцией SQL, требующей еще более специфичных знаний о типе данных, является RANGE offset PRECEDING/FOLLOWING опция кадрирования для оконных функций. Для запроса, такого как:

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-дерева может указать функцию поддержки в пределах диапазона, которая инкапсулирует поведение сложения и вычитания, которое имеет смысл для его порядка сортировки. Он даже может предоставить более одной функции поддержки в пределах диапазона, если существует более одного типа данных, которые имеют смысл использовать в качестве смещения в предложениях RANGE. Если класс операторов B-дерева, связанный с предложением ORDER BY окна, не имеет соответствующей функции поддержки в пределах диапазона, параметр RANGE offset PRECEDING/FOLLOWING не поддерживается.

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

Операторы упорядочения

Некоторые методы доступа к индексам (на данный момент только 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 на столбце местоположения может делать это эффективно, потому что <-> является оператором упорядочивания.

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

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

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

Особенности классов операторов

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

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

SELECT * FROM table WHERE integer_column < 4;

может быть точно удовлетворено индексом B-дерева на целочисленном столбце. Но есть случаи, когда индекс полезен как неточный ориентир для совпадающих строк. Например, если GiST-индекс хранит только ограничивающие рамки для геометрических объектов, то он не может точно удовлетворить условие WHERE, которое проверяет перекрытие между не прямоугольными объектами, такими как многоугольники. Тем не менее, можно было бы использовать индекс для поиска объектов, ограничивающая рамка которых пересекается с ограничивающей рамкой целевого объекта, а затем выполнить точную проверку перекрытия только для объектов, найденных с помощью индекса. Если этот сценарий применим, говорят, что индекс является «потерянным» для оператора. Потеря при поиске в индексе реализуется путем того, что метод индекса возвращает флаг проверки, когда строка может или не может действительно удовлетворять условию запроса. Основная система тогда проверит исходное условие запроса на извлеченной строке, чтобы увидеть, следует ли возвращать ее в качестве допустимого совпадения. Этот подход работает, если индекс гарантированно возвращает все необходимые строки плюс, возможно, некоторые дополнительные строки, которые могут быть исключены путем выполнения исходного вызова оператора. Методы индексации, поддерживающие потерю при поиске (в настоящее время 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 для преобразования в тип хранения, когда он отличается; если класс операций SP-GiST также поддерживает извлечение данных, обратное преобразование должно обрабатываться функцией consistent. В GIN тип STORAGE определяет тип значений «ключа», которые обычно отличаются от типа индексируемого столбца – например, класс операторов для столбцов целочисленных массивов может иметь ключи, которые являются просто целыми числами. Процедуры поддержки GIN extractValue и extractQuery отвечают за извлечение ключей из индексируемых значений. BRIN аналогичен GIN: тип STORAGE определяет тип сохраненных сводных значений, а процедуры поддержки классов операторов несут ответственность за правильную интерпретацию сводных значений.