[Oracle10G_R2]01.SQL 和 PL/SQL 特性
?
??? 透明數據加密和 XQuery支持是Oracle數據庫10g第2版中與SQL相關的兩個最突出的新特性,但還有其它新特性。
?
??? 這一部分涉及:
?
??? ● 透明數據加密
??? ● XML 查詢
??? ● 增強的 COMMIT
??? ● 錯誤事件記錄子句
??? ● WRAP 程序包
??? ● 條件編譯
??? ● 無限制的 DBMS 輸出
?
?
透明數據加密
?
??? 對于加密,許多用戶深感矛盾:他們既感興趣,又因意識密鑰管理的復雜性而感到慎重,如果處理不當,則會導致設置的效率低下。加密和解密值還會帶來相關的性能開銷,這使得大部分應用程序架構師不太樂于接受該過程。結果是,很多系統設計根本沒有加密,只是構筑了強大的外圍防護,如強大的口令和適當的授權方案。
?
??? 但是,請想象一下如果整個服務器被盜了,甚至只是磁盤被盜,這些磁盤可以裝配在具有相同操作系統的服務器上,然后其中的數據將被銷毀殆盡。或者有一個的 DBA 品行不端,在日常業務活動中惡意突破了外圍防護,然后將您所有重要的客戶信息洗劫一空。在這兩種情況下,如果所涉及的商業機構是在加利福尼亞州(可能不久之后在美國的其他州),它們在法律上有責任將安全漏洞的情況通知給所有受到影響的客戶。
??? 在上述罕見(但確是事實)的情況中,認證方案沒有實際意義。這就是為什么對于那些將安全作為頭等大事的機構而言,透明數據加密(TDE)是一個如此有用的特性;它支持加密,同時將密鑰管理的復雜性交給數據庫引擎來處理。同時,它允許DBA在不必實際看到數據的情況下管理數據庫表。
?
??? 在Oracle數據庫10g第2版中使用TDE時,可以隨時地對表中的一列或多列進行加密;只需將列定義為加密形式即可,不用編寫代碼。請記住,加密需要使用密鑰和算法對輸入值進行加密。TDE 為特定的表生成單獨的密鑰。這種方法方便了密鑰管理卻也更易被他們竊取,所以數據庫提供了另一種密鑰——萬能密鑰——它可以在數據庫級別上設置。表密鑰是利用萬能密鑰進行加密的,要獲得表密鑰就需要這個萬能密鑰。因此,對列進行解密時需要萬能密鑰和表密鑰。(有關常規加密以及在 Oracle 中使用供應程序包的進一步探討,請參見Oracle雜志專欄“為您的數據資產加密”。)
?
??? 萬能密鑰存儲在數據庫外一個稱為“錢夾”的地方——默認位置在 $ORACLE_BASE/admin/$ORACLE_SID/wallet。在概念上,它類似于下圖。
?
?
??? 在配置 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
)
?
??? 在這里,您在列 SSN 和 FOLIO_ID 上使用了 TDE,它們現在以加密方式存儲在表本身。但是,當用戶從表中選擇時,她看到以明文表示的數據,因為在檢索過程中已經完成了解密。如果磁盤被盜,則包含在表段中的信息仍然保持加密狀態。盜竊者需要表密鑰才能看到加密的值,但是要獲得表密鑰,他需要萬能密鑰,而萬能密鑰存儲在外部,因此無法獲得。
?
??? 注意列 SSN 和 FOLIO_ID 后面的子句,這些子句指定 ENCRYPT 使用 128 位高級加密標準。
?
??? 數據庫擁有預先配置的錢夾。要設置錢夾口令,可使用命令:
?
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 是訪問控制的理想補充(而不是替代)。
?
在 SQL 中查詢 XML
?
??? 長期以來,對于很多包含大量字符內容的應用程序的數據類型而言,XML 已成為事實上的標準。最近它也已成為其他應用程序的的存儲方法,而不僅僅限于大量的內容。
?
??? Oracle 從 Oracle9i 數據庫開始就提供 XML 與數據庫的集成。在該版本中,您可以使用很多不同的方法來查詢 XML 內容。在 Oracle 數據庫 10g 第 2 版中,新的 XQuery 和 XMLTable 函數使查詢 XML 內容變得更容易。(注意:本文將不全面討論 XQuery 的規范,有關背景知識,請閱讀 Oracle 雜志文章“XQuery:一種新的搜索方法”。)
?
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 數據庫中推出的。
?
??? XQuery 的一般結構由縮略語 FLOWR(發音為“flower”)來描述,它代表 FOR、LET、ORDER BY、WHERE 和 RETURN。在以上的示例中,我們看到第 2 行定義了數據的來源,即從 1 到 9 的一系列數字。它可以是任何來源 — 一組標量值或者 XML 數據的一個元素,由 FOR 子句指定。該行還指定一個變量來存取這些值 (var1)。在第 3 行中,另一個變量 var2 擁有的值是 var1 加 1,由 LET 子句指定。
?
??? 對于所有這些返回值,我們只關心 6 以下的值,這是由子句 WHERE 指定的。然后我們根據 var2 的值以降序方式對結果集排序,如第 6 行中的 ORDER BY 子句所示。最后,利用 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 的對比
??? 既然您已經了解了在常規 SQL 查詢中使用 XML 的兩種方法,就讓我們來看這二種方法適用的情形。
?
??? 第一種方法 XQuery 允許您獲取 XMLType 形式的數據,在任何支持它的程序或應用程序中都可以將其作為 XML 來處理。在您所看到的示例中,帳戶數據的結果輸出是 XML 格式,而您可以使用任何工具(不必是關系型工具)來處理和顯示這些數據。第二種方法 XMLTable 結合了常規 SQL 和 XML 的功能。帳戶數據的結果輸出不是 XML 格式,而是關系型數據。
?
??? 注意兩個案例中的源代碼都是 XML,但是 XQuery 使用 XMLType 來表示 XML 格式的數據,而 XMLTable 將其表示為關系表,可以像常規表一樣進行處理。這種功能非常適用于要輸出表的現有程序,它引入了 XML 的特性。
?
??? XML 在預先不太了解確切的數據結構的場合中非常有用。在以上示例中,根據不同模式,通信記錄也不相同。如果是電子郵件,則屬性可能是接收方的電子郵件地址、回復地址、任何復本(cc:、bcc: 等等)、消息的文本等等。如果是電話呼叫,則屬性是所呼叫的電話號碼、號碼的類型(家庭、工作、移動電話等等)、應答者、留下的語音郵件等等。如果您要設計一個包含所有可能的屬性類型的表,則它會包括很多列,并且極其冗長,造成讀取困難。但是,如果您只有一個 XMLType 列,則可以將所有內容填在那里,但仍然保持通信類型的獨特屬性。查詢仍然可以使用簡單的 SQL 接口,使應用程序的開發變得輕而易舉。
?
增強的 COMMIT
?
??? 當提交會話時,將刷新重做日志緩沖區,將其內容存儲到磁盤上的聯機重做日志中。此過程確保在對數據庫進行恢復操作時,可以根據需要利用重做日志中回放事務處理。
?
??? 但是有時您可能想對一部分受保證的恢復能力進行調整,以獲得更好的性能。利用 Oracle 數據庫 10g 第 2 版,您現在可以控制如何將重做流寫入到聯機日志文件。您可以在執行提交語句時控制這種行為,或者只需更改數據庫的默認行為即可。
?
??? 讓我們來看提交語句是工作過程。在事務處理后,當執行 COMMIT 時,可以附帶一個子句:
?
COMMIT WRITE <option>
?
??? 其中 <option> 是影響重做流的部分。選項 WAIT 是默認行為。例如,您可以執行:
?
COMMIT WRITE WAIT;
?
??? 此命令與 COMMIT 本身的效果相同。在重做流寫入到聯機重做日志文件之前,提交命令不會將控制權交還給用戶。如果您不希望等待,則可以執行:
?
COMMIT WRITE NOWAIT;
?
??? 這樣,控制權立即返還給會話,甚至是在將重做流寫入聯機重做日志之前。
?
??? 當執行提交命令時,日志寫入器進程將重做流寫入到聯機重做日志。如果您正在進行一系列事務處理(如在批處理環境中),則可能不希望如此頻繁地進行提交。當然,最好的操作過程是更改應用程序以減少提交數量;但這可能說起來容易做起來難。在這種情況下,您只需執行以下的提交語句:
?
COMMIT WRITE BATCH;
?
??? 此命令將以批量方式將重做流寫入到日志文件中,而不是每次提交都執行寫操作。在頻繁提交的環境中,您可以使用這種技術來減少日志緩沖區刷新。如果您希望立即寫入日志緩沖區,則可以執行:
?
COMMIT WRITE IMMEDIATE;
?
??? 如果您數據庫默認使用某種特定的提交行為,則可以執行以下語句。
?
ALTER SYSTEM SET COMMIT_WRITE = NOWAIT;
?
??? 此命令將使數據庫默認使用這一行為。您還可以使其成為會話級默認行為:
?
ALTER SESSION SET COMMIT_WORK = NOWAIT;
?
??? 對于任何參數,如果完成設置后,則該參數在系統級執行。如果有會話級的設置,則會話級設置優先,而最后如果 COMMIT 語句后面有子句,則該子句優先。
?
??? 此選項不可用于分布式事務處理。
記錄錯誤并繼續運行:錯誤事件記錄子句
?
??? 假設您正試圖將表 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 中的記錄均未被裝載。現在,將錯誤事件記錄項打開,嘗試同樣的操作。首先,您需要創建一個表來保存由 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 包含 10 行,但只有六行被插入;其他四行由于某種錯誤而被拒絕。要找出錯誤是什么,可查詢 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) vi? 9997
olated
1? ORA-00001:unique constraint (ARUP.PK_ACCOUNTS)vi? 9998
olated
1? ORA-00001:unique constraint (ARUP.PK_ACCOUNTS) vi 9999
olated
1? ORA-00001:unique constraint (ARUP.PK_ACCOUNTS) vi 10000
olated
?
??? 請注意列 ORA_ERR_NUMBER$,它顯示在 DML 語句執行期間所遇到的 Oracle 錯誤號,還有 ORA_ERR_MESG$,它顯示錯誤消息。在本例中,您可以看到四條記錄被丟棄是因為它們與主鍵約束 PK_ACCOUNTS 相沖突。該表還捕獲表 ACCOUNTS 的所有列,包括列 ACC_NO。查看被拒絕的記錄,注意這些帳號已經在表中存在,因此這些記錄由于 ORA-00001 錯誤而被拒絕。如果沒有錯誤事件記錄子句,則整個語句將會出錯,不會拒絕任務記錄。通過這個子句,只有無效的記錄被拒絕;其他所有記錄均得以接受。
從源頭保護代碼:WRAP 程序包
?
??? PL/SQL 程序單元經常包含關于公司流程和商業秘密的非常敏感和機密的信息,這使得它們與表相類似,成為受保護的實體組。為防止未經授權而查看源代碼的情況,我們要經常使用 wrap 命令行實用程序,這將使程序變得很混亂。
?
??? 只有在創建 PL/SQL 腳本后才能調用 wrap;該實用程序將輸入的明文打包為一個文件。但是,在某些情況下,您可能希望在 PL/SQL 代碼中動態生成包裝。在這種情況下,因為還不存在源文件,不能調用 wrap 實用程序。
?
??? 由于 Oracle 數據庫 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
... and so on ...
?
??? 第一行 procedure p1 wrapped 是確認以打包方式創建過程。如果您利用 DBMS_METADATA.GET_DDL() 函數來獲取該過程的 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
... and so on ...
?
??? WRAP 函數的輸出是一個可傳遞的參數,它代表著 PL/SQL 代碼的打包輸出結果。該參數可以保存在純文件文件或表中,可以在以后執行。如果您生成的代碼要在其他地方部署,并且必須要保證代碼的安全性,則這種方法很有用。
?
??? 如果您可以將所存儲代碼的全部文本作為一個 varchar2 數據類型(大小限制為 32K)來傳遞,則這一方法可以正常工作。如果 PL/SQL 代碼超過 32K,則您必須使用一種略微不同的方法:接受一個集合變量作為輸入。
?
??? 在這里您可以使用一個供應的數據類型:程序包 DBMS_SQL 中的 varchar2。這是一個集合數據類型 (TABLE OF VARCHAR2),表的每個單元都接收多達 32K 的文本;可隨意增加該表所含的單元數,以滿足您的需要。例如,假設您必須包裝一個名為 myproc 的非常長的過程,其定義如下:
create or replace procedure myproc as
l_key VARCHAR2(200);
begin
l_key := 'ARUPNANDA';
end;
?
??? 當然,這根本不是一個非常長的過程;但是為了示范起見,假設它很長。為了將其創建為打包形式,您要執行以下的 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 行中,我們用要打包的代碼來填充這些行。在本示例中,同樣為了簡單起見,我使用了非常短的行。實際上,您可能要使用非常長的行,其大小多達 32KB。同樣,我在數組中只使用了 7 個單元;實際上您可能要使用若干單元來填充全部代碼。
?
??? 第 11 到第 15 行表明我如何調用該過程,以便將該過程創建為打包形式。在第 12 行中,我將集合作為一個參數 DDL 來傳遞。但是,在這里暫停一下 — 我已經分配了一個注釋作為數組的第一個單元,可能用于文檔。但它不是有效的語法。同樣,我將另一個注釋分配給數組的最后一個單元 (7),它也不是用于創建過程的有效語法。為了使包裝操作僅僅處理有效的行,我在第 13 和第 14 行中指定了存儲我們代碼的集合的最低 (2) 和最高 (6) 的單元。參數 LB 表示數組的下界,在本示例中是 2,而 HB 是上界 (6)。
?
??? 使用這種方法,現在可以從您的 PL/SQL 代碼中以打包方式創建任意大小的過程。
PL/SQL 中的條件編譯:一次編寫,多次執行
?
??? 你們中很多人曾經使用過 C 語言,它支持編譯器指令的概念。在 C 程序中,根據相關編譯器的版本,特定變量的值有可能不同。
?
??? 在 Oracle 數據庫 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 的值被設為 false。現在更改該變量的值并重新執行該函數。
SQL> alter session set plsql_ccflags = 'PPVAL:FALSE';
?
Session altered.
?
SQL> select myfunc from dual;
?
MYFUNC
---------------------------------------------------------
PPVAL was TRUE
?
??? 雖然這里 ppval 的值在會話中是 FALSE,但函數沒有采用它;而是采用了在編譯期間所設置的值。現在,重新編譯該函數并執行它。
SQL> alter function myfunc compile;
?
Function altered.
?
SQL> select myfunc from dual;
?
MYFUNC
---------------------------------------------------
PPVAL was FALSE
?
??? 在編譯期間,ppval 的值是 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 確保在以后重新編譯函數時使用相同的編譯器指令。
?
??? 讓我們來看這個新特性的另一個變體。除了條件變量的定義之外,您還可以在條件編譯中檢查程序包的靜態常量。例如,假設您希望基于一個布爾型打包常數來控制 PL/SQL 過程的調試輸出。首先創建程序包
create or replace package debug_pkg
is
debug_flag constant boolean := FALSE;
end;
?
??? debug_flag 是在代碼中確定條件邏輯的常數。現在您可以將代碼嵌入程序包,如下所示:
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 數據庫 10g 第 2 版中,該限制已經解除:現在最大輸出數量是不封頂的。您只需通過執行以下命令,就可以將其設為“unlimited”
set serveroutput on
?
??? 以上語句的輸出結果如下:
SQL> show serveroutput
serveroutput ON size 2000 format WORD_WRAPPED
?
??? 注意輸出的最大默認值過去是 2000 。在 Oracle 數據庫 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 數據庫 10g 第 2 版中,行可以具有任意長度。