Thread: Re: OPERATOR CLASS
> Есть вопросы по сабжу.
> 1. Можно ли как-то их просматривать в консоли (команды \d* не нашел).
У нас есть патч для psql, который реализует требуемые команды для показа информации об опклассах.
(см. тред "Psql patch to show access methods info" https://www.postgresql.org/message-id/flat/1529675324.14193.5.camel%40postgrespro.ru)
Но он, к сожалению, уже не войдет в PostgreSQL 12.
Пример вывода этих команд для интересующего Вас jsonb_path_ops:
\dAo+ gin jsonb_path_ops List operators of family related to access methodAM | Opfamily Schema | Opfamily Name | Operator | Strategy | Purpose | Sort family -----+-----------------+----------------+----------------------+----------+---------+-------------gin | pg_catalog | jsonb_path_ops | @> (jsonb, jsonb) | 7 | search | gin | pg_catalog | jsonb_path_ops | @? (jsonb, jsonpath) | 15 | search | gin | pg_catalog | jsonb_path_ops | @@ (jsonb, jsonpath) | 16 | search | \dAp+ gin jsonb_path_ops List of operator family proceduresAM | Family schema | Family name | Left | Right | Number | Proc name -----+---------------+----------------+-------+-------+--------+------------------------------gin | pg_catalog | jsonb_path_ops | jsonb | jsonb | 1 | btint4cmpgin | pg_catalog | jsonb_path_ops | jsonb | jsonb | 2 | gin_extract_jsonb_pathgin | pg_catalog | jsonb_path_ops | jsonb | jsonb | 3 | gin_extract_jsonb_query_pathgin | pg_catalog | jsonb_path_ops | jsonb | jsonb | 4 | gin_consistent_jsonb_pathgin | pg_catalog | jsonb_path_ops | jsonb | jsonb | 6 | gin_triconsistent_jsonb_path (5 rows) \dAc gin jsonb* Operator classes for index AM "gin"Input type | Storage type | Operator class | Default? ------------+--------------+----------------------+----------jsonb | text | jsonb_ops | yesjsonb | integer | jsonb_path_ops | no
Если есть какие-то пожелания, каким хотелось бы видеть вывод этих команд, мы рады будем их выслушать (а сообщение в том треде о полезности этих команд от пользователя-разработчика опклассов, думаю, очень поможет их продвижению в PostgreSQL 13).
> Если нет - в каких-то внутренних схемах?
pg_opclass, pg_opfamily, pg_amop, pg_amproc:
SELECT amopstrategy AS "Strategy", amoppurpose AS "Purpose", oprname AS "Operator", amoplefttype::regtype AS "Left type", amoprighttype::regtype AS "Right type" FROM pg_amop amop, pg_opfamily opf, pg_operator opr WHERE opfname = 'jsonb_path_ops' AND amopfamily = opf.oid AND amopopr = opr.oid; Strategy | Purpose | Operator | Left type | Right type ----------+---------+----------+-----------+------------ 7 | s | @> | jsonb | jsonb 15 | s | @? | jsonb | jsonpath 16 | s | @@ | jsonb | jsonpath (3 rows) SELECT amprocnum AS "Number", amproc::regproc AS "Function", amproclefttype::regtype AS "Left type", amprocrighttype::regtype AS "Right type" FROM pg_amproc, pg_opfamily WHERE opfname = 'jsonb_path_ops' AND amprocfamily = pg_opfamily.oid; Number | Function | Left type | Right type --------+------------------------------+-----------+------------ 1 | btint4cmp | jsonb | jsonb 2 | gin_extract_jsonb_path | jsonb | jsonb 3 | gin_extract_jsonb_query_path | jsonb | jsonb 4 | gin_consistent_jsonb_path | jsonb | jsonb 6 | gin_triconsistent_jsonb_path | jsonb | jsonb (5 rows)
> 2. Можно ли как-то определить свой класс операторов по методу "такой же как оператор класс с таким-то именем, но оператор сравнения у него вот такой?"
> 3. Если п.2 нельзя, то можно ли вынуть из сабжа ссылки на его функции/операторы как-то?
Создать новый опкласс на основе имеющегося, переопределяя только лишь некоторые его операторы или функции, к сожалению, невозможно.
Но, используя CREATE OPERATOR CLASS, действительно можно выполнить эту задачу, вручную добавив в него все переиспользумые операторы и функции, помимо своего оператора сравнения. Как получить ссылки на функции/операторы — описано выше.
> Все вопросы про SQL, а не C
> PS: что хочу
> хочу GIN индекс по jsonb со своим оператором сортировки.
> Насколько я понимаю для того что мне хочется нужно проделать следующее:
> 1. Создать свой класс аналогичный jsonb_path_ops у которого будет всё так же как у предка, но моя функция FUNCTION 1 mycompare(jsonb, jsonb)
> 2. Построить индекс по полю jsonb указав этот opclass
> 3. PROFIT
Если переиспользовать jsonb_path_ops, то функция сравнения должна быть не для jsonb, а для int4 — того типа, что используется для хранения внутри (pg_opclass.opckeytype, "Storage type" в \dAc). Функцию mycompare(jsonb, jsonb) вместе со своим оператором сравнения имеет смысл использовать в опклассе для btree, а не для gin.
jsonb_path_ops хранит int4-хеши вида "hash (key1, key2, ..., keyN, value)", где "key1, key2, ..., keyN" — это путь к полю объекта или элемента массива "value", состоящий из названий ключей (массивы не учитываются в путях).
jsonb_ops же хранит text'овые строки вида "Tvalue", где "T" это префикс типа (key, string, number, boolean, null), а "value" это название/значение поля объекта или значение элемента массива в тестовом виде.
Поэтому для нового gin-опкласса, возможно, имеет смысл взять за основу jsonb_ops, а не на jsonb_path_ops. Но вообще, хотелось бы поподробнее узнать об этом новом операторе сравнения, тогда мы сможем лучше Вам помочь.
Вот на всякий случай пример создания копии jsonb_path_ops.
CREATE OPERATOR CLASS my_jsonb_path_ops FOR TYPE jsonb USING gin AS OPERATOR 7 @>, OPERATOR 15 @? (jsonb, jsonpath), OPERATOR 16 @@ (jsonb, jsonpath), FUNCTION 1 btint4cmp, FUNCTION 2 gin_extract_jsonb_path, FUNCTION 3 gin_extract_jsonb_query_path, FUNCTION 4 gin_consistent_jsonb_path, FUNCTION 6 gin_triconsistent_jsonb_path, STORAGE integer;
On 11.04.2019 5:40, Dmitry E. Oboukhov wrote:
11.04.2019, 01:58, "Nikita Glukhov" <n.gluhov@postgrespro.ru>:FUNCTION 1 btint4cmp,
FUNCTION 2 gin_extract_jsonb_path,
FUNCTION 3 gin_extract_jsonb_query_path,
FUNCTION 4 gin_consistent_jsonb_path,
FUNCTION 6 gin_triconsistent_jsonb_path,
STORAGE integer;А вот по этим функциям где-то можно почитать разъяснения?
В официальной документации вроде бы все достаточно подробно описано:
https://www.postgresql.org/docs/11/gin-extensibility.html (на английском)
https://postgrespro.ru/docs/postgresql/11/gin-extensibility (на русском)
насколько я понимаю функция 1 - сравнивает именно то, что возвращает функция 2функция 3 нужна за тем же зачем и функция 2, но она работает над запросомто есть если у насWHERE json_field @> json_queryто реально получается следующееjson_field пропускается через функцию 2 при индексированииjson_query - пропускается через функцию 3 при самом запросефункция 1 используется для сравнения двух элементов между собой
Да, все правильно. Хочу еще обратить внимание, что запрос может быть другого, не jsonb, типа, а функция extractQuery() различает разные типы запорсов по их номеру стратегии.
а вот функции 4 и 6 - они для чего используются?
В документации про это написано, но если будут вопросы, отвечу на них.
Вот файл, в котором находится реализация этих функций для jsonb:
https://github.com/postgres/postgres/blob/master/src/backend/utils/adt/jsonb_gin.c
и вот еще вопрос. Некоторые из этих функций принимают internal в виде аргументов, соответственно вызвать их и посмотреть их поведение прямо из SELECT - непонятно как.соответственно непонятно - как полностью с нуля написать свой OPERATOR CLASS не используя программирование на C (допустим хотим сделать скетч на SQL, возможно ли это сделать?)
Да, все GIN-функции, за исключением первой compare(), можно написать сейчас только на C, и вызывать их из SQL нельзя. Но можно попробовать сделать один раз специальный обобщенный OPERATOR CLASS, в котором из функциий 2-6, написанных на C, будут вызываться другие функции с соответствующими номерами (например, 12-16), в которых уже будут использоваться только SQL-типы. Но полностью перенести всю функциональность C-функций на уровень SQL будет не так просто, потому что многие функции могут возвращать несколько значений сразу, а внутри Postgres, по-моему, нет такого удобного способа вызова для функций с OUT-параметрами, который есть для обычных функций. Например,
Datum *extractQuery(Datum query, int32 *nkeys, StrategyNumber n, bool **pmatch, Pointer **extra_data, bool **nullFlags, int32 *searchMode)
возвращает массив извлеченных элементов, а также необязательные массив флагов pmatch, массив произвольных С-указателй extra_data, массив null-флагов nullFlags, и режим поиска searchMode. Если оставить только обязательный массив элементов, то сигнатрура SQL-функции extract_query будет совсем простой:
extract_query (query anyelement, strategy int2) RETURNS storage_type[]
но я не уверен, что с anyelement не возникнет каких-либо проблем.
И еще хочу на всякий случай напомнить, что у нас есть еще расширение JsQuery (https://github.com/postgrespro/jsquery), которое содержит опклассы, позволяющие выполнять по GIN-индексам более широкий ряд jsonb-запросов (например, range-запросов, которые реализованы с использования partialMatch). Возможно даже, что JsQuery решит Вашу задачу. Сейчас мы работает в JsQuery на поддержкой jsonpath, который выйдет в PostgreSQL12, а в будущем планируем даже перенести эти JsQuery-опклассы в ядро.
В plpgsql, конечно, тоже нельзя использовать internal типы, поэтому это не получится.11.04.2019, 13:32, "Nikita Glukhov" <n.gluhov@postgrespro.ru>:On 11.04.2019 5:40, Dmitry E. Oboukhov wrote:
11.04.2019, 01:58, "Nikita Glukhov" <n.gluhov@postgrespro.ru>:FUNCTION 1 btint4cmp,
FUNCTION 2 gin_extract_jsonb_path,
FUNCTION 3 gin_extract_jsonb_query_path,
FUNCTION 4 gin_consistent_jsonb_path,
FUNCTION 6 gin_triconsistent_jsonb_path,
STORAGE integer;А вот по этим функциям где-то можно почитать разъяснения?В официальной документации вроде бы все достаточно подробно описано:
https://www.postgresql.org/docs/11/gin-extensibility.html (на английском)
https://postgrespro.ru/docs/postgresql/11/gin-extensibility (на русском)интересно - можно ли писать эти функции на SQL (plpgsql)?
если смотреть на функцию gin_triconsistent_jsonb, например, тоТип данных результата | "char"
Типы данных аргументов | internal, smallint, jsonb, integer, internal, internal, internalесли посмотреть на неё со стороны документации, тоbool consistent(bool check[], StrategyNumber n, Datum query, int32 nkeys, Pointer extra_data[], bool *recheck, Datum queryKeys[], bool nullFlags[])
я хочу (хотел, пока Ваш ответ не прочитал) написать скетч индекса на plpgsql. пусть он медленно работает в области сплита данных на индексируемые массивы итп, но если он заработает - появится смысл сесть это и на C написать.
Согласен, что для прототипирования опклассов возможность обходиться только лишь SQL или PL/pgSQL была бы очень полезной.
и вот еще вопрос. Некоторые из этих функций принимают internal в виде аргументов, соответственно вызвать их и посмотреть их поведение прямо из SELECT - непонятно как.соответственно непонятно - как полностью с нуля написать свой OPERATOR CLASS не используя программирование на C (допустим хотим сделать скетч на SQL, возможно ли это сделать?)
Да, все GIN-функции, за исключением первой compare(), можно написать сейчас только на C, и вызывать их из SQL нельзя. Но можно попробовать сделать один раз специальный обобщенный OPERATOR CLASS, в котором из функциий 2-6, написанных на C, будут вызываться другие функции с соответствующими номерами (например, 12-16), в которых уже будут использоваться только SQL-типы. Но полностью перенести всю функциональность C-функций на уровень SQL будет не так просто, потому что многие функции могут возвращать несколько значений сразу, а внутри Postgres, по-моему, нет такого удобного способа вызова для функций с OUT-параметрами, который есть для обычных функций. Например,
Datum *extractQuery(Datum query, int32 *nkeys, StrategyNumber n, bool **pmatch, Pointer **extra_data, bool **nullFlags, int32 *searchMode)спасибо, помедитирую над этим всем.я сишечную реализацию не смотрел вовсе, думал со стороны SQL до скетча добраться можно.возвращает массив извлеченных элементов, а также необязательные массив флагов pmatch, массив произвольных С-указателй extra_data, массив null-флагов nullFlags, и режим поиска searchMode. Если оставить только обязательный массив элементов, то сигнатрура SQL-функции extract_query будет совсем простой:
extract_query (query anyelement, strategy int2) RETURNS storage_type[]но я не уверен, что с anyelement не возникнет каких-либо проблем.
И еще хочу на всякий случай напомнить, что у нас есть еще расширение JsQuery (https://github.com/postgrespro/jsquery), которое содержит опклассы, позволяющие выполнять по GIN-индексам более широкий ряд jsonb-запросов (например, range-запросов, которые реализованы с использования partialMatch). Возможно даже, что JsQuery решит Вашу задачу. Сейчас мы работает в JsQuery на поддержкой jsonpath, который выйдет в PostgreSQL12, а в будущем планируем даже перенести эти JsQuery-опклассы в ядро.
цитата оттуда:int compare(Datum a, Datum b)
Сравнивает два ключа (не индексированные объекты!) и возвращает целое меньше нуля, ноль или целое больше нуля, показывающее, что первый ключ меньше, равен или больше второго. Ключи NULL никогда не передаются этой функции.
Если же класс операторов не определяет метод
compare
, GIN попытается найти класс операторов B-дерева по умолчанию для типа данных ключа индекса и воспользоваться его функцией сравнения. Если класс операторов GIN предназначен только для одного типа данных, рекомендуется задавать функцию сравнения в этом классе операторов, так как поиск класса операторов B-дерева занимает несколько циклов. Однако для полиморфных классов операторов GIN (например,array_ops
) задать одну функцию сравнения обычно не представляется возможным.для полиморфных типов (например массивы) a и b - это ключи возвращённыеextractValue или это массивы ключей извлечённые из записей? (я еще не разобрался с API низкого уровня, сорри если
вопрос глупый: пытаюсь понять как индекс работает).то есть extractValue вернул например:1. для записи 1 - массив "a", "b", "c"2. для записи 2 - массив "c", "d", "e"3. для записи 3 - массив "x", "y", "z"потом пользователь ищет"ary" @> [ "c" ]compare что с чем сравнивает при этом?
Для приведенного выше примера дерево получится примерно таким:
d | * / \ a b c d e x y z 1 1 1,2 2 2 3 3 3Т.е. в листьях хранятся указатели на записи, которые содержат данный ключ. "c" содержится в двух записях, поэтому его список (он называется "posting list" внутри GIN) содержит указатели на записи 1 и 2.
Немного более сложный запрос array @> [ "c", "e" ] будет выполняться по этому индексу следующим образом:
- extractQuery() извлекает ключи "c" и "e" из запроса.
- GIN ищет эти ключи в B-дереве, и получает два списка указателей на записи "1,2" и "1".
- Далее, используя алгоритм слияния для этих списков, GIN вызывает функцию [tri]consistent() ровно один раз для каждой из записей, присутствующих хотя бы в одном из списков, передавая в [tri]consistent() массив флагов, показывающих наличие данной записи в списке записей каждого из ключей.
Таким образом, для записи 1 будет вызвана
consistent(check = [true, true]),
а для записи 2
consistent(check = [false, true]) - consistent() в свою очередь интерпретирует этих флаги так, как ей нужно.
Для оператора @> (сontains) она вычислиет логическое И над этим флагами и возвращент этот как результат, т.к. contains предполагает наличие всех ключей в искомой записи.
Для оператора && (overlaps) наборот будет вычислено логическое ИЛИ, т.к. тут достаточно наличия хотя бы одного из искомых ключей. Но на самом деле здесь вычислять ничего не нужно, потому что один из check флагов всегда будет true (правда, это касается только consistent(), а в triconsistent() флаги могут иметь еще и GIN_MAYBE (NULL) значения). - Далее все записи, для которых [tri]consistent() вернула не false, добавлются в результирующий битмап.
При этом, если consistent() установила флаг recheck или trinconsistent() вернула GIN_MAYBE, запись будет еще раз перепроверена (т.е. выполнен оператор @>), когда она будет вытащена из таблицы. Это нужно для lossy-индексов (например, если в индексе хранятся хеши) и lossy-запросов (например, оператор "=" потребует проверки отутствия лишних элементов, а также соответствия порядка элементов), которые нельзя выполнить по индексу точно.
я хочу своей функцией compare иметь возможность поднять или опустить ввверх-низ списка записи в зависимости от своих критериев.
Не совсем понятно, что значит "поднять или опустить ввверх-вниз списка записи". Тем более, что GIN выдает записи в физическом порядке из-за использования BitmapIndexScan.
Если же на самом деле требуется отсутствующая сейчас возможность выполнения ORDER BY по GIN (я про это думал недавно, кстати), то ее, скорее всего, не так просто будет реализовать, но она выглядет довольно полезной. Например, можно было бы выполнять по GIN запросы вида:
SELECT * FROM tabWHERE js @> '{"foo": "bar"}'ORDER BY js -> 'baz';