Возникло желание посмотреть как в pgAdmin работает отладчик PL/pgSQL и можно ли его использовать самостоятельно, например в psql.
В итоге всё получилось. Но не всё прошло гладко, поэтому есть смысл остановиться подробнее на каждом шаге:
Для обладателей Windows и MacOS программы установки уже подготовлены в разделе Download. Пользователи Ubuntu могут воспользоваться инструкцией с askubuntu.com. Только вместо несуществующего пакета virtualenv нужно установить python-virtualenv.
В файле README.pldebugger есть раздел про установку, ориентированный на сборку PostgreSQL из исходных кодов. Для PostgreSQL, установленного из пакета, можно сделать так:
Понадобятся два сеанса psql: 1 - сеанс управления отладкой (proxy), 2 - сеанс, в котором запускается функция для отладки (target).
Установим отладчик:
1| => show shared_preload_libraries;
1| shared_preload_libraries
1| ---------------------------
1| "$libdir/plugin_debugger"
1| (1 row)
1|
1| => create extension pldbgapi;
1| CREATE EXTENSION
Создадим функцию для подсчета количества строк в таблице, имя которой
передается параметром.
При помощи отладчика мы хотим убедиться, что текст команды SELECT формируется корректно:
1| => CREATE FUNCTION get_count (tabname text) RETURNS bigint
1| AS $$DECLARE
1| cmd text;
1| retval bigint;
1| BEGIN
1| cmd := 'SELECT COUNT(*) FROM '
1| || quote_ident(tabname);
1|
1| EXECUTE cmd INTO retval;
1| RETURN retval;
1| END;
1| $$ LANGUAGE plpgsql STABLE;
1| CREATE FUNCTION
Получим идентификатор сеанса отладки и OID функции:
1| => select pldbg_create_listener() as sessionid,
1| 'get_count'::regproc::oid as funcoid \gset
1| => \echo :sessionid :funcoid
1| 1 115750
Установим точку останова в начале функции (3-й параметр: -1) и ждем вызова функции из любого (4-й параметр: NULL) сеанса:
1| => select * from pldbg_set_global_breakpoint(:sessionid, :funcoid, -1, NULL);
1| pldbg_set_global_breakpoint
1| -----------------------------
1| t
1| (1 row)
1|
1| => select * from pldbg_wait_for_target(:sessionid);
Отладочный сеанс перешел в режим ожидания...
Откроем второй сеанс и вызываем функцию:
2| => select get_count('pg_class');
Выполнение доходит до точки останова и теперь второй сеанс переходит в режим ожидания команд отладчика.
Первый сеанс просыпается и переходит в режим управления отладкой:
1| pldbg_wait_for_target
1| -----------------------
1| 21413
1| (1 row)
В режиме управления можно:
1| => select name, varClass, value, pg_catalog.format_type(dtype, NULL) as dtype
1| from pldbg_get_variables(:sessionid)
1| order by varClass;
1| name | varclass | value | dtype
1| ---------+----------+----------+--------
1| tabname | A | pg_class | text
1| cmd | L | NULL | text
1| retval | L | NULL | bigint
1| (3 rows)
Видим, что параметр функции tabname = 'pg_class', а значения переменных cmd и retval еще не установлены.
Добавим еще одну точку останова на команде EXECUTE (8 строка функции):
1| => select * from pldbg_set_breakpoint(:sessionid, :funcoid, 8);
1| pldbg_set_breakpoint
1| ----------------------
1| t
1| (1 row)
Продолжим выполнение до новой точки останова:
1| => select p.targetName,
1| (select string_agg(t.n::text||
1| case when t.n=p.linenumber-1 then '*' else '' end,
1| chr(10)) from generate_series(1, 11) as t(n)) as line,
1| pldbg_get_source(:sessionid, p.func) AS src
1| from pldbg_continue(:sessionid) p;
1| targetname | line | src
1| -----------------+------+-------------------------------------
1| get_count(text) | 1 +| DECLARE +
1| | 2 +| cmd text; +
1| | 3 +| retval bigint; +
1| | 4 +| BEGIN +
1| | 5 +| cmd := 'SELECT COUNT(*) FROM ' +
1| | 6 +| || quote_ident(tabname);+
1| | 7 +| +
1| | 8* +| EXECUTE cmd INTO retval; +
1| | 9 +| RETURN retval; +
1| | 10 +| END; +
1| | 11 |
1| (1 row)
Еще раз посмотрим значения локальных переменных:
1| => select name, varClass, value, pg_catalog.format_type(dtype, NULL) as dtype
1| from pldbg_get_variables(:sessionid)
1| order by varClass;
1| name | varclass | value | dtype
1| ---------+----------+-------------------------------+--------
1| tabname | A | pg_class | text
1| cmd | L | SELECT COUNT(*) FROM pg_class | text
1| retval | L | NULL | bigint
1| (3 rows)
Видим, что переменной cmd присвоен текст команды SELECT.
Выполним следующую строку функции:
1| => select s.targetname, s.linenumber-1 as line
1| from pldbg_step_over(:sessionid) s;
1| targetname | line
1| -----------------+------
1| get_count(text) | 9
1| (1 row)
И еще раз посмотрим значения локальных переменных:
1| => select name, varClass, value, pg_catalog.format_type(dtype, NULL) as dtype
1| from pldbg_get_variables(:sessionid)
1| order by varClass;
1| name | varclass | value | dtype
1| ---------+----------+-------------------------------+--------
1| tabname | A | pg_class | text
1| cmd | L | SELECT COUNT(*) FROM pg_class | text
1| retval | L | 321 | bigint
1| (3 rows)
Команда SELECT выполнена, в переменную retval записан результат.
Прекратим сеанс отладки:
1| => select * from pldbg_abort_target(:sessionid);
1| pldbg_abort_target
1| --------------------
1| t
1| (1 row)
2| ERROR: canceling statement due to user request
2| CONTEXT: PL/pgSQL function get_count(text) line 9 at RETURN
Выполнение функции во втором сеансе завершается ошибкой.
В итоге всё получилось. Но не всё прошло гладко, поэтому есть смысл остановиться подробнее на каждом шаге:
- Установить pgAdmin 4.
- Установить pldebugger.
- Проверить отладчик в pgAdmin и найти описание использования api.
- Сделать пример в psql.
- ОС Ubuntu 14.04
- PostgreSQL 9.6.2 (установлен из пакета пользователем root)
1. Установка pgAdmin 4
Установка pgAdmin 4 v1.2 на Ubuntu осложняется тем, что на сайте и в документации нет раздела "Установка".Для обладателей Windows и MacOS программы установки уже подготовлены в разделе Download. Пользователи Ubuntu могут воспользоваться инструкцией с askubuntu.com. Только вместо несуществующего пакета virtualenv нужно установить python-virtualenv.
2. Установка отладчика pldebugger
Отладчик PL/pgSQL устанавливается отдельно от pgAdmin. Он представляет собой расширение PostgreSQL, которое нужно собрать и установить, а также прописать в параметре shared_preload_libraries библиотеку plugin_debugger.В файле README.pldebugger есть раздел про установку, ориентированный на сборку PostgreSQL из исходных кодов. Для PostgreSQL, установленного из пакета, можно сделать так:
- Понадобятся пакеты:
apt-get install postgresql-server-dev-9.6 libssl-dev libkrb5-dev - Расширение устанавливаем из под root, используя PGXS:
sudo su root
git clone git://git.postgresql.org/git/pldebugger.git
cd pldebugger
export USE_PGXS=1
make
make install - Не забываем перезапустить PostgreSQL после установки параметра shared_preload_libraries.
3. Проверка отладчика в pgAdmin и описание работы api
Отладчик работает согласно документации. Здесь особо нечего добавить. Разве что для установки точки останова, нужно "тыкать" мышкой не в число с номером строки исходного кода, а чуть-чуть правее, на свободный участок, в котором должна появиться точка.
Что касается описания функций API для использования без pgAdmin. Отдельного описания найти не удалось, но можно воспользоваться следующим:
- Достаточно подробные комментарии в исходном коде. Например, в файле pldbgapi.c.
- В переписке разработчиков нашлось такое письмо за 2007 год от Korry Douglas.
- Никто не отменял самый "честный" способ. Включить журналирование всех команд (log_statement='all'), запустить отладку в pgAdmin и смотреть, что и в каком порядке пишется в журнал сервера.
4. Пример отладки в psql
Вооружившись необходимыми знаниями можно попробовать выполнить отладку в psql, используя расширение pldbgapi.Понадобятся два сеанса psql: 1 - сеанс управления отладкой (proxy), 2 - сеанс, в котором запускается функция для отладки (target).
Установим отладчик:
1| => show shared_preload_libraries;
1| shared_preload_libraries
1| ---------------------------
1| "$libdir/plugin_debugger"
1| (1 row)
1|
1| => create extension pldbgapi;
1| CREATE EXTENSION
Создадим функцию для подсчета количества строк в таблице, имя которой
передается параметром.
При помощи отладчика мы хотим убедиться, что текст команды SELECT формируется корректно:
1| => CREATE FUNCTION get_count (tabname text) RETURNS bigint
1| AS $$DECLARE
1| cmd text;
1| retval bigint;
1| BEGIN
1| cmd := 'SELECT COUNT(*) FROM '
1| || quote_ident(tabname);
1|
1| EXECUTE cmd INTO retval;
1| RETURN retval;
1| END;
1| $$ LANGUAGE plpgsql STABLE;
1| CREATE FUNCTION
Получим идентификатор сеанса отладки и OID функции:
1| => select pldbg_create_listener() as sessionid,
1| 'get_count'::regproc::oid as funcoid \gset
1| => \echo :sessionid :funcoid
1| 1 115750
Установим точку останова в начале функции (3-й параметр: -1) и ждем вызова функции из любого (4-й параметр: NULL) сеанса:
1| => select * from pldbg_set_global_breakpoint(:sessionid, :funcoid, -1, NULL);
1| pldbg_set_global_breakpoint
1| -----------------------------
1| t
1| (1 row)
1|
1| => select * from pldbg_wait_for_target(:sessionid);
Отладочный сеанс перешел в режим ожидания...
Откроем второй сеанс и вызываем функцию:
2| => select get_count('pg_class');
Выполнение доходит до точки останова и теперь второй сеанс переходит в режим ожидания команд отладчика.
Первый сеанс просыпается и переходит в режим управления отладкой:
1| pldbg_wait_for_target
1| -----------------------
1| 21413
1| (1 row)
В режиме управления можно:
- проверить стек вызова (pldbg_get_stack),
- перемещаться по функциям в стеке (pldbg_select_frame),
- смотреть значения параметров и локальных переменных текущей функции в стеке (pldbg_get_variables),
- изменить значения переменных (pldbg_deposit_value),
- установить/удалить точки останова (pldbg_set_breakpoint/pldbg_drop_breakpoint),
- продолжить выполнение до следующей точки (pldbg_continue),
- выполнять пошаговую отладку с заходом внутрь функций (pldbg_step_into),
- или выполняя строку целиком (pldbg_step_over),
- в конечном итоге, прекратить сеанс отладки (pldbg_abort_target).
1| => select name, varClass, value, pg_catalog.format_type(dtype, NULL) as dtype
1| from pldbg_get_variables(:sessionid)
1| order by varClass;
1| name | varclass | value | dtype
1| ---------+----------+----------+--------
1| tabname | A | pg_class | text
1| cmd | L | NULL | text
1| retval | L | NULL | bigint
1| (3 rows)
Видим, что параметр функции tabname = 'pg_class', а значения переменных cmd и retval еще не установлены.
Добавим еще одну точку останова на команде EXECUTE (8 строка функции):
1| => select * from pldbg_set_breakpoint(:sessionid, :funcoid, 8);
1| pldbg_set_breakpoint
1| ----------------------
1| t
1| (1 row)
Продолжим выполнение до новой точки останова:
1| => select p.targetName,
1| (select string_agg(t.n::text||
1| case when t.n=p.linenumber-1 then '*' else '' end,
1| chr(10)) from generate_series(1, 11) as t(n)) as line,
1| pldbg_get_source(:sessionid, p.func) AS src
1| from pldbg_continue(:sessionid) p;
1| targetname | line | src
1| -----------------+------+-------------------------------------
1| get_count(text) | 1 +| DECLARE +
1| | 2 +| cmd text; +
1| | 3 +| retval bigint; +
1| | 4 +| BEGIN +
1| | 5 +| cmd := 'SELECT COUNT(*) FROM ' +
1| | 6 +| || quote_ident(tabname);+
1| | 7 +| +
1| | 8* +| EXECUTE cmd INTO retval; +
1| | 9 +| RETURN retval; +
1| | 10 +| END; +
1| | 11 |
1| (1 row)
Еще раз посмотрим значения локальных переменных:
1| => select name, varClass, value, pg_catalog.format_type(dtype, NULL) as dtype
1| from pldbg_get_variables(:sessionid)
1| order by varClass;
1| name | varclass | value | dtype
1| ---------+----------+-------------------------------+--------
1| tabname | A | pg_class | text
1| cmd | L | SELECT COUNT(*) FROM pg_class | text
1| retval | L | NULL | bigint
1| (3 rows)
Видим, что переменной cmd присвоен текст команды SELECT.
Выполним следующую строку функции:
1| => select s.targetname, s.linenumber-1 as line
1| from pldbg_step_over(:sessionid) s;
1| targetname | line
1| -----------------+------
1| get_count(text) | 9
1| (1 row)
И еще раз посмотрим значения локальных переменных:
1| => select name, varClass, value, pg_catalog.format_type(dtype, NULL) as dtype
1| from pldbg_get_variables(:sessionid)
1| order by varClass;
1| name | varclass | value | dtype
1| ---------+----------+-------------------------------+--------
1| tabname | A | pg_class | text
1| cmd | L | SELECT COUNT(*) FROM pg_class | text
1| retval | L | 321 | bigint
1| (3 rows)
Команда SELECT выполнена, в переменную retval записан результат.
Прекратим сеанс отладки:
1| => select * from pldbg_abort_target(:sessionid);
1| pldbg_abort_target
1| --------------------
1| t
1| (1 row)
2| ERROR: canceling statement due to user request
2| CONTEXT: PL/pgSQL function get_count(text) line 9 at RETURN
Выполнение функции во втором сеансе завершается ошибкой.
Итоги
Отладчик PL/pgSQL работает, его API можно использовать без pgAdmin.
Осталось дождаться, когда найдется умелец, который возьмет и сделает удобный графический интерфейс, лишенный недостатков, которыми столь "грешит" pgAdmin.
1 комментарий:
https://github.com/dbeaver/dbeaver/wiki/PGDebugger
Отправить комментарий