PL/V8: JavaScript в PostgreSQL

от автора

Привет, Хабр!

Сегодня рассмотрим PL/V8. Это расширение PostgreSQL, которое позволяет использовать JavaScript через движок V8 (тот самый, который стоит за Node.js). Это открывает массу возможностей:

  • Писать хранимые процедуры на JavaScript.

  • Выполнять сложную бизнес-логику прямо в базе данных.

  • Создавать триггеры, которые обрабатывают данные.

Звучит круто, но сразу уточним, где это применимо:

  1. Когда вы работаете с JSON-структурами.

  2. Если нужна динамическая обработка данных.

  3. Когда хочется упростить и ускорить логику, не гоняя данные между базой и приложением.

Установим:

На Ubuntu/Debian:

sudo apt update sudo apt install postgresql-plv8

Теперь подключаем расширение в базе:

CREATE EXTENSION plv8;

Проверим, всё ли работает:

SELECT plv8_version();

Если видите номер версии (например, 3.2.0), значит, всё окей.

Для macOS/Linux: если нужно всё делать своими руками (или на системе нет готовых пакетов), PL/V8 можно собрать из исходников:

  1. Скачиваем код:

git clone https://github.com/plv8/plv8 cd plv8
  1. Устанавливаем зависимости:

sudo apt install build-essential cmake libstdc++-12-dev
  1. Сборка:

make sudo make install

После установки можно настроить некоторые параметры в postgresql.conf:

  • plv8.execution_timeout — время выполнения JS-кода в секундах. Значение по дефолту 300 секунд.

  • plv8.memory_limit — лимит памяти для JavaScript (в МБ). По умолчанию 256 МБ.

Пример настройки:

SET plv8.execution_timeout = 10; -- максимум 10 секунд SET plv8.memory_limit = 128;    -- максимум 128 МБ

Применение

Работа с JSON

Допустим, есть массивы ключей и значений, которые нужно объединить в JSON. Вот как это сделать:

CREATE FUNCTION json_from_arrays(keys TEXT[], values TEXT[]) RETURNS JSON AS $$     var result = {};     for (var i = 0; i < keys.length; i++) {         result[keys[i]] = values[i];     }     return result; $$ LANGUAGE plv8 IMMUTABLE STRICT;  -- Проверим: SELECT json_from_arrays(ARRAY['name', 'age'], ARRAY['Roman', '30']);

Результат:

{"name": "Roman", "age": "30"}

Подсчёт суммы в массиве

Пусть есть массив чисел, и нужно быстро посчитать их сумму:

CREATE FUNCTION array_sum(numbers plv8_int4array) RETURNS INT AS $$     var sum = 0;     for (var i = 0; i < numbers.length; i++) {         sum += numbers[i];     }     return sum; $$ LANGUAGE plv8 IMMUTABLE STRICT;  -- Тестируем: SELECT array_sum(ARRAY[1, 2, 3, 4, 5]); -- 15

Триггер для логирования изменений

Допустим, нужно логировать изменения в таблице. PL/V8 позволяет реализовать эту логику:

CREATE FUNCTION log_changes() RETURNS TRIGGER AS $$     plv8.elog(NOTICE, "Row changed: ", JSON.stringify(NEW));     return NEW; $$ LANGUAGE plv8;  CREATE TRIGGER log_trigger     AFTER INSERT OR UPDATE     ON your_table     FOR EACH ROW     EXECUTE FUNCTION log_changes();

Теперь каждое изменение в таблице будет выводиться в лог PostgreSQL.

SPI и подготовленные запросы

PL/V8 имеет интерфейсы для работы с SQL-запросами. Пример использования подготовленных запросов:

CREATE FUNCTION find_users_by_age(age INT) RETURNS JSON AS $$     var plan = plv8.prepare("SELECT id, name FROM users WHERE age = $1", ['int']);     var result = plan.execute([age]);     plan.free();     return result; $$ LANGUAGE plv8;  -- Используем: SELECT find_users_by_age(30);

Это ускоряет выполнение часто используемых запросов, так как они заранее компилируются PostgreSQL.

Обработка данных с транзакциями

Предположим, есть таблица заказов, и нужно в одной транзакции обновить статусы всех заказов, а также записать лог об этом:

CREATE FUNCTION process_orders() RETURNS VOID AS $$     plv8.subtransaction(function () {         // Обновляем статусы заказов         plv8.execute("UPDATE orders SET status = 'processed' WHERE status = 'pending'");                  // Логируем изменения         var log_message = "Processed orders at " + new Date().toISOString();         plv8.execute("INSERT INTO logs (message) VALUES ($1)", [log_message]);     }); $$ LANGUAGE plv8;  -- Запуск функции SELECT process_orders();

Если одна из операций внутри транзакции не выполнится (например, ошибка в INSERT INTO logs), все изменения будут откатаны.

Генерация отчёта с использованием JavaScript

Допустим, нужно сгенерировать отчёт в формате JSON, который объединяет данные из нескольких таблиц. PL/V8 позволяет это сделать без привлечения прочих инструментов:

CREATE FUNCTION generate_report() RETURNS JSON AS $$     // Получаем данные о пользователях     var users = plv8.execute("SELECT id, name FROM users");      // Получаем данные о заказах     var orders = plv8.execute("SELECT user_id, amount FROM orders");      // Объединяем данные     var report = {};     users.forEach(function(user) {         report[user.name] = orders             .filter(order => order.user_id === user.id)             .map(order => order.amount);     });      return report; $$ LANGUAGE plv8;  -- Генерация отчёта SELECT generate_report();

Прочие нюансы

  1. Каждый вызов функции PL/V8 изолирован, но можно сохранять глобальные данные в объекте plv8.global.

    CREATE FUNCTION set_global_var(val TEXT) RETURNS VOID AS $$     plv8.global.my_var = val; $$ LANGUAGE plv8;  CREATE FUNCTION get_global_var() RETURNS TEXT AS $$     return plv8.global.my_var || ' is global!'; $$ LANGUAGE plv8;  -- Устанавливаем значение: SELECT set_global_var('Hello'); SELECT get_global_var(); -- "Hello is global!"
  2. Избегайте использования eval() и динамических SQL-запросов. Лушче использовать параметры вместо конкатенации строк:

    plv8.elog(NOTICE, "Debug info: ", some_variable);
  3. Для отладки используйте plv8.elog:

    plv8.elog(NOTICE, "Debug info: ", some_variable);
  4. PL/V8 поддерживает выполнение JavaScript-кода на лету с помощью plv8.run_script. Полезно для выполнения динамических операций:

    DO $$      plv8.run_script('globalThis.dynamicVar = "I am dynamic!"');     plv8.elog(NOTICE, globalThis.dynamicVar); $$ LANGUAGE plv8; 

Более подробно с библиотекой можно ознакомиться здесь.

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

  • 10 февраля: «Почему ClickHouse становится выбором №1 при импортозамещении: сравнение с другими NoSQL решениями и практический кейс». Подробнее

  • 11 февраля: «Запуск СУБД (MySQL, PostgreSQL) в Docker». Подробнее

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


ссылка на оригинал статьи https://habr.com/ru/articles/876982/


Комментарии

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

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