70.3. Расширяемость #
Интерфейс GIN характеризуется высоким уровнем абстракции и таким образом требует от разработчика метода доступа реализовать только смысловое наполнение обрабатываемого типа данных. Уровень GIN берёт на себя заботу о параллельном доступе, поддержке журнала и поиске в структуре дерева.
Всё, что нужно, чтобы получить работающий метод доступа GIN — это реализовать несколько пользовательских методов, определяющих поведение ключей в дереве и отношения между ключами, индексируемыми объектами и поддерживаемыми запросами. Словом, GIN сочетает расширяемость с универсальностью, повторным использованием кода и аккуратным интерфейсом.
Класс операторов должен предоставить GIN следующие три метода:
Datum *extractValue(Datum itemValue, int32 *nkeys, bool **nullFlags)
Возвращает массив ключей (выделенный через palloc) для индексируемого объекта. Число возвращаемых ключей должно записываться в
*nkeys
. Если какой-либо из ключей может быть NULL, нужно так же выделить через palloc массив из*nkeys
полейbool
, записать его адрес в*nullFlags
и установить эти флаги NULL как требуется. В*nullFlags
можно оставить значениеNULL
(это начальное значение), если все ключи отличны от NULL. Эта функция может возвратитьNULL
, если объект не содержит ключей.Datum *extractQuery(Datum query, int32 *nkeys, StrategyNumber n, bool **pmatch, Pointer **extra_data, bool **nullFlags, int32 *searchMode)
Возвращает массив ключей (выделенный через palloc) для запрашиваемого значения; то есть, в
query
поступает значение, находящееся по правую сторону индексируемого оператора, по левую сторону которого указан индексируемый столбец. Аргументn
задаёт номер стратегии оператора в классе операторов (см. Подраздел 40.16.2). Часто функцияextractQuery
должна проанализироватьn
, чтобы определить тип данных аргументаquery
и выбрать метод для извлечения значений ключей. Число возвращаемых ключей должно быть записано в*nkeys
. Если какие-либо ключи могут быть NULL, нужно так же выделить через palloc массив из*nkeys
полейbool
, сохранить его адрес в*nullFlags
, и установить эти флаги NULL как требуется. В*nullFlags
можно оставить значениеNULL
(это начальное значение), если все ключи отличны от NULL. Эта функция может возвратитьNULL
, еслиquery
не содержит ключей.Выходной аргумент
searchMode
позволяет функцииextractQuery
выбрать режим, в котором должен выполняться поиск. Если*searchMode
имеет значениеGIN_SEARCH_MODE_DEFAULT
(это значение устанавливается перед вызовом), подходящими кандидатами считаются только те объекты, которые соответствуют минимум одному из возвращённых ключей. Если в*searchMode
установлено значениеGIN_SEARCH_MODE_INCLUDE_EMPTY
, то в дополнение к объектам с минимум одним совпадением ключа, подходящими кандидатами будут считаться и объекты, вообще не содержащие ключей. (Этот режим полезен для реализации, например, операторов A-является-подмножеством-B.) Если в*searchMode
установлено значениеGIN_SEARCH_MODE_ALL
, подходящими кандидатами считаются все отличные от NULL объекты в индексе, независимо от того, встречаются ли в них возвращаемые ключи. (Этот режим намного медленнее двух других, так как он по сути требует сканирования всего индекса, но он может быть необходим для корректной обработки крайних случаев. Оператор, который выбирает этот режим в большинстве ситуаций, скорее всего не подходит для реализации в классе операторов GIN.) Символы для этих значений режима определены вaccess/gin.h
.Выходной аргумент
pmatch
используется, когда поддерживается частичное соответствие. Чтобы использовать его,extractQuery
должна выделить массив из*nkeys
логических элементов и сохранить его адрес в*pmatch
. Элемент этого массива должен содержать true, если соответствующий ключ требует частичного соответствия, и false в противном случае. Если переменная*pmatch
содержитNULL
, GIN полагает, что частичное соответствие не требуется. В эту переменную записываетсяNULL
перед вызовом, так что этот аргумент можно просто игнорировать в классах операторов, не поддерживающих частичное соответствие.Выходной аргумент
extra_data
позволяет функцииextractQuery
передать дополнительные данные методамconsistent
иcomparePartial
. Чтобы использовать его,extractQuery
должна выделить массив из*nkeys
указателей и сохранить его адрес в*extra_data
, а затем сохранить всё, что ей требуется, в отдельных указателях. В эту переменную записываетсяNULL
перед вызовом, поэтому данный аргумент может просто игнорироваться классами операторов, которым не нужны дополнительные данные. Если массив*extra_data
задан, он целиком передаётся в методconsistent
, а вcomparePartial
передаётся соответствующий его элемент.
Класс операторов должен также предоставить функцию для проверки, соответствует ли индексированный объект запросу. Поддерживаются две её вариации: булева consistent
и троичная triConsistent
. Функция triConsistent
покрывает функциональность обоих, так что достаточно реализовать только её. Однако если вычисление булевой вариации оказывается значительно дешевле, может иметь смысл реализовать их обе. Если представлена только булева вариация, некоторые оптимизации, построенные на отбраковывании объектов до выборки всех ключей, отключаются.
bool consistent(bool check[], StrategyNumber n, Datum query, int32 nkeys, Pointer extra_data[], bool *recheck, Datum queryKeys[], bool nullFlags[])
Возвращает true, если индексированный объект удовлетворяет оператору запроса с номером стратегии
n
(или потенциально удовлетворяет, когда возвращается указание перепроверки). Эта функция не имеет прямого доступа к значению индексированного объекта, так как GIN не хранит сами объекты. Вместо этого, она знает о значениях ключей, извлечённых из запроса и встречающихся в данном индексированном объекте. Массивcheck
имеет длинуnkeys
, что равняется числу ключей, ранее возвращённых функциейextractQuery
для данного значенияquery
. Элемент массиваcheck
равняется true, если индексированный объект содержит соответствующий ключ запроса; то есть, если (check[i] == true), то i-ый ключ в массиве результатаextractQuery
присутствует в индексированном объекте. Исходное значениеquery
передаётся на случай, если оно потребуется методуconsistent
; с той же целью ему передаются массивыqueryKeys[]
иnullFlags[]
, ранее возвращённые функциейextractQuery
. В аргументеextra_data
передаётся массив дополнительных данных, возвращённый функциейextractQuery
, илиNULL
, если дополнительных данных нет.Когда
extractQuery
возвращает ключ NULL вqueryKeys[]
, соответствующий элементcheck[]
содержит true, если индексированный объект содержит ключ NULL; то есть можно считать, что элементыcheck[]
отражают условиеIS NOT DISTINCT FROM
. Функцияconsistent
может проверить соответствующий элементnullFlags[]
, если ей нужно различать соответствие с обычным значением и соответствие с NULL.В случае успеха в
*recheck
нужно записать true, если кортеж данных нужно перепроверить с оператором запроса, либо false, если проверка по индексу была точной. То есть результат false гарантирует, что кортеж данных не соответствует запросу; результат true со значением*recheck
, равным false, гарантирует, что кортеж данных соответствует запросу; а результат true со значением*recheck
, равным true, означает, что кортеж данных может соответствовать запросу, поэтому его нужно выбрать и перепроверить, применив оператор запроса непосредственно к исходному индексированному элементу.GinTernaryValue triConsistent(GinTernaryValue check[], StrategyNumber n, Datum query, int32 nkeys, Pointer extra_data[], Datum queryKeys[], bool nullFlags[])
Функция
triConsistent
подобнаconsistent
, но вместо булевых значений в вектореcheck
ей передаются три варианта сравнений для каждого ключа:GIN_TRUE
,GIN_FALSE
иGIN_MAYBE
.GIN_FALSE
иGIN_TRUE
имеют обычное логическое значение, тогда какGIN_MAYBE
означает, что присутствие ключа неизвестно. Когда присутствуют значенияGIN_MAYBE
, функция должна возвращатьGIN_TRUE
, только если объект удовлетворяет запросу независимо от того, содержит ли индекс соответствующие ключи запроса. Подобным образом, функция должна возвращатьGIN_FALSE
, только если объект не удовлетворяет запросу независимо от того, содержит ли он ключиGIN_MAYBE
. Если результат зависит от элементовGIN_MAYBE
, то есть соответствие нельзя утверждать или отрицать в зависимости от известных ключей запроса, функция должна вернутьGIN_MAYBE
.Когда в векторе
check
нет элементовGIN_MAYBE
, возвращаемое значениеGIN_MAYBE
равнозначно установленному флагуrecheck
в булевой функцииconsistent
.
Кроме того, GIN нужно каким-то образом сортировать значения ключа, хранящиеся в индексе. Класс операторов может определить порядок сортировки, указав метод сравнения:
int compare(Datum a, Datum b)
Сравнивает два ключа (не индексированные объекты!) и возвращает целое меньше нуля, ноль или целое больше нуля, показывающее, что первый ключ меньше, равен или больше второго. Ключи NULL никогда не передаются этой функции.
Если же класс операторов не определяет метод compare
, GIN попытается найти класс операторов B-дерева по умолчанию для типа данных ключа индекса и воспользоваться его функцией сравнения. Если класс операторов GIN предназначен только для одного типа данных, рекомендуется задавать функцию сравнения в этом классе операторов, так как поиск класса операторов B-дерева занимает несколько циклов. Однако для полиморфных классов операторов GIN (например, array_ops
) задать одну функцию сравнения обычно не представляется возможным.
Дополнительно класс операторов для GIN может предоставить следующие методы:
int comparePartial(Datum partial_key, Datum key, StrategyNumber n, Pointer extra_data)
Сравнивает ключ запроса с частичным соответствием с ключом индекса. Возвращает целое число, знак которого отражает результат сравнения: отрицательное число означает, что ключ индекса не соответствует запросу, но нужно продолжать сканирование индекса; ноль означает, что ключ индекса соответствует запросу; положительное число означает, что сканирование индекса нужно прекратить, так как других соответствий не будет. Функции передаётся номер стратегии
n
оператора, сформировавшего запрос частичного соответствия, на случай, если нужно знать его смысл, чтобы определить, когда прекращать сканирование. Кроме того, ей передаётсяextra_data
— соответствующий элемент массива дополнительных данных, сформированного функциейextractQuery
, либоNULL
, если дополнительных данных нет. Значения NULL этой функции никогда не передаются.void options(local_relopts *relopts)
Определяет набор видимых пользователю параметров, управляющих поведением класса операторов.
Функции
options
передаётся указатель на структуруlocal_relopts
, в которую нужно внести набор параметров, относящихся к классу операторов. Обращаться к этим параметрам из других опорных функций можно с помощью макросовPG_HAS_OPCLASS_OPTIONS()
иPG_GET_OPCLASS_OPTIONS()
.Так как в GIN и извлечение ключа из индексируемых значений, и его представление допускают гибкость, могут быть полезны параметры для настройки этого индекса.
Для поддержки проверок на «частичное соответствие» класс операторов должен предоставить метод comparePartial
, а метод extractQuery
должен устанавливать параметр pmatch
, когда встречается запрос на частичное соответствие. За подробностями обратитесь к Подразделу 70.4.2.
Фактические типы данных различных значений Datum
, упоминаемых выше, зависят от класса операторов. Значения объектов, передаваемые в extractValue
, всегда имеют входной тип класса операторов, а все значения ключей должны быть типа, заданного параметром STORAGE
для класса. Типом аргумента query
, передаваемого функциям extractQuery
, consistent
и triConsistent
, будет тот тип, что указан в качестве типа правого операнда оператора-члена класса, определяемого по номеру стратегии. Это не обязательно должен быть индексируемый тип, достаточно лишь, чтобы из него можно было извлечь значения ключей, имеющие нужный тип. Однако рекомендуется, чтобы в SQL-объявлениях этих трёх опорных функций для аргумента query
назначался индексируемый тип класса операторов, даже несмотря на то, что фактический тип может быть другим, в зависимости от оператора.