Перейти к основному содержимому

plpgsql_check. Средство контроля plpgsql

Версия: 2.5.

В исходном дистрибутиве установлено по умолчанию: нет.

Связанные компоненты: plpgsql.

Схема размещения: ext.

Расширение plpgsql_check — это полноценный статический анализатор кода plpgsql для PostgreSQL, предоставляющий набор функций для анализа и профилирования функций и процедур, написанных на языке PL/pgSQL.

Оно выполняет проверку встроенных SQL-запросов, выявляя ошибки, которые обычно не обнаруживаются при создании процедур с помощью команды CREATE FUNCTION/PROCEDURE. Также можно настраивать уровни многих предупреждений и подсказок для различных проверок, а также использовать директиву PRAGMA, чтобы управлять отображением сообщений — скрывать уже известные проблемы или оставлять напоминания для дальнейшего анализа.

Функциональные возможности расширения:

  • проверка соответствия типов и полей таблиц, представлений и других объектов, используемых внутри SQL кода процедур/функций;
  • проверка передачи параметров в функцию/процедуру в соответствии с их объявленными типами;
  • идентификация неиспользуемых переменных и аргументов функций, а также возвращаемые значения, остающиеся неизменными;
  • частичное обнаружение «мертвого кода» (код после команды RETURN) и пропущенных команд RETURN в функции (распространено после обработки исключений, сложной логики);
  • выявление нежелательных скрытых затрат, приводящих к проблемам с производительностью, например, неиспользуемых индексов;
  • возможность собрать список объектов (таблицы, представления) и другие функции, к которым обращается функция, чтобы дать представление об ее зависимостях;
  • возможность проверки инструкций EXECUTE на наличие уязвимости SQL-инъекции.

Помимо статического анализа расширение предоставляет возможность динамического анализа кода с отслеживанием выполнения функций (в том числе триггерных) и процедур на языке pl/pgsql.

Доработка

Не производилась.

Ограничения

Для корректной работы plpgsql_check необходимо наличие расширения plpgsql.

Расширение поддерживается СУБД Pangolin не ниже 5.X.X версии.

Внимание!

Использование расширения в пассивном режиме может влиять на производительность при сборе и хранении в статистике данных, переданных в параметрах функции, поэтому пассивный режим работы расширения не рекомендован к использованию в промышленной среде.

Также решение затрагивает аспекты безопасности хранения данных, так как позволяет перехватывать выполнение plpgsql кода и получать доступ к полному стеку вызовов, включая значения переменных. Поэтому не рекомендуется к использованию в промышленной среде.

Установка

sudo dnf install pangolin-plpgsql-check-{version_component}-{OS}.x86_64.rpm
Подсказка

Пример заполненной команды:

cd distributive

sudo apt-get -y install pangolin-plpgsql-check-2.5-sberlinux8.x86_64.rpm

Для использования расширения на развернутой СУБД необходимо установить его в целевую БД:

CREATE EXTENSION plpgsql_check SCHEMA ext;

Настройка

Расширение может работать в нескольких режимах:

  • активный (статический анализ кода) — расширение никак не настраивается и не работает в фоне. Вся информация о функциях получается путем ручного вызова функций;
  • пассивный — анализ и проверка кода выполняются автоматически перед запуском отлаживаемой функции. Для этого необходимо добавить plpgsql_check в shared_preload_libraries;
  • профилирования нагрузки — расширение собирает статистику о запусках и работе функций, а также выражений в теле функций. Статистика хранится в памяти сеанса, при добавлении библиотеки расширения в shared_preload_libraries используется общая разделяемая память и статистика хранится в общей памяти СУБД;
  • трассировки — каждый вызов любой plpgsql-функции сопровождается подробной информацией о выполняемой в текущий момент строке кода функции.

Расширение может быть добавлено в shared_preload_libraries:

shared_preload_libraries = 'plpgsql_check'

В случае, если расширение используется совместно с расширением pldebugger, библиотека расширения должна быть указана после pldebugger при перечислении в shared_preload_libraries:

shared_preload_libraries = 'plugin_debugger,plpgsql_check'

Конфигурационные параметры расширения

Конфигурационные параметры plpgsql_check

Параметр
Значение по умолчанию
Описание
plpgsql.check_asserts
on
Выполнение проверок, указанных в ASSERT-инструкциях
plpgsql_check.compatibility_warnings
off
Вывод предупреждения о совместимости
plpgsql_check.enable_tracer
off
Включение функциональности трассировщика
plpgsql_check.fatal_errors
on
Остановка работы plpgsql при возникновении ошибки
plpgsql_check.mode
by_function
Выбор режима расширенной проверки:

- disabled для отключения;
- by_function для ручного запуска функций проверки;
- fresh_start для первого запуска проверяемой функции;
- every_start для каждого запуска проверяемой функции
plpgsql_check.profiler
off
Фоновое профилирование функции
plpgsql_check.profiler_max_shared_chunks
15000
Максимальное количество выражений в общей памяти
plpgsql_check.regress_test_mode
off
Изменение формата вывода для регрессионного тестирования
plpgsql_check.show_nonperformance_extra_warnings
off
Вывод дополнительных предупреждений (кроме предупреждений о производительности)
plpgsql_check.show_nonperformance_warnings
off
Вывод всех предупреждений (кроме предупреждений о производительности)
plpgsql_check.show_performance_warnings
off
Вывод предупреждений о производительности
plpgsql_check.trace_assert
off
Отслеживание ASSERT-оператора
plpgsql_check.trace_assert_verbosity
default
Детализация ASSERT-инструкции (terse/default/verbose)
plpgsql_check.tracer
off
Включение функции трассировки
plpgsql_check.tracer_errlevel
notice
Установка уровня сообщения трассировщика (log/notice/info/debug/debug1/debug2/debug3/debug4/debug5)
plpgsql_check.tracer_verbosity
default
Детализация вывода трассировщика (terse/default/verbose)

Функции расширения

Функции расширения (с аргументами и возвращаемыми значениями можно ознакомиться по ссылке https://github.com/okbob/plpgsql_check):

Функции plpgsql_check

Имя функции
Описание
__plpgsql_show_dependency_tb()
Обнаружение зависимостей для функции
plpgsql_check_function()
Расширенная проверка (статический анализ кода) функции с выводом в виде форматированного текста
plpgsql_check_function_tb()
Расширенная проверка (статический анализ кода) функции с выводом в виде таблицы с несколькими столбцами
plpgsql_check_pragma()
Реализация функции pragma. Существует два разных варианта использования: для статического анализа с помощью plpgsql_check, где аргументы считываются из дерева синтаксического анализа и для управления трассировкой кода во время выполнения (аргументы обрабатываются аналогичным для variadic text образом)
plpgsql_check_profiler()
Проверка/установка статуса профайлера
plpgsql_check_tracer()
Проверка/установка статуса трассировщика
plpgsql_coverage_branches()
Расчет показателя покрытия профилировщиком вариантов использования функции
plpgsql_coverage_statements()
Расчет показателя покрытия профилировщиком строк с выражениями в функции
plpgsql_profiler_function_statements_tb()
Отображение собранного профиля функции в виде таблицы
plpgsql_profiler_function_tb()
Отображение собранного профиля функции в виде таблицы
plpgsql_profiler_functions_all()
Отображение собранных профилей всех функций в виде таблицы
plpgsql_profiler_install_fake_queryid_hook()
Отображение собранных профилей всех функций в виде таблицы
plpgsql_profiler_remove_fake_queryid_hook()
Очистка сгенерированного функцией plpgsql_profiler_install_fake_queryid_hook() идентификатора запроса и возврат предыдущего значения
plpgsql_profiler_reset()
Очистка статистики, собранной профайлером и относящейся к переданной в аргументе функции
plpgsql_profiler_reset_all()
Очистка всей статистики, собранной профайлером
plpgsql_show_dependency_tb()
Обнаружение зависимостей для функции

Возможно установить уровни предупреждений через следующие аргументы функций:

Обязательные аргументы функций

Аргумент
значение
Описание
Имя функции
funcoid

имя функции

определение функции
Различные функции в СУБД Pangolin могут быть заданы с помощью OID, по имени или по определению.

Если известен OID или полное определение функции, возможно использовать тип regprocedure в качестве параметра следующим образом: fx()::regprocedure или 16799::regprocedure.

Возможно альтернативное использование только имени, когда имя функции уникально - например fx. Если имя функции не уникально (функция перегружена с разным набором аргументов) или функции с таким именем не существует — это приведет к ошибке

Аргументы функций

Дополнительные аргументы функций

Аргумент
Тип значения
значение по умолчанию
Описание
relid
int
0
OID отношения, связанного с триггерной функцией. Если необходимо проверить какие-либо триггерные функции. Передается таблица, в которой работает триггер
fatal_errors
boolean
true
остановка при первой ошибке (чтобы избежать разрастания отчета из-за обилия ошибок)
other_warnings
boolean
true
отображает предупреждения, такие как разное количество атрибутов в присвоении, переменная перекрывает параметр функции, неиспользуемые переменные, нежелательное приведение и другое
extra_warnings
boolean
true
показывает предупреждения о пропущенной команде RETURN, необъявленных переменных, неиспользуемом коде, неиспользуемых параметрах функции, неизменяемых переменных и другое
performance_warnings
boolean
false
Предупреждения, связанные с производительностью, включая использование типов с модификаторами, явные или неявные преобразования типов, а также неявные приведения в условиях WHERE, которые могут помешать использованию индексов
security_warnings
boolean
false
проверки безопасности, такие как обнаружение уязвимости SQL-инъекции
compatibility_warnings
boolean
false
проверки, связанные с совместимостью, такие как устаревшая явная настройка внутренних имен курсоров в ссылочных курсорах или курсорных переменных
anyelementtype
regtype
int
фактический тип, который будет использоваться при тестировании типа anyelement
anyenumtype
regtype
-
фактический тип, который будет использоваться при тестировании типа anyenum
anyrangetype
regtype
int4range
фактический тип, который будет использоваться при тестировании типа anyrange
anycompatibletype
-
int
фактический тип, который будет использоваться при тестировании типа anycompatible
anycompatiblerangetype
-
int4range
фактический тип, который будет использоваться при тестировании типа anycompatible range
without_warnings
boolean
false
отключает все предупреждения (игнорируются все xxxx_warnings параметры, быстрое отключение)
all_warnings
boolean
false
включает все предупреждения (игнорируются все остальные xxx_warnings параметры, быстрое включение)
newtable
имя таблицы
NULL
имена новых переходных таблиц. Параметр необходим, когда переходные таблицы используются в триггерных функциях
oldtable
имя таблицы
NULL
имена старых переходных таблиц. Параметр необходим, когда переходные таблицы используются в триггерных функциях
use_incomment_options
boolean
true
в случае true, активируются in-comment опции
incomment_options_usage_warning
boolean
false
в случае true предупреждение будет срабатывать когда используются in-comment опции

Управление

Решение не оказывает влияния на кластерную архитектуру СУБД Pangolin, включая потоковую и логическую репликацию, процессы резервного копирования, интеграцию с СРК, а также сохраняет полную обратную совместимость с текущей версией СУБД Pangolin.

Также оно не затрагивает систему мониторинга: не требуется добавление новых метрик или их интеграция с системой WatchDog, кроме того не требуется внедрение новых точек трассировки в системные средства наблюдения. Влияние на другие модули и компоненты отсутствует.

Поскольку решение предоставляет возможность перехвата исполнения функций и доступа к полному стеку выполнения plpgsql кода, необходимо обеспечить аудит вызовов функций из задействованных расширений.

Использование модуля

Настройка и демонстрация отладки и анализа функций PL/pgSQL с помощью расширения plpgsql_check

  1. Добавьте в конфигурационный параметр shared_preload_libraries библиотеки расширений в следующей последовательности:

    shared_preload_libraries = 'plugin_debugger,plpgsql_check'
  2. Для применения параметров из предыдущего действия перезагрузите СУБД.

  3. В демонстрационной БД выполните установку расширений:

    CREATE EXTENSION plpgsql_check SCHEMA ext;
    CREATE EXTENSION pldbgapi SCHEMA ext;
  4. При отсутствии установите на клиент pgAdmin4.

  5. Создайте в демонстрационной БД функции:

    CREATE OR REPLACE FUNCTION test_raise (test_int integer) RETURNS int
    AS $$
    DECLARE
    res int;
    i int;
    BEGIN
    res := 1 / test_int;
    for i in 1..4
    LOOP
    RAISE NOTICE 'test %', test_int;
    END LOOP;
    RETURN 1;
    END;
    $$ LANGUAGE plpgsql;

    CREATE OR REPLACE FUNCTION get_count (tabname text, div integer) RETURNS bigint
    AS $$ DECLARE
    cmd text;
    test_result int;
    retval bigint;
    aux int := plpgsql_check_pragma('enable:extra_warnings');
    BEGIN
    cmd := 'SELECT COUNT(*) FROM '
    || quote_ident(tabname);
    EXECUTE cmd INTO retval;
    select test_raise(retval::int) into test_result;
    select test_raise(div) into test_result;
    RETURN retval;
    END;
    $$ LANGUAGE plpgsql STABLE;

Выполнение расширенной проверки функций в «активном» режиме

  1. В psql выполните функцию:

    select * from plpgsql_check_function_tb('get_count(text, int)');

    Результат:

    functionid    | lineno | statement | sqlstate |              message              | detail | hint |     level     | position | query | context
    ------------------+--------+-----------+----------+-----------------------------------+--------+------+---------------+----------+-------+---------
    public.get_count | 3 | DECLARE | 00000 | never read variable "test_result" | | | warning extra | | |
    (1 row)
  2. Измените функцию get_count, исправив в ней ошибку:

    CREATE OR REPLACE FUNCTION get_count (tabname text, div integer) RETURNS bigint
    AS $$ DECLARE
    cmd text;
    test_result int;
    retval bigint;
    aux int := plpgsql_check_pragma('enable:extra_warnings');
    BEGIN
    cmd := 'SELECT COUNT(*) FROM '
    || quote_ident(tabname);
    EXECUTE cmd INTO retval;
    select test_raise(retval::int) into test_result;
    RAISE NOTICE 'test_result is %', test_result;
    select test_raise(div) into test_result;
    RETURN retval;
    END;
    $$ LANGUAGE plpgsql STABLE;
  3. Выполните:

    select * from plpgsql_check_function_tb('get_count(text, int)');

    Результат:

    functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context
    ------------+--------+-----------+----------+---------+--------+------+-------+----------+-------+---------
    (0 rows)

Выполнение расширенной проверки функций в «пассивном» режиме

  1. Добавьте в postgresql.conf следующие параметры и выполните reload:

    plpgsql_check.show_nonperformance_warnings = true
    plpgsql_check.show_performance_warnings = true
  2. Добавьте в код функции директиву PRAGMA:

    CREATE OR REPLACE FUNCTION get_count (tabname text, div integer) RETURNS bigint
    AS $$ DECLARE
    cmd text;
    test_result int;
    retval bigint;
    aux int := plpgsql_check_pragma('enable:extra_warnings');
    BEGIN
    PERFORM 'PRAGMA:echo:str @@id @@signature';
    cmd := 'SELECT COUNT(*) FROM '
    || quote_ident(tabname);
    EXECUTE cmd INTO retval;
    select test_raise(retval::int) into test_result;
    select test_raise(div) into test_result;
    RETURN retval;
    END;
    $$ LANGUAGE plpgsql STABLE;
  3. Убедитесь, что код не анализируется в активном режиме:

    select * from get_count('pg_class', 2);

    Результат:

    NOTICE:  test 584
    NOTICE: test 584
    NOTICE: test 584
    NOTICE: test 584
    NOTICE: test_result is 1
    NOTICE: test 2
    NOTICE: test 2
    NOTICE: test 2
    NOTICE: test 2
    get_count
    -----------
    584
    (1 row)
  4. Добавьте в postgresql.conf следующий параметр и выполните reload:

    plpgsql_check.show_nonperformance_warnings = 'every_start'
  5. Выполните функцию:

    select * from get_count('pg_class', 2);

    Результат:

    WARNING:  unused variable "i"
    WARNING: routine is marked as VOLATILE, should be IMMUTABLE
    HINT: When you fix this issue, please, recheck other functions that uses this function.
    NOTICE: test 584
    NOTICE: test 584
    NOTICE: test 584
    NOTICE: test 584
    NOTICE: test_result is 1
    WARNING: unused variable "i"
    WARNING: routine is marked as VOLATILE, should be IMMUTABLE
    HINT: When you fix this issue, please, recheck other functions that uses this function.
    NOTICE: test 2
    NOTICE: test 2
    NOTICE: test 2
    NOTICE: test 2
    get_count
    -----------
    584
    (1 row)
  6. Верните параметр plpgsql_check.show_nonperformance_warnings в значение by_function и выполните reload.

Сбор статистики использования функций

  1. Выполните функцию:

    select * from plpgsql_profiler_functions_all();

    Результат: сохраненные профили отсутствуют:

    funcoid | exec_count | exec_stmts_err | total_time | avg_time | stddev_time | min_time | max_time
    --------+------------+----------------+------------+----------+-------------+----------+----------
    (0 rows)
  2. Выполните функцию:

    select * from plpgsql_profiler_function_tb('get_count(text)');

    Результат: счетчики пустые:

    lineno | stmt_lineno | queryids | cmds_on_row | exec_stmts | exec_stmts_err | total_time | avg_time | max_time | processed_rows |                             source

    --------+-------------+----------+-------------+------------+----------------+------------+----------+----------+----------------+-----------------------------------------------------------------
    1 | | | | | | | | | | DECLARE
    2 | | | | | | | | | | cmd text;
    3 | | | | | | | | | | test_result int;
    4 | | | | | | | | | | retval bigint;
    5 | | | | | | | | | | aux int := plpgsql_check_pragma('enable:extra_warnings');
    6 | | | | | | | | | | BEGIN
    7 | | | | | | | | | | cmd := 'SELECT COUNT(*) FROM '
    8 | | | | | | | | | | || quote_ident(tabname);
    9 | | | | | | | | | | EXECUTE cmd INTO retval;
    10 | | | | | | | | | | select test_raise(retval::int) intotest_result;
    11 | | | | | | | | | | RAISE NOTICE 'test_result is %', test_result;
    12 | | | | | | | | | | select test_raise(div) into test_result;
    13 | | | | | | | | | | RETURN retval;
    14 | | | | | | | | | | END;
    15 | | | | | | | | | |
    (15 rows)
  3. Выполните функцию:

    select * from plpgsql_profiler_function_statements_tb('get_count(text, int)');

    Результат: счетчики пустые:

    stmtid | parent_stmtid | parent_note | block_num | lineno | queryid | exec_stmts | exec_stmts_err | total_time | avg_time | max_time | processed_rows |    stmtname
    -------+---------------+-------------+-----------+--------+---------+------------+----------------+------------+----------+----------+----------------+-----------------
    1 | | | 1 | 6 | | 0 | 0 | 0 | | 0 | 0 | statement block
    2 | 1 | body | 1 | 7 | | 0 | 0 | 0 | | 0 | 0 | assignment
    3 | 1 | body | 2 | 9 | | 0 | 0 | 0 | | 0 | 0 | EXECUTE
    4 | 1 | body | 3 | 10 | | 0 | 0 | 0 | | 0 | 0 | SQL statement
    5 | 1 | body | 4 | 11 | | 0 | 0 | 0 | | 0 | 0 | RAISE
    6 | 1 | body | 5 | 12 | | 0 | 0 | 0 | | 0 | 0 | SQL statement
    7 | 1 | body | 6 | 13 | | 0 | 0 | 0 | | 0 | 0 | RETURN
    (7 rows)
  4. Включите профилирование выполнения функций:

    select plpgsql_check_profiler(true);

    Результат:

    NOTICE:  profiler is active
    plpgsql_check_profiler
    ------------------------
    t
    (1 row)
  5. Выполните функцию:

    select * from get_count('pg_class', 2);

    Результат:

    NOTICE:  test 584
    NOTICE: test 584
    NOTICE: test 584
    NOTICE: test 584
    NOTICE: test_result is 1
    NOTICE: test 2
    NOTICE: test 2
    NOTICE: test 2
    NOTICE: test 2
    get_count
    -----------
    584
    (1 row)
  6. Выполните функцию:

    select * from plpgsql_profiler_functions_all();

    Результат:

    funcoid                 | exec_count | exec_stmts_err | total_time | avg_time | stddev_time | min_time | max_time
    ------------------------+------------+----------------+------------+----------+-------------+----------+----------
    get_count(text,integer) | 1 | 0 | 2.171 | 2.171 | 0 | 2.171 | 2.171
    test_raise(integer) | 2 | 0 | 0.366 | 0.183 | 0.13 | 0.053 | 0.313
    (2 rows)
  7. Выполните функцию:

    select * from plpgsql_profiler_function_tb('get_count(text, int)');

    Результат:

    lineno | stmt_lineno | queryids | cmds_on_row | exec_stmts | exec_stmts_err | total_time | avg_time | max_time | processed_rows |                             source
    -------+-------------+----------+-------------+------------+----------------+------------+----------+----------+----------------+-----------------------------------------------------------------
    1 | | | | | | | | | | DECLARE
    2 | | | | | | | | | | cmd text;
    3 | | | | | | | | | | test_result int;
    4 | | | | | | | | | | retval bigint;
    5 | | | | | | | | | | aux int := plpgsql_check_pragma('enable:extra_warnings');
    6 | 6 | | 1 | 1 | 0 | 0.187 | 0.187 | {2.119} | {0} | BEGIN
    7 | 7 | | 1 | 1 | 0 | 0.193 | 0.193 | {0.193} | {0} | cmd := 'SELECT COUNT(*) FROM '
    8 | | | | | | | | | | || quote_ident(tabname);
    9 | 9 | | 1 | 1 | 0 | 1.026 | 1.026 | {1.026} | {0} | EXECUTE cmd INTO retval;
    10 | 10 | | 1 | 1 | 0 | 0.578 | 0.578 | {0.578} | {0} | select test_raise(retval::int) into test_result;
    11 | 11 | | 1 | 1 | 0 | 0.032 | 0.032 | {0.032} | {0} | RAISE NOTICE 'test_result is %', test_result;
    12 | 12 | | 1 | 1 | 0 | 0.103 | 0.103 | {0.103} | {0} | select test_raise(div) into test_result;
    13 | 13 | | 1 | 1 | 0 | 0 | 0 | {0} | {0} | RETURN retval;
    14 | | | | | | | | | | END;
    15 | | | | | | | | | |
    (15 rows)
  8. Выполните функцию:

    select * from plpgsql_profiler_function_statements_tb('get_count(text, int)');

    Результат:

    stmtid | parent_stmtid | parent_note | block_num | lineno | queryid | exec_stmts | exec_stmts_err | total_time | avg_time | max_time | processed_rows |    stmtname
    -------+---------------+-------------+-----------+--------+---------+------------+----------------+------------+----------+----------+----------------+-----------------
    1 | | | 1 | 6 | | 1 | 0 | 0.187 | 0.187 | 2.119 | 0 | statement block
    2 | 1 | body | 1 | 7 | | 1 | 0 | 0.193 | 0.193 | 0.193 | 0 | assignment
    3 | 1 | body | 2 | 9 | | 1 | 0 | 1.026 | 1.026 | 1.026 | 0 | EXECUTE
    4 | 1 | body | 3 | 10 | | 1 | 0 | 0.578 | 0.578 | 0.578 | 0 | SQL statement
    5 | 1 | body | 4 | 11 | | 1 | 0 | 0.032 | 0.032 | 0.032 | 0 | RAISE
    6 | 1 | body | 5 | 12 | | 1 | 0 | 0.103 | 0.103 | 0.103 | 0 | SQL statement
    7 | 1 | body | 6 | 13 | | 1 | 0 | 0 | 0 | 0 | 0 | RETURN
    (7 rows)
  9. Проверьте покрытие собранной статистики:

    select * from plpgsql_coverage_statements('get_count(text, int)');

    Результат:

    plpgsql_coverage_statements
    -----------------------------
    1
    (1 row)

    Проверьте покрытие ветвлений в функции:

    select * from plpgsql_coverage_branches('get_count(text, int)');

    Результат:

    plpgsql_coverage_branches
    ---------------------------
    1
    (1 row)
  10. Сбросьте статистику:

select plpgsql_profiler_reset('get_count(text, int)');

Результат:

plpgsql_profiler_reset
------------------------

(1 row)
  1. Сбросьте остальную статистику:
select plpgsql_profiler_reset_all();

Результат:

plpgsql_profiler_reset_all
----------------------------

(1 row)
  1. Выключите профилирование:
select plpgsql_check_profiler(false);

Результат:

NOTICE:  profiler is not active
plpgsql_check_profiler
------------------------
f
(1 row)

Трассировка выполнения функций

  1. Добавьте в postgresql.conf опцию enable_tracer и перезагрузите СУБД:

    plpgsql_check.enable_tracer = on
  2. Включите трассировку:

    select plpgsql_check_tracer(true);

    Результат:

    NOTICE:  tracer is active
    NOTICE: tracer verbosity is default
    plpgsql_check_tracer
    ----------------------
    t
    (1 row)
  3. Выполните функцию:

    select * from get_count('pg_class', 2);

    Результат:

    NOTICE:  #0   ->> start of function get_count(text,integer) (oid=44282)
    NOTICE: #0 "tabname" => 'pg_class', "div" => '2'
    NOTICE: #2 ->> start of function test_raise(integer) (oid=44281)
    NOTICE: #2 context: SQL statement "select test_raise(retval::int)"
    NOTICE: #2 "test_int" => '584'
    NOTICE: test 584
    NOTICE: test 584
    NOTICE: test 584
    NOTICE: test 584
    NOTICE: #2 <<- end of function test_raise (elapsed time=0.141 ms)
    NOTICE: test_result is 1
    NOTICE: #2 ->> start of function test_raise(integer) (oid=44281)
    NOTICE: #2 context: SQL statement "select test_raise(div)"
    NOTICE: #2 "test_int" => '2'
    NOTICE: test 2
    NOTICE: test 2
    NOTICE: test 2
    NOTICE: test 2
    NOTICE: #2 <<- end of function test_raise (elapsed time=0.099 ms)
    NOTICE: #0 <<- end of function get_count (elapsed time=0.899 ms)
    get_count
    -----------
    584
    (1 row)
  4. Измените подробности трассировки:

    set plpgsql_check.tracer_verbosity TO verbose;
  5. Выполните функцию:

    select * from get_count('pg_class', 2);

    Результат:

    Details
    NOTICE:  #0            ->> start of function get_count(text,integer) (oid=44282)
    NOTICE: #0 "tabname" => 'pg_class', "div" => '2'
    NOTICE: #0.1 6 --> start of statement block
    NOTICE: #0.2 7 --> start of assignment 'SELECT COUNT(*) FROM '
    ..
    NOTICE: #0.2 "tabname" => 'pg_class'
    NOTICE: #0.1 <-- end of assignment (elapsed time=0.029 ms)
    NOTICE: #0.2 "cmd" => 'SELECT COUNT(*) FROM pg_class'
    NOTICE: #0.3 9 --> start of EXECUTE
    NOTICE: #0.2 <-- end of EXECUTE (elapsed time=0.359 ms)
    NOTICE: #0.4 10 --> start of SQL statement (query='select test_raise(retval::int)')
    NOTICE: #0.4 "retval" => '584'
    NOTICE: #2 ->> start of function test_raise(integer) (oid=44281)
    NOTICE: #2 context: SQL statement "select test_raise(retval::int)"
    NOTICE: #2 "test_int" => '584'
    NOTICE: #2.1 5 --> start of statement block
    NOTICE: #2.2 6 --> start of assignment 1 / test_int
    NOTICE: #2.2 "test_int" => '584'
    NOTICE: #2.1 <-- end of assignment (elapsed time=0.023 ms)
    NOTICE: #2.2 "res" => '0'
    NOTICE: #2.3 7 --> start of FOR with integer loop variable
    NOTICE: #2.4 9 --> start of RAISE
    NOTICE: test 584
    NOTICE: #2.3 <-- end of RAISE (elapsed time=0.021 ms)
    NOTICE: #2.4 9 --> start of RAISE
    NOTICE: test 584
    NOTICE: #2.3 <-- end of RAISE (elapsed time=0.017 ms)
    NOTICE: #2.4 9 --> start of RAISE
    NOTICE: test 584
    NOTICE: #2.3 <-- end of RAISE (elapsed time=0.016 ms)
    NOTICE: #2.4 9 --> start of RAISE
    NOTICE: test 584
    NOTICE: #2.3 <-- end of RAISE (elapsed time=0.017 ms)
    NOTICE: #2.2 <-- end of FOR with integer loop variable (elapsed time=0.122 ms)
    NOTICE: #2.5 11 --> start of RETURN (expr='1')
    NOTICE: #2.4 <-- end of RETURN (elapsed time=0.011 ms)
    NOTICE: #2.5 <-- end of statement block (elapsed time=0.208 ms)
    NOTICE: #2 <<- end of function test_raise (elapsed time=0.261 ms)
    NOTICE: #0.3 <-- end of SQL statement (elapsed time=0.335 ms)
    NOTICE: #0.5 11 --> start of RAISE
    NOTICE: test_result is 1
    NOTICE: #0.4 <-- end of RAISE (elapsed time=0.015 ms)
    NOTICE: #0.6 12 --> start of SQL statement (query='select test_raise(div)')
    NOTICE: #0.6 "div" => '2'
    NOTICE: #2 ->> start of function test_raise(integer) (oid=44281)
    NOTICE: #2 context: SQL statement "select test_raise(div)"
    NOTICE: #2 "test_int" => '2'
    NOTICE: #2.1 5 --> start of statement block
    NOTICE: #2.2 6 --> start of assignment 1 / test_int
    NOTICE: #2.2 "test_int" => '2'
    NOTICE: #2.1 <-- end of assignment (elapsed time=0.018 ms)
    NOTICE: #2.2 "res" => '0'
    NOTICE: #2.3 7 --> start of FOR with integer loop variable
    NOTICE: #2.4 9 --> start of RAISE
    NOTICE: test 2
    NOTICE: #2.3 <-- end of RAISE (elapsed time=0.020 ms)
    NOTICE: #2.4 9 --> start of RAISE
    NOTICE: test 2
    NOTICE: #2.3 <-- end of RAISE (elapsed time=0.015 ms)
    NOTICE: #2.4 9 --> start of RAISE
    NOTICE: test 2
    NOTICE: #2.3 <-- end of RAISE (elapsed time=0.019 ms)
    NOTICE: #2.4 9 --> start of RAISE
    NOTICE: test 2
    NOTICE: #2.3 <-- end of RAISE (elapsed time=0.024 ms)
    NOTICE: #2.2 <-- end of FOR with integer loop variable (elapsed time=0.131 ms)
    NOTICE: #2.5 11 --> start of RETURN (expr='1')
    NOTICE: #2.4 <-- end of RETURN (elapsed time=0.014 ms)
    NOTICE: #2.5 <-- end of statement block (elapsed time=0.240 ms)
    NOTICE: #2 <<- end of function test_raise (elapsed time=0.290 ms)
    NOTICE: #0.5 <-- end of SQL statement (elapsed time=0.348 ms)
    NOTICE: #0.7 13 --> start of RETURN
    NOTICE: #0.7 "retval" => '584'
    NOTICE: #0.6 <-- end of RETURN (elapsed time=0.016 ms)
    NOTICE: #0.7 <-- end of statement block (elapsed time=1.224 ms)
    NOTICE: #0 <<- end of function get_count (elapsed time=1.324 ms)
    get_count
    -----------
    584
    (1 row)
  6. Выключите трассировку:

    select plpgsql_check_tracer(false);

    Результат:

    NOTICE:  tracer is not active
    NOTICE: tracer verbosity is verbose
    plpgsql_check_tracer
    ----------------------
    f
    (1 row)
  7. Выполните функцию:

    select * from get_count('pg_class', 2);

    Результат:

    NOTICE:  test 584
    NOTICE: test 584
    NOTICE: test 584
    NOTICE: test 584
    NOTICE: test_result is 1
    NOTICE: test 2
    NOTICE: test 2
    NOTICE: test 2
    NOTICE: test 2
    get_count
    -----------
    584
    (1 row)

Отключение функциональности

Для отключения данной функциональности необходимо:

  1. Удалить расширения командой:
DROP EXTENSION plpgsql_check;
  1. Убрать библиотеку расширения plpgsql_check из shared_preload_libraries.
  2. Перезагрузить сервер СУБД:
pg_ctl restart

Ссылки на документацию разработчика

plpgsql_check: https://github.com/okbob/plpgsql_check.