Привет, Хабр!
Сегодня рассмотрим PL/V8. Это расширение PostgreSQL, которое позволяет использовать JavaScript через движок V8 (тот самый, который стоит за Node.js). Это открывает массу возможностей:
-
Писать хранимые процедуры на JavaScript.
-
Выполнять сложную бизнес-логику прямо в базе данных.
-
Создавать триггеры, которые обрабатывают данные.
Звучит круто, но сразу уточним, где это применимо:
-
Когда вы работаете с JSON-структурами.
-
Если нужна динамическая обработка данных.
-
Когда хочется упростить и ускорить логику, не гоняя данные между базой и приложением.
Установим:
На Ubuntu/Debian:
sudo apt update sudo apt install postgresql-plv8
Теперь подключаем расширение в базе:
CREATE EXTENSION plv8;
Проверим, всё ли работает:
SELECT plv8_version();
Если видите номер версии (например, 3.2.0
), значит, всё окей.
Для macOS/Linux: если нужно всё делать своими руками (или на системе нет готовых пакетов), PL/V8 можно собрать из исходников:
-
Скачиваем код:
git clone https://github.com/plv8/plv8 cd plv8
-
Устанавливаем зависимости:
sudo apt install build-essential cmake libstdc++-12-dev
-
Сборка:
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();
Прочие нюансы
-
Каждый вызов функции 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!"
-
Избегайте использования
eval()
и динамических SQL-запросов. Лушче использовать параметры вместо конкатенации строк:plv8.elog(NOTICE, "Debug info: ", some_variable);
-
Для отладки используйте
plv8.elog
:plv8.elog(NOTICE, "Debug info: ", some_variable);
-
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/
Добавить комментарий