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



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

    Как вам может быть известно, PostgreSQL позволяет писать хранимые процедуры на разных языках, в том числе на C, PL/pgSQL, PL/Perl, PL/Python и других. Также PostgreSQL позволяет хранить JSON, строить по нему индексы и производить различные манипуляции с документами. Для хранения документов есть два встроенных типа: JSON, являющийся по сути обычной строкой, и JSONB, реализующий более эффективное бинарное представление.

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

    -- Важно! Синтаксис `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('{"aaa":123, "ййй":456}' :: 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-трансформов, все же, не повредит.
    • +27
    • 7,6k
    • 3

    Postgres Professional

    166,00

    Российский вендор PostgreSQL

    Поделиться публикацией

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

    Комментарии 3
      +2
      {"aaa":123, "ййй":456}

      Попробуйте в качестве домашнего задания выяснить, чем вызвано такое несоответствие.

      При переводе из JSONB в TEXT после двоеточий добавляются пробелы. Может ведь ещё и поля местами поменять.

        0
        Верно :)
        0
        Всё, вроде как, очевидно, но на всякий случай пример
        Наглядно

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

        Самое читаемое