Механизм условной компиляции, появившийся в 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