预处理器指令
指令由指令控制标记“$”和普通的 PL/SQL
文本组成。条件编译使用三个指令:选择、查询和错误。特殊的触发器字符“$”代表条件编译指令。选择指令是条件编译机制的重要组成部分,而查询和错误指令支持有用的附加功能。
选择指令
选择指令对条件编译表达式进行评估,并根据评估的结果选择要包含在该编译中的代码。完全忽略未选中的代码。这不会干扰到现有程序,因为这些程序并未使用条件编译。条件选择指令以
$if 开始并使用常规语法:
$if $then? $elsif $then? …$else? $end
其中, 表示静态布尔表达式。静态布尔表达式是一个或多个程序包常量或一个或多个查询指令的任意组合。以下是选择指令将程序包常量用作静态布尔表达式的示例:
$if Trace_Pkg.Trace > 2 $then ?$end
其中,Trace_pkg 是程序包的名称,Trace
是声明为 PLS_INTEGER 的常量。请注意,如果 PL/SQL 编译单元 U 内的选择指令中使用了在程序包
Trace_pkg 内声明的常量,那么,U 将在 Trace_pkg 上具有一个依赖项,就好像
Trace_pkg 已经在常规 PL/SQL 代码中进行了引用。
查询指令允许访问编译环境,以便将选择基于当前环境。查询指令的形式为 $$ 开头,例如:
$if $$debug_level > 3 $then … $end
标识符 debug_level 是使用
plsql_ccflags 初始化参数(Oracle 数据库 10g 第 2
版中的新增内容)定义的,因此:
...plsql_ccflags =
'debug_level:4, ...'
错误指令
错误指令的形式如下所示
$error $end。
它可以使编译器报告编译错误,包括 VARCHAR2 表达式中提供的消息。
Oracle 数据库 10g 第 2
版提供了以下可以在条件查询指令中使用的 PL/SQL 编译器参数:
PLSQL_CCFLAGS
PLSQL_DEBUG
PLSQL_OPTIMIZE_LEVEL
PLSQL_CODE_TYPE
PLSQL_WARNINGS
NLS_LENGTH_SEMANTICS
编译时,PL/SQL 编译器参数的值存储在编译单元中,并可以使用
all_plsql_object_settings 系列视图进行查看。此外,还提供了预定义的查询指令
$$PLSQL_UNIT, $$PLSQL_LINE。有关条件编译指令语法的详细信息,请参阅 PL/SQL
用户指南和参考 手册。
使用指令
示例 1:使用程序包常量进行跟踪和调试
在条件编译指令中使用程序包常量为通过单一机制控制一个或多个 PL/SQL
编译单元提供了一种方法。例如,假设应用程序由许多 PL/SQL 编译单元组成。在该应用程序内,已嵌入了执行调试或跟踪的
ed
方法。这些方法可以通过使用程序包常量的条件编译指令启用。因此,可以通过随时重新编译该程序包来更改该常量的值。在重新编译程序包时,所有的相关对象都将自动重新编译,以接受该程序包常量的新值。这可以用于在整个应用程序中启用跟踪和调试功能。在进行跟踪和调试时,利用保护跟踪和调试代码的新常量值重新编译程序包规范。这将使所有相关
PL/SQL 单元无效,以便下一次使用时,将不会选中跟踪和调试代码进行编译。程序包常量的使用是一种控制所有相关 PL/SQL
单元的有效机制,这些单元在选择指令内使用打包常量进行条件处理。以下示例演示了这一用法。
1.
打开一个终端窗口,执行以下命令:
cd /home/oracle/wkdir
sqlplus hr/hr
2.
创建程序包 STATIC_CONSTANTS
以声明可用在条件编译中的程序包常量。从 SQL*Plus 会话中,执行以下脚本:
@static_constants
static_constants.sql 脚本包含以下内容:
CREATE OR REPLACE PACKAGE static_constants is
debug CONSTANT BOOLEAN := FALSE;
trace CONSTANT BOOLEAN := FALSE;
END ;
/
3.
创建两个过程:CHECK_DEBUG(用于检查程序包常量调试的值是否是
TRUE)和 CHECK_TRACE(用于检查程序包跟踪的值是否是
TRUE)。从 SQL*Plus
会话中,执行以下脚本:
@debug_trc
debug_trc.sql
脚本包含以下内容:
CREATE OR REPLACE PROCEDURE check_Debug IS
BEGIN
$IF static_constants.debug $THEN DBMS_OUTPUT.put_line('Debugging ON');
$ELSE DBMS_OUTPUT.put_line('Debugging OFF'); $END
END;
/
CREATE or REPLACE PROCEDURE Check_trace IS
BEGIN
$IF static_constants.trace $THEN DBMS_OUTPUT.put_line('Tracing ON');
$ELSE DBMS_OUTPUT.put_line('Tracing OFF'); $END
END;
/
4.
Set serveroutput on using the
new SIZE UNLIMITED
syntax available in Oracle Database 10g Release 2. Now execute both
the procedures.您将看到它显示跟踪和调试均被关闭。
注意:可以将每一行单独地复制到 sqlplus 中,但不要一起复制这 3 个语句。
set serveroutput on size unlimited
exec check_debug
exec check_trace
5.
现在,将更改程序包常量的值。从 SQL*Plus 会话中,执行以下脚本:
@reset_const
reset_const.sql
脚本包含以下内容:
CREATE OR REPLACE PACKAGE static_constants is
debug CONSTANT BOOLEAN := TRUE;
trace CONSTANT BOOLEAN := TRUE;
END ;
/
6.
现在,再次执行这两个过程。您将看到调试和跟踪自动启用了。在重新编译程序包时,所有相关的对象都变为无效,并在下一次执行时重新编译。因此,程序包常量的新值可即刻适用于所有相关对象。
exec check_debug
exec check_trace
这可以扩展至任意数量的相关程序。在需要通过单一机制控制大量程序时,使用这个方法最为有效。用例的其他示例包括针对同一应用程序按州更改赋税,或根据许可选项更改软件特性等等。
通过设置 ALTER COMPILE
命令只影响正在编译的程序。利用 CREATE or REPLACE
重新编译这些程序,GET_RECORD 的过程,以接受
employee_id 的值,并显示相应的记录。为了保持良好的编程实践,该过程将调用
请注意,已删除了所有条件编译指令。
2.
创建 SEND_MESSAGE_TO_DBA
和 CHECK_UNIQUE
过程。CHECK_UNIQUE
过程包含两个不同的代码段,一个用于开发中的测试,另一个部署在最终生产环境中。开发代码使用条件编译指令,而生产代码使用传统的
IF-THEN-ELSE 逻辑检查重复的记录。从
SQL*Plus 会话中,执行以下脚本:
@dba_email
@chk_unq
show errors
dba_email.sql
脚本包含以下内容:
CREATE OR REPLACE PROCEDURE send_message_to_DBA(emp_id number)
IS
mailhost VARCHAR2(64) := 'mailhost.fictional-domain.com';
sender VARCHAR2(64) := 'HR_APP@fictional-domain.com';
recipient VARCHAR2(64) := 'DBA@fictional-domain.com';
mail_conn utl_smtp.connection;
BEGIN
mail_conn := utl_smtp.open_connection(mailhost, 25);
utl_smtp.helo(mail_conn, mailhost);
utl_smtp.mail(mail_conn, sender);
utl_smtp.rcpt(mail_conn, recipient);
-- open_data(), write_data(), and close_data() into a single call to data().
utl_smtp.open_data(mail_conn);
utl_smtp.write_data(mail_conn, 'A primary key violation has occured for record '||
emp_id || 'in the EMPLOYEES table.This is an automatically generated e-mail
message.Please do not respond to this, this is an alert.'|| chr(13));
utl_smtp.write_data(mail_conn, 'This is line 2.'|| chr(13));
utl_smtp.close_data(mail_conn);
utl_smtp.quit(mail_conn);
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
/
chk_unq.sql
脚本包含以下内容:
Create or replace Procedure check_unique(emp_id NUMBER) is
v_num number;
force_pk_violation exception;
Begin
Select count(*) into v_num from employees where employee_id = emp_id;
-- production code
If v_num > 1
then SEND_MESSAGE_TO_DBA(emp_id);
raise force_pk_violation;
END IF ;
-- Development code
$if $$FORCE
$then SEND_MESSAGE_TO_DBA(emp_id);
raise force_pk_violation;
$end
END;
/
3.
现在,更改会话将 $$force 的值设为
FALSE。从 SQL*Plus 执行以下命令。
ALTER SESSION SET
PLSQL_CCFLAGS='force:FALSE';
4.
现在,再次执行 chk_unq.sql。
因为变量 $$force 现在的值是
FALSE,所以没有出现警告;
5.
创建 GET_RECORD 过程。该过程接受
employee id 并返回员工记录。如果 CHECK_UNIQUE
过程检查出存在重复记录,则会显示一个友好消息。从 SQL*Plus 会话执行以下脚本:
@get_record
get_record.sql
脚本包含以下内容:
Create or replace procedure get_record(emp_id IN NUMBER)
as
emp_record employees%rowtype;
Begin
check_unique(emp_id);
Select * into emp_record from employees where employee_id =emp_id;
Dbms_output.put_line ( 'Name:'|| emp_record.first_name||'
'||emp_record.last_name||' '||'Hiredate:
' ||emp_record.hire_date||'
'||'Job:'||' '||emp_record.job_id);
Exception
When others then
DBMS_OUTPUT.PUT_LINE('We are unable to process your request at this time.
Please try again later.We apologize for any inconvenience');
End;
/
6.
现在,执行 GET_RECORD 过程,传入
100 作为参数。
exec GET_RECORD(100)
因为没有重复记录,所以该过程成功执行。
7.
现在,测试是否在出现重复记录时显示消息。更改会话将 $$force 的值设为
TRUE,并再次执行 GET_RECORD。从 SQL*Plus 会话执行以下命令:
ALTER SESSION SET PLSQL_CCFLAGS='force:TRUE';exec GET_RECORD(100)
由于未重新编译该过程,因此它不会受到影响。
8.
使用 ALTER...COMPILE 语句重新编译
CHECK_UNIQUE 过程,并为
PLSQL_CCFLAGS 参数提供新值。使用
REUSE SETTINGS
选项。然后,以值 100 再次执行 GET_RECORD。从 SQL*Plus 会话执行以下命令。
ALTER PROCEDURE CHECK_UNIQUE compile
plsql_ccflags = 'force:TRUE' REUSE SETTINGS;
exec GET_RECORD(100)
现在,PLSQL_CCFLAGS
值生效,因而程序发出消息。该方法可以用于影响会话内的特定程序,比如后生产调试。然而,每个需要新设置的程序都必须使用
CREATE OR REPLACE 或
ALTER...COMPILE 语句进行重新编译。
示例 3:使用 DBMS_PREPROCESSOR 过程打印或检索源文本
DBMS_PREPROCESSOR
子程序会在处理条件编译指令后,打印或检索 PL/SQL 单元的处理后源文本。这个处理后文本是用于编译有效 PL/SQL
单元的实际源。执行以下步骤:
1.
Execute the following command to see
the actual source code used to compile a valid PL/SQL
program:
EXEC DBMS_PREPROCESSOR.PRINT_POST_PROCESSED_SOURCE('PROCEDURE', 'HR', 'CHECK_UNIQUE');
请注意,已删除了所有条件编译指令。
示例
4:使用条件编译分支代码确定最佳性能版本
Oracle 数据库 10g 引入了 BINARY_DOUBLE 数据类型,该数据类型可用于算法密集的操作。本例中,将对
BINARY_DOUBLE 数据类型与 NUMBER 数据类型进行比较。需要在两个不同版本中创建同样的代码,一个使用
NUMBER,另一个使用 BINARY_DOUBLE。然后,可将两个版本包含在同一过程中,以使用 PLSQL_CCFLAGS 进行测试。执行以下步骤:
1.
启用 ALTER SESSION
以设置标记,从而在两个版本之间进行选择。首先,选择使用 NUMBER 数据类型。从 SQL*Plus 会话中,执行以下命令:
ALTER SESSION SET
PLSQL_CCFLAGS = ' numversion:TRUE';
2.
您希望知道哪个代码执行速度更快。创建一个 CALC_CIRCLE
过程计算给定半径的圆的周长和面积。从 SQL*Plus 会话中,执行以下脚本:
@num_bdbl
num_bdbl.sql
脚本包含以下内容:
CREATE or REPLACE PROCEDURE Calc_circle( RADIUS $IF $$NUMVERSION $THEN NUMBER
$ELSE BINARY_DOUBLE $END)
as
SUBTYPE my_real IS
$IF $$numversion $THEN
NUMBER;
$ELSE
BINARY_DOUBLE;
$END
num_circ my_real;
num_area my_real;
BDBL_circ my_real;
BDBL_AREA my_real;
BEGIN
num_CIRC:= (3.14016408289008292431940027343666863227 * 2 * RADIUS);
NUM_AREA := (3.14016408289008292431940027343666863227*radius*radius);
DBMS_OUTPUT.PUT_LINE('The circumference is:'||num_circ);
DBMS_OUTPUT.PUT_LINE('The area is:'||num_area);
END ;
/
3.
执行 CALC_CIRCLE 过程,使用数字 1234567890 作为半径
set timing on
exec calc_circle(1234567890)
4.
使用 ALTER COMPILE 命令将 $$numversion 的值更改为 FALSE
来编译 CALC_CIRCLE,并使用相同的参数再次执行
CALC_CIRCLE。
ALTER PROCEDURE calc_circle COMPILE plsql_ccflags = 'numversion :false' REUSE SETTINGS;exec calc_circle(1234567890)
set timing off
您可以看出二者性能间的差异。本例阐明了可以使用条件编译在相同程序内有选择地测试两个版本的代码。
示例 5:将条件编译与不同的 Oracle
数据库版本结合使用
以下示例演示了 DBMS_DB_VERSION
常量与条件编译的结合使用。其中对 Oracle 数据库版本和发布均进行了检查。这个 CHECK_VERSIONS 过程使用
COMMIT WRITE IMMEDIATE NOWAIT 命令(已在 Oracle 数据库 10g 第 2 版中引入)。如果在
Oracle 数据库早期版本中执行该过程,则无法识别该命令。因此,该过程 DBMS_DB_VERSION 程序包检查数据库的版本。 如果返回的版本低于
10.2,那么将使用常规的 COMMIT 提交事务。当在版本为 10.2 或更高版本的数据库中执行该过程时,将使用 COMMIT
WRITE IMMEDIATE NOWAIT 命令完成该事务。
1.
现在可以测试 Oracle 数据库版本了。从终端窗口中执行以下脚本:
@check_version
check_version.sql
脚本包含以下内容:
CREATE OR REPLACE PROCEDURE check_version AS
BEGIN
-- some code which performs transaction processing ...
$if DBMS_DB_VERSION.VER_LE_10_2 $then
-- traditional commit
COMMIT;
DBMS_OUTPUT.PUT_LINE ('The transaction has been successfully committed.');
$else
-- faster COMMIT supported in 10.2
COMMIT WRITE IMMEDIATE NOWAIT;
DBMS_OUTPUT.PUT_LINE ('The transaction has been successfully committed.');
$end
END;
/
2.
现在,在 SQL*Plus 窗口中执行以下命令来运行 check_version
过程。
exec check_version
check_version
的输出说明:无论版本如何,该过程都能成功完成而且对最终用户而言是透明的。因此,没有理由在不同的数据库版本中使用不同的程序单元。