66.3. Расширяемость
SP-GiST предлагает интерфейс с высоким уровнем абстракции и таким образом требует от разработчика метода доступа реализовать только методы, специфичные для конкретного типа данных. Ядро SP-GiST отвечает за эффективную схему обращений к диску и поиск в структуре дерева, а также берёт на себя заботу о параллельном доступе и поддержке журнала.
Кортежи в листьях дерева SP-GiST обычно содержат значения того же типа данных, что и индексируемый столбец, но могут содержать и неточное представление индексируемого столбца. На верхнем уровне эти кортежи содержат исходное индексируемое значение данных, но на более нижних могут содержать только часть значения, например, суффикс. В этом случае опорные функции класса операторов должны уметь восстанавливать исходное значение, собирая его из внутренних кортежей, которые нужно пройти для достижения уровня конкретного листа.
Когда создаётся индекс SP-GiST с неключевыми столбцами (INCLUDE), значения этих столбцов также будут храниться в кортежах уровня листьев. Класс операторов SP-GiST не обращает внимания на неключевые столбцы, поэтому здесь они рассматриваться не будут.
Внутренние кортежи устроены сложнее, так как они представляют собой точки разветвления в дереве поиска. Каждый внутренний кортеж содержит набор из одного или нескольких узлов, представляющих группы сходных значений листьев. Узел содержит ответвление, приводящее либо к другому, внутреннему кортежу нижнего уровня, либо к короткому списку кортежей в листьях, лежащих в одной странице индекса. Для каждого узла обычно задаётся метка, описывающая его; например, в префиксном дереве меткой может быть очередной символ в строковом значении. (С другой стороны, класс операторов может опускать метки узлов, если он имеет дело с фиксированным набором узлов во всех внутренних кортежах; см. Подраздел 66.4.2.) Дополнительно внутренний кортеж может хранить префикс, описывающий все его члены. В префиксном дереве это может быть общий префикс всех представленных ниже строк. Значением префикса не обязательно должен быть префикс, а могут быть любые данные, требующиеся классу операторов; например, в дереве квадрантов это может быть центральная точка, от которой отмеряются четыре квадранта. В этом случае внутренний кортеж дерева квадрантов будет также содержать четыре узла, соответствующие квадрантам вокруг этой центральной точки.
Некоторые алгоритмы деревьев требует знания уровня (или глубины) текущего кортежа, так что ядро SP-GiST даёт возможность классам операторов контролировать число уровней при спуске по дереву. Также имеется поддержка пошагового восстановления представленного значения, когда это требуется, и передачи вниз дополнительных данных (так называемых переходящих значений) при спуске.
Примечание
Ядро SP-GiST берёт на себя заботу о значениях NULL. Хотя в индексах SP-GiST не хранятся записи для NULL в индексируемых столбцах, это скрыто от кода класса операторов; записи индексов или условия поиска с NULL никогда не передаются методам класса операторов. (Предполагается, что операторы SP-GiST строгие и не могут возвращать положительный результат для значений NULL.) Поэтому значения NULL здесь больше обсуждаться не будут.
Класс операторов индекса для SP-GiST должен предоставить пять методов и может дополнительно предоставить ещё два. Все пять обязательных методов должны по единому соглашению принимать два аргумента internal, первым из которых будет указатель на структуру C, содержащую входные значения для опорного метода, а вторым — указатель на структуру C, в которую должны помещаться выходные значения. Четыре из этих методов должны возвращать просто void, так как их результаты помещаются в выходную структуру; однако leaf_consistent возвращает результат boolean. Эти методы не должны менять никакие поля в их входных структурах. Выходная структура всегда обнуляется перед вызовом пользовательского метода. Необязательный шестой метод compress принимает в единственном аргументе значение datum, подлежащее индексированию, и возвращает значение, подходящее для физического хранения в кортеже уровня листьев. Необязательный седьмой метод options принимает указатель типа internal на структуру C, в которую должны помещаться параметры для класса операторов, и возвращает void.
Пользователь должен определить следующие пять обязательных методов:
configВозвращает статическую информацию о реализации индекса, включая OID типов данных префикса и метки узла.
В SQL эта функция должна объявляться так:
CREATE FUNCTION my_config(internal, internal) RETURNS void ...
В первом аргументе передаётся указатель на структуру
spgConfigInязыка C, содержащие входные данные для функции. Во втором аргументе передаётся указатель на структуруspgConfigOutязыка C, в которую функция должна поместить результат.typedef struct spgConfigIn { Oid attType; /* Индексируемый тип данных */ } spgConfigIn; typedef struct spgConfigOut { Oid prefixType; /* Тип данных префикса во внутренних кортежах */ Oid labelType; /* Тип данных метки узла во внутренних кортежах */ Oid leafType; /* Тип данных в кортежах уровня листьев */ bool canReturnData; /* Класс операторов может восстановить исходные данные */ bool longValuesOK; /* Класс может принимать значения, не умещающиеся на одной странице */ } spgConfigOut;Поле
attTypeпередаётся для поддержки полиморфных классов операторов; для обычных классов операторов с фиксированным типом оно будет всегда содержать одно значение и поэтому его можно просто игнорировать.Для классов операторов, не использующих префиксы, в
prefixTypeможно установитьVOIDOID. Подобным образом, для классов операторов, не использующих метки узлов, вlabelTypeтоже можно установитьVOIDOID. ПризнакcanReturnDataследует установить, если класс операторов может восстановить изначально переданное в индекс значение. ПризнакlongValuesOKдолжен устанавливаться, только еслиattTypeпеременной длины и класс операторов может фрагментировать длинные значения, повторяя суффиксы (см. Подраздел 66.4.1).Значение
leafTypeдолжно соответствовать типу хранения индекса, заданному в полеopckeytypeзаписи, которая описывает этот класс операторов в каталоге. (Обратите внимание, чтоopckeytypeможет быть нулевым, тогда тип хранения определяется входным типом класса операторов, как бывает чаще всего.) Для обеспечения обратной совместимости методуconfigразрешается задатьleafTypeдругое значение (которое и будет использоваться); в результате содержимое индекса нельзя будет правильно идентифицировать в каталогах, поэтому данная возможность считается устаревшей. Кроме того, допускается оставитьleafTypeнеинициализированным (нулевым), что будет означать, что тип хранения индекса определяется значениемopckeytype.Когда
attTypeиleafTypeразличаются, должен предоставляться необязательный методcompress. Методcompressотвечает за преобразование данных, подлежащих индексации, из типаattTypeв типleafType.chooseВыбирает метод для добавления нового значения во внутренний кортеж.
В SQL эта функция должна объявляться так:
CREATE FUNCTION my_choose(internal, internal) RETURNS void ...
В первом аргументе передаётся указатель на структуру
spgChooseInязыка C, содержащую входные данные для функции. Во втором аргументе передаётся указатель на структуруspgChooseOut, в которую функция должна поместить результат.typedef struct spgChooseIn { Datum datum; /* исходное значение, которое должно индексироваться */ Datum leafDatum; /* текущее значение, которое должно сохраниться в листе */ int level; /* текущий уровень (начиная с нуля) */ /* Данные из текущего внутреннего кортежа */ bool allTheSame; /* кортеж с признаком все-равны? */ bool hasPrefix; /* у кортежа есть префикс? */ Datum prefixDatum; /* если да, то это значение префикса */ int nNodes; /* число узлов во внутреннем кортеже */ Datum *nodeLabels; /* значения меток узлов (NULL, если их нет) */ } spgChooseIn; typedef enum spgChooseResultType { spgMatchNode = 1, /* спуститься в существующий узел */ spgAddNode, /* добавить узел во внутренний кортеж */ spgSplitTuple /* разделить внутренний кортеж (изменить его префикс) */ } spgChooseResultType; typedef struct spgChooseOut { spgChooseResultType resultType; /* код действия, см. выше */ union { struct /* результаты для spgMatchNode */ { int nodeN; /* спуститься к этому узлу (нумерация с 0) */ int levelAdd; /* шаг увеличения уровня */ Datum restDatum; /* новое значение листа */ } matchNode; struct /* результаты для spgAddNode */ { Datum nodeLabel; /* метка нового узла */ int nodeN; /* куда вставлять её (нумерация с 0) */ } addNode; struct /* результаты для spgSplitTuple */ { /* Информация для формирования нового внутреннего кортежа верхнего уровня с одним дочерним кортежем */ bool prefixHasPrefix; /* кортеж должен иметь префикс? */ Datum prefixPrefixDatum; /* если да, его значение */ int prefixNNodes; /* число узлов */ Datum *prefixNodeLabels; /* их метки (или NULL, если * меток нет) */ int childNodeN; /* узел, который получит дочерний кортеж */ /* Информация для формирования нового внутреннего кортежа нижнего уровня со всеми старыми узлами */ bool postfixHasPrefix; /* кортеж должен иметь префикс? */ Datum postfixPrefixDatum; /* если да, его значение */ } splitTuple; } result; } spgChooseOut;В
datumпередаётся исходное значение типаspgConfigIn.attType, которое должно быть вставлено в индекс. ВleafDatumсодержится значение типаspgConfigOut.leafType, изначально представляющее собой результат методаcompress, применённого кdatum, если методcompressреализован, а иначе — собственно значениеdatum.leafDatumможет быть другим на нижних уровнях дерева, если его изменят функцииchooseилиpicksplit. Когда поиск места добавления достигает страницы уровня листа, в создаваемом кортеже листа сохраняется текущее значениеleafDatum. Вlevelзадаётся текущий уровень внутреннего кортежа, начиная с нуля для уровня корня. ПризнакallTheSameустанавливается, если текущий внутренний кортеж содержит несколько равнозначных узлов (см. Подраздел 66.4.3). ПризнакhasPrefixустанавливается, если текущий внутренний кортеж содержит префикс; в этом случае вprefixDatumзадаётся его значение. ПолеnNodesзадаёт число дочерних узлов, содержащихся во внутреннем кортеже, аnodeLabelsпредставляет массив их меток или NULL, если меток у них нет.Функция
chooseможет определить, соответствует ли новое значение одному из существующих дочерних узлов, или что нужно добавить новый дочерний узел, или что новое значение не согласуется с префиксом кортежа и внутренний кортеж нужно разделить, чтобы получить менее ограничивающий префикс.Если новое значение соответствует одному из существующих дочерних узлов, установите в
resultTypeзначениеspgMatchNode. Установите вnodeNномер этого узла в массиве узлов (нумерация начинается с нуля). Установите вlevelAddзначение, на которое должен увеличиваться уровень (level) при спуске через этот узел, либо оставьте его нулевым, если класс операторов не отслеживает уровни. УстановитеrestDatum, равнымleafDatum, если класс операторов не меняет значения данных от уровня к уровню, а в противном случае запишите в него изменённое значение, которое должно использоваться в качествеleafDatumна следующем уровне.Если нужно добавить новый дочерний узел, установите в
resultTypeзначениеspgAddNode. ВnodeLabelзадайте метку для нового узла, а вnodeNпозицию (отсчитываемую от нуля), в которую должен вставляться узел в массиве узлов. После того как узел будет добавлен, функцияchooseвызывается снова с изменённым внутренним кортежем; в результате этого вызова должен быть получен результатspgMatchNode.Если новое значение не согласуется с префиксом кортежа, установите в
resultTypeзначениеspgSplitTuple. Это действие приводит к перемещению всех существующих узлов в новый внутренний кортеж нижнего уровня и замене существующего внутреннего кортежа кортежем, содержащим одну ссылку вниз на новый внутренний кортеж. Установите признакprefixHasPrefix, чтобы указать, должен ли новый верхний кортеж иметь префикс, и если да, задайте вprefixPrefixDatumзначение префикса. Это новое значение префикса должно быть в достаточной мере менее ограничивающим, чем исходное, чтобы в индекс было принято новое значение. Запишите вprefixNNodesчисло требующихся узлов в новом кортеже, а вprefixNodeLabels— указатель на выделенный через palloc массив с их метками или NULL, если метки узлов не нужны. Заметьте, что общий размер нового кортежа верхнего уровня не должен превышать общий размер кортежа, который он замещает; это ограничивает длины нового префикса и новых меток. Установите вchildNodeNиндекс (начиная с нуля) узла, который будет ссылаться на новый внутренний кортеж нижнего уровня. Установите признакpostfixHasPrefix, чтобы указать, должен ли новый внутренний кортеж нижнего уровня иметь префикс, и если да, задайте вpostfixPrefixDatumзначение префикса. Сочетание этих двух префиксов и метки узла, ссылающегося вниз, (если она есть) должно иметь то же значение, что и исходный префикс, так как нет возможности ни изменить метки узлов, перемещённых в новый кортеж нижнего уровня, ни изменить какие-либо нижние записи индекса. После того как узел разделён, функцияchooseбудет вызвана снова с заменяемым внутренним кортежем. При этом вызове может быть возвращён результатspgAddNode, если подходящий узел не был создан действиемspgSplitTuple. В конце концовchooseдолжна вернутьspgMatchNode, чтобы операция добавления могла перейти на следующий уровень.picksplitВыбирает, как создать новый внутренний кортеж по набору кортежей в листьях.
В SQL эта функция должна объявляться так:
CREATE FUNCTION my_picksplit(internal, internal) RETURNS void ...
В первом аргументе передаётся указатель на структуру
spgPickSplitInязыка C, содержащую входные данные для функции. Во втором аргументе передаётся указатель на структуруspgPickSplitOutязыка C, в которую функция должна поместить результат.typedef struct spgPickSplitIn { int nTuples; /* число кортежей в листьях */ Datum *datums; /* их значения (массив длины nTuples) */ int level; /* текущий уровень (отсчитывая от 0) */ } spgPickSplitIn; typedef struct spgPickSplitOut { bool hasPrefix; /* новый внутренний кортеж должен иметь префикс? */ Datum prefixDatum; /* если да, его значение */ int nNodes; /* число узлов для нового внутреннего кортежа */ Datum *nodeLabels; /* их метки (или NULL, если их нет) */ int *mapTuplesToNodes; /* номер узла для каждого кортежа в листе */ Datum *leafTupleDatums; /* значения, помещаемые в каждый новый кортеж */ } spgPickSplitOut;В
nTuplesзадаётся число предоставленных кортежей уровня листьев, а вdatums— массив их значений типаspgConfigOut.leafType. Вlevelуказывается текущий уровень, который должны разделять все кортежи листьев, и который станет уровнем нового внутреннего кортежа.Установите признак
hasPrefix, чтобы указать, должен ли новый внутренний кортеж иметь префикс, и если да, задайте вprefixDatumзначение префикса. Установите вnNodesколичество узлов, которые будут содержаться во внутреннем кортеже, а вnodeLabels— массив значений их меток либо NULL, если узлам не нужны метки. Поместите вmapTuplesToNodesуказатель на массив, назначающий номера узлов (начиная с нуля) каждому кортежу листа. ВleafTupleDatumsпередайте массив значений, которые должны быть сохранены в новых кортежах листьев (они будут совпадать со входными значениями (datums), если класс операторов не изменяет значения от уровня к следующему). Заметьте, что функцияpicksplitсама должна выделить память, используя palloc, для массивовnodeLabels,mapTuplesToNodesиleafTupleDatums.Если передаётся несколько кортежей листьев, ожидается, что функция
picksplitклассифицирует их и разделит на несколько узлов; иначе нельзя будет разнести кортежи листьев по разным страницам, что является конечной целью этой операции. Таким образом, еслиpicksplitв итоге помещает все кортежи листьев в один узел, ядро SP-GiST меняет это решение и создаёт внутренний кортеж, в котором кортежи листьев связываются случайным образом с несколькими узлами с одинаковыми метками. Такой кортеж помечается флагомallTheSame, показывающим, что все узлы равны. Функцииchooseиinner_consistentдолжны работать с такими внутренними кортежами особым образом. За дополнительными сведениями обратитесь к Подразделу 66.4.3.picksplitможет применяться к одному кортежу на уровне листьев, только когда функцияconfigустановила вlongValuesOKзначение true и было передано входное значение, большее страницы. В этом случае цель операции — отделить префикс и получить новое, более короткое значение для листа. Этот вызов будет повторяться, пока значение уровня листа не уменьшится настолько, чтобы уместиться в странице. За дополнительными сведениями обратитесь к Подразделу 66.4.1.inner_consistentВозвращает набор узлов (ветвей), по которым надо продолжать поиск.
В SQL эта функция должна объявляться так:
CREATE FUNCTION my_inner_consistent(internal, internal) RETURNS void ...
В первом аргументе передаётся указатель на структуру
spgInnerConsistentInязыка C, содержащую входные данные для функции. Во втором аргументе передаётся указатель на структуруspgInnerConsistentOutязыка C, в которую функция должна поместить результат.typedef struct spgInnerConsistentIn { ScanKey scankeys; /* массив операторов и искомых значений */ ScanKey orderbys; /* массив операторов упорядочивания и * сравниваемых значений */ int nkeys; /* длина массива scankeys */ int norderbys; /* длина массива orderbys */ Datum reconstructedValue; /* значение, восстановленное для родителя */ void *traversalValue; /* переходящее значение, специфичное для класса операторов */ MemoryContext traversalMemoryContext; /* переходящие значения нужно помещать сюда */ int level; /* текущий уровень (отсчитывается от нуля) */ bool returnData; /* нужно ли возвращать исходные данные? */ /* Данные из текущего внутреннего кортежа */ bool allTheSame; /* кортеж с признаком все-равны? */ bool hasPrefix; /* у кортежа есть префикс? */ Datum prefixDatum; /* если да, то это значение префикса */ int nNodes; /* число узлов во внутреннем кортеже */ Datum *nodeLabels; /* значения меток узлов (NULL, если их нет) */ } spgInnerConsistentIn; typedef struct spgInnerConsistentOut { int nNodes; /* число дочерних узлов, которые нужно посетить */ int *nodeNumbers; /* их номера в массиве узлов */ int *levelAdds; /* шаги увеличения уровня для этих узлов */ Datum *reconstructedValues; /* связанные восстановленные значения */ void **traversalValues; /* переходящие значения, специфичные для класса операторов */ double **distances; /* связанные расстояния */ } spgInnerConsistentOut;Массив
scankeysдлиныnkeysописывает условия поиска по индексу. Эти условия объединяются операцией И — найдены должны быть только те записи, которые удовлетворяют всем условиям. (Заметьте, что сnkeys= 0 подразумевается, что запросу удовлетворяют все записи в индексе.) Обычно эту функцию интересуют только поляsk_strategyиsk_argumentв каждой записи массива, в которых определяется соответственно индексируемый оператор и искомое значение. В частности, нет необходимости проверятьsk_flags, чтобы распознать NULL в искомом значении, так как ядро SP-GiST отфильтрует такие условия. Массивorderbysдлиныnorderbysподобным образом описывает упорядочивающие операторы (если они есть). ВreconstructedValueпередаётся значение, восстановленное для родительского кортежа; это может быть(Datum) 0на уровне корня или если функцияinner_consistentне установила значение на предыдущем уровне. ВtraversalValueпередаётся указатель на переходящие данные, полученные из предыдущего вызоваinner_consistentдля родительского кортежа индекса, либо NULL на уровне корня. ПолеtraversalMemoryContextуказывает на контекст памяти, в котором нужно сохранить выходные переходящие данные (см. ниже). Вlevelпередаётся уровень текущего внутреннего кортежа (уровень корня считается нулевым). ФлагreturnDataустанавливается, когда для этого запроса нужно получить восстановленные данные; это возможно, только если функцияconfigустановила признакcanReturnData. ПризнакallTheSameустанавливается, если текущий внутренний кортеж имеет пометку «все-равны»; в этом случае все узлы имеют одну метку (если имеют) и значит, либо все они, либо никакой не соответствует запросу (см. Подраздел 66.4.3). ПризнакhasPrefixустанавливается, если текущий внутренний кортеж содержит префикс; в этом случае вprefixDatumнаходится его значение. ВnNodesзадаётся число дочерних узлов, содержащихся во внутреннем кортеже, а вnodeLabels— массив их меток либо NULL, если они не имеют меток.В
nNodesнужно записать число дочерних узлов, которые потребуется посетить при поиске, а вnodeNumbers— массив их индексов. Если класс операторов отслеживает уровни, вlevelAddsнужно передать массив с шагами увеличения уровня при посещении каждого узла. (Часто шаг будет одним для всех узлов, но может быть и по-другому, поэтому применяется массив.) Если потребовалось восстановить значения, поместите вreconstructedValuesмассив значений, восстановленных для каждого дочернего узла, который нужно посетить; в противном случае оставьтеreconstructedValuesравным NULL. Предполагается, что восстановленные значения имеют типspgConfigOut.leafType. (Однако ядро системы ничего не будет делать с ними, кроме как, возможно, копировать, поэтому они должны иметь такие жеtyplenиtypbyval, что иleafType.) Если выполняется поиск с упорядочиванием, поместите вdistancesмассив расстояний в соответствии с массивомorderbys(узлы с меньшими расстояниями будут обрабатываться первыми). В противном случае оставьте в этом поле NULL. Если желательно передать дополнительные данные («переходящие значения») на нижние уровни при поиске по дереву, поместите вtraversalValuesуказатель на массив соответствующих переходящих значений, по одному для каждого дочернего узла, который нужно посетить; в противном случае оставьте вtraversalValuesзначение NULL. Заметьте, что функцияinner_consistentсама должна выделять память, используя palloc, для массивовnodeNumbers,levelAdds,distances,reconstructedValuesиtraversalValuesв текущем контексте памяти. Однако выходные переходящие значения, на которые указывает массивtraversalValues, должны размещаться в контекстеtraversalMemoryContext. При этом каждое переходящее значения должно располагаться в отдельном блоке памяти palloc.leaf_consistentВозвращает true, если кортеж листа удовлетворяет запросу.
В SQL эта функция должна объявляться так:
CREATE FUNCTION my_leaf_consistent(internal, internal) RETURNS bool ...
В первом аргументе передаётся указатель на структуру
spgLeafConsistentInязыка C, содержащую входные данные для функции. Во втором аргументе передаётся указатель на структуруspgLeafConsistentOutязыка C, в которую функция должна поместить результат.typedef struct spgLeafConsistentIn { ScanKey scankeys; /* массив операторов и искомых значений */ ScanKey orderbys; /* массив операторов упорядочивания и * сравниваемых значений */ int nkeys; /* длина массива scankeys */ int norderbys; /* длина массива orderbys */ Datum reconstructedValue; /* значение, восстановленное для родителя */ void *traversalValue; /* переходящее значение, специфичное для класса операторов */ int level; /* текущий уровень (отсчитывая от нуля) */ bool returnData; /* нужно ли возвращать исходные данные? */ Datum leafDatum; /* значение в кортеже листа */ } spgLeafConsistentIn; typedef struct spgLeafConsistentOut { Datum leafValue; /* восстановленные исходные данные, при наличии */ bool recheck; /* true, если оператор нужно перепроверить */ bool recheckDistances; /* true, если расстояния нужно перепроверить */ double *distances; /* связанные расстояния */ } spgLeafConsistentOut;Массив
scankeysдлиныnkeysописывает условия поиска по индексу. Эти условия объединяются операцией И — запросу удовлетворяют только те записи в индексе, которые удовлетворяют всем этим условиям. (Заметьте, что сnkeys= 0 подразумевается, что запросу удовлетворяют все записи в индексе.) Обычно эту функцию интересуют только поляsk_strategyиsk_argumentв каждой записи массива, в которых определяются соответственно индексируемый оператор и искомое значение. В частности, нет необходимости проверятьsk_flags, чтобы распознать NULL в искомом значении, так как ядро SP-GiST отфильтрует такие условия. Массивorderbysдлиныnorderbysподобным образом описывает упорядочивающие операторы. ВreconstructedValueпередаётся значение, восстановленное для родительского кортежа; это может быть(Datum) 0на уровне корня или если функцияinner_consistentне установила значение на предыдущем уровне. ВtraversalValueпередаётся указатель на переходящие данные, полученные из предыдущего вызоваinner_consistentдля родительского кортежа индекса, либо NULL на уровне корня. Вlevelпередаётся уровень текущего внутреннего кортежа (уровень корня считается нулевым). ФлагreturnDataустанавливается, когда для этого запроса нужно получить восстановленные данные; это возможно, только если функцияconfigустановила признакcanReturnData. ВleafDatumпередаётся значение ключа, записанное в текущем кортеже листа.Эта функция должна вернуть
true, если кортеж листа соответствует запросу, илиfalseв противном случае. В случае положительного результата, если в полеreturnDataпереданоtrue, нужно поместить вleafValueзначение (типаspgConfigIn.attType), изначально переданное для индексации в этот кортеж. Кроме того, флагуrecheckможно присвоитьtrue, если соответствие неточное, так что для установления точного результата проверки нужно повторно применить оператор(ы) к собственно кортежу данных. Если выполняется упорядочивающий поиск, поместите вdistancesмассив со значениями расстояния, соответствующими массивуorderbys. В противном случае оставьте в этом поле NULL. Если хотя бы одно из возвращаемых расстояний определено неточно, присвойте true полюrecheckDistances. В этом случае исполнитель вычислит точные расстояния после получения кортежа из кучи и переупорядочит кортежи, если потребуется.
Дополнительно пользователь может определить методы:
Datum compress(Datum in)Преобразует элемент данных в формат, подходящий для физического хранения в кортеже уровня листьев в индексе. Эта функция принимает значение типа
spgConfigIn.attTypeи возвращает значение типаspgConfigOut.leafType. Возвращаемое значение не должно содержать указатель на внешние TOAST-данные.Обратите внимание, что метод
compressприменяется только к сохраняемым значениям. Методы, оценивающие согласованность, получают сканируемые в запросе ключи (scankeys) неизменёнными, без обработки функциейcompress.optionsОпределяет набор видимых пользователю параметров, управляющих поведением класса операторов.
В SQL эта функция должна объявляться так:
CREATE OR REPLACE FUNCTION my_options(internal) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
Этой функции передаётся указатель на структуру
local_relopts, в которую нужно внести набор параметров, относящихся к классу операторов. Обращаться к этим параметрам из других опорных функций можно с помощью макросовPG_HAS_OPCLASS_OPTIONS()иPG_GET_OPCLASS_OPTIONS().Так как в SP-GiST представление ключа допускает гибкость, могут быть полезны параметры для настройки этого индекса.
Все опорные методы SP-GiST обычно вызываются в кратковременных контекстах памяти; то есть CurrentMemoryContext сбрасывается после обработки каждого кортежа. Таким образом, можно не заботиться об освобождении любых блоков памяти, выделенных функцией palloc. (Метод config является исключением: в нём нужно не допускать утечек памяти. Но обычно метод config не делает ничего, кроме как присваивает константы переданной структуре параметров.)
Если индексируемый столбец имеет сортируемый тип данных, правило сортировки индекса будет передаваться всем опорным методам, используя стандартный механизм PG_GET_COLLATION().
66.3. Extensibility
SP-GiST offers an interface with a high level of abstraction, requiring the access method developer to implement only methods specific to a given data type. The SP-GiST core is responsible for efficient disk mapping and searching the tree structure. It also takes care of concurrency and logging considerations.
Leaf tuples of an SP-GiST tree usually contain values of the same data type as the indexed column, although it is also possible for them to contain lossy representations of the indexed column. Leaf tuples stored at the root level will directly represent the original indexed data value, but leaf tuples at lower levels might contain only a partial value, such as a suffix. In that case the operator class support functions must be able to reconstruct the original value using information accumulated from the inner tuples that are passed through to reach the leaf level.
When an SP-GiST index is created with INCLUDE columns, the values of those columns are also stored in leaf tuples. The INCLUDE columns are of no concern to the SP-GiST operator class, so they are not discussed further here.
Inner tuples are more complex, since they are branching points in the search tree. Each inner tuple contains a set of one or more nodes, which represent groups of similar leaf values. A node contains a downlink that leads either to another, lower-level inner tuple, or to a short list of leaf tuples that all lie on the same index page. Each node normally has a label that describes it; for example, in a radix tree the node label could be the next character of the string value. (Alternatively, an operator class can omit the node labels, if it works with a fixed set of nodes for all inner tuples; see Section 66.4.2.) Optionally, an inner tuple can have a prefix value that describes all its members. In a radix tree this could be the common prefix of the represented strings. The prefix value is not necessarily really a prefix, but can be any data needed by the operator class; for example, in a quad-tree it can store the central point that the four quadrants are measured with respect to. A quad-tree inner tuple would then also contain four nodes corresponding to the quadrants around this central point.
Some tree algorithms require knowledge of level (or depth) of the current tuple, so the SP-GiST core provides the possibility for operator classes to manage level counting while descending the tree. There is also support for incrementally reconstructing the represented value when that is needed, and for passing down additional data (called traverse values) during a tree descent.
Note
The SP-GiST core code takes care of null entries. Although SP-GiST indexes do store entries for nulls in indexed columns, this is hidden from the index operator class code: no null index entries or search conditions will ever be passed to the operator class methods. (It is assumed that SP-GiST operators are strict and so cannot succeed for null values.) Null values are therefore not discussed further here.
There are five user-defined methods that an index operator class for SP-GiST must provide, and two are optional. All five mandatory methods follow the convention of accepting two internal arguments, the first of which is a pointer to a C struct containing input values for the support method, while the second argument is a pointer to a C struct where output values must be placed. Four of the mandatory methods just return void, since all their results appear in the output struct; but leaf_consistent returns a boolean result. The methods must not modify any fields of their input structs. In all cases, the output struct is initialized to zeroes before calling the user-defined method. The optional sixth method compress accepts a datum to be indexed as the only argument and returns a value suitable for physical storage in a leaf tuple. The optional seventh method options accepts an internal pointer to a C struct, where opclass-specific parameters should be placed, and returns void.
The five mandatory user-defined methods are:
configReturns static information about the index implementation, including the data type OIDs of the prefix and node label data types.
The SQL declaration of the function must look like this:
CREATE FUNCTION my_config(internal, internal) RETURNS void ...
The first argument is a pointer to a
spgConfigInC struct, containing input data for the function. The second argument is a pointer to aspgConfigOutC struct, which the function must fill with result data.typedef struct spgConfigIn { Oid attType; /* Data type to be indexed */ } spgConfigIn; typedef struct spgConfigOut { Oid prefixType; /* Data type of inner-tuple prefixes */ Oid labelType; /* Data type of inner-tuple node labels */ Oid leafType; /* Data type of leaf-tuple values */ bool canReturnData; /* Opclass can reconstruct original data */ bool longValuesOK; /* Opclass can cope with values > 1 page */ } spgConfigOut;attTypeis passed in order to support polymorphic index operator classes; for ordinary fixed-data-type operator classes, it will always have the same value and so can be ignored.For operator classes that do not use prefixes,
prefixTypecan be set toVOIDOID. Likewise, for operator classes that do not use node labels,labelTypecan be set toVOIDOID.canReturnDatashould be set true if the operator class is capable of reconstructing the originally-supplied index value.longValuesOKshould be set true only when theattTypeis of variable length and the operator class is capable of segmenting long values by repeated suffixing (see Section 66.4.1).leafTypeshould match the index storage type defined by the operator class'sopckeytypecatalog entry. (Note thatopckeytypecan be zero, implying the storage type is the same as the operator class's input type, which is the most common situation.) For reasons of backward compatibility, theconfigmethod can setleafTypeto some other value, and that value will be used; but this is deprecated since the index contents are then incorrectly identified in the catalogs. Also, it's permissible to leaveleafTypeuninitialized (zero); that is interpreted as meaning the index storage type derived fromopckeytype.When
attTypeandleafTypeare different, the optional methodcompressmust be provided. Methodcompressis responsible for transformation of datums to be indexed fromattTypetoleafType.chooseChooses a method for inserting a new value into an inner tuple.
The SQL declaration of the function must look like this:
CREATE FUNCTION my_choose(internal, internal) RETURNS void ...
The first argument is a pointer to a
spgChooseInC struct, containing input data for the function. The second argument is a pointer to aspgChooseOutC struct, which the function must fill with result data.typedef struct spgChooseIn { Datum datum; /* original datum to be indexed */ Datum leafDatum; /* current datum to be stored at leaf */ int level; /* current level (counting from zero) */ /* Data from current inner tuple */ bool allTheSame; /* tuple is marked all-the-same? */ bool hasPrefix; /* tuple has a prefix? */ Datum prefixDatum; /* if so, the prefix value */ int nNodes; /* number of nodes in the inner tuple */ Datum *nodeLabels; /* node label values (NULL if none) */ } spgChooseIn; typedef enum spgChooseResultType { spgMatchNode = 1, /* descend into existing node */ spgAddNode, /* add a node to the inner tuple */ spgSplitTuple /* split inner tuple (change its prefix) */ } spgChooseResultType; typedef struct spgChooseOut { spgChooseResultType resultType; /* action code, see above */ union { struct /* results for spgMatchNode */ { int nodeN; /* descend to this node (index from 0) */ int levelAdd; /* increment level by this much */ Datum restDatum; /* new leaf datum */ } matchNode; struct /* results for spgAddNode */ { Datum nodeLabel; /* new node's label */ int nodeN; /* where to insert it (index from 0) */ } addNode; struct /* results for spgSplitTuple */ { /* Info to form new upper-level inner tuple with one child tuple */ bool prefixHasPrefix; /* tuple should have a prefix? */ Datum prefixPrefixDatum; /* if so, its value */ int prefixNNodes; /* number of nodes */ Datum *prefixNodeLabels; /* their labels (or NULL for * no labels) */ int childNodeN; /* which node gets child tuple */ /* Info to form new lower-level inner tuple with all old nodes */ bool postfixHasPrefix; /* tuple should have a prefix? */ Datum postfixPrefixDatum; /* if so, its value */ } splitTuple; } result; } spgChooseOut;datumis the original datum ofspgConfigIn.attTypetype that was to be inserted into the index.leafDatumis a value ofspgConfigOut.leafTypetype, which is initially a result of methodcompressapplied todatumwhen methodcompressis provided, or the same value asdatumotherwise.leafDatumcan change at lower levels of the tree if thechooseorpicksplitmethods change it. When the insertion search reaches a leaf page, the current value ofleafDatumis what will be stored in the newly created leaf tuple.levelis the current inner tuple's level, starting at zero for the root level.allTheSameis true if the current inner tuple is marked as containing multiple equivalent nodes (see Section 66.4.3).hasPrefixis true if the current inner tuple contains a prefix; if so,prefixDatumis its value.nNodesis the number of child nodes contained in the inner tuple, andnodeLabelsis an array of their label values, or NULL if there are no labels.The
choosefunction can determine either that the new value matches one of the existing child nodes, or that a new child node must be added, or that the new value is inconsistent with the tuple prefix and so the inner tuple must be split to create a less restrictive prefix.If the new value matches one of the existing child nodes, set
resultTypetospgMatchNode. SetnodeNto the index (from zero) of that node in the node array. SetlevelAddto the increment inlevelcaused by descending through that node, or leave it as zero if the operator class does not use levels. SetrestDatumto equalleafDatumif the operator class does not modify datums from one level to the next, or otherwise set it to the modified value to be used asleafDatumat the next level.If a new child node must be added, set
resultTypetospgAddNode. SetnodeLabelto the label to be used for the new node, and setnodeNto the index (from zero) at which to insert the node in the node array. After the node has been added, thechoosefunction will be called again with the modified inner tuple; that call should result in anspgMatchNoderesult.If the new value is inconsistent with the tuple prefix, set
resultTypetospgSplitTuple. This action moves all the existing nodes into a new lower-level inner tuple, and replaces the existing inner tuple with a tuple having a single downlink pointing to the new lower-level inner tuple. SetprefixHasPrefixto indicate whether the new upper tuple should have a prefix, and if so setprefixPrefixDatumto the prefix value. This new prefix value must be sufficiently less restrictive than the original to accept the new value to be indexed. SetprefixNNodesto the number of nodes needed in the new tuple, and setprefixNodeLabelsto a palloc'd array holding their labels, or to NULL if node labels are not required. Note that the total size of the new upper tuple must be no more than the total size of the tuple it is replacing; this constrains the lengths of the new prefix and new labels. SetchildNodeNto the index (from zero) of the node that will downlink to the new lower-level inner tuple. SetpostfixHasPrefixto indicate whether the new lower-level inner tuple should have a prefix, and if so setpostfixPrefixDatumto the prefix value. The combination of these two prefixes and the downlink node's label (if any) must have the same meaning as the original prefix, because there is no opportunity to alter the node labels that are moved to the new lower-level tuple, nor to change any child index entries. After the node has been split, thechoosefunction will be called again with the replacement inner tuple. That call may return anspgAddNoderesult, if no suitable node was created by thespgSplitTupleaction. Eventuallychoosemust returnspgMatchNodeto allow the insertion to descend to the next level.picksplitDecides how to create a new inner tuple over a set of leaf tuples.
The SQL declaration of the function must look like this:
CREATE FUNCTION my_picksplit(internal, internal) RETURNS void ...
The first argument is a pointer to a
spgPickSplitInC struct, containing input data for the function. The second argument is a pointer to aspgPickSplitOutC struct, which the function must fill with result data.typedef struct spgPickSplitIn { int nTuples; /* number of leaf tuples */ Datum *datums; /* their datums (array of length nTuples) */ int level; /* current level (counting from zero) */ } spgPickSplitIn; typedef struct spgPickSplitOut { bool hasPrefix; /* new inner tuple should have a prefix? */ Datum prefixDatum; /* if so, its value */ int nNodes; /* number of nodes for new inner tuple */ Datum *nodeLabels; /* their labels (or NULL for no labels) */ int *mapTuplesToNodes; /* node index for each leaf tuple */ Datum *leafTupleDatums; /* datum to store in each new leaf tuple */ } spgPickSplitOut;nTuplesis the number of leaf tuples provided.datumsis an array of their datum values ofspgConfigOut.leafTypetype.levelis the current level that all the leaf tuples share, which will become the level of the new inner tuple.Set
hasPrefixto indicate whether the new inner tuple should have a prefix, and if so setprefixDatumto the prefix value. SetnNodesto indicate the number of nodes that the new inner tuple will contain, and setnodeLabelsto an array of their label values, or to NULL if node labels are not required. SetmapTuplesToNodesto an array that gives the index (from zero) of the node that each leaf tuple should be assigned to. SetleafTupleDatumsto an array of the values to be stored in the new leaf tuples (these will be the same as the inputdatumsif the operator class does not modify datums from one level to the next). Note that thepicksplitfunction is responsible for palloc'ing thenodeLabels,mapTuplesToNodesandleafTupleDatumsarrays.If more than one leaf tuple is supplied, it is expected that the
picksplitfunction will classify them into more than one node; otherwise it is not possible to split the leaf tuples across multiple pages, which is the ultimate purpose of this operation. Therefore, if thepicksplitfunction ends up placing all the leaf tuples in the same node, the core SP-GiST code will override that decision and generate an inner tuple in which the leaf tuples are assigned at random to several identically-labeled nodes. Such a tuple is markedallTheSameto signify that this has happened. Thechooseandinner_consistentfunctions must take suitable care with such inner tuples. See Section 66.4.3 for more information.picksplitcan be applied to a single leaf tuple only in the case that theconfigfunction setlongValuesOKto true and a larger-than-a-page input value has been supplied. In this case the point of the operation is to strip off a prefix and produce a new, shorter leaf datum value. The call will be repeated until a leaf datum short enough to fit on a page has been produced. See Section 66.4.1 for more information.inner_consistentReturns set of nodes (branches) to follow during tree search.
The SQL declaration of the function must look like this:
CREATE FUNCTION my_inner_consistent(internal, internal) RETURNS void ...
The first argument is a pointer to a
spgInnerConsistentInC struct, containing input data for the function. The second argument is a pointer to aspgInnerConsistentOutC struct, which the function must fill with result data.typedef struct spgInnerConsistentIn { ScanKey scankeys; /* array of operators and comparison values */ ScanKey orderbys; /* array of ordering operators and comparison * values */ int nkeys; /* length of scankeys array */ int norderbys; /* length of orderbys array */ Datum reconstructedValue; /* value reconstructed at parent */ void *traversalValue; /* opclass-specific traverse value */ MemoryContext traversalMemoryContext; /* put new traverse values here */ int level; /* current level (counting from zero) */ bool returnData; /* original data must be returned? */ /* Data from current inner tuple */ bool allTheSame; /* tuple is marked all-the-same? */ bool hasPrefix; /* tuple has a prefix? */ Datum prefixDatum; /* if so, the prefix value */ int nNodes; /* number of nodes in the inner tuple */ Datum *nodeLabels; /* node label values (NULL if none) */ } spgInnerConsistentIn; typedef struct spgInnerConsistentOut { int nNodes; /* number of child nodes to be visited */ int *nodeNumbers; /* their indexes in the node array */ int *levelAdds; /* increment level by this much for each */ Datum *reconstructedValues; /* associated reconstructed values */ void **traversalValues; /* opclass-specific traverse values */ double **distances; /* associated distances */ } spgInnerConsistentOut;The array
scankeys, of lengthnkeys, describes the index search condition(s). These conditions are combined with AND — only index entries that satisfy all of them are interesting. (Note thatnkeys= 0 implies that all index entries satisfy the query.) Usually the consistent function only cares about thesk_strategyandsk_argumentfields of each array entry, which respectively give the indexable operator and comparison value. In particular it is not necessary to checksk_flagsto see if the comparison value is NULL, because the SP-GiST core code will filter out such conditions. The arrayorderbys, of lengthnorderbys, describes ordering operators (if any) in the same manner.reconstructedValueis the value reconstructed for the parent tuple; it is(Datum) 0at the root level or if theinner_consistentfunction did not provide a value at the parent level.traversalValueis a pointer to any traverse data passed down from the previous call ofinner_consistenton the parent index tuple, or NULL at the root level.traversalMemoryContextis the memory context in which to store output traverse values (see below).levelis the current inner tuple's level, starting at zero for the root level.returnDataistrueif reconstructed data is required for this query; this will only be so if theconfigfunction assertedcanReturnData.allTheSameis true if the current inner tuple is marked “all-the-same”; in this case all the nodes have the same label (if any) and so either all or none of them match the query (see Section 66.4.3).hasPrefixis true if the current inner tuple contains a prefix; if so,prefixDatumis its value.nNodesis the number of child nodes contained in the inner tuple, andnodeLabelsis an array of their label values, or NULL if the nodes do not have labels.nNodesmust be set to the number of child nodes that need to be visited by the search, andnodeNumbersmust be set to an array of their indexes. If the operator class keeps track of levels, setlevelAddsto an array of the level increments required when descending to each node to be visited. (Often these increments will be the same for all the nodes, but that's not necessarily so, so an array is used.) If value reconstruction is needed, setreconstructedValuesto an array of the values reconstructed for each child node to be visited; otherwise, leavereconstructedValuesas NULL. The reconstructed values are assumed to be of typespgConfigOut.leafType. (However, since the core system will do nothing with them except possibly copy them, it is sufficient for them to have the sametyplenandtypbyvalproperties asleafType.) If ordered search is performed, setdistancesto an array of distance values according toorderbysarray (nodes with lowest distances will be processed first). Leave it NULL otherwise. If it is desired to pass down additional out-of-band information (“traverse values”) to lower levels of the tree search, settraversalValuesto an array of the appropriate traverse values, one for each child node to be visited; otherwise, leavetraversalValuesas NULL. Note that theinner_consistentfunction is responsible for palloc'ing thenodeNumbers,levelAdds,distances,reconstructedValues, andtraversalValuesarrays in the current memory context. However, any output traverse values pointed to by thetraversalValuesarray should be allocated intraversalMemoryContext. Each traverse value must be a single palloc'd chunk.leaf_consistentReturns true if a leaf tuple satisfies a query.
The SQL declaration of the function must look like this:
CREATE FUNCTION my_leaf_consistent(internal, internal) RETURNS bool ...
The first argument is a pointer to a
spgLeafConsistentInC struct, containing input data for the function. The second argument is a pointer to aspgLeafConsistentOutC struct, which the function must fill with result data.typedef struct spgLeafConsistentIn { ScanKey scankeys; /* array of operators and comparison values */ ScanKey orderbys; /* array of ordering operators and comparison * values */ int nkeys; /* length of scankeys array */ int norderbys; /* length of orderbys array */ Datum reconstructedValue; /* value reconstructed at parent */ void *traversalValue; /* opclass-specific traverse value */ int level; /* current level (counting from zero) */ bool returnData; /* original data must be returned? */ Datum leafDatum; /* datum in leaf tuple */ } spgLeafConsistentIn; typedef struct spgLeafConsistentOut { Datum leafValue; /* reconstructed original data, if any */ bool recheck; /* set true if operator must be rechecked */ bool recheckDistances; /* set true if distances must be rechecked */ double *distances; /* associated distances */ } spgLeafConsistentOut;The array
scankeys, of lengthnkeys, describes the index search condition(s). These conditions are combined with AND — only index entries that satisfy all of them satisfy the query. (Note thatnkeys= 0 implies that all index entries satisfy the query.) Usually the consistent function only cares about thesk_strategyandsk_argumentfields of each array entry, which respectively give the indexable operator and comparison value. In particular it is not necessary to checksk_flagsto see if the comparison value is NULL, because the SP-GiST core code will filter out such conditions. The arrayorderbys, of lengthnorderbys, describes the ordering operators in the same manner.reconstructedValueis the value reconstructed for the parent tuple; it is(Datum) 0at the root level or if theinner_consistentfunction did not provide a value at the parent level.traversalValueis a pointer to any traverse data passed down from the previous call ofinner_consistenton the parent index tuple, or NULL at the root level.levelis the current leaf tuple's level, starting at zero for the root level.returnDataistrueif reconstructed data is required for this query; this will only be so if theconfigfunction assertedcanReturnData.leafDatumis the key value ofspgConfigOut.leafTypestored in the current leaf tuple.The function must return
trueif the leaf tuple matches the query, orfalseif not. In thetruecase, ifreturnDataistruethenleafValuemust be set to the value (of typespgConfigIn.attType) originally supplied to be indexed for this leaf tuple. Also,recheckmay be set totrueif the match is uncertain and so the operator(s) must be re-applied to the actual heap tuple to verify the match. If ordered search is performed, setdistancesto an array of distance values according toorderbysarray. Leave it NULL otherwise. If at least one of returned distances is not exact, setrecheckDistancesto true. In this case, the executor will calculate the exact distances after fetching the tuple from the heap, and will reorder the tuples if needed.
The optional user-defined methods are:
Datum compress(Datum in)Converts a data item into a format suitable for physical storage in a leaf tuple of the index. It accepts a value of type
spgConfigIn.attTypeand returns a value of typespgConfigOut.leafType. The output value must not contain an out-of-line TOAST pointer.Note: the
compressmethod is only applied to values to be stored. The consistent methods receive queryscankeysunchanged, without transformation usingcompress.optionsDefines a set of user-visible parameters that control operator class behavior.
The SQL declaration of the function must look like this:
CREATE OR REPLACE FUNCTION my_options(internal) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT;
The function is passed a pointer to a
local_reloptsstruct, which needs to be filled with a set of operator class specific options. The options can be accessed from other support functions using thePG_HAS_OPCLASS_OPTIONS()andPG_GET_OPCLASS_OPTIONS()macros.Since the representation of the key in SP-GiST is flexible, it may depend on user-specified parameters.
All the SP-GiST support methods are normally called in a short-lived memory context; that is, CurrentMemoryContext will be reset after processing of each tuple. It is therefore not very important to worry about pfree'ing everything you palloc. (The config method is an exception: it should try to avoid leaking memory. But usually the config method need do nothing but assign constants into the passed parameter struct.)
If the indexed column is of a collatable data type, the index collation will be passed to all the support methods, using the standard PG_GET_COLLATION() mechanism.