Re: OPERATOR CLASS - Mailing list pgsql-ru-general

From Nikita Glukhov
Subject Re: OPERATOR CLASS
Date
Msg-id 9730431c-7e0b-bcb7-741b-912e81f1ff31@postgrespro.ru
Whole thread Raw
In response to Re: OPERATOR CLASS  (Nikita Glukhov <n.gluhov@postgrespro.ru>)
List pgsql-ru-general
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

pgsql-ru-general by date:

Previous
From: Nikita Glukhov
Date:
Subject: Re: OPERATOR CLASS
Next
From: Dmitry E. Oboukhov
Date:
Subject: INSERT ... ON CONFLICT & SERIAL