Процедуры, описанные до этого момента, позволяют определять новые типы, новые функции и новые операторы. Однако мы пока не можем определить индекс для столбца нового типа данных. Для этого необходимо определить класс операторов для нового типа данных. Далее в этом разделе мы проиллюстрируем эту концепцию на примере: новый класс операторов для метода индекса B-tree, который хранит и сортирует комплексные числа в порядке возрастания их абсолютных значений.
Классы операторов могут быть сгруппированы в семейства операторов, чтобы показать взаимосвязи между семантически совместимыми классами. Когда задействован только один тип данных, достаточно класса операторов, поэтому мы сначала сосредоточимся на этом случае, а затем вернемся к семействам операторов.
Классы операторов связаны с методом доступа к индексу, таким как B-Tree или GIN. Пользовательский метод доступа к индексу может быть определен с помощью CREATE ACCESS METHOD. Подробности см. в Глава 7.12.
Процедуры метода индекса напрямую ничего не знают о
типах данных, с которыми будет работать этот метод индекса.
Вместо этого класс операторов
определяет набор операций, которые необходимы методу индекса для работы
с конкретным типом данных. Классы операторов называются так потому, что
одна из вещей, которые они определяют, — это набор операторов предложения
WHERE, которые могут использоваться с индексом
(т. е. могут быть преобразованы в условие сканирования индекса).
Класс операторов также может указывать некоторые вспомогательные
функции, которые необходимы для внутренних операций метода
индекса, но не соответствуют напрямую ни одному оператору предложения
WHERE, используемому с индексом.
Можно определить несколько классов операторов для одного и того же типа данных и метода индекса. Таким образом, для одного типа данных можно определить несколько наборов семантик индексирования. Например, индекс B-tree требует определения порядка сортировки для каждого типа данных, с которым он работает. Для типа данных «комплексное число» может быть полезно иметь один класс операторов B-tree, который сортирует данные по абсолютному значению комплексного числа, другой — по вещественной части и так далее. Обычно один из классов операторов считается наиболее общеприменимым и помечается как класс операторов по умолчанию для этого типа данных и метода индекса.
Одно и то же имя класса операторов может использоваться для нескольких различных
методов индекса (например, методы индекса B-tree и hash имеют классы операторов
с именем int4_ops), но каждый такой класс является независимой
сущностью и должен быть определен отдельно.
Операторы, связанные с классом операторов, идентифицируются
«номерами стратегий», которые служат для определения семантики
каждого оператора в контексте его класса операторов.
Например, B-деревья накладывают строгий порядок на ключи (от меньшего к большему),
и поэтому такие операторы, как «меньше» и «больше или равно»,
представляют интерес для B-tree.
Поскольку Digital Q.DataBase позволяет пользователю определять операторы,
Digital Q.DataBase не может просто посмотреть на имя оператора
(например, < или >=) и определить, что это за
сравнение. Вместо этого метод индекса определяет набор
«стратегий», которые можно рассматривать как обобщенные операторы.
Каждый класс операторов указывает, какой фактический оператор соответствует каждой
стратегии для конкретного типа данных и интерпретации семантики индекса.
Метод индекса B-tree определяет пять стратегий, показанных в Таблица 5.1.3.
Таблица 5.1.3. Стратегии B-Tree
| Операция | Номер стратегии |
|---|---|
| меньше | 1 |
| меньше или равно | 2 |
| равно | 3 |
| больше или равно | 4 |
| больше | 5 |
Хеш-индексы поддерживают только сравнение на равенство и поэтому используют только одну стратегию, показанную в Таблица 5.1.4.
Таблица 5.1.4. Стратегии Hash
| Операция | Номер стратегии |
|---|---|
| равно | 1 |
Индексы GiST более гибки: они вообще не имеют фиксированного набора стратегий. Вместо этого вспомогательная процедура «согласованности» каждого конкретного класса операторов GiST интерпретирует номера стратегий так, как ей угодно. Например, некоторые встроенные классы операторов GiST индексируют двумерные геометрические объекты, предоставляя стратегии «R-tree», показанные в Таблица 5.1.5. Четыре из них являются истинными двумерными проверками (перекрывает, совпадает, содержит, содержится в); четыре из них учитывают только направление X; а остальные четыре обеспечивают те же проверки в направлении Y.
Таблица 5.1.5. Двумерные стратегии «R-tree» для GiST
| Операция | Номер стратегии |
|---|---|
| строго слева от | 1 |
| не выходит правее | 2 |
| перекрывает | 3 |
| не выходит левее | 4 |
| строго справа от | 5 |
| совпадает | 6 |
| содержит | 7 |
| содержится в | 8 |
| не выходит выше | 9 |
| строго ниже | 10 |
| строго выше | 11 |
| не выходит ниже | 12 |
Индексы SP-GiST аналогичны индексам GiST по гибкости: у них нет фиксированного набора стратегий. Вместо этого вспомогательные процедуры каждого класса операторов интерпретируют номера стратегий в соответствии с определением класса операторов. Например, номера стратегий, используемые встроенными классами операторов для точек, показаны в Таблица 5.1.6.
Таблица 5.1.6. Стратегии SP-GiST для точек
| Операция | Номер стратегии |
|---|---|
| строго слева от | 1 |
| строго справа от | 5 |
| совпадает | 6 |
| содержится в | 8 |
| строго ниже | 10 |
| строго выше | 11 |
Индексы GIN похожи на индексы GiST и SP-GiST тем, что у них также нет фиксированного набора стратегий. Вместо этого вспомогательные процедуры каждого класса операторов интерпретируют номера стратегий согласно определению класса операторов. В качестве примера номера стратегий, используемые встроенным классом операторов для массивов, приведены в Таблица 5.1.7.
Таблица 5.1.7. Стратегии GIN для массивов
| Операция | Номер стратегии |
|---|---|
| пересекается | 1 |
| содержит | 2 |
| содержится в | 3 |
| равно | 4 |
Индексы BRIN похожи на GiST, SP-GiST и GIN тем, что у них тоже
нет фиксированного набора стратегий. Вместо этого вспомогательные процедуры
каждого класса операторов интерпретируют номера стратегий в соответствии с
определением класса операторов. Например, номера стратегий, используемые
встроенными классами операторов Minmax, показаны в
Таблица 5.1.8.
Таблица 5.1.8. Стратегии BRIN Minmax
| Операция | Номер стратегии |
|---|---|
| меньше | 1 |
| меньше или равно | 2 |
| равно | 3 |
| больше или равно | 4 |
| больше | 5 |
Обратите внимание, что все перечисленные выше операторы возвращают логические значения. На
практике все операторы, определенные как операторы поиска метода индекса, должны
возвращать тип boolean, так как они должны появляться на верхнем
уровне предложения WHERE для использования с индексом.
(Некоторые методы доступа к индексам также поддерживают операторы упорядочивания,
которые обычно не возвращают логические значения; эта функция обсуждается
в Раздел 5.1.16.7.)
Одной информации о стратегиях обычно недостаточно системе для понимания того, как использовать индекс. На практике методам индекса требуются дополнительные вспомогательные процедуры для работы. Например, метод индекса B-tree должен уметь сравнивать два ключа и определять, является ли один больше, равен или меньше другого. Аналогично, метод индекса hash должен иметь возможность вычислять хеш-коды для значений ключей. Эти операции не соответствуют операторам, используемым в условиях в командах SQL; это административные процедуры, используемые методами индекса внутри системы.
Как и в случае со стратегиями, класс операторов определяет, какие конкретные функции должны играть каждую из этих ролей для данного типа данных и семантической интерпретации. Метод индекса определяет набор функций, которые ему необходимы, а класс операторов идентифицирует нужные функции, назначая им «номера вспомогательных функций», заданные методом индекса.
Кроме того, некоторые классы операторов позволяют пользователям указывать параметры, которые
управляют их поведением. Каждый встроенный метод доступа к индексу имеет необязательную
вспомогательную функцию options, которая определяет набор
параметров, специфичных для класса операторов.
B-деревья требуют наличия вспомогательной функции сравнения и позволяют предоставить четыре дополнительные вспомогательные функции по выбору автора класса операторов, как показано в Таблица 5.1.9. Требования к этим вспомогательным функциям более подробно объяснены в Раздел 7.14.1.3.
Таблица 5.1.9. Вспомогательные функции B-Tree
| Функция | Вспомогательный номер |
|---|---|
| Сравнить два ключа и вернуть целое число меньше нуля, ноль или больше нуля, указывающее, является ли первый ключ меньше, равен или больше второго | 1 |
| Вернуть адреса вызываемых из C функций поддержки сортировки (необязательно) | 2 |
| Сравнить проверяемое значение с базовым значением плюс/минус смещение и вернуть true или false в соответствии с результатом сравнения (необязательно) | 3 |
| Определить, безопасно ли для индексов, использующих этот класс операторов, применять оптимизацию дедупликации btree (необязательно) | 4 |
| Определить параметры, специфичные для этого класса операторов (необязательно) | 5 |
Хеш-индексы требуют одну вспомогательную функцию и позволяют предоставить две дополнительные по выбору автора класса операторов, как показано в Таблица 5.1.10.
Таблица 5.1.10. Вспомогательные функции Hash
| Функция | Вспомогательный номер |
|---|---|
| Вычислить 32-битное хеш-значение для ключа | 1 |
| Вычислить 64-битное хеш-значение для ключа при заданной 64-битной «соли» (salt); если соль равна 0, младшие 32 бита результата должны совпадать со значением, которое было бы вычислено функцией 1 (необязательно) | 2 |
| Определить параметры, специфичные для этого класса операторов (необязательно) | 3 |
Индексы GiST имеют одиннадцать вспомогательных функций, шесть из которых являются необязательными, как показано в Таблица 5.1.11. (Для получения дополнительной информации см. Раздел 7.14.2.)
Таблица 5.1.11. Вспомогательные функции 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 имеют шесть вспомогательных функций, одна из которых является необязательной, как показано в Таблица 5.1.12. (Для получения дополнительной информации см. Раздел 7.14.3.)
Таблица 5.1.12. Вспомогательные функции SP-GiST
| Функция | Описание | Вспомогательный номер |
|---|---|---|
config | предоставить базовую информацию о классе операторов | 1 |
choose | определить способ вставки нового значения во внутренний кортеж | 2 |
picksplit | определить способ разбиения набора значений | 3 |
inner_consistent | определить, в каких подразделах необходимо выполнить поиск для запроса | 4 |
leaf_consistent | определить, удовлетворяет ли ключ квалификатору запроса | 5 |
options | определить параметры, специфичные для этого класса операторов (необязательно) | 6 |
Индексы GIN имеют семь вспомогательных функций, четыре из которых являются необязательными, как показано в Таблица 5.1.13. (Для получения дополнительной информации см. Раздел 7.14.4.)
Таблица 5.1.13. Вспомогательные функции GIN
| Функция | Описание | Вспомогательный номер |
|---|---|---|
compare | сравнить два ключа и вернуть целое число меньше нуля, ноль или больше нуля, указывающее, является ли первый ключ меньше, равен или больше второго | 1 |
extractValue | извлечь ключи из значения для индексации | 2 |
extractQuery | извлечь ключи из условия запроса | 3 |
consistent | определить, соответствует ли значение условию запроса (логический вариант) (необязательно, если присутствует вспомогательная функция 6) | 4 |
comparePartial | сравнить частичный ключ из запроса и ключ из индекса и вернуть целое число меньше нуля, ноль или больше нуля, указывающее, должен ли GIN игнорировать эту запись индекса, считать запись совпадением или остановить сканирование индекса (необязательно) | 5 |
triConsistent | определить, соответствует ли значение условию запроса (тернарный вариант) (необязательно, если присутствует вспомогательная функция 4) | 6 |
options | определить параметры, специфичные для этого класса операторов (необязательно) | 7 |
Индексы BRIN имеют пять основных вспомогательных функций, одна из которых является необязательной, как показано в Таблица 5.1.14. Некоторые версии основных функций требуют предоставления дополнительных вспомогательных функций. (Для получения дополнительной информации см. Раздел 7.14.5.3.)
Таблица 5.1.14. Вспомогательные функции BRIN
| Функция | Описание | Вспомогательный номер |
|---|---|---|
opcInfo | вернуть внутреннюю информацию, описывающую сводные данные индексируемых столбцов | 1 |
add_value | добавить новое значение в существующий кортеж сводного индекса | 2 |
consistent | определить, соответствует ли значение условию запроса | 3 |
union | вычислить объединение двух сводных кортежей | 4 |
options | определить параметры, специфичные для этого класса операторов (необязательно) | 5 |
В отличие от поисковых операторов, вспомогательные функции возвращают тот тип данных, который ожидает конкретный метод индекса; например, в случае функции сравнения для B-деревьев — это знаковое целое число. Количество и типы аргументов каждой вспомогательной функции также зависят от метода индекса. Для B-tree и hash функции поддержки сравнения и хеширования принимают те же типы входных данных, что и операторы, включенные в класс операторов, но это не так для большинства вспомогательных функций GiST, SP-GiST, GIN и BRIN.
Теперь, когда мы ознакомились с идеями, приведем обещанный пример
создания нового класса операторов.
(Рабочую копию этого примера вы можете найти в файлах
src/tutorial/complex.c и
src/tutorial/complex.sql в дистрибутиве
исходного кода.)
Класс операторов инкапсулирует
операторы, которые сортируют комплексные числа в порядке их абсолютных значений, поэтому мы
выбираем имя complex_abs_ops. Сначала нам понадобится
набор операторов. Процедура определения операторов
обсуждалась в Раздел 5.1.14. Для класса операторов на
B-деревьях нам требуются следующие операторы:
Самый надежный способ определить связанный набор операторов сравнения — сначала написать вспомогательную функцию сравнения B-tree, а затем написать остальные функции как однострочные обертки вокруг этой вспомогательной функции. Это уменьшает вероятность получения противоречивых результатов в граничных случаях. Следуя этому подходу, мы сначала пишем:
#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.
Хотя Digital Q.DataBase может справляться с
функциями, имеющими одинаковое SQL-имя, при условии, что у них разные
типы данных аргументов, C может справиться только с одной глобальной функцией
с заданным именем. Поэтому нам не следует называть C-функцию
чем-то простым вроде abs_eq. Обычно хорошей практикой
является включение имени типа данных в имя C-функции, чтобы
избежать конфликта с функциями для других типов данных.
Мы могли бы дать SQL-имя функции abs_eq,
полагаясь на то, что Digital Q.DataBase отличит её
по типам данных аргументов от любой другой 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-tree в столбцах типа complex.
Мы могли бы записать записи операторов более подробно, например:
OPERATOR 1 < (complex, complex) ,
но в этом нет необходимости, когда операторы принимают тот же тип данных, для которого мы определяем класс операторов.
В приведенном выше примере предполагается, что вы хотите сделать этот новый класс операторов
классом B-tree по умолчанию для типа данных complex.
Если нет, просто опустите слово DEFAULT.
До сих пор мы неявно предполагали, что класс операторов имеет дело только с одним типом данных. Хотя в конкретном столбце индекса действительно может быть только один тип данных, часто бывает полезно индексировать операции, сравнивающие индексируемый столбец со значением другого типа данных. Также, если есть необходимость в использовании межтипового оператора в связи с классом операторов, часто бывает так, что другой тип данных имеет собственный связанный класс операторов. Полезно сделать связи между связанными классами явными, так как это может помочь планировщику в оптимизации SQL-запросов (особенно для классов операторов B-tree, поскольку планировщик обладает большими знаниями о том, как с ними работать).
Для решения этих задач Digital Q.DataBase использует концепцию семейства операторов. Семейство операторов содержит один или несколько классов операторов, а также может содержать индексируемые операторы и соответствующие вспомогательные функции, которые принадлежат семейству в целом, но не какому-то одному классу внутри семейства. Мы говорим, что такие операторы и функции являются «свободными» внутри семейства, в отличие от тех, что привязаны к конкретному классу. Обычно каждый класс операторов содержит операторы для одного типа данных, в то время как межтиповые операторы являются свободными в семействе.
Все операторы и функции в семействе операторов должны иметь совместимую семантику, требования к которой устанавливаются методом индекса. Поэтому вы можете задаться вопросом, зачем вообще выделять отдельные подмножества семейства в качестве классов операторов; и действительно, для многих целей деление на классы не имеет значения, и семейство является единственной интересной группировкой. Причина определения классов операторов в том, что они указывают, какая часть семейства необходима для поддержки любого конкретного индекса. Если существует индекс, использующий класс операторов, то этот класс операторов не может быть удален без удаления индекса — но другие части семейства операторов, а именно другие классы операторов и свободные операторы, могут быть удалены. Таким образом, класс операторов должен быть определен так, чтобы содержать минимальный набор операторов и функций, разумно необходимых для работы с индексом для конкретного типа данных, а затем связанные, но не обязательные операторы могут быть добавлены как свободные члены семейства операторов.
В качестве примера в Digital Q.DataBase есть встроенное
семейство операторов B-tree 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-tree все операторы в семействе должны сортировать совместимым образом, как подробно описано в Раздел 7.14.1.2. Для каждого оператора в семействе должна быть вспомогательная функция, имеющая те же два входных типа данных, что и оператор. Рекомендуется, чтобы семейство было полным, т. е. для каждой комбинации типов данных были включены все операторы. Каждый класс операторов должен включать только не межтиповые операторы и вспомогательную функцию для своего типа данных.
Чтобы построить семейство хеш-операторов для нескольких типов данных, необходимо создать совместимые вспомогательные хеш-функции для каждого типа данных, поддерживаемого семейством. Здесь совместимость означает, что функции гарантированно возвращают один и тот же хеш-код для любых двух значений, которые считаются равными согласно операторам равенства семейства, даже если значения имеют разные типы. Это обычно трудно реализовать, когда типы имеют разные физические представления, но в некоторых случаях это возможно. Кроме того, приведение значения одного типа данных, представленного в семействе операторов, к другому типу данных, также представленному в семействе операторов, посредством неявного приведения или приведения двоичной совместимости не должно изменять вычисленное хеш-значение. Заметьте, что на каждый тип данных приходится только одна вспомогательная функция, а не на каждый оператор равенства. Рекомендуется, чтобы семейство было полным, т. е. предоставляло оператор равенства для каждой комбинации типов данных. Каждый класс операторов должен включать только не межтиповой оператор равенства и вспомогательную функцию для своего типа данных.
Индексы GiST, SP-GiST и GIN не имеют явного понятия межтиповых операций. Набор поддерживаемых операторов — это просто то, что могут обработать основные вспомогательные функции для данного класса операторов.
В BRIN требования зависят от фреймворка, предоставляющего классы операторов.
Для классов операторов, основанных на minmax, требуемое поведение
такое же, как для семейств операторов B-tree: все операторы в семействе должны
сортировать совместимым образом, и приведение типов не должно изменять связанный порядок сортировки.
До появления Digital Q.DataBase 8.3 концепции семейств операторов не существовало, поэтому любые межтиповые операторы, предназначенные для использования с индексом, должны были быть привязаны напрямую к классу операторов индекса. Хотя этот подход всё ещё работает, он не рекомендуется, так как делает зависимости индекса слишком широкими и потому что планировщик может более эффективно обрабатывать межтиповые сравнения, когда оба типа данных имеют операторы в одном и том же семействе операторов.
Digital Q.DataBase использует классы операторов для вывода свойств операторов не только для того, можно ли их использовать с индексами. Поэтому вы можете захотеть создать классы операторов, даже если у вас нет намерения индексировать столбцы вашего типа данных.
В частности, существуют такие функции SQL, как ORDER BY и
DISTINCT, которые требуют сравнения и сортировки значений.
Чтобы реализовать эти функции для пользовательского типа данных,
Digital Q.DataBase ищет класс операторов B-tree по умолчанию для этого типа данных.
Член этого класса операторов «равно» определяет системное понятие равенства значений для
GROUP BY и DISTINCT, а порядок сортировки,
накладываемый классом операторов, определяет порядок ORDER BY по умолчанию.
Если для типа данных нет класса операторов B-tree по умолчанию, система будет искать хеш-класс операторов по умолчанию. Но поскольку этот вид класса операторов обеспечивает только равенство, он может поддерживать только группировку, но не сортировку.
Когда для типа данных нет класса операторов по умолчанию, вы получите ошибки вроде «could not identify an ordering operator» (не удалось определить оператор упорядочивания), если попытаетесь использовать эти функции SQL с данным типом данных.
В версиях Digital Q.DataBase до 7.4 операции сортировки и группировки
неявно использовали операторы с именами =, < и >.
Новое поведение, основанное на класса операторов по умолчанию, позволяет избежать необходимости делать
какие-либо предположения о поведении операторов с конкретными именами.
Сортировка с использованием класса операторов B-tree, не являющегося классом по умолчанию, возможна путём указания
оператора «меньше» этого класса в опции USING, например:
SELECT * FROM mytable ORDER BY somecol USING ~<~;
Альтернативно, указание в USING оператора «больше» для этого класса
выбирает сортировку в порядке убывания.
Сравнение массивов пользовательского типа также опирается на семантику, определенную классом операторов B-tree по умолчанию для этого типа. Если нет класса операторов B-tree по умолчанию, но есть хеш-класс операторов по умолчанию, то равенство массивов поддерживается, но не сравнения для упорядочивания.
Другой функцией SQL, требующей ещё больше знаний о типах данных, является
опция обрамления оконных функций RANGE offset
PRECEDING/FOLLOWING (см. Раздел 2.1.2.8).
Для запроса вида:
SELECT sum(x) OVER (ORDER BY x RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING) FROM mytable;
недостаточно знать, как выполнять ORDER BY по x;
база данных также должна понимать, как «вычесть 5» или «добавить 10» к значению
x в текущей строке, чтобы определить границы текущего окна. Сравнение
полученных границ с другими значениями x в строках
возможно с использованием операторов сравнения, предоставляемых классом операторов B-tree,
который определяет порядок ORDER BY — но операторы сложения и вычитания
не являются частью класса операторов, так какие из них следует использовать?
Жесткое закрепление этого выбора было бы нежелательным, так как разные порядки сортировки
(разные классы операторов B-tree) могут требовать разного поведения. Поэтому класс
операторов B-tree может указывать вспомогательную функцию in_range, которая
инкапсулирует поведение сложения и вычитания, имеющее смысл для его порядка сортировки.
Она может даже предоставлять более одной вспомогательной функции in_range, если
существует более одного типа данных, который имеет смысл использовать в качестве смещения
в предложениях RANGE. Если класс операторов B-tree, связанный с предложением
ORDER BY окна, не имеет соответствующей вспомогательной функции in_range, опция
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-tree, которое определяет порядок сортировки результирующего
типа данных. Как было сказано в предыдущем разделе, семейства операторов B-tree
определяют понятие порядка в Digital Q.DataBase, так что
это естественное представление. Поскольку оператор расстояния для точек <->
возвращает float8, его можно указать в команде создания класса
операторов так:
OPERATOR 15 <-> (point, point) FOR ORDER BY float_ops
где float_ops — это встроенное семейство операторов, включающее
операции над float8. Это объявление гласит, что индекс
способен возвращать строки в порядке возрастания значений оператора <->.
Существуют две особые возможности классов операторов, которые мы ещё не обсуждали, главным образом потому, что они не полезны с наиболее часто используемыми методами индекса.
Обычно объявление оператора членом класса (или семейства) операторов
означает, что метод индекса может извлечь именно тот набор строк,
который удовлетворяет условию WHERE с использованием этого оператора. Например:
SELECT * FROM table WHERE integer_column < 4;
может быть точно выполнено индексом B-tree в целочисленном столбце.
Но есть случаи, когда индекс полезен как неточный указатель на
подходящие строки. Например, если индекс GiST хранит только ограничивающие рамки (bounding boxes)
геометрических объектов, он не может точно выполнить условие WHERE,
которое проверяет пересечение непрямоугольных объектов, таких как
многоугольники. Тем не менее, мы могли бы использовать индекс, чтобы найти объекты, чья ограничивающая
рамка пересекается с ограничивающей рамкой целевого объекта, а затем выполнить
точную проверку на пересечение только для объектов, найденных по индексу. Если этот
сценарий применим, говорят, что индекс является «с потерями» (lossy) для этого
оператора. Поиски по индексам с потерями реализуются за счёт того, что метод
индекса возвращает флаг recheck (перепроверить), когда строка может как удовлетворять, так и
не удовлетворять условию запроса. Затем основная система проверяет исходное условие запроса на извлечённой строке, чтобы увидеть, следует ли её
возвращать как действительное совпадение. Этот подход работает, если
гарантируется, что индекс вернёт все необходимые строки, плюс, возможно,
некоторые дополнительные строки, которые можно исключить путём выполнения вызова исходного
оператора. Методы индекса, поддерживающие поиск с потерями
(в настоящее время GiST, SP-GiST и GIN), позволяют вспомогательным функциям отдельных
классов операторов устанавливать флаг recheck, поэтому по сути это
возможность класса операторов.
Рассмотрим ещё раз ситуацию, когда мы храним в индексе только
ограничивающую рамку сложного объекта, такого как многоугольник. В этом
случае нет смысла хранить весь многоугольник в записи индекса — мы могли бы хранить просто более простой объект типа
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 идентифицирует тип
сохраненных сводных значений, а вспомогательные процедуры классов
операторов отвечают за правильную интерпретацию сводных значений.