Thread: Re: OPERATOR CLASS

Re: OPERATOR CLASS

From
Nikita Glukhov
Date:

> Есть вопросы по сабжу.

> 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;

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Re: OPERATOR CLASS

From
Nikita Glukhov
Date:

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-опклассы в ядро.

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Re: OPERATOR CLASS

From
Nikita Glukhov
Date:
On 11.04.2019 15:40, Dmitry E. Oboukhov wrote:
 
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)?
В plpgsql, конечно, тоже нельзя использовать internal типы, поэтому это не получится.


 если смотреть на функцию 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 что с чем сравнивает при этом?


a и b — это ключи (не массивы), извлеченнные extractValue() или exrtactQuery(). compare() используется для поиска в B-дереве, построенном на этих ключах.

Для приведенного выше примера дерево получится примерно таким:
             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" ]  будет выполняться по этому индексу следующим образом:
  1. extractQuery() извлекает ключи "c" и "e" из запроса. 
  2. GIN ищет эти ключи в B-дереве, и получает два списка указателей на записи "1,2" и "1".
  3. Далее, используя алгоритм слияния для этих списков, GIN вызывает функцию [tri]consistent() ровно один раз для каждой из записей, присутствующих хотя бы в одном из списков, передавая в [tri]consistent() массив флагов, показывающих наличие данной записи в списке записей каждого из ключей.
      Таким образом,  для записи 1 будет вызвана
        consistent(check = [true, true]),
      а для записи 2
       consistent(check = [false, true])
  4. consistent() в свою очередь интерпретирует этих флаги так, как ей нужно.
    Для оператора @> (сontains) она вычислиет логическое И над этим флагами и возвращент этот как результат, т.к. contains предполагает наличие всех ключей в искомой записи.
    Для оператора && (overlaps) наборот будет вычислено логическое ИЛИ, т.к. тут достаточно наличия хотя бы одного из искомых ключей. Но на самом деле здесь вычислять ничего не нужно, потому что один из check флагов всегда будет true (правда, это касается только consistent(), а в triconsistent() флаги могут иметь еще и GIN_MAYBE (NULL) значения).
  5. Далее все записи, для которых [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';

--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company