Oracle Magazine - Русское издание (Октябрь 2007)

Стивен Ферстайн

Условная компиляция
(On Conditional Compilation, by Steven Feuerstein)

Источник: Oracle Magazine, #4 2006, http://www.oracle.com/technology/oramag/oracle/06-jul/o46plsql.html

Применяйте самые лучшие способы для условной компиляции PL/SQL.

Что такое условная компиляция PL/SQL, как она работает и что о ней можно узнать больше?

Oracle Database 10g привнес значительный прогресс в мир PL/SQL-разработок, особенно в результате развития компилятора PL/SQL. Эта статья уделяет внимание возможности Oracle Database 10g Release 2, называемой условной компиляцией (conditional compilation).

Oracle Database 10g Release 1 ввел оптимизацию компилятора и сообщения, возникающие в процессе компиляции. Оптимизатор PL/SQL автоматически преобразовывает код, который в результате может работать более эффективно. (Этот оптимизатор – не то же самое, что стоимостной оптимизатор, который Oracle Database использует для оптимизации выполнения SQL-предложений.) Oracle оптимизирует таким образом, что можно ожидать увидеть повышение скорости выполнения PL/SQL-предложений в среднем в два раза по сравнению с более ранними версиями Oracle Database. (Заметьте, что компилятор PL/SQL влияет только на PL/SQL-предложения, а не на SQL-предложения внутри программ).

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

PL/SQL в Oracle Database 10g Release 2 делает еще один большой шаг вперед, благодаря добавлению поддержки условной компиляции. Теперь можно указывать компилятору по какому условию следует применять условную компиляцию, что определяется директивой компилятора (предложению, имеющему префикс $), следует ли включать или исключать отдельные тексты программ.

Одна из особо привлекательных сторон условной компиляции состоит в том, что текст, который исключается во время условной компиляции, не обязательно должен быть правильным PL/SQL-кодом; это дает гигантскую гибкость при написании программ, которые работают различно в различных версиях Oracle Database.

Множество способов условной компиляции PL/SQL позволяют улучшить процесс разработки приложения. Ниже показано несколько наиболее общих случаев применения условной компиляции в приложениях:

  • Написание единого программного модуля, работа которого автоматически определяется в зависимости от особенностей версии Oracle Database. Больше не требуется разрабатывать один и тот же модуль отдельно для Oracle9i Database и Oracle Database 10g, или рассчитывать на логику SQL*Plus с использованием переменных подстановки для достижения такого же результата.
  • Запуск или выполнение частей кода во время тестирования, а затем скрытие этого кода, когда модуль станет промышленным. Эта техника весьма полезна для трассировки выполнения и тестирования блоков пакетных подпрограмм.
  • Повышение эксплуатационной надежности базового PL/SQL-кода путем “мягкого” кодирования элементов приложения, которые в противном случае должны бы быть закодированы как константы.

Чтобы воспользоваться условной компиляцией, надо добавить директивы (команды) компилятора в код. Компилятор PL/SQL, обрабатывая директивы перед тем, как выполнить компиляцию, выясняет, какие части программного текста подлежат генерации выполняемого кода. Модифицированный исходный код передается затем компилятору для выполнения самой компиляции.

Существует три типа директив:

  • Selection directives (Директивы выбора). Используйте директиву $IF для вычисления выражений и определения, какой код должен быть включен в скомпилированный код.
  • Inquiry directives (Вопросительные директивы). Используйте идентификатор $$ для обращения к флагам условной компиляции. Эти вопросительные директивы могут быть доступны в директиве $IF или использоваться независимо в вашем коде.
  • Error directives (Директивы генерации ошибок). Используйте директиву $ERROR для генерации ошибки компиляции, связанной с условиями, вычисленными в Oracle Database во время подготовки кода для компиляции.

Oracle также добавил два пакета для поддержки условной компиляции:

  • DBMS_DB_VERSION, который можно использовать для анализа версии (как в абсолютных, так и в относительных значениях) экземпляра Oracle Database, к которому выполнено присоединение, и
  • DBMS_PREPROCESSOR, который позволяет увидеть код, полученный после применения всех директив условной компиляции.

Доступность применения условной компиляции. Условная компиляция PL/SQL может применяться в Oracle Database 10g Release 1 и более поздних версиях (начиная от 10.1.0.4). В Oracle Database 10g Release 2, условная компиляция включена по умолчанию и не может быть отключена.

В Oracle Database 10g Release 1 условная компиляция включена по умолчанию, но ее можно отключить, установив underscore-параметр [параметр, имя которого начинается с "_". Раньше, а м.б. и теперь такие параметры назывались и являлись недокументированными. - прим. А.Бачин]. Условная компиляция также поддерживается в Oracle9i Database Release 2 (начиная с 9.2.0.6); она по умолчанию отключена, но ее можно включить, установив underscore-параметр.

Чтобы узнать, как отключить условную компиляцию в Oracle Database 10g Release 1 или включить ее в Oracle9i Database, свяжитесь с Oracle Support для получения информации о underscore-параметре.

Где более подробно узнать об условной компиляции. Эта статья представляет некоторое введение в понятие условной компиляции, отвечая на вопросы читателей. Конечно, следует более глубоко изучить эту возможность перед тем, как применять ее в своем приложении. Я предполагаю ознакомиться со следующей документацией:

  • "Conditional Compilation in Oracle Database 10g Release 2." Помогает облегчить разработку с условной компиляцией, это руководство (white paper), написанное PL/SQL Product Manager Брином Левеллином (Bryn Llewellyn) содержат 96 страниц подробных объяснений и примеров, включая начальный курс по использованию условной компиляции с тестированием блоков. Его адрес: oracle.com/technology/tech/pl_sql/pdf/Plsql_Conditional_Compilation.pdf.
  • Документация Oracle об условной компиляции: "Oracle Database PL/SQL User's Guide and Reference" доступна по адресу: download.oracle.com/docs/cd/B19306_01/appdev.102/b14261/fundamentals.htm#BABIHIHF.
  • Моя книга по программированию на PL/SQL: "Oracle PL/SQL Programming", 4-е издание, Стивен Ферстайн в соавторстве с Биллом Приблом: Глава 20 “Управление PL/SQL кодом” (4th Edition, by Steven Feuerstein with Bill Pribyl: Chapter 20, "Managing PL/SQL Cod,") демонстрирует условную компиляцию и предлагает несколько примеров.

Применение с директивой генерации ошибки

Я часто работаю со многими программными блоками, фиксируя одно и в то же время разрабатывая другое. Так как я кручусь между ними, иногда приходится откладывать наполовину законченную работу. Как вы думаете, может, лучше помечать такие программы как незавершенные и поддерживать список того, что необходимо сделать?

Включите списки “to-do” в ваш исходный код!

Это можно сделать с помощью стандартного комментария, означающего "незавершено, надо закончить ", примерно такого:

/* INCOMPLETE - START

Обратить внимание: добавить логику для прохождения по коллекции в цикле */ . . . code /* INCOMPLETE - END */

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

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

Пример. Я написал программу для разбора строки с разделителями и помещения значений в коллекцию. Я пока не могу закончить последнюю операцию SUBSTR и хочу вспомнить об этом позже. Поэтому у меня используется директива $ERROR, для того чтобы напомнить о задаче, и еще специальные вопросительные директивы, $$PLSQL_UNIT и $$PLSQL_LINE, показывающие название программного блока и номер строки при попытке скомпилировать программу.

На Листинге 1 показана функция list_to_collection и результат попытки компиляции.

Код Листинга 1: Функция _LIST_TO_COLLECTION и попытка компиляции

SQL> CREATE OR REPLACE FUNCTION list_to_collection (
 2 string_in IN VARCHAR2
 3 , delimiter_in IN VARCHAR2 DEFAULT ','
 4 )
 5 RETURN DBMS_SQL.varchar2a
 6 IS
 7 l_next_location PLS_INTEGER := 1;
 8 l_start_location PLS_INTEGER := 1;
 9 l_return DBMS_SQL.varchar2a;
 10 BEGIN
 11 IF string_in IS NOT NULL
 12 THEN
 13 WHILE ( l_next_location > 0 )
 14 LOOP
 15 – Поиск следующего разделителя
 16 l_next_location :=
 17 NVL (INSTR ( string_in, delimiter_in, l_start_location ), 0);
 18
 19 IF l_next_location = 0
 20 THEN
 21 – Разделителей больше нет, идем в конец строки
 22 l_return ( l_return.COUNT + 1 ) :=
 23 SUBSTR ( string_in, l_start_location );
 24 ELSE
 25 $ERROR
 26 'list_to_collection INCOMPLETE!
 27 Finish extraction of next item from list.
 28 Go to ' || $$PLSQL_UNIT || ' at line ' || $$PLSQL_LINE
 29 $END
 30 END IF;
 31 l_start_location := l_next_location + 1;
 32 END LOOP; 33 END IF;
 34 RETURN l_return;
 35 END list_to_collection;
 36 /

Warning: Function created with compilation errors.

SQL> SHOW ERRORS
Errors for FUNCTION LIST_TO_COLLECTION:

LINE/COL ERROR
-------- ---------------------------------------------------------------
25/13 PLS-00179: $ERROR: list_to_collection INCOMPLETE!
 Finish extraction of next item from list.
 Go to LIST_TO_COLLECTION at line 28

Директива генерации ошибки, между прочим, подходит для любых видов исключений. Предположим, например, необходимо убедиться, что чрезвычайно сложная и напичканная вычислениями программа всегда компилируется с максимально оптимизированным уровнем. Я могу просто применить директиву выбора, которая проверит значение параметра PL/SQL-компиляции с флагом $$PLSQL_OPTIMIZE_LEVEL ccflag, в сочетании с ошибкой, в определении программного блока, как показано на Листинге 2.

Код Листинга 2: COMPUTE_INTENSIVE_PROGRAM

SQL> CREATE OR REPLACE PROCEDURE compute_intensive_program
 2 IS
 3 BEGIN
 4 $IF $$PLSQL_OPTIMIZE_LEVEL <> 2
 5 $THEN
 6 $ERROR
 7 'compute_intensive_program must be compiled with maximum optimization!'
 8 $END
 9 $END
 10 NULL; -- Здесь много-много другого кода...
 11 END compute_intensive_program;
 12 /

Warning: Procedure created with compilation errors.

SQL> SHOW ERRORS
Errors for PROCEDURE COMPUTE_INTENSIVE_PROGRAM:

LINE/COL ERROR
-------- -----------------------------------------------------------

6/4 PLS-00179: $ERROR: compute_intensive_program must be compiled with maximum optimization!

И, наконец, можно рассмотреть использование директивы генерации ошибки для выявления ситуации "вариант не найден". На Листинге 3 показан пример. Он требует, чтобы командой ALTER SESSION, было установлено значение ccflag, на которое можно указать ссылки в директиве выбора.

Код Листинга 3: Использование директивы генерации ошибки для выявления "вариант не найден"

SQL> ALTER SESSION SET PLSQL_CCFLAGS = 'current_user_type:1'
 2 /

Session altered.

SQL> CREATE OR REPLACE PACKAGE user_types
 2 IS
 3 administrator CONSTANT PLS_INTEGER := 1;
 4 enduser CONSTANT PLS_INTEGER := 2;
 5 END user_types;
 6 /

Package created.

SQL> CREATE OR REPLACE PROCEDURE show_info
 2 IS
 3 BEGIN
 4 $IF $$current_user_type = user_types.administrator
 5 $THEN
 6 DBMS_OUTPUT.PUT_LINE ('Administrator!');
 7 $ELSIF $$current_user_type = user_types.enduser
 8 $THEN
 9 DBMS_OUTPUT.PUT_LINE ('End user!');
 10 $ELSE
 11 $ERROR 'Current user type of ' || $$current_user_type || ' is not known.' $END
 12 $END
 13 END show_info;
 14 /

Procedure created.

SQL> ALTER PROCEDURE show_info COMPILE
 2 PLSQL_CCFLAGS = 'current_user_type:0'
 3 REUSE SETTINGS
 4 /

Warning: Procedure altered with compilation errors.

SQL> show errors
Errors for PROCEDURE SHOW_INFO:

LINE/COL ERROR
------- ----------------------------------------------------------
11/4 PLS-00179: $ERROR: Current user type of 0 is not known.

FORALL и версия PL/SQL

Мне нравится оператор FORALL, который Oracle добавил к PL/SQL в Oracle8i Database. Я использую его всегда, когда требуется высокоскоростная обработка DML. Я также пишу код, который должен работать как Oracle9i Database, так и в Oracle Database 10g. Я очень рад, что Oracle Database 10g предлагает выражения INDICES OF и VALUES OF, позволяющие использовать FORALL со слабо заполненными коллекциями. Мне нравится использовать преимущества FORALL, но не хочется создавать два различных набора кодов, один для Oracle9i Database и другой для Oracle Database 10g. Как бы мне избежать этого?

Используйте все преимущества каждой версии PL/SQL. FORALL весьма хорош, не так ли?! И хочется получать максимум выгоды от каждого нюанса этого механизма, вот только управление множеством наборов кодов очень трудоемко.

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

Например, в Oracle Database 10g Release 2, этот пакет определен, как показано на Листинге 4.

Код Листинга 4: Определение пакета DBMS_DB_VERSION

CREATE OR REPLACE package dbms_db_version is
 version constant pls_integer := 10; -- номер версии RDBMS
 release constant pls_integer := 2; -- номер пакета обновлений RDBMS
 ver_le_9_1 constant boolean := FALSE;
 ver_le_9_2 constant boolean := FALSE;
 ver_le_9 constant boolean := FALSE;
 ver_le_10_1 constant boolean := FALSE;
 ver_le_10_2 constant boolean := TRUE;
 ver_le_10 constant boolean := TRUE;
end dbms_db_version;
/

А теперь применим этот пакет для написания некоторой программы, которая будет автоматически использовать выражение INDICES OF оператора FORALL, если это возможно.

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

CREATE TABLE otn_demo (
 num NUMBER, name VARCHAR2(100))
/

CREATE OR REPLACE PACKAGE
 otn_demo_insert
IS
 TYPE otn_demo_aat
 IS TABLE OF otn_demo%ROWTYPE
 INDEX BY PLS_INTEGER;
 PROCEDURE insert_rows (
 rows_in IN otn_demo_aat);
END otn_demo_insert;
/

Затем тело пакета: Процедура insert_rows использует INDICES OF, если запускается Oracle Database 10g и выше. Если запускается Oracle9i Database Release 2 и ниже, содержимое с возможными пропусками в коллекции копируется в полностью заполненную коллекцию. В обоих случаях, имеются преимущества вставки на уровне записи, как показано на Листинге 5.

Код Листинга 5: Тело пакета OTN_DEMO_INSERT

CREATE OR REPLACE PACKAGE BODY otn_demo_insert
IS
 PROCEDURE insert_rows ( rows_in IN otn_demo_aat )
 IS
 BEGIN
 $IF DBMS_DB_VERSION.VER_LE_9_2
 $THEN
 DECLARE
 l_dense otn_demo_aat;
 l_index PLS_INTEGER := rows_in.FIRST;
 BEGIN
 WHILE (l_index IS NOT NULL)
 LOOP
 l_dense (l_dense.COUNT + 1) := rows_in (l_index);
 l_index := rows_in.NEXT (l_index);
 END LOOP;

 FORALL indx IN 1.. l_dense.COUNT
 INSERT INTO otn_demo VALUES l_dense (indx);
 END;
 $ELSE
 FORALL indx IN INDICES OF rows_in
 INSERT INTO otn_demo VALUES rows_in (indx);
 $END
 END insert_rows;
END otn_demo_insert;

/

Итак, получился один программный блок, в коде которого, однако, автоматически используется максимум преимуществ этой возможности. Классная штука!

Давайте посмотрим на еще один пример. Oracle Database 10g ввел новые типы данных для дробных чисел, BINARY_FLOAT и BINARY_DOUBLE. Они часто повышают производительность при обработке очень мелких чисел. Следующая программа автоматически объявляет локальную переменную с типом BINARY_FLOAT, если такой тип данных доступен. Иначе, устанавливается традиционный NUMBER.

CREATE OR REPLACE PROCEDURE
crunch_numbers
IS
 n $IF DBMS_DB_VERSION.VER_LE_9_2
 $THEN NUMBER;
 $ELSE BINARY_FLOAT;
 $END
BEGIN
 $IF DBMS_DB_VERSION.VER_LE_9_2
 $THEN n := 1.0;
 $ELSE n := 1.0f;
 $END
 DBMS_OUTPUT.put_line ( n );
END crunch_numbers;
/

Заметьте, что в этом случае условная компиляция используется в тексте обычного предложения: Выбор типа возникает в секции объявления переменной n. Мне не нужно использовать условную компиляцию только для выбора между двумя и более целыми выполняемыми предложениями.

Другие вопросы и ответы об условной компиляции PL/SQL ищите в Best Practice PL/SQL at oracle.com/technology/pub/columns/plsql.


Steven Feuerstein ( steven@stevenfeuerstein.com) is considered one of the world's leading experts on the Oracle PL/SQL language, having written 10 books on the subject, including Oracle PL/SQL Programming and Oracle PL/SQL Best Practices (O'Reilly Media). Feuerstein serves as a senior technology advisor for Quest Software and is currently building a unit testing tool for PL/SQL programs (www.unit-test.com).

E-mail this page