diff --git a/source/core/coverage/ut_coverage.pkb b/source/core/coverage/ut_coverage.pkb index 5754cf4e9..caa75ae51 100644 --- a/source/core/coverage/ut_coverage.pkb +++ b/source/core/coverage/ut_coverage.pkb @@ -35,101 +35,122 @@ create or replace package body ut_coverage is return g_develop_mode; end; - function get_cov_sources_sql(a_coverage_options ut_coverage_options) return varchar2 is - l_result varchar2(32767); - l_full_name varchar2(100); - l_view_name varchar2(200) := ut_metadata.get_source_view_name(); + function get_cov_sources_sql(a_coverage_options ut_coverage_options, a_skip_objects ut_object_names) return varchar2 is + l_result varchar2(32767); + l_full_name varchar2(32767); + l_join_mappings varchar2(32767); + l_filters varchar2(32767); + l_mappings_cardinality integer := 0; begin - if a_coverage_options.file_mappings is not null and a_coverage_options.file_mappings.count > 0 then - l_full_name := 'f.file_name'; - else - l_full_name := 'lower(s.owner||''.''||s.name)'; - end if; - l_result := ' - select full_name, owner, name, line, to_be_skipped, text - from ( - select '||l_full_name||q'[ as full_name, - s.owner, - s.name, - s.line - - coalesce( - case when type!='TRIGGER' then 0 end, - (select min(t.line) - 1 - from ]'||l_view_name||q'[ t - where t.owner = s.owner and t.type = s.type and t.name = s.name - and regexp_like( t.text, '[A-Za-z0-9$#_]*(begin|declare|compound).*','i')) - ) as line, - s.text, ]'; - l_result := l_result || - q'[case - when - -- to avoid execution of regexp_like on every line - -- first do a rough check for existence of search pattern keyword - (lower(s.text) like '%procedure%' - or lower(s.text) like '%function%' - or lower(s.text) like '%begin%' - or lower(s.text) like '%end%' - or lower(s.text) like '%package%' - ) and - regexp_like( - s.text, - '^([\t ]*(((not)?\s*(overriding|final|instantiable)[\t ]*)*(static|constructor|member)?[\t ]*(procedure|function)|package([\t ]+body)|begin|end([\t ]+\S+)*[ \t]*;))', 'i' - ) - then 'Y' - end as to_be_skipped ]'; - - l_result := l_result ||' from '||l_view_name||q'[ s]'; - + l_result := q'[ + with + trigger_source_offsets as ( + select min(s.line) - 1 offset, s.owner, s.name, s.type + from {sources_view} s + where s.type = 'TRIGGER' + {filters} + and (lower(s.text) like '%begin%' or lower(s.text) like '%declare%' or lower(s.text) like '%compound%') + group by s.owner, s.name, s.type + ), + sources as ( + select /*+ cardinality(f {mappings_cardinality}) */ + {l_full_name} as full_name, s.owner, s.name, + s.line - case when s.type = 'TRIGGER' then o.offset else 0 end as line, + s.text + from {sources_view} s {join_file_mappings} + left join trigger_source_offsets o + on (s.owner = o.owner and s.name = o.name and s.type = o.type) + where s.type in ('PACKAGE BODY', 'TYPE BODY', 'PROCEDURE', 'FUNCTION', 'TRIGGER') + {filters} + ), + coverage_sources as ( + select full_name, owner, name, line, text, + case + when + -- to avoid execution of regexp_like on every line + -- first do a rough check for existence of search pattern keyword + (lower(s.text) like '%procedure%' + or lower(s.text) like '%function%' + or lower(s.text) like '%begin%' + or lower(s.text) like '%end%' + or lower(s.text) like '%package%' + ) and + regexp_like( + s.text, + '^([\t ]*(((not)?\s*(overriding|final|instantiable)[\t ]*)*(static|constructor|member)?[\t ]*(procedure|function)|package([\t ]+body)|begin|end([\t ]+\S+)*[ \t]*;))', 'i' + ) + then 'Y' + end as to_be_skipped + from sources s + ) + select full_name, owner, name, line, to_be_skipped, text + from coverage_sources s + -- Exclude calls to utPLSQL framework, Unit Test packages and objects from a_exclude_list parameter of coverage reporter + where (s.owner, s.name) not in ( select /*+ cardinality(el {skipped_objects_cardinality})*/el.owner, el.name from table(:l_skipped_objects) el ) + and line > 0 + ]'; + if a_coverage_options.file_mappings is not empty then - l_result := l_result || ' + l_mappings_cardinality := ut_utils.scale_cardinality(cardinality(a_coverage_options.file_mappings)); + l_full_name := 'f.file_name'; + l_join_mappings := ' join table(:file_mappings) f on s.name = f.object_name and s.type = f.object_type - and s.owner = f.object_owner - where 1 = 1'; - elsif a_coverage_options.include_objects is not empty then - l_result := l_result || ' - where (s.owner, s.name) in (select il.owner, il.name from table(:include_objects) il)'; + and s.owner = f.object_owner'; else - l_result := l_result || ' - where s.owner in (select upper(t.column_value) from table(:l_schema_names) t)'; + l_full_name := q'[lower(s.owner||'.'||s.name)]'; + l_filters := case + when a_coverage_options.include_objects is not empty then ' + and (s.owner, s.name) in ( + select /*+ cardinality(il '||ut_utils.scale_cardinality(cardinality(a_coverage_options.include_objects))||') */ + il.owner, il.name + from table(:include_objects) il + )' + else ' + and s.owner in ( + select /*+ cardinality(t '||ut_utils.scale_cardinality(cardinality(a_coverage_options.schema_names))||') */ + upper(t.column_value) + from table(:l_schema_names) t)' + end; end if; - l_result := l_result || q'[ - and s.type not in ('PACKAGE', 'TYPE', 'JAVA SOURCE') - --Exclude calls to utPLSQL framework, Unit Test packages and objects from a_exclude_list parameter of coverage reporter - and (s.owner, s.name) not in (select el.owner, el.name from table(:l_skipped_objects) el) - ) - where line > 0]'; + + l_result := replace(l_result, '{sources_view}', ut_metadata.get_source_view_name()); + l_result := replace(l_result, '{l_full_name}', l_full_name); + l_result := replace(l_result, '{join_file_mappings}', l_join_mappings); + l_result := replace(l_result, '{filters}', l_filters); + l_result := replace(l_result, '{mappings_cardinality}', l_mappings_cardinality); + l_result := replace(l_result, '{skipped_objects_cardinality}', ut_utils.scale_cardinality(cardinality(a_skip_objects))); + return l_result; + end; - function get_cov_sources_cursor(a_coverage_options in ut_coverage_options,a_sql in varchar2) return sys_refcursor is + function get_cov_sources_cursor(a_coverage_options in ut_coverage_options) return sys_refcursor is l_cursor sys_refcursor; l_skip_objects ut_object_names; l_sql varchar2(32767); - l_valid_pattern varchar2(250) := '^\s*select.+$'; begin if not is_develop_mode() then --skip all the utplsql framework objects and all the unit test packages that could potentially be reported by coverage. l_skip_objects := ut_utils.get_utplsql_objects_list() multiset union all coalesce(a_coverage_options.exclude_objects, ut_object_names()); end if; - if regexp_like(a_sql, l_valid_pattern, 'mi') then - -- pseudo assert for PL/SQL Cop - l_sql := sys.dbms_assert.noop(a_sql); - else - raise_application_error(-20542, 'Possible SQL injection detected. a_sql parameter does not match valid pattern "' || l_valid_pattern || '".'); - end if; + + l_sql := get_cov_sources_sql(a_coverage_options, l_skip_objects); + + ut_event_manager.trigger_event(ut_event_manager.gc_debug, ut_key_anyvalues().put('l_sql',l_sql) ); + if a_coverage_options.file_mappings is not empty then open l_cursor for l_sql using a_coverage_options.file_mappings, l_skip_objects; elsif a_coverage_options.include_objects is not empty then - open l_cursor for l_sql using a_coverage_options.include_objects, l_skip_objects; + open l_cursor for l_sql using a_coverage_options.include_objects, a_coverage_options.include_objects, l_skip_objects; else - open l_cursor for l_sql using a_coverage_options.schema_names, l_skip_objects; + open l_cursor for l_sql using a_coverage_options.schema_names, a_coverage_options.schema_names, l_skip_objects; end if; return l_cursor; end; - procedure populate_tmp_table(a_coverage_options ut_coverage_options, a_sql in varchar2) is + procedure populate_tmp_table(a_coverage_options ut_coverage_options) is pragma autonomous_transaction; l_cov_sources_crsr sys_refcursor; l_cov_sources_data ut_coverage_helper.t_coverage_sources_tmp_rows; @@ -137,8 +158,7 @@ create or replace package body ut_coverage is if not ut_coverage_helper.is_tmp_table_populated() or is_develop_mode() then ut_coverage_helper.cleanup_tmp_table(); - ut_event_manager.trigger_event(ut_event_manager.gc_debug, ut_key_anyvalues().put('a_sql',a_sql) ); - l_cov_sources_crsr := get_cov_sources_cursor(a_coverage_options, a_sql); + l_cov_sources_crsr := get_cov_sources_cursor(a_coverage_options); loop fetch l_cov_sources_crsr bulk collect into l_cov_sources_data limit 10000; @@ -212,7 +232,7 @@ create or replace package body ut_coverage is begin --prepare global temp table with sources ut_event_manager.trigger_event('about to populate coverage temp table'); - populate_tmp_table(a_coverage_options, get_cov_sources_sql(a_coverage_options)); + populate_tmp_table(a_coverage_options); ut_event_manager.trigger_event('coverage temp table populated'); -- Get raw data for both reporters, order is important as tmp table will skip headers and dont populate diff --git a/source/core/coverage/ut_coverage.pks b/source/core/coverage/ut_coverage.pks index 9254e6d37..1d87d50c9 100644 --- a/source/core/coverage/ut_coverage.pks +++ b/source/core/coverage/ut_coverage.pks @@ -18,7 +18,6 @@ create or replace package ut_coverage authid current_user is gc_proftab_coverage constant varchar2(32) := 'proftab'; gc_block_coverage constant varchar2(32) := 'block'; - gc_extended_coverage constant varchar2(32) := 'extended'; type tt_coverage_id_arr is table of integer index by varchar2(30); @@ -75,7 +74,7 @@ create or replace package ut_coverage authid current_user is * Allows overwriting of private global variable g_coverage_id * Used internally, only for unit testing of the framework only */ - procedure mock_coverage_id(a_coverage_id integer,a_coverage_type in varchar2); + procedure mock_coverage_id(a_coverage_id integer, a_coverage_type in varchar2); procedure mock_coverage_id(a_coverage_id tt_coverage_id_arr); diff --git a/source/core/coverage/ut_coverage_helper_profiler.pkb b/source/core/coverage/ut_coverage_helper_profiler.pkb index 6b685ede0..c90a5b36a 100644 --- a/source/core/coverage/ut_coverage_helper_profiler.pkb +++ b/source/core/coverage/ut_coverage_helper_profiler.pkb @@ -54,26 +54,23 @@ create or replace package body ut_coverage_helper_profiler is end; function proftab_results(a_object_owner varchar2, a_object_name varchar2, a_coverage_id integer) return t_proftab_rows is - l_raw_coverage sys_refcursor; - l_coverage_rows t_proftab_rows; + l_coverage_rows t_proftab_rows; begin - open l_raw_coverage for q'[select d.line#, + select + d.line#, case when sum(d.total_occur) = 0 and sum(d.total_time) > 0 then 1 else sum(d.total_occur) end total_occur - from plsql_profiler_units u - join plsql_profiler_data d - on u.runid = d.runid - and u.unit_number = d.unit_number - where u.runid = :a_coverage_id - and u.unit_owner = :a_object_owner - and u.unit_name = :a_object_name - and u.unit_type not in ('PACKAGE SPEC', 'TYPE SPEC', 'ANONYMOUS BLOCK') - group by d.line#]' using a_coverage_id,a_object_owner,a_object_name; + bulk collect into l_coverage_rows + from plsql_profiler_units u + join plsql_profiler_data d + on u.runid = d.runid + and u.unit_number = d.unit_number + where u.runid = a_coverage_id + and u.unit_owner = a_object_owner + and u.unit_name = a_object_name + and u.unit_type in ('PACKAGE BODY', 'TYPE BODY', 'PROCEDURE', 'FUNCTION', 'TRIGGER') + group by d.line#; - FETCH l_raw_coverage BULK COLLECT - INTO l_coverage_rows; - CLOSE l_raw_coverage; - - RETURN l_coverage_rows; + return l_coverage_rows; end; function get_raw_coverage_data(a_object_owner varchar2, a_object_name varchar2, a_coverage_id integer) return ut_coverage_helper.t_unit_line_calls is