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