|
Стивен Ферстайн
Условная компиляция
(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 10
g 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).
|