Хабрахабр

Что нового в PostgreSQL 11: JSONB-трансформы

Эти фичи появятся в PostgreSQL 11, релиз которого, напомню, планируется в октябре. В предыдущих постах вы могли прочитать про INCLUDE-индексы и встроенный веб-поиск. Оба патча написаны Антоном Быковым.
Как вам может быть известно, PostgreSQL позволяет писать хранимые процедуры на разных языках, в том числе на C, PL/pgSQL, PL/Perl, PL/Python и других. Сегодня мне хотелось бы продолжить обзор новых фичей грядущего релиза, рассказав про JSONB-трансформы для языков PL/Python (3f44e3db) и PL/Perl (341e1661). Для хранения документов есть два встроенных типа: JSON, являющийся по сути обычной строкой, и JSONB, реализующий более эффективное бинарное представление. Также PostgreSQL позволяет хранить JSON, строить по нему индексы и производить различные манипуляции с документами.

Проще всего объяснить его на примере: Однако на границе этих механизмов есть небольшой косяк.

-- Важно! Синтаксис `create language` задепрекейчен с 9.1
create extension plpython3u; CREATE OR REPLACE FUNCTION foo(val jsonb) RETURNS int
LANGUAGE plpython3u
AS $$
assert(isinstance(val, str))
return len(val)
$$; select foo('' :: jsonb);

В результате код выполнится без ошибок и вернет результат 24. То есть, при передаче в хранимую процедуру JSONB был в закодирован в строку, от которой мы вычислили длину.

Как ни странно, никакого бага здесь нет, как можно было бы предположить. Fun fact! На самом деле, длина исходного документа — 22 символа, а у нас почему-то получилось 24. Попробуйте в качестве домашнего задания выяснить, чем вызвано такое несоответствие.

Само собой разумеется, строку можно декодировать обратно: Так вот, но ведь мы хотели передать документ, а не строку.

CREATE OR REPLACE FUNCTION bar(val_str jsonb) RETURNS int
LANGUAGE plpython3u
AS $$
import json
val = json.loads(val_str)
assert(isinstance(val, dict))
plpy.info(sorted(val.items()))
return len(val)
$$; select bar('{"aaa": 123, "ййй": 456}' :: jsonb);

Результат:

INFO: [('aaa', 123), ('ййй', 456)]
bar
-----
2
(1 row)

Но если подумать, мы тут делаем много бесполезной работы — сначала кодируем документ, а затем тут же декодируем. Вроде, похоже на правду. Если документ большой, а хранимая процедура вызывается часто, накладные расходы на это кодирование-декодирование могут быть довольно большими.

Оказывается, что можно, и названные патчи делают именно это. Спрашивается, а нельзя ли просто передать JSONB в Python или Perl в виде понятных им объектов (массивов, словарей/хэшей, и так далее)? Вот как этим примерно пользоваться:

create extension jsonb_plpython3u; CREATE OR REPLACE FUNCTION baz(val jsonb) RETURNS int
LANGUAGE plpython3u
TRANSFORM FOR TYPE jsonb
AS $$
assert isinstance(val, dict)
plpy.info(sorted(val.items()))
return len(val)
$$;

Кстати, в обратную сторону, то есть, кодировать возвращаемые документы, тоже можно:

CREATE OR REPLACE FUNCTION qux(val int) RETURNS jsonb
LANGUAGE plpython3u
TRANSFORM FOR TYPE jsonb
AS $$
obj = { "val": val }
return obj
$$;

Для Perl и Python 2 все делается аналогично, поэтому соответствующий код я не привожу. Кроме того, не стану грузить вас тонкостями работы кода в различных граничных случаях, например, что происходит, если в возвращаемом документе встретятся комплесные числа, NaN, ±Inf, и вот это вот все. Заинтересованных читателей я призываю ознакомиться с кодом патчей. Они обильно обмазаны тестами, как раз проверяющими подобные граничные случаи.

Конечно, ситуации, в которых она оказывается полезна, возникают не часто. Такая вот интересная фича. Но знать про существование JSONB-трансформов, все же, не повредит.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»