Условная компиляция Oracle и программирование на PL/SQL

Условная компиляция Oracle и программирование на PL/SQL

Механизм условной компиляции, появившийся в Oracle Database 10g Release 2, позволяет организовать избирательную компиляцию частей программы в зависимости от условий, заданных в директиве $IF.



Например, условная компиляция будет очень полезна, если вам потребуется:

  •  Написать программу, которая работает в разных версиях Oracle и использует специфические возможности этих версий. А конкретнее — если вы хотите использовать новые возможности Oracle там, где они доступны, но при этом ваша программа также должна компилироваться и работать в старых версиях. Без условной компиляции вам придется вести несколько разных файлов или использовать сложную логику подстановки переменных в SQL*Plus.
  •  Выполнять некоторый код во время тестирования и отладки, но пропускать его в окончательной версии. До появления условной компиляции вам пришлось бы либо закрывать нежелательные строки комментариями, либо добавлять лишние проверки в логику приложения — даже в окончательную версию.
  •  Устанавливать/компилировать разные элементы приложения в зависимости от требований (например, набора компонентов, на которые у пользователя имеется лицензия). Условная компиляция очень сильно упрощает ведение сложной кодовой базы. Условная компиляция реализуется посредством включения директив компилятора в исходный код. При компиляции программы препроцессор PL/SQL обрабатывает директивы и выбирает те части кода, которые требуется откомпилировать. «Урезанный» исходный код передается компилятору для дальнейшей обработки.

Директивы делятся на три типа:

  •  Директивы выбора — директива $IF проверяет выражение и определяет, какой код следует включить в обработку (или исключить из нее).
  •  Директивы получения информации — синтаксис $$identifier используется для обращения к флагам условной компиляции. К директивам получения информации можно обращаться в директиве $IF или использовать их независимо в коде.
  •  Директивы ошибок — директива $ERROR используется для вывода ошибок компиляции на основании условий, вычисленных при подготовке кода препроцессором. Сначала я приведу несколько примеров, а затем мы более подробно рассмотрим каждую директиву. Вы также научитесь пользоваться двумя пакетами, связанными с условной компиляцией: DBMS_DB_VERSION и DBMS_PREPROCESSOR.

 

Примеры условной компиляции

Начнем с примеров использования разных видов условной компиляции.

Использование констант пакета приложения в директиве $IF

Директива $IF может обращаться к константам, определенным в ваших пакетах. В следующем примере начисляемая премия изменяется в зависимости от того, соответствует ли место установки стороннего приложения положениям закона Сарбейнза-Оксли. Скорее всего, эта настройка будет оставаться неизменной в течение долгого времени. Если я положусь на традиционную условную команду, в приложении останется ветвь логики, которая никогда не будет применяться. С условной компиляцией лишний код удаляется перед компиляцией:


PROCEDURE apply_bonus (
   id_in IN employee.employee_id%TYPE
  ,bonus_in IN employee.bonus%TYPE)
IS
BEGIN
   UPDATE employee
      SET bonus =
       $IF employee_rp.apply_sarbanes_oxley
       $THEN
          LEAST (bonus_in, 10000)
       $ELSE
          bonus_in
       $END
     WHERE employee_id = id_in;
   NULL;
END apply_bonus;

 

Переключение состояния трассировки с использованием флагов условной компиляции

Теперь я могу создать собственные механизмы отладки/трассировки и выполнить их условную компиляцию в своем коде. Это означает, что в окончательной версии кода этот код будет полностью удален, чтобы избежать непроизводительных затрат на выполнение этой логики. Следует отметить, что специальный параметр компилятора PLSQL_CCFLAGS может задавать как логические значения, так и значения PLS_INTEGER:


ALTER SESSION SET PLSQL_CCFLAGS = 'oe_debug:true, oe_trace_level:10';

PROCEDURE calculate_totals
IS
BEGIN
$IF $$oe_debug AND $$oe_trace_level >= 5
$THEN
   DBMS_OUTPUT.PUT_LINE ('Tracing at level 5 or higher');
$END
   NULL;
END calculate_totals;

 

Директива получения информации

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

Синтаксис директивы получения информации выглядит так:

$$идентификатор — здесь идентификатор представляет собой любой идентификатор PL/SQL, который может представлять:

  •  Параметры среды компиляции — значения из представления словаря данных USER_PLSQL_OBJECT_SETTINGS.
  •  Пользовательская директива — определяется командой ALTER...SET PLSQL_CCFLAGS (см. далее «Использование параметра PLSQL_CCFLAGS»).
  •  Предопределенные директивы$$PLSQL_LINE и $$PLSQL_UNIT; возвращают номер строки и имя программы.

Директивы получения информации предназначены для использования в конструкциях условной компиляции, но они также могут использоваться в других местах кода PL/ SQL. Например, для вывода текущего номера строки в моей программе я могу использовать следующий код:

DBMS_OUTPUT.PUT_LINE ($$PLSQL_LINE);

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

ALTER SESSION SET PLSQL_CCFLAGS = 'max_years:100';

PROCEDURE work_with_data (num_years_in IN PLS_INTEGER)
IS
BEGIN
   IF num_years_in > $$max_years THEN ...
END  work_with_data;

Что еще важнее, директивы получения информации могут использоваться в тех местах, где переменные запрещены. Пара примеров:

DECLARE
   l_big_string VARCHAR2($$MAX_VARCHAR2_SIZE);

   l_default_app_err EXCEPTION;
   PRAGMA EXCEPTION_INIT (l_default_app_err, $$DEF_APP_ERR_CODE);
BEGIN

Встроенный пакет DBMS_DB_VERSION предоставляет набор констант для получения абсолютной и относительной информации о версии установленной базы данных. Константы, определенные в версии 12.1, приведены в табл. 1. В каждой новой версии Oracle добавляются две новые относительные константы, а значения, возвращаемые константами VERSION и RELEASE, обновляются.

Таблица 1. Константы DBMS_DB_VERSION

Имя пакетной константы Описание Значение в Oracle Database 12c Release 1
DBMS_DB_VERSION.VERSION Номер версии базы данных 12
DBMS_DB_VERSION.RELEASE Номер выпуска базы данных 1
DBMS_DB_VERSION.VER_LE_9 TRUE, если текущая версия меньше
либо равна Oracle9i
FALSE
DBMS_DB_VERSION.VER_LE_9_1 TRUE, если текущая версия меньше
либо равна 9.1
FALSE
DBMS_DB_VERSION.VER_LE_9_2 TRUE, если текущая версия меньше
либо равна 9.2
FALSE
DBMS_DB_VERSION.VER_LE_10 TRUE, если текущая версия меньше
либо равна Oracle10g
FALSE
DBMS_DB_VERSION.VER_LE_10_1 TRUE, если текущая версия меньше
либо равна 10.1
FALSE
DBMS_DB_VERSION.VER_LE_10_2 TRUE, если текущая версия меньше
либо равна 10.2
FALSE
DBMS_DB_VERSION.VER_LE_11_1 TRUE, если текущая версия меньше
либо равна 11.1
FALSE
DBMS_DB_VERSION.VER_LE_11_2 TRUE, если текущая версия меньше
либо равна 11.2
FALSE
DBMS_DB_VERSION.VER_LE_12 TRUE, если текущая версия меньше
либо равна 12.1
TRUE
DBMS_DB_VERSION.VER_LE_12_1 TRUE, если текущая версия меньше
либо равна 12.1
TRUE

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

Интересно, что вы можете писать выражения со ссылками на еще не определенные константы пакета DBMS_DB_VERSI0N. Если они не будут обрабатываться, как в следующем примере, это не приведет к ошибкам. Пример:

$IF DBMS_DB_VERSION.VER_LE_12_1
$THEN
   Используется этот код.
$ELSIF DBMS_DB_VERSION.VER_LE_13
   Заглушка на будущее.
$END

 

Определение параметров среды компиляции

Следующая информация (соответствующая значениям из представления словаря данных USER_PLSQL_OBJECT_SETTlNGS) доступна в директивах получения информации:

  •  $$PLSQL_DEBUG — режим отладки для единицы компиляции.
  •  $$PLSQL_OPTIMIZE_LEVEL — уровень оптимизации для единицы компиляции.
  •  $$PLSQL_CODE_TYPE — режим компиляции для единицы компиляции.
  •  $$PLSQL_WARNINGS — режим предупреждений компиляции для единицы компиляции.
  •  $$NLS_LENGTH_SEMANTICS — значение, заданное для семантики длины NLS.

Примеры использования всех перечисленных параметров представлены в файле cc_plsql_parameters.sql на сайте книги.

 

имя и номер строки единицы компиляции

Oracle неявно определяет четыре очень полезные директивы получения информации, предназначенные для использования в директивах $IF и $ERROR:

  •  $$PLSQL_UNIT — имя единицы компиляции, в которой находится ссылка. Если этой единицей является анонимный блок, то $$PLSQL_UNIT содержит NULL.
  •  $$PLSQL_LINE — номер строки в единице компиляции, в которой находится ссылка.
  •  $$PLSQL_UNIT_0WNER (Oracle Database 12c) — имя владельца текущей программной единицы PL/SQL. Если этой единицей является анонимный блок, то $$PLSQL_UNIT_ OWNER содержит NULL.
  •  $$PLSQL_UNIT_TYPE (Oracle Database 12c) — тип текущей программной единицы PL/ SQL: ANONYMOUS BLOCK, FUNCTION, PACKAGE, PACKAGE BODY, PROCEDURE, TRIGGER, TYPE или TYPE BODY. В анонимных блоках или триггерах, не являющихся триггерами DML, $$plsql_unit_type имеет значение ANONYMOUS BLOCK.

Для получения текущих номеров строк можно использовать встроенные функции DBMS_UTILITY.FORMAT_CALL_STACK и DBMS_UTILITY.FORMAT_ERROR_BACKTRACE, но тогда вам придется разбирать эти строки для получения имени строки и имени программной единицы. Директивы получения информации позволяют проще получить нужные сведения. Пример:

BEGIN
   IF l_balance < 10000
   THEN
      raise_error (
         err_name => 'BALANCE TOO LOW'
        ,failed_in => $$plsql_unit
        ,failed_on => $$plsql_line
      );
   END IF;
   ...
END;

Использование последних двух директив продемонстрировано в файле cc_line_unit.sql на сайте книги.

Обратите внимание: если директива $$PLSQL_UNIT находится внутри пакета, то она воз­вращает имя пакета, а не имя отдельной процедуры или функции в пакете.

 

Использование параметра PLSQL_CCFLAGS

Oracle также предоставляет параметр PLSQL_CCFLAGS, который может использоваться с условной компиляцией. По сути он позволяет определять пары «имя/значение», причем имя в дальнейшем может использоваться в директивах получения информации в логике условной компиляции. Пример:

ALTER SESSION SET PLSQL_CCFLAGS = 'use_debug:TRUE, trace_level:10';

Имя флага представляет собой любой допустимый идентификатор PL/SQL, включая зарезервированные и ключевые слова (перед идентификатором ставится префикс $$, исключающий путаницу с обычным кодом PL/SQL). Имени может присваиваться один из следующих литералов: TRUE, FALSE, NULL или PLS_INTEGER.

Значение PLSQL_CCFLAGS будет ассоциироваться с каждой программой, компилируемой в этом сеансе. Если вы хотите сохранить свои настройки в программе, то будущие компиляции командой alter...compile должны включать секцию REUSE SETTINGS.

Так как вы можете изменить значение параметра, а затем откомпилировать выбранные программные единицы, это позволяет легко определять разные наборы директив получения информации для разных программ.

Обратите внимание: директива позволяет ссылаться на флаги, не определенные в PLSQL_ CCFLAGS; результат будет равен NULL. Если включить предупреждения компилятора, при ссылке на неопределенный флаг база данных вернет предупреждение PLW-06003.

 

Директива $IF

Конструкция выбора, реализованная в виде директивы $IF, используется для передачи шага условной компиляции препроцессору. Общий синтаксис этой директивы:

$IF Boolean-expression
$THEN
   code_fragment
[$ELSIF Boolean-expression
$THEN
   code_fragment]
[$ELSE
   code_fragment]
$END

Здесь логическое выражение представляет собой статическое выражение (которое мо­жет вычисляться на момент компиляции), результатом которого является TRUE, FALSE или NULL. Фрагмент кода содержит произвольную последовательность команд PL/ SQL, которая передается компилятору для обработки (в соответствии с правилами вычисления выражений).

Статические выражения могут строиться из следующих элементов:

  •  Логические значения, PLS_INTEGER и NULL, а также комбинации этих литералов.
  •  Статические выражения с логическими значениями, PLS_INTEGER и VARCHAR2.
  •  Директивы получения информации (то есть идентификаторы с префиксом $$). Такие директивы могут предоставляться Oracle (например, $$PLSQL_0PTIMIZE_LEVEL; полный список приведен в статье «Оптимизирующий компилятор») или задаваться параметром компиляции PLSQL_CCFLAGS.
  •  Статические константы, определенные в пакете PL/SQL.
  •  Большинство операторов сравнения (>, <, = и <> допустимы, но использовать выражение IN нельзя), логические операции — такие, как AND и OR, конкатенации статических символьных выражений и проверки на NULL.

Статическое выражение не может содержать вызовы процедур или функций, требующих выполнения; они не могут вычисляться во время компиляции, а следовательно, сделают недействительным выражение в директиве $IF. Компилятор в таком случае выдает сообщение об ошибке (PLS-00174).

Несколько примеров статических выражений в директивах $IF:

  •  Если пользовательская директива получения информации, управляющая отладкой, отлична от NULL, инициализировать подсистему отладки:
$IF $$app_debug_level IS NOT NULL $THEN
   debug_pkg.initialize;
$END
  •  Проверка значения пользовательской пакетной константы и уровня оптимизации:
$IF $$PLSQL_OPTIMIZE_LEVEL = 2 AND appdef_pkg.long_compilation
$THEN
   $ERROR 'Do not use optimization level 2 for this program!' $END
$END

Строковые литералы и конкатенация строк разрешены только в директиве $ERROR; в директиве $IF их присутствие недопустимо.

 

Директива $ERROR

Директива $ERROR инициирует сбой в текущей единице компиляции и возвращает ука­занное сообщение об ошибке. Синтаксис директивы:

$ERROR VARCHAR2_expression $END

Предположим, я хочу задать для некоторой программной единицы уровень оптимизации 1, чтобы ускорить компиляцию. В следующем примере директива $$ используется для проверки уровня оптимизации, предоставляемого средой компиляции. Далее при необходимости программа инициирует ошибку директивой $ERROR:


SQL> CREATE OR REPLACE PROCEDURE long_compilation
  2  IS
  3  BEGIN
  4  $IF $$plsql_optimize_level != 1
  5  $THEN
  6     $ERROR 'This program must be compiled with optimization level = 1' $END
  7  $END
  8     NULL;
  9  END long_compilation;
 10  /

Warning: Procedure created with compilation errors.

SQL> SHOW ERRORS
Errors for PROCEDURE LONG_COMPILATION:

LINE/COL ERROR
-------- -----------------------------------------------------------------
6/4      PLS-00179: $ERROR: This program must be compiled with
         optimization level = 1

 

Синхронизация кода с использованием пакетных констант

Использование пакетных констант в директиве выбора позволяет легко синхронизировать несколько программных единиц по некоторому параметру условной компиляции. Такая синхронизация возможна благодаря тому, что автоматическое управление зависимостями Oracle распространяется на директивы выбора. Иначе говоря, если программная единица PROG содержит директиву выбора, которая ссылается на пакет PKG, то она помечается как зависящая от PKG. Когда спецификация PKG перекомпилируется, все программные единицы, использующие пакетную константу, помечаются как INVALID и должны быть перекомпилированы.

Допустим, я хочу использовать условную компиляцию для автоматического включения или исключения логики отладки и трассировки в своей кодовой базе. Я определяю спецификацию пакета для хранения необходимых констант:


PACKAGE cc_debug
IS
   debug_active CONSTANT BOOLEAN := TRUE;
   trace_level CONSTANT PLS_INTEGER := 10;
END cc_debug;

Затем эти константы используются в процедуре calc_totals:

PROCEDURE calc_totals
IS
BEGIN
$IF cc_debug.debug_active AND cc_debug.trace_level > 5 $THEN
   log_info (...);
$END
   ...
END calc_totals;

В процессе разработки константа debug_active инициализируется значением TRUE. Когда нужно переводить код в окончательную версию, я меняю значение флага на FALSE и пере­компилирую пакет. Программа calc_totals и все остальные программы с похожими директивами выбора объявляются неработоспособными и должны пройти перекомпиляцию.

 

Применение директив получения информации для определения конфигурации конкретных программ

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

После того как программа будет откомпилирована с некоторым набором значений, она сохраняет эти значения до следующей компиляции (из файла или простой перекомпи­ляции командой ALTER...COMPILE). Кроме того, программа будет заведомо перекомпилирована с тем же исходным кодом, который был выбран при предыдущей компиляции, если истинны все следующие условия:

  •  Ни одна из директив условной компиляции не ссылается на пакетные константы (а только на директивы получения информации).
  •  При перекомпиляции программы используется секция REUSE SETTINGS, а параметр PLSQL_CCFLAGS не включен в команду ALTER...COMPILE.

Данная возможность продемонстрирована в сценарии cc_reuse_settings.sql (на сайте книги); результат выполнения этого сценария приведен ниже. Сначала я присваиваю app_debug значение TRUE, а затем перекомпилирую программу с этим значением. Запрос к USER_PLSQL_OBJECT_SETTlNGS показывает, что это значение теперь связано с программной единицей:



SQL> ALTER SESSION SET plsql_ccflags = 'app_debug:TRUE';

SQL> CREATE OR REPLACE PROCEDURE test_ccflags
  2  IS
  3  BEGIN
  4     NULL;
  5  END test_ccflags;
  6  /

SQL> SELECT name, plsql_ccflags
  2    FROM user_plsql_object_settings
  3   WHERE NAME LIKE '%CCFLAGS%';

NAME                           PLSQL_CCFLAGS
------------------------------ ----------------------------
TEST_CCFLAGS                   app_debug:TRUE

Далее я изменяю настройки сеанса так, чтобы вычисление $$app_debug давало FALSE. Новая программа компилируется с этой настройкой:

SQL> ALTER SESSION SET plsql_ccflags = 'app_debug:FALSE';

SQL> CREATE OR REPLACE PROCEDURE test_ccflags_new
  2  IS
  3  BEGIN
  4     NULL;
  5  END test_ccflags_new;
  6/

Затем существующая программа перекомпилируется c секцией REUSE SETTINGS:

SQL> ALTER  PROCEDURE test_ccflags COMPILE REUSE SETTINGS;

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

SQL> SELECT name, plsql_ccflags
  2    FROM user_plsql_object_settings
  3   WHERE NAME LIKE '%CCFLAGS%';

NAME                           PLSQL_CCFLAGS
------------------------------ ----------------------------
TEST_CCFLAGS                   app_debug:TRUE
TEST_CCFLAGS_NEW               app_debug:FALSE

 

Вас заинтересует / Intresting for you:

 Версии PL/SQL и Oracle 12c
Версии PL/SQL и Oracle 12c 1509 просмотров Александров Попков Tue, 21 Nov 2017, 13:28:01
Oracle IDE: JDeveloper, SQL De...
Oracle IDE: JDeveloper, SQL De... 1871 просмотров Ольга Потемкина Tue, 21 Nov 2017, 13:18:46
Oracle и Java: использование P...
Oracle и Java: использование P... 1606 просмотров sepia Tue, 08 May 2018, 08:52:34
Значения NULL в PL/SQL Oracle
Значения NULL в PL/SQL Oracle 1717 просмотров Дэн Tue, 21 Nov 2017, 13:28:01