如何以编程方式判断存储过程是否有提交?在理想的世界中,如果一个过程调用另一个执行提交的过程,则可以传递地检测到它。
我不需要知道是否会执行提交(因为它可能在某些条件逻辑中),只是调用了提交。
例如,在这个片段中,我想知道p1调用commit,p2从不调用commit,p3调用p1调用。
create or replace procedure p1 as
begin
insert into foo values(99);
commit;
end;
create or replace procedure p2 as
begin
insert into foo values(99);
end;
create or replace procedure p3 as
begin
p1;
end;
答案 0 :(得分:2)
我有一个我为此写的包程序。我将粘贴下面的代码。
要使用它,只需使用您提供的名称调用“start_no_commit_section”即可。然后,稍后,使用相同的名称调用“end_no_commit_section”。如果已发出提交(或回滚),则对“end_no_commit_section”的调用将引发错误。
不幸的是,这并没有告诉你 提交的位置。如果我需要查看大量代码,我通常会在代码上运行DBMS_HPROF,然后在HPROF结果中查找提交(这将告诉我确切的行号)。
CREATE OR REPLACE PACKAGE BODY XXCUST_TRANSACTION_UTIL AS
----------------------------------------------------------------
-- See package spec for comments
----------------------------------------------------------------
TYPE no_commit_section_t IS RECORD (local_transaction_id VARCHAR2 (200));
TYPE no_commit_sections_tab IS TABLE OF no_commit_section_t
INDEX BY VARCHAR2 (80);
g_no_commit_sections no_commit_sections_tab;
PROCEDURE start_no_commit_section (p_section_name VARCHAR2) IS
l_section no_commit_section_t;
BEGIN
l_section.local_transaction_id := DBMS_TRANSACTION.local_transaction_id (create_transaction => TRUE);
g_no_commit_sections (SUBSTR (p_section_name, 1, 80)) := l_section;
END start_no_commit_section;
PROCEDURE end_no_commit_section (p_section_name VARCHAR2) IS
l_local_transaction_id VARCHAR2 (200);
BEGIN
l_local_transaction_id := DBMS_TRANSACTION.local_transaction_id (create_transaction => TRUE);
IF l_local_transaction_id != g_no_commit_sections (SUBSTR (p_section_name, 1, 80)).local_transaction_id THEN
-- There has been a commit or a rollback in the no-commit section
raise_application_error(-20001,'A commit or rollback has been detected in "No commit" section ' || p_section_name || '.');
END IF;
EXCEPTION
WHEN no_data_found THEN
-- Caller specified a non-existent commit section
raise_application_error(-20001,'"No commit" section ' || p_section_name || ' not established.');
END end_no_commit_section;
END XXCUST_TRANSACTION_UTIL;
答案 1 :(得分:0)
通过将DBA_DEPENDENCIES
与一些词法分析相结合,您可以找到所有(静态,显式,未包装)提交的99.99%。
如你所说,排除execute immediate 'c'||'o'||'m'||'m'||'i'||'t'
等病态案件是合理的。但像select * from dba_source where lower(text) like '%commit%';
这样的简单查询可能会导致合理代码出错。一些例子是其他单词的注释,字符串和子串。
对代码进行标记可以轻松过滤出注释,字符串和子字符串中使用的COMMIT
。展望未来,一个令牌可以进一步减少错误,因为COMMIT
syntax需要下一个真实的"令牌是WORK
,COMMENT
,FORCE
或;
之一。这消除了几乎所有使用COMMIT
作为标识符的情况。只有完整的解析器才能100%准确,但现有的PL / SQL解析器都无法应对挑战。 (除了Oracle的解析器,他们不共享。)
以下代码使用open-source tokenizer that I created。
安装强>
create or replace type nvarchar2_table is table of nvarchar2(2 char);
create or replace type token is object
(
type varchar2(4000),
value nclob,
--Although called "SQL" code and errm, these may also apply to PL/SQL.
--They would not match the real PL/SQL error messages, but the information
--should still be helpful to parse broken code.
sqlcode number,
sqlerrm varchar2(4000)
);
--Use VARRAY because it is guaranteed to maintain order.
create or replace type token_table is varray(2147483647) of token;
--Download file from https://raw.githubusercontent.com/jonheller1/plsql_lexer/master/tokenizer.plsql
@tokenizer.plsql
自定义功能
--Return 1 if a COMMIT; is found, else return a 0.
create or replace function has_commit(p_owner varchar2, p_name varchar2, p_type varchar2) return number is
type varchar2_nt is table of varchar2(4000);
v_text varchar2_nt := varchar2_nt();
v_source nclob;
v_tokens token_table;
--Return the next concrete token's value, or NULL if there are no more.
function get_next_concrete_value(p_token_index in number) return nvarchar2 is
begin
--Loop through the tokens.
for i in p_token_index + 1 .. v_tokens.count loop
--Return it if it's concrete.
if v_tokens(i).type not in ('whitespace', 'comment', 'EOF') then
return v_tokens(i).value;
end if;
end loop;
--Return NULL if nothing was found.
return null;
end get_next_concrete_value;
begin
--Get source.
select text
bulk collect into v_text
from all_source
where owner = p_owner
and name = p_name
and type = p_type;
--Convert source into a single NCLOB.
for i in 1 .. v_text.count loop
v_source := v_source || v_text(i);
end loop;
--Tokenize the source.
v_tokens := tokenizer.tokenize(v_source);
--Look for a COMMIT token followed by either WORK, COMMENT, FORCE, or ;.
--Return 1 (true) if it's found.
--http://docs.oracle.com/database/121/SQLRF/statements_4011.htm#i2060233
for v_token_index in 1 .. v_tokens.count loop
if
(
upper(v_tokens(v_token_index).value) = 'COMMIT'
and
upper(get_next_concrete_value(v_token_index))
in ('WORK', 'COMMENT', 'FORCE', ';')
) then
return 1;
end if;
end loop;
--Not found, return 0.
return 0;
end has_commit;
/
更多样本程序
除了P1,P2和P3之外,下面还有一些不常见的语法示例。
--A valid, but unusual way to commit.
create or replace procedure p4 as
begin
commit work comment 'commiting';
end;
/
--Difficult but reasonable code that does not contain a commit.
create or replace procedure p5 as
--Good luck parsing this string with a regular expression.
a_string varchar2(1000) := q'<'
commit;
>';
begin
/* Don't count this!
commit;
*/
--Or this
--commit;
--Getting a bit ridiculous here...
select 1 commit into a_string from dual;
end;
/
--Unreasonable code that will incorrectly reports as having a commit.
create or replace procedure p6 as
v_number number;
begin
select 1
into v_number
from
(
--Naming a variable commit is legal but obviously a bad idea.
select 1 commit
from dual
)
--Uh-oh... "COMMIT" followed by ";" is bad news.
where 1 = commit;
end;
/
测试程序和功能
select object_name, has_commit(owner, object_name, object_type) has_commit
from dba_objects
where owner = user
and object_type = 'PROCEDURE'
and object_name like 'P_'
order by object_name;
OBJECT_NAME HAS_COMMIT
----------- ----------
P1 1 --Correct
P2 0 --Correct
P3 0 --Correct
P4 1 --Correct
P5 0 --Correct
P6 1 --WRONG
对象层次结构
使用这样的语句来创建对象层次结构。
--Object hierarchy.
--Creating temporary table for quick performance issues.
create table object_hierarchy as
select dba_objects.owner, dba_objects.object_name, dba_objects.object_type
,level object_level
,connect_by_root(dba_objects.owner) root_owner
,connect_by_root(dba_objects.object_name) root_object_name
,connect_by_root(dba_objects.object_type) root_object_type
from dba_objects
left join dba_dependencies
on dba_objects.owner = dba_dependencies.owner
and dba_objects.object_name = dba_dependencies.name
and dba_objects.object_type = dba_dependencies.type
start with (dba_objects.owner, dba_objects.object_name, dba_objects.object_type) in
(
select owner, object_name, object_type
from dba_objects
where owner = user
and object_type = 'PROCEDURE'
and object_name like 'P_'
)
connect by dba_objects.owner = prior referenced_owner
and dba_objects.object_name = prior referenced_name
and dba_objects.object_type = prior referenced_type
order siblings by object_name;
具有COMMIT状态的层次结构
--Object Hierarchy with HAS_COMMIT per row and per group.
select root_owner, root_object_name, root_object_type, owner, object_name, object_type, object_level,
max(has_commit) over (partition by root_owner, root_object_name, root_object_type) has_any_commit,
has_commit
from
(
--Combine hierarchy with HAS_COMMIT.
select root_owner, root_object_name, root_object_type, object_level
,object_hierarchy.owner, object_hierarchy.object_name, object_hierarchy.object_type
,has_commit
from object_hierarchy
join
(
--Add has_commit
select owner, object_name, object_type
--Standard is an exception because it defines the procedure "COMMIT;".
,case
when owner = 'SYS' and object_name = 'STANDARD' then
0
else
has_commit(owner, object_name, object_type)
end has_commit
from
(
select distinct owner, object_name, object_type
from object_hierarchy
)
) add_has_commit
on object_hierarchy.owner = add_has_commit.owner
and object_hierarchy.object_name = add_has_commit.object_name
and object_hierarchy.object_type = add_has_commit.object_type
) hierarchy_has_commit
order by root_owner, root_object_name, root_object_type, object_level,
owner, object_name, object_type;
结果示例
结果按root用户,名称和类型分组。 HAS_ANY_COMMIT指示该族中的任何过程是否具有COMMIT,HAS_COMMIT指示该特定对象是否具有COMMIT。
ROOT_OWNER ROOT_OBJECT_NAME ROOT_OBJECT_TYPE OWNER OBJECT_NAME OBJECT_TYPE OBJECT_LEVEL HAS_ANY_COMMIT HAS_COMMIT
JHELLER P1 PROCEDURE JHELLER P1 PROCEDURE 1 1 1
JHELLER P2 PROCEDURE JHELLER P2 PROCEDURE 1 0 0
JHELLER P3 PROCEDURE JHELLER P3 PROCEDURE 1 1 0
JHELLER P3 PROCEDURE JHELLER P1 PROCEDURE 2 1 1