以前寫過一篇文章總結了oracle存儲過程轉達夢8存儲過程時踩過的坑(https://www.cnblogs.com/kingstarer/p/13379053.html)html
當時裏面只總結了3個大坑,實際上我還碰到過很多小坑java
由於這段時間,咱們項目組決定使用java重寫舊系統,放棄了原來使用存儲過程那一套,因此最近就一直沒再去整理以前的小坑。正則表達式
今天正好記起來這事,就花點時間整理一下。雖然我已經不用到這些經驗了,但但願對其餘人有幫助。sql
(用java寫邏輯比用存儲過程方便好多,建議你們仍是儘可能放棄存儲過程吧)數據庫
達夢8安裝後默認的時區,不是操做系統的時區,而是0時區。這會致使sysdate返回時間有誤,須要修改/etc/dm_svc.conf文件,在文件中添加TIME_ZONE=(480)才正常,以下:session
[root@ecs-htgx-0003 etc]# vi /etc/dm_svc.conf
# 以#開頭的行表示是註釋
# 全局配置區 dm_svc.conf
TIME_ZONE=(480)
LANGUAGE=(cn)oracleDMHTGX=(192.168.0.137:5236)app
# 服務配置區
[DMHTGX]
LOGIN_MODE=(2)函數
達夢的正則匹配有問題,我踩的一個坑是這個:oop
select regexp_replace('CC4.city', '([(+-*/|><=,]|^)(.+)', '\2', 1, 1, 'i') from dual;
這個語句執行結果oracle跟達夢不同
select regexp_replace('CC4.city', '([+-*]|^)(.+)', '\2', 1, 1, 'i') from dual; --輸出 C4.city
--把+和-調換位置 oracle輸出結果是同樣的,但達夢倒是不同
select regexp_replace('CC4.city', '([-+*]|^)(.+)', '\2', 1, 1, 'i') from dual; --輸出 CC4.city
仔細分析一下,是由於達夢把[]裏面的+號字符,認爲是正則表達式的元字符+(匹配前面的子表達式一次或屢次)
oracle用戶遷移到達夢數據庫後,發現多了好多觸發器。仔細看了一下代碼,應該是實現外鍵case delete的。估計是達夢不支持外鍵級聯刪除,在遷移時自動把這些級聯刪除改爲觸發器。
不過改爲觸發器後,就沒法實現oracle的延遲約束功能了(alter session set constraints=deferred)
這個問題無解
使用BULK COLLECT的查詢語句,查不到記錄時行爲不一樣:oracle的BULK COLLECT查詢默認是不會拋出no_data_found異常的,而達夢會。
解決方法是捕獲no_data_found異常後作忽略處理。
DBMS_SQL有bug呀,獲取出來的col_max_len是0,例子以下:
create table mydual as select * from dual; declare v_col_cnt NUMBER; v_cursorid NUMBER; v_desc_t DBMS_SQL.desc_tab2; begin dbms_output.enable; v_cursorid := DBMS_SQL.open_cursor; DBMS_SQL.parse(v_cursorid, 'select ''123'' c1, DUMMY c2 from mydual', dbms_sql.native); DBMS_SQL.describe_columns(v_cursorid, v_col_cnt, v_desc_t); FOR i IN 1..v_col_cnt LOOP dbms_output.put_line('i ' || i || ' name = ' || v_desc_t(i).col_name || ' col_max_len = ' || v_desc_t(i).col_max_len); END LOOP; end;
DBMS_SQL這個包還有其它好多bug,具體我沒記下來,你們使用當心點了。
當下標值在容器中找不到時,達夢沒法正確獲取prior和next,驗證的存儲過程以下:
declare type v_mp_type is table of number index by PLS_INTEGER; v_mp v_mp_type; begin dbms_output.enable; v_mp(1) := 1; v_mp(3) := 2; -- oracle輸出1 達夢輸出空 dbms_output.put_line('v_mp.prior(2) = ' || v_mp.prior(2)); end;
解決方法是本身寫prior和next函數:
-- 須要寫函數代替oracle的prior和next function get_prior_index(v_mp IN v_mp_type, v_ind IN PLS_INTEGER) return PLS_INTEGER is v_vv_last PLS_INTEGER := null; vv PLS_INTEGER := v_mp.first; begin -- 遍歷v_mp 作比較 while vv is not null loop -- 若是發現某個下標值比傳進來的v_ind大或者相等 則返回上一個下標值 -- (若是是第一個下標則返回NULL) if (vv >= v_ind) then return v_vv_last; end if; v_vv_last := vv; vv := v_mp.next(vv); end loop; -- 若是遍歷完全部下標,仍未找到大於等於v_ind的值,則返回最大的下標v_mp.last return v_vv_last; end; function get_next_index(v_mp IN v_mp_type, v_ind IN PLS_INTEGER) return PLS_INTEGER is v_vv_last PLS_INTEGER := null; vv PLS_INTEGER := v_mp.last; begin -- 反序遍歷v_mp 作比較 while vv is not null loop -- 若是發現某個下標值小於等於v_ind 則返回上一個下標值 --(若是是最大的下標則返回NULL) if (vv <= v_ind) then return v_vv_last; end if; v_vv_last := vv; vv := v_mp.prior(vv); end loop; -- 若是反序遍歷完全部下標,仍未找到小於等於v_ind的值,則返回最小的下標v_mp.first return v_vv_last; end;
這個網上有不少文章介紹過了,達夢默認兩個整數相除,結果類型仍是整數,而oracle是小數。
因此在oracle咱們可使用trunc(v_date)-1/86400獲取1秒前的時間,但在達夢,這樣寫跟trunc(v_date) - 0是同樣的。
解決方法是改爲trunc(v_date)-1.0/86400
若是把一個變量傳給一個函數作爲函數出參,以獲取函數返回值,oracle默認會把這個函數清空,而達夢不會。
這就致使一個問題,
驗證代碼以下:
/*測試出參 在oracle期待輸出爲空 可是達夢會出現error*/ create or replace procedure testKinstarerOutParam(str OUT varchar2) as begin dbms_output.put_line('str = ' || str); if (str is not null) THEN RAISE_APPLICATION_ERROR(-20001, '出參沒有清空'); end if; end; / create or replace procedure testKinstarerCallOutParam as strIn varchar2(64) := 'error'; begin testKinstarerOutParam(strIn); end; / dbms_output.enable; begin testKinstarerCallOutParam(); end;
oracle可使用to_char函數對lob類型字段操做,但在達夢,有時這樣操做會失敗,報錯爲DBMS_LOB.READ line 1157
不知道爲何,達夢沒有提供diutil包。裏面有一些函數,挺方便,沒有真惋惜。因此我本身寫了一個
CREATE OR REPLACE PACKAGE diutil IS -- bool_to_int: translates 3-valued BOOLEAN TO NUMBER FOR USE -- IN sending BOOLEAN parameter / RETURN VALUES -- BETWEEN pls v1 (client) AND pls v2. since sqlnet -- has no BOOLEAN bind variable TYPE, we encode -- booleans AS false = 0, true = 1, NULL = NULL FOR -- network transfer AS NUMBER -- FUNCTION bool_to_int( b BOOLEAN) RETURN NUMBER; -- int_to_bool: translates 3-valued NUMBER encoding TO BOOLEAN FOR USE -- IN sending BOOLEAN parameter / RETURN VALUES -- BETWEEN pls v1 (client) AND pls v2. since sqlnet -- has no BOOLEAN bind variable TYPE, we encode -- booleans AS false = 0, true = 1, NULL = NULL FOR -- network transfer AS NUMBER -- function int_to_bool( n NUMBER) return boolean; function get_sql_hash(name IN varchar2, v_hash OUT RAW, pre10ihash OUT number) return number; function rpad_dm(string varchar2, padded_length number, pad_string varchar2 := ' ') return varchar2; function copy1kList(v_input ua_utl_def.t_str_1k_list) return ua_utl_def.t_str_1k_list; end diutil; CREATE OR REPLACE PACKAGE BODY diutil IS -------------------- -- bool_to_int -------------------- FUNCTION bool_to_int(b BOOLEAN) RETURN NUMBER IS BEGIN IF b THEN RETURN 1; ELSIF NOT b THEN RETURN 0; ELSE RETURN NULL; END IF; END bool_to_int; -------------------- -- int_to_bool -------------------- FUNCTION int_to_bool(n NUMBER) RETURN BOOLEAN IS BEGIN IF n IS NULL THEN RETURN NULL; ELSIF n = 1 THEN RETURN true; ELSIF n = 0 THEN RETURN false; ELSE RAISE value_error; END IF; END int_to_bool; function get_sql_hash(name IN varchar2, v_hash OUT RAW, pre10ihash OUT number) return number IS v_hash_varchar2 VARCHAR2(128); v_hash_tmp VARCHAR2(128); BEGIN -- Compute a hash value for the given string using md5 algo -- Input arguments: -- name - The string to be hashed. -- hash - An optional field to store all 16 bytes of returned -- hash value. -- pre10ihash - An optional field to store the pre 10i database -- version hash value. -- Returns: -- A hash value (last 4 bytes) based on the input string. -- The md5 hash algorithm computes a 16 byte hash value, but -- we only return the last 4 bytes so that we can return an -- actual number. One could use an optional RAW parameter to -- get all 16 bytes and to store the pre 10i hash value of 4 -- 4 bytes in the pre10ihash optional parameter. -- Utl_Raw.Cast_To_Raw( v_hash_varchar2 := DBMS_OBFUSCATION_TOOLKIT.MD5(name); v_hash := Utl_Raw.cast_to_raw(v_hash_varchar2); v_hash_tmp := substrb(v_hash, 13, 4); pre10ihash := to_number(v_hash_tmp, 'XXXXXXXXXX'); --TODO: 這裏實現有問題 pre10ihash是啥意思我沒看懂 -- select Utl_Raw.Cast_To_Raw(DBMS_OBFUSCATION_TOOLKIT.MD5(input_string =>'abc')) a from Dual return to_number(v_hash_tmp, 'XXXXXXXXXX'); END; function rpad_dm(string varchar2, padded_length number, pad_string varchar2 := ' ') return varchar2 IS v_len number := lengthb(string); BEGIN dbms_output.put_line('v_len - padded_length = ' ); if padded_length < v_len THEN return substrb(string, 1, padded_length); --若是輸入長度小於原字符串長度,則調用substrb截斷 elsif padded_length = v_len THEN return string; --若是長度相等直接返回原串便可 else return string || rpad(' ', padded_length - v_len, pad_string); --若是長度大於原字符串,則在後面補空格 end if; END; function copy1kList(v_input ua_utl_def.t_str_1k_list) return ua_utl_def.t_str_1k_list IS v_tmplist ua_utl_def.t_str_1k_list; v_ind PLS_INTEGER; begin if v_input.count > 0 then /* for vv in v_input.first .. v_input.last LOOP v_tmplist(vv) := v_input(vv); end loop; */ v_ind = v_input.first; while v_ind is not null loop v_tmplist(v_ind) := v_input(v_ind); v_ind = v_input.next(v_ind); end loop; end if; return v_tmplist; end; end diutil;
在達夢客戶端執行新建存儲過程時須要注意,即便建立成功了,也只表明語法正確。極可能存儲過程有其它問題致使沒建成功,還是無效狀態。
解決方法是建立存儲過程以後再手動執行 alter PROCEDURE 存儲過程名稱 compile;
衆所周知,oracle提供一個函數dbms_utility.format_error_backtrace,用於獲取異常模塊處理時調用,獲取函數堆棧信息,裏面會有明確的函數名稱和源碼位置信息
但達夢調用這個函數返回的是一堆看不懂的內部符號
這個問題對我遷移形成很多困擾,由於咱們業務的主要邏輯就是在存儲過程裏面實現的。咱們須要在程序出異常時登記日誌,記錄函數堆棧信息,以方便跟蹤。
通過我不懈研究,終於解決了達夢沒法獲取堆棧信息的問題,這裏跟你們分享一下解決方法:
dbms_output.enable; select * from q$log order by 1 desc; select * from q$error_instance order by 1 desc; CREATE OR REPLACE PROCEDURE logIntoDb(loglevel PLS_INTEGER, inf IN varchar2, callStack IN varchar2) IS PRAGMA AUTONOMOUS_TRANSACTION; --日誌登記須要使用自治事務 BEGIN -- loglevel 0 debug 10 inf 20 err INSERT INTO q$log (id, "CONTEXT", text, call_stack, created_on, created_by, app_system, app_module) VALUES (q$log_seq.nextval, decode(logLevel, 0, 'debug', 'other'), inf, callStack, SYSDATE, USER, 'unify_audit', 'logIntoDb'); commit; END; alter PROCEDURE logIntoDb compile; CREATE OR REPLACE FUNCTION getErrorBackTrace() return varchar2 IS -- 達夢不能直接獲取堆棧信息,須要套在函數裏面 c_stack VARCHAR2(6000) := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; BEGIN return c_stack; END; / alter FUNCTION getErrorBackTrace COMPILE; CREATE OR REPLACE PROCEDURE debugHt(inf IN varchar2) IS -- 默認不使用異常 這樣不能記錄行號 -- 使用異常能夠記錄行號但性能會降低,用於調試 v_useException boolean := true; BEGIN if (v_useException) then -- 主動建立一個異常,這樣才能夠FORMAT_ERROR_BACKTRACE函數纔有值 RAISE_APPLICATION_ERROR(-20001, 'debug'); else logIntoDb(0, inf, DBMS_UTILITY.format_call_stack); end if; exception when others then -- 達夢的DBMS_UTILITY.FORMAT_ERROR_BACKTRACE函數必須隔位獲取 -- 否則只能獲取當前函數的堆棧信息 logIntoDb(0, inf, getErrorBackTrace()); END; / alter PROCEDURE debugHt COMPILE; CREATE OR REPLACE PROCEDURE proc2 IS BEGIN debugHt('hello log'); execute immediate 'delete * from dual1233'; exception when others then debugHt('hello exp'); END; / alter PROCEDURE proc2 COMPILE; CREATE OR REPLACE PROCEDURE proc3 IS BEGIN proc2(); END; / CREATE OR REPLACE PROCEDURE proc4 IS BEGIN proc3(); END; / begin proc4(); end;