Анализ PL/SQL кода в Oracle 11g

Анализ PL/SQL кода в Oracle 11g

С момента первого релиза PL/SQL, Oracle предоставляет набор представлений позволяющих разработчику получать информацию о PL/SQL объектах. Эти представления помогают нам понимать и анализировать наш код. Oracle Database 11g предоставляет еще более мощный аналитический инструмент - PL/Scope, собирающий информацию обо всех идентификаторах в вашем PL/SQL коде, которая затем доступна через представления словаря данных. Эти представления могут помочь отследить использование каждой переменной в определении, ссылках, вызовах, а также местоположение каждого вхождения переменной в исходном коде.

Благодаря PL/Scope разработчики могут значительно лучше и легче выполнять анализ кода. Некоторые полезные приемы его использования и будут рассмотрены в данной статье.

Включение PL/Scope

Чтобы использовать PL/Scope, необходимо сначала настроить компилятор на анализ идентификаторов во время компиляции. Вы можете сделать это, изменив значение параметра plscope_settings на уровне сеанса:

Существуют два возможных значения: IDENTIFIERS:ALL и IDENTIFIERS:NONE. По умолчанию - IDENTIFIERS:NONE. Вы можете увидеть значение plscope_settings для каждой программной единицы с помощью запроса к [ALL/USER/DBA]_PLSQL_OBJECT_SETTINGS [прим пер.: также как и другие полезные вещи, включая уровень pl/sql оптимизации и т.д.]

Описание представления USER_PLSQL_OBJECT_SETTINGS:

NAME VARCHAR2(30)Название объекта TYPE VARCHAR2(12)Тип объекта(Type,package,package body,procedure,function. ) PLSQL_OPTIMIZE_LEVEL NUMBERУровень оптимизации(10g: 0-2, 11g: 0-3) PLSQL_CODE_TYPE VARCHAR2(4000)Тип компиляции: Interpreted/Native PLSQL_DEBUG VARCHAR2(4000)Включен ли debug PLSQL_WARNINGS VARCHAR2(4000)Включен ли вывод предупреждений при компиляции.Кстати, "любимый" Кайтовский "when others" без raise обрел свое личное предупреждение-PLW-06009, а если установить параметр в 'ERRORS:ALL', то объект не будет скомпилирован пока не избавитесь от всех предупреждений NLS_LENGTH_SEMANTICS VARCHAR2(4000)Параметр сравнения длины строковых переменных: байты/символы PLSQL_CCFLAGS VARCHAR2(4000)Переменные условной компиляции PLSCOPE_SETTINGS VARCHAR2(4000)IDENTIFIERS:ALL/ IDENTIFIERS:NONE

У этого параметра есть два возможных значения: IDENTIFIERS:ALL и IDENTIFIERS:NONE. По умолчанию - IDENTIFIERS:NONE - сбор идентификаторов отключен.

Представление ALL_IDENTIFIERS

При компиляции объекта с включенным PL/Scope собирается информация обо всех идентификаторах используемых в объекте, которая доступна через представление ALL_IDENTIFIERS.

Описание представления ALL_IDENTIFIERS:

Столбец Значение OWNER Владелец объекта, содержащего идентификатор NAME Имя идентификатора TYPE Тип идентификатора, например: FORALL OUT (out аргумент), CONSTANT, PACKAGE, or RECORD SIGNATURE Уникальная строка для каждого идентификатора сквозная для всех объектов, для однозначного определения из идентификаторов с одинаковыми именами(прим. переводчика: далее сигнатура) OBJECT_NAME Имя объекта OBJECT_TYPE Тип объекта, например: PACKAGE, TRIGGER, или PROCEDURE USAGE Тип использования(объявление или присваивание) USAGE_ID Порядковый номер вхождения идентификатора внутри объекта USAGE_CONTEXT_ID Внешний ключ на родительский USAGE_ID; (например, контекст определения переменной - это имя подпрограммы, в которой эта переменная определена) LINE Порядковый номер строки вхождения переменной COL Порядковый номер символа с начала строки места положения переменной

Таблица 1: Описание ALL_IDENTIFIERS

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

Отслеживание использования переменной с помощью PL/Scope

PL/Scope сохраняет детальную информацию о каждом идентификаторе используемом в вашем коде. Каждая строка в ALL_IDENTIFIERS относится к определенной строке определенного объекта. Тип использования указывается в столбце USAGES:

  • ASSIGNMENT - присваивание - значение переменной в данном месте может измениться, то есть переменная находится либо в левой части оператора присваивания, либо находится в запросе в блоке INTO, либо передается OUT/IN OUT параметром.
  • CALL - вызов функции, процедуры или sql-запроса.
  • DECLARATION - объявление идентификатора.
  • REFERENCE - обращение. Означает, что идентификатор используется без изменения значения, например: вызов исключения, передача идентификатора как IN или IN OUT параметр подпрограммы или списке USING блока EXECUTE IMMEDIATE, и использование идентификатора в определении %TYPE.
  • DEFINITION - определение: говорит компилятору как использовать или использовать объявленный ранее идентификатор. Следующие типы будут иметь строку с DEFINITION в ALL_IDENTIFIERS: FUNCTION, OBJECT, PACKAGE, PROCEDURE, TRIGGER, и EXCEPTION.

Если вы захотите увидеть все объявленные переменные в коде, вы можете выполнить следующий запрос:

Использование usage_id для понимания иерархии идентификаторов

Пакет может содержать несколько подпрограмм, которые также могут содержать один или несколько параметров. Вы можете использовать PL/Scope, чтобы показать эту иерархию. Покажем на примере пакета, представленного в Листинге 1.

Листинг 1: Код пакета plscope_demo

Вы можете выполнить следующий иерархический запрос(Листинг 2), чтобы показать родителя строки в ALL_IDENTIFIERS в столбце usage_context_id.

Листинг 2: Запрос отображения иерархии идентификаторов

Использование Signature для отличия идентификаторов с одинаковыми именами

Всегда можно найти вхождение строки в исходном коде поиском по представлению ALL_SOURCE. Также возможно использовать одно название переменной в нескольких различных элементах в вашем коде. К примеру, вы можете использовать в подпрограмме переменную с таким же названием:

Это очень запутанный, но это, безусловно, валидный код, и будет очень тяжело различить использование различных идентификаторов с этим имени в поиске по ALL_SOURCE.

PL/Scope упростил эту задачу, добавив столбец SIGNATURE в представление ALL_IDENTIFIERS. Каждый идентификатор имеет собственную сигнатуру - 32-байтную строку уникальную как внутри данной программной единицы (далее - юнит), так и вне ее (в отличие от USAGE_ID уникального только внутри своего юнита)

Листинг 3: Идентификаторы с одним именем

Заметьте, что каждая сигнатура появляется в выводе дважды. Для самой процедуры - DEFINITION и DECLARATION. Для переменных, констант, исключений и т.п. обязательно сначала будут строки с DECLARATION и лишь потом строки с такой же сигнатурой, показывающие строки кода в которых эти идентификаторы используются.

С помощью сигнатур можно легко отфильтровать только те строки кода, в которых используется конкретный искомый идентификатор, несмотря на многочисленные идентификаторы с тем же именем. Например, в Листинге 4 запрашиваются все присваивания и обращения к переменной PLSCOPE_DEMO_PROC .

Листинг 4: Запрос всех присваиваний и обращений к переменной PLSCOPE_DEMO_PROC

Давайте теперь рассмотрим для чего еще можно использовать PL/Scope:

  • Проверка соглашений именования объектов и переменных
  • Определение нарушений рекомендаций "Best practices"

Проверка соглашений именования объектов и переменных

С помощью PL/Scope легко и просто различить типы идентификаторов (переменные, константы, параметры и т.д.), поэтому можно также проверять не нарушают ли они правила именования для своего типа.

К примеру, я следую следующим соглашениям при именовании параметров: IN параметры заканчивается постфиксом "_in" OUT параметры - _out IN OUT параметры - _io

Чтобы проверить следует ли юнит данным правилам, я посмотрю в строки с usage = declaration и типом FORMAL IN, FORMAL OUT или FORMAL IN OUT.

Предположим, есть пакет с описанием, представленным в Листинге 5.

Листинг 5: Создание пакета plscope_demo

Выполнив запрос из Листинга 6, мы можем найти нарушения правил именования параметров в данном пакете.

Листинг 6:Поиск нарушений правил именования

Обратите внимание на использование usage_context_id для поиска подпрограммы-"владельца" параметра.

Определение нарушений рекомендаций "Best practices"

В дополнение к предупреждениям при компиляции PL/Scope предлагает отличный способ реализации собственных проверок на нарушения рекомендаций. Ниже два сценария проверки, которые было бы тяжело реализовать простым сканированием исходного кода:

  • Переменные декларированные в спецификации пакета. Это опасно тем, что любой пользователь с правом execute на пакет может читать и менять содержимое этих переменных напрямую.
  • Объявленные, но не используемые в коде исключения. Разработчики могут объявить собственные исключения в блоке и, если они не используются, то от них желательно избавиться.(Это же, кстати, относится и к неиспользуемым переменным)

Чтобы проверить объявленные переменные в спецификации пакета, мы должны их сначала найти. Это довольно просто:

Чтобы проверить объявленные, но не вызываемые исключения, нужно сначала понять какие значения USAGE возможны для исключений. Рассмотрим следующую процедуру:

Посмотрим, что покажет PL/Scope об идентификаторе "e_bad_data":

Из этого можно сделать вывод, что EXCEPTION_INIT отображается как assignment - присваивание именованному исключению кода ошибки, а RAISE и WHEN - как references - использование.

Исходя из этого, мы можем найти все исключения, которые объявлены, но используются в коде:

Листинг 7:Запрос всех объявленных, но неиспользуемых исключений

📎📎📎📎📎📎📎📎📎📎