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
sudo yum install pangolin-plpgsql-check-{version_component}-{OS}.x86_64.rpm
sudo apt install pangolin-plpgsql-check-{version_component}_amd64.deb
sudo apt-get 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);
    

    Результат:

    Примечание
    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.