Продолжается подписка на наши издания! Вы не забыли подписаться?

Наиболее важные возможности для АБД вOracle Database 10g Выпуск 2

Автор: Аруп Нанда - Oracle ACE
Опубликовано: 18.04.2007

Часть 1: Особенности SQL и PL/SQL

Прозрачное шифрование данных

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

Однако, представим себе ситуацию, когда похищен или собственно сервер, или хотя бы только диски, коль скоро они могут быть установлены на сервере с той же самой ОС, а затем с них сняты данные. Или рассмотрим случай, когда администратор базы данных – жулик, который в процессе своей работы проходит сквозь внешнюю защиту и затем выгружает конфиденциальную клиентскую информацию. В обоих случаях пострадавшие предприниматели, если подобное имело бы место в штате Калифорния (а, возможно, вскоре так будет и в других штатах U.S.), юридически обязаны уведомить всех клиентов, затронутых нарушением защиты.

В этих редких (но, конечно, реальных) случаях использование схема аутентификации является спорным решением. Именно поэтому прозрачное шифрование данных (transparent data encryption, TDE) так важно для организаций, для которых установка защиты является высшим приоритетом. Это средство обеспечивает шифрование, передавая сложность управления ключами в ведение механизма базы данных. В то же самое время оно позволяет АБД управлять таблицами базы данных, фактически лишая его возможности видеть данные.

Используя TDE в Oracle Database 10g Выпуск 2, вы можете зашифровать один или более столбцов таблицы. Все, что вы должны сделать, это определить столбец, как шифруемый, при этом не пишется ни одной строки кода. Помните, шифрование требует ключа и алгоритма, чтобы зашифровать входные значения. TDE генерирует отдельный ключ для каждой таблицы. Поскольку такой подход делает управление ключами более простым, но и более подверженным хищению, то применяется еще один ключ – мастер-ключ, который может быть установлен на уровне базы данных. Ключ таблицы шифруется мастер-ключом, который обязан быть применен к ключу таблицы. Следовательно, чтобы расшифровывать столбец, обязательно применение мастер-ключа, так же как и ключа таблицы. (Более детальные сведения о шифровании вообще и использовании предоставляемых Oracle пакетов можно получить, в частности, в моей статье в Magazine Oracle "Encrypt Your Data Assets" (перевод этой статьи опубликован в февральском выпуске журнала Oracle Magazine – http://www.oracle.com/global/ru/oramag/feb2005/gen_encrypt.html – прим.ред.)).

Мастер-ключ хранится вне базы данных в месте, известном как «wallet» («бумажник»), по умолчанию в $ORACLE_BASE/admin/$ORACLE_SID/wallet. Концептуально, это показано на рисунке ниже.


Рисунок 1.

После того, как сконфигурировано TDE или, более конкретно, сконфигурированы «бумажник» и мастер-ключ, вы можете использовать этот механизм, чтобы защитить значения данных. Чтобы зашифровать некий столбец таблицы, можно воспользоваться следующим SQL-предложением:

create table accounts
(
   acc_no       number       not null,
   first_name   varchar2(30) not null,
   last_name    varchar2(30) not null,
   SSN          varchar2(9)             ENCRYPT USING 'AES128',
   acc_type     varchar2(1)  not null,
   folio_id     number                  ENCRYPT USING 'AES128',
   sub_acc_type varchar2(30),
   acc_open_dt  date         not null,
   acc_mod_dt   date,
   acc_mgr_id   number
)

Здесь было применено TDE на столбцах SSN и FOLIO_ID, данные в которых теперь хранятся в таблице в зашифрованном виде. Однако, когда пользователь выбирает данные из таблицы, он видит их расшифрованными, поскольку расшифровка выполняется при выборке. Если же похищаются диски, информация, содержавшаяся в табличных сегментах, остается зашифрованной. Вор нуждается в ключе таблицы, чтобы увидеть зашифрованные значения, но чтобы получить его, ему нужен мастер-ключ, который хранится вне БД, и, следовательно, недоступен.

Обратите внимание на фразы в определениях столбцов SSN и FOLIO_ID, которые задают ENCRYPT с использованием 128-битного механизма Advanced Encryption Standard.

База данных приходит с заранее сконфигурированным бумажником. Чтобы установить пароль бумажника, используйте команду:

alter system set encryption key authenticated BY "topSecret"; 

Эта команда создает бумажник, если он еще не создан, и затем установит пароль "topSecret" (зависимый от регистра). Затем вы можете начать использовать шифрование в определениях столбцов в процессах, как создания таблицы, так и ее модификации.

Шифрование внешних таблиц

В вышеупомянутом примере, чтобы зашифровать столбцы, я использовал обычную таблицу. Вы можете также использовать TDE для внешних таблиц. Например, если вы хотите сгенерировать дамп-файл, содержащий данные из таблицы ACCOUNTS, для ее переноса в другое место, можно использовать простую фразу ENCRYPT.

create table account_ext
organization external
(
   type oracle_datapump
   default directory dump_dir
   location ('accounts_1_ext.dmp',
             'accounts_2_ext.dmp',
             'accounts_3_ext.dmp',
             'accounts_4_ext.dmp')
)
parallel 4
as
select 
ACC_NO,
FIRST_NAME,
LAST_NAME,
SSN           ENCRYPT IDENTIFIED BY "topSecret",
ACC_TYPE,
FOLIO_ID      ENCRYPT IDENTIFIED BY "topSecret",
SUB_ACC_TYPE,
ACC_OPEN_DT,
ACC_MOD_DT
from accounts;

В файлах accounts_*_ext.dmp значения столбцов SSN и FOLIO_ID не будут идти открытым текстом, а будут зашифрованы. Если вы захотите использовать эти файлы, как внешние таблицы, то нужно предоставить пароль topSecret, чтобы прочитать эти файлы.

Как видно из предыдущего, TDE – это очень полезное дополнение (но не замена) к управлению доступом.

Запрос XML в SQL

Oracle обеспечивает интеграцию XML с базой данных, начиная с Oracle9i Database. В том выпуске можно было различными способами обратиться к XML-контенту. В Oracle Database 10g Выпуск 2 новые функции XQUERY и XMLTABLE еще больше упрощают выполнение запросов к XML-контенту.

Примечание: более подробное обсуждение спецификации XQuery не входит в план этой статьи; интересующимся могу рекомендовать прочитать статью в журнале Oracle Magazine “XQuery: A New Way to Search”, http://www.oracle.com/technology/oramag/oracle/05-jan/o15industry.html.

XQuery

Сначала давайте посмотрим на более простую из этих двух функций, XQuery. Приведем пример:

SQL> xquery
  2     for $var1 in (1,2,3,4,5,6,7,8,9)
  3     let $var2 := $var1 + 1
  4     where $var2 < 6
  5     order by $var2 descending
  6     return $var2
  7 /
 
Result Sequence
------------------
5
4
3
2

Новая SQL-команда xquery указывает на XQuery-запрос. Внимательно посмотрите на этот пример. В нем показан новый синтаксис FOR ... IN ..., введенный в Oracle9i Database.

Общая структура XQuery описывается акронимом FLOWR (произносится как "flower"), который расшифровывается как FOR, LET, ORDER BY, WHERE и RETURN. В вышеупомянутом примере мы видим, что строка 2 определяет источник данных, который является рядом чисел от 1 до 9. Это может быть любой источник: связка скалярных значений или элементов XML-данных, указанных фразой FOR. Строка также задает переменную, которая поочередно сопоставляется с этими значениями (var1). В строке 3 другая переменная var2 получает значение var1, к которому добавляется 1.

В результирующем наборе нас интересуют значения меньше 6, что указано в выражении WHERE. Затем мы сортируем по убыванию результирующий набор по значению var2, что показано фразой ORDER BY в строке 6. Наконец, эти значения предоставляются пользователю фразой RETURN.

Если сравнить этот синтаксис с правильным SQL-синтаксисом, то фразы RETURN, FOR, WHERE и ORDER BY походят на SELECT, FROM, WHERE и ORDER BY. LET не имеет прямого аналога в SQL, но ее можно выразить с помощью других конструкций.

Давайте рассмотрим действие этого нового мощного инструмента на практическом примере. Сначала создадим таблицу, содержащую данные об общении с владельцами банковских счетов.

create table acc_comm_log 
(
   acc_no number,
   comm_details xmltype
;
) 

Теперь вставим в нее несколько записей.

insert into acc_comm_log
values
(
   1,
   xmltype(
   '<CommRecord>
      <CommType>EMAIL</CommType>
      <CommDate>3/11/2005</CommDate>
      <CommText>Dear Mr Smith</CommText>
   </CommRecord>')
)
/

insert into acc_comm_log
values
(
   2,
   xmltype(
   '<CommRecord>
      <CommType>LETTER</CommType>
      <CommDate>3/12/2005</CommDate>
      <CommText>Dear Mr Jackson</CommText>
   </CommRecord>')
);

insert into acc_comm_log
values
(
   3,
   xmltype(
   '<CommRecord>
      <CommType>PHONE</CommType>
      <CommDate>3/10/2005</CommDate>
      <CommText>Dear Ms Potter</CommText>
   </CommRecord>')
;
) 

А сейчас посмотрим, какие записи находятся в этой таблице:

SQL> l
  1 select acc_no,
  2       XMLQuery(
  3        'for $i in /CommRecord
  4         where $i/CommType != "EMAIL"
  5         order by $i/CommType
  6         return $i/CommDate'
  7           passing by value COMM_DETAILS
  8       returning content) XDetails
  9 from acc_comm_log
 10 /
 
    ACC_NO XDETAILS
---------- ------------------------------
         1
         2 <CommDate>3/12/2005</CommDate>
         3 <CommDate>3/10/2005</CommDate>

XMLTable

Другая функция, XMLTable, имеет сходное назначение, но возвращает столбцы, как в случае обычного SQL-запроса. Вот как она действует.

1  select t.column_value
  2  from acc_comm_log a,
  3         xmltable (
  4            'for $root in $date
  5             where $root/CommRecord/CommType!="EMAIL"
  6             return $root/CommRecord/CommDate/text()'
  7               passing a.comm_details as "date"
  8*        ) t
SQL> /
 
COLUMN_VALUE
---------------------
3/12/2005
3/10/2005

Этот пример иллюстрирует, как можно использовать обычные SQL-запросы с XML-таблицей, возвращаемой посредством XML-запроса. В запросах при определении команд используется сильно структурированная FLOWR-модель.

XQuery против XMLTable

Теперь, когда представлены оба способа, которые позволяют использовать XML в обычном SQL-запросе, давайте посмотрим, где следует применять каждый из них и при каких обстоятельствах.

Первый метод – XQuery – позволяет получать данные в виде XMLType, с которыми в приложениях можно обращаться как с XML. В примере XQuery-запроса вы видели, что результат возвращается в XML-формате. Второй метод – XMLTable – объединяет функциональные возможности обычного SQL и XML. Результат возвращается не в виде XML, а в реляционном виде.

Обратите внимание, что в обоих случаях источник – XML, но XQuery представляет данные в формате XML, используя XMLType, тогда как XMLTable представляет данные как реляционная таблица, с которой можно манипулировать как обычной таблицей.

XML весьма полезен там, где заранее не известна точная структура данных. В приведенном выше примере записи о коммуникациях различны по способам связи (E-MAIL, PHONE, LETTER). Если связь осуществляется по электронной почте, то атрибутами могут быть email-адрес получателя, адреса возврата, любые точные копии (cc:, bcc:, и так далее), текст сообщения и так далее. Если связь – обращение по телефону, то атрибуты – вызванный телефонный номер, тип номера (домашний, рабочий, сотовый и так далее), ответивший человек, оставленная голосовая почта и так далее. Если нужно было бы спроектировать таблицу, которая содержит все подобные возможные типы атрибутов, это составило бы очень много столбцов и, возможно, было бы утомительно для чтения. Однако, если имеется только один XMLType-столбец, то туда можно поместить все, и при этом сохранить уникальные атрибуты типа коммуникации.

Запрос же может использовать простой SQL-интерфейс, упрощая разработку приложения.

Для получения подробной информации о реализации Oracle XQuery посетите на OTN страницу XQuery – http://www.oracle.com/technology/tech/xml/xquery/index.html.

Расширение возможностей COMMIT

При фиксации (commit) буфер журнала (redo log buffer) сбрасывается на диск в оперативный файл журнала. Этот процесс гарантирует, что при необходимости восстановления БД удастся «накатить» транзакции.

Иногда, однако, разумно будет пожертвовать гарантиями восстановления БД ради обеспечения лучшей производительности. В Oracle Database 10g Выпуск 2 теперь можно указывать, в каком режиме поток redo-данных записывается в оперативные журнальные файлы. Этим поведением можно управлять при помощи как собственно конструкции commit, так и просто изменяя поведение базы данных по умолчанию.

Давайте посмотрим, как работает конструкция commit. При подтверждении транзакции в конструкции COMMIT можно задать дополнительные настройки:

COMMIT WRITE <option> 

где <option> – это то, что влияет на поток redo-данных. По умолчанию применяется опция WAIT. Например, вы ввели:

COMMIT WRITE WAIT; 

Эта команда имеет тот же самый эффект, что и собственно COMMIT. Фиксация не вернет управление пользователю, пока поток redo-данных не будет записан в оперативные журнальные файлы. Если же вы не хотите ждать, то можно ввести:

COMMIT WRITE NOWAIT;

В этом случае управление немедленно возвращается в сессию, даже прежде, чем поток redo-данных будет записан.

Когда выдается команда на фиксацию, процесс Log Writer пишет поток redo-данных в оперативные журнальные файлы. Если вы выполняете серию транзакций, скажем, в среде пакетной обработки, то вы можете не захотеть, чтобы фиксация совершалась так часто. Конечно, лучший способ действий – изменить приложение, чтобы сократить число фиксаций; но это, вероятнее всего, легче сказать, чем сделать. В таком случае можно просто выдать следующее предложение фиксации:

COMMIT WRITE BATCH; 

Эта команда заставит записывать redo-данные в журнальный файл пакетами, а не поодиночке. Эту методику следует использовать для сокращения числа записей на диск буфера журнала в среде с частыми фиксациями. Если же надо записать буфер журнала немедленно, воспользуйтесь командой:

COMMIT WRITE IMMEDIATE; 

Если нужно установить определенное поведение фиксации по умолчанию для СУБД, то следует выполнить следующее:

ALTER SYSTEM SET COMMIT_WRITE = NOWAIT; 

Эта команда сделает заданное поведение режимом по умолчанию для всей СУБД. Это же можно сделать на уровне сессии:

ALTER SESSION SET COMMIT_WORK = NOWAIT; 

Параметр в выражении ALTER SYSTEM задает поведение на системном уровне. Если имеет место установка значения на уровне сессии, то она имеет приоритет над системной установкой, и, наконец, явно заданная фраза в предложении COMMIT имеет самый высокий приоритет.

Эта опция не доступна для распределенных транзакций.

Поймайте ошибку и идите дальше: предложение Error Logging (регистрация ошибок)

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

SQL> insert into accounts 
2      select * from accounts_ny;
insert into accounts
*
ERROR at line 1:
ORA-00001: unique constraint (ARUP.PK_ACCOUNTS) violated

Ни одна из записей таблицы ACCOUNTS_NY не была добавлена. Теперь пробуем сделать то же самое с включенной регистрацией ошибок (error logging). Сначала нужно создать таблицу для сохранения записей, отклоненных DML-оператором. Назовем эту таблицу ERR_ACCOUNTS.

exec dbms_errlog.CREATE_ERROR_LOG ('ACCOUNTS','ERR_ACCOUNTS')

Затем выполним предыдущий оператор с конструкцией регистрации ошибок.

 SQL> insert into accounts
  2     select * from accounts_ny
  3     log errors into err_accounts
  4     reject limit 200
  5  /

6 rows created.

Обратите внимание, что таблица ACCOUNTS_NY содержит десять строк, но только шесть строк были вставлены; остальные четыре строки были отклонены из-за какой-то ошибки. Чтобы узнать, что это было, обратимся с запросом к таблице ERR_ACCOUNTS.

SQL> select ORA_ERR_NUMBER$, ORA_ERR_MESG$, ACC_NO
 2  from err_accounts;

ORA_ERR_NUMBER$ ORA_ERR_MESG$                                          ACC_NO
 ---------------  --------------------------------------------------  ------
              1 ORA-00001: unique constraint (ARUP.PK_ACCOUNTS) violated 9997
              1 ORA-00001: unique constraint (ARUP.PK_ACCOUNTS) violated 9998
              1 ORA-00001: unique constraint (ARUP.PK_ACCOUNTS) violated 9999
              1 ORA-00001: unique constraint (ARUP.PK_ACCOUNTS) violated 10000

Обратите внимание на столбцы: ORA_ERR_NUMBER$, который показывает номер ошибки, выдаваемый Oracle при исполнении DML-оператора, и ORA_ERR_MESG$, который показывает сообщение об ошибке. В нашем случае видно, что четыре записи были отклонены, поскольку они нарушили ограничение первичного ключа PK_ACCOUNTS.

Эта таблица также включает в себя все столбцы таблицы ACCOUNTS, включая столбец ACC_NO. Посмотрев на отклоненные записи, обратим внимание, что их учетные номера (account numbers) уже существуют в таблице; поэтому записи были отклонены с ошибкой ORA-00001. Без конструкции регистрации ошибок запрос терпел неудачу в целом. А при использовании этой конструкции были отклонены только недопустимые записи; все другие были успешно загружены.

Защитите исходный код: пакет WRAP

Программные модули PL/SQL часто содержат весьма секретную и конфиденциальную информацию о деятельности компании, о ее торговых секретах, что заставляет защищать эту группу объектов, подобно таблицам. Чтобы предотвратить неправомочное чтение исходного текста, программы часто подвергаются обфускации с помощью утилиты командной строки wrap.

Wrap можно вызвать только после того, как создан PL/SQL-скрипт; эта утилита из введенного понятного текста создает свернутый (wrapped) файл. Однако в некоторых случаях может понадобиться генерировать свернутый код динамически из PL/SQL-кода. В этом случае утилита wrap не может быть вызвана, потому что еще не существует никакого исходного файла.

К счастью, в поставку Oracle Database 10g Выпуск 2 входит пакет, который можно использовать для создания кода в свернутом формате. Этот пакет призван дополнить, а не заменить утилиту wrap. Она по-прежнему применима в тех случаях, когда требуется быстро свернуть большое количество исходных файлов, используя командную строку.

Представим, что нам надо создать простую процедуру p1 в свернутом формате.

create or replace procedure p1 as 
begin 
   null; 
end;

В PL/SQL-модуле это можно сделать динамически:

begin dbms_ddl.create_wrapped (
  'create or replace procedure p1 as begin null; end;') end; / 

Теперь надо проверить, что процедура свернута. Ее исходный текст можно взять из словаря данных:

SQL> select text from user_source where name = 'P1'; 
TEXT
-----------------------------------------------------------------
procedure p1 wrapped
a000000
369
abcd
abcd
...и так далее ...

Первая строка, procedure p1 wrapped, подтверждает, что процедура была создана со свертыванием. Если даже посмотреть DDL этой процедуры посредством функции DBMS_METADATA.GET_DDL(), то и в этом случае исходный код виден как свернутый.

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

SQL> select dbms_ddl.wrap
  2      ('create or replace procedure p1 as begin null; end;')
  3  from dual
  4 /

DBMS_DDL.WRAP('CREATEORREPLACEPROCEDUREP1ASBEGINNULL;END;')
----------------------------------------------------------------------
create or replace procedure p1 wrapped
a000000
369
abcd
abcd
... и так далее ...

Результат работы функции WRAP – свернутый PL/SQL-код, переданный как параметр. Этот параметр может быть сохранен в плоском файле или таблице и выполнен позже. Это удобно в ситуациях, когда вы генерируете код, который будет развернут в другом месте, а нарушение безопасности кода недопустимо.

Показанное выше решение прекрасно работает до тех пор, пока можно передать полный текст сохраненного кода в виде типа данных VARCHAR2, который ограничен объемом в 32К. Если размер PL/SQL-кода превышает 32К, следует использовать немного другой прием: использовать переменную, содержащую коллекцию строк. Для этого можно использовать поддерживаемый Oracle тип данных varchar2s из пакета DBMS_SQL – TABLE OF VARCHAR2, с размером элементов таблицы до 32К текста. По мере необходимости таблицу можно расширить до нужного числа элементов. Предположим, что нужно свернуть очень длинную процедуру myproc, которая определена следующим образом:

create or replace procedure myproc as
  l_key VARCHAR2(200);
begin
  l_key := 'ARUPNANDA';
end;

Конечно, myproc – не очень длинная процедура, но в целях демонстрации предположим, что очень. Чтобы создать ее как свернутую, можно выполнить следующий PL/SQL-блок:

1  declare
2     l_input_code    dbms_sql.varchar2s;
3  begin
4     l_input_code (1) := 'Array to hold the MYPROC';
5     l_input_code (2) := 'create or replace procedure myproc as ';
6     l_input_code (3) := '  l_key VARCHAR2(200);';
7     l_input_code (4) := 'begin ';
8     l_input_code (5) := '  l_key := ''ARUPNANDA'';';
9     l_input_code (6) := 'end;';
10    l_input_code (7) := 'the end';
11    sys.dbms_ddl.create_wrapped (
12             ddl     => l_input_code,
13             lb      => 2,
14             ub      => 6
15     );
16* end;

Здесь мы определили переменную l_input_code для хранения исходного кода процедуры. В строках 4 – 10 мы заполняем строки кодом, который собираемся сворачивать. В этом примере для простоты я использовал очень короткие строки. В действительности вы можете быть вынуждены использовать весьма длинные строки, длиной до 32К. По этой же причине я использовал только 7 элементов массива; в действительности можно использовать сколько угодно, лишь бы весь код поместился.

Строки 11-15 показывают, как вызвать процедуру создания свернутой процедуры. В строке 12 я передал коллекцию как DDL-параметр. Здесь сделаем паузу. Я использовал комментарий как первый элемент массива, возможно, для документации. Однако это не допустимый синтаксис. Точно так же я использовал другой комментарий в последнем элементе (7) массива, снова недопустимый синтаксис при создании процедуры. Чтобы процедура обрабатывала только допустимые строки, я определил в строках 13 и 14 самый низкий (2) и самые высокий (6) элементы коллекции, в которой хранится код. Параметр lb показывает нижнюю границу массива, (в нашем примере 2), а ub – верхнюю границу (6).

Как видите, используя этот подход, из PL/SQL-кода можно создать процедуру любого размера в свернутом формате.

Условная компиляция PL/SQL: пишем единожды, выполняем многократно

Многие из вас работали с языком C, который поддерживает концепцию директив компилятора. При компиляции C-кода разными компиляторами, значение некоторых переменных может отличаться.

Oracle Database 10g Выпуск 2 PL/SQL содержит подобную возможность: теперь можно предусмотреть директивы препроцессора, которые срабатывают в течение компиляции, а не во время выполнения. Для примера давайте создадим очень простую функцию, которая возвращает строку.

1  create or replace function myfunc
2    return varchar2
3  as
4  begin
5    $if $$ppval $then
6      return 'PPVAL was TRUE';
7    $else
8      return 'PPVAL was FALSE';
9    $end
10* end;

Обратите внимание на строку 5, где были использованы директивы препроцессора, проверяющие переменную ppval. То, что ppval – переменная препроцессора, а не обычная переменная PL/SQL, указывается с помощью $$-нотации. Далее, чтобы дать компилятору знать, что он должен обработать строки только при компиляции, надо использовать специальную $-нотацию, то есть использовать $if вместо обычного if. Давайте скомпилируем эту функцию с различными значениями переменной ppval.

SQL> alter session set plsql_ccflags = 'PPVAL:TRUE';
Session altered.

Компилируем и выполняем функцию.

SQL> alter function myfunc compile;
Function altered.

SQL> select myfunc from dual;

MYFUNC
-------------------------------------
PPVAL was TRUE

Значение ppval при компиляции было установлено в true. Теперь изменим значение переменной и снова выполним функцию.

SQL> alter session set plsql_ccflags = 'PPVAL:FALSE';
Session altered.

SQL> select myfunc from dual;

MYFUNC
---------------------------------------------------------
PPVAL was TRUE

Здесь, хотя в сессии значение ppval равно FALSE, функция не воспринимает это значение; все равно выводит TRUE. Это поисходит из-за того, что директивы препроцессора не выполняются при исполнении функции, а преобразуют код функции во время ее компиляции. Снова перекомпилируем и выполним функцию.

SQL> alter function myfunc compile;
Function altered.

SQL> select myfunc from dual;

MYFUNC
---------------------------------------------------
PPVAL was FALSE

В процессе компиляции значение ppval было FALSE, что привело к формированию кода функции возвращающего строку «PPVAL was FALSE».

Итак, когда следует использовать эту возможность? Есть несколько возможных ситуаций, например, можно использовать ее как флаг отладки для вывода дополнительных сообщений, или можно написать программу, которая компилируется по-разному, в зависимости от платформы. Поскольку вычисление производится в процессе компиляции, а не во время выполнения, эффективность выполнения сильно возрастает.

Упомянутый выше пример работает прекрасно, пока вы используете тот же самый флаг препроцессора, на который ссылаются все компилируемые функции. Но что будет, если в разных случаях используются различные флаги? Например, функция calculate_interest может иметь флаг ACTIVE_STATUS_ONLY, установленный в TRUE, а функция apply_interest может иметь флаг FOREIGN_ACCOUNTS, установленный в FALSE. Чтобы скомпилировать их с соответствующими флагами, можно ввести:

alter function calculate_interest compile 
   plsql_ccflags = 'ACTIVE_STATUS_ONLY:TRUE' 
   reuse settings;
alter function apply_interest compile 
   plsql_ccflags = FOREIGN_ACCOUNTS:TRUE' 
   reuse settings;

Обратите внимание, что здесь нет установок уровня сессии. Конструкция reuse settings гарантирует, что при последующей перекомпиляции функций будут использоваться те же самые директивы компилятора.

Давайте рассмотрим другую разновидность этой новой возможности. Кроме проверки значений, определяемых в параметре инициализации plsql_ccflags, препроцессор может также проверять статические константы, объявленные в пакетах PL/SQL. Предположим, что вы хотите осуществлять вывод отладочной информации в PL/SQL-процедуре, если debug_flag равна TRUE. Сначала создадим пакет:

create or replace package debug_pkg
is
   debug_flag constant boolean := FALSE;
end;

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

create or replace procedure myproc
as
begin
  $if debug_pkg.debug_flag $then 
    dbms_output.put_line ('Debug=T');
  $else
    dbms_output.put_line ('Debug=F');
  $end
end;

Обратим внимание, что на константу из пакета ссылаются непосредственно, без префикса «$». В этом случае нет никакой необходимости задавать какие-либо параметры условной компиляции на уровне сессии или системы. Коль скоро функция откомпилирована, не нужно передавать каких-либо дополнительных выражений. Чтобы увидеть, как это работает, выполним:

SQL> exec myproc

Debug=F

Поскольку значение debug_pkg.debug_flag сейчас равно FALSE, выполненная процедура возвратила "F", как и ожидалось. Теперь изменим значение константы:

create or replace package debug_pkg
is
   debug_flag constant boolean := TRUE;
end;

И снова исполним процедуру:

SQL> exec myproc

Debug=T

Процедура взяла значение константы, чтобы показать "T", как и ожидалось. Обратите внимание на очень важное различие: перекомпиляции процедуры не потребовалось; изменение константы было отслежено автоматически!

Неограниченный DBMS-вывод

А помните ли вы страшную ошибку, о которой напоминают следующие строки?

ERROR at line 1:
ORA-20000: ORU-10027: buffer overflow, limit of 1000000 bytes
ORA-06512: at "SYS.DBMS_OUTPUT", line 32
ORA-06512: at "SYS.DBMS_OUTPUT", line 97
ORA-06512: at "SYS.DBMS_OUTPUT", line 112
ORA-06512: at line 2

Это происходило вследствие того, что максимально возможный символьный текст, обрабатываемый встроенным пакетом dbms_output, не мог превышать 1 миллион байтов. В Oracle Database 10g Выпуск 2 это ограничение снято: теперь максимальный размер вывода может быть таким, какой потребуется. Вы можете снять ограничение, просто задав:

set serveroutput on 

Это выражение давало следующий результат:

SQL> show serveroutput
serveroutput ON size 2000 format WORD_WRAPPED

Обратите внимание, что значение по умолчанию максимального размера вывода было равно 2 000. В Oracle Database 10g Выпуск 2 команда выдает следующий результат:

SQL> show serveroutput
serveroutput ON SIZE UNLIMITED FORMAT WORD_WRAPPED

Значение по умолчанию – UNLIMITED.

Другое неудобство было связано с максимальной длиной строки, отображаемой dbms_output. Ниже показано типичное сообщение об ошибке, выдававшееся для строк длиннее 255 байтов:

ERROR at line 1:
ORA-20000: ORU-10028: line length overflow, limit of 255 chars per line
ORA-06512: at "SYS.DBMS_OUTPUT", line 35
ORA-06512: at "SYS.DBMS_OUTPUT", line 115
ORA-06512: at line 2

В Oracle Database 10g Выпуск 2 строки могут иметь произвольную длину.

Часть 2. Возможности управления

<...>

Ссылки

........................
"С полным содержанием данной статьи можно ознакомиться в печатной версии журнала"

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

Copyright © 1994-2016 ООО "К-Пресс"