Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a009558

Browse files
committed
* Cleanup of the code.
* Removed unused code. * Refactored some of code to make it more readable. * Separated annotations parsing from metadata. * Split parse_package_annotations into smaller responsibilities for readability. * Separated annotations parsing from obtaining the source code. * Improved exception handling for dba_source accessibility check. * Renamed some variables.
1 parent dc3c3ef commit a009558

9 files changed

Lines changed: 474 additions & 384 deletions

examples/RunExampleTestAnnotationsHugePackage.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ set echo off
1010
declare
1111
l_suite ut_test_suite;
1212
begin
13-
ut_suite_manager.config_package(a_owner_name => USER,a_object_name => 'TST_PKG_HUGE',a_suite => l_suite);
13+
l_suite := ut_suite_manager.config_package(a_owner_name => USER,a_object_name => 'TST_PKG_HUGE');
1414
end;
1515
/
1616

source/install.sql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ whenever oserror exit failure rollback
99
@@types/ut_executable.tps
1010
@@types/ut_assert_result.tps
1111
@@types/ut_assert_list.tps
12-
@@ut_assert.pks
1312
@@types/ut_reporter.tps
1413
@@types/ut_reporters_list.tps
1514
@@types/ut_composite_reporter.tps
@@ -20,6 +19,8 @@ whenever oserror exit failure rollback
2019
@@types/ut_dbms_output_suite_reporter.tps
2120
@@ut_utils.pks
2221
@@ut_metadata.pks
22+
@@ut_assert.pks
23+
@@ut_annotations.pks
2324
@@ut_suite_manager.pks
2425

2526
@@ut_utils.pkb
@@ -34,6 +35,7 @@ whenever oserror exit failure rollback
3435
@@types/ut_reporter_decorator.tpb
3536
@@types/ut_dbms_output_suite_reporter.tpb
3637
@@ut_metadata.pkb
38+
@@ut_annotations.pkb
3739
@@ut_assert.pkb
3840
@@ut_suite_manager.pkb
3941

source/uninstall.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ drop package ut_suite_manager;
22

33
drop package ut_assert;
44

5+
drop package ut_annotations;
6+
57
drop package ut_metadata;
68

79
drop package ut_utils;

source/ut_annotations.pkb

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
create or replace package body ut_annotations as
2+
3+
------------------------------
4+
--private definitions
5+
6+
type tt_comment_list is table of varchar2(32767) index by pls_integer;
7+
8+
gc_annotation_qualifier constant varchar2(1) := '%';
9+
c_multiline_comment_pattern constant varchar2(50) := '/\*.*?\*/';
10+
c_singleline_comment_pattern constant varchar2(20) := ' *--(.*?)$';
11+
c_nonannotat_comment_pattern constant varchar2(20) := ' *-{2,}\s*[^'||gc_annotation_qualifier||']*?$';
12+
c_comment_replacer_patter constant varchar2(50) := '{COMMENT#%N%}';
13+
c_comment_replacer_regex_ptrn constant varchar2(25) := '{COMMENT#(\d+)}';
14+
c_rgexp_identifier constant varchar2(50) := '[a-z][a-z0-9#_$]*';
15+
c_annotation_block_pattern constant varchar2(200) := '((.*?{COMMENT#\d+}\s?)+)\s*(procedure|function)\s+(' ||
16+
c_rgexp_identifier || ')';
17+
c_annotation_pattern constant varchar2(50) := gc_annotation_qualifier || c_rgexp_identifier || '(\(.*?\))?';
18+
19+
20+
function delete_multiline_comments(a_source in clob) return clob is
21+
begin
22+
return regexp_replace(
23+
srcstr => regexp_replace(
24+
srcstr => regexp_replace( srcstr => a_source, pattern => c_multiline_comment_pattern, modifier => 'n')
25+
,pattern => c_nonannotat_comment_pattern, modifier => 'm')
26+
,pattern => '((procedure|function)\s+' || c_rgexp_identifier || ')[^;]*'
27+
,replacestr => '\1'
28+
,modifier => 'mn'
29+
);
30+
end;
31+
32+
function get_annotations(a_source varchar2, a_comments tt_comment_list) return tt_annotations is
33+
l_loop_index pls_integer := 1;
34+
l_comment_index pls_integer;
35+
l_comment varchar2(32767);
36+
l_annotation_str varchar2(32767);
37+
l_annotation_params_str varchar2(32767);
38+
l_annotation_name varchar2(1000);
39+
l_annotation_params tt_annotation_params;
40+
l_annotations_list tt_annotations;
41+
begin
42+
-- loop while there are unprocessed comment blocks
43+
while 0 != nvl(regexp_instr(srcstr => a_source
44+
,pattern => c_comment_replacer_regex_ptrn
45+
,occurrence => l_loop_index
46+
,subexpression => 1)
47+
,0) loop
48+
49+
-- define index of the comment block and get it's content from cache
50+
l_comment_index := to_number(regexp_substr(a_source
51+
,c_comment_replacer_regex_ptrn
52+
,1
53+
,l_loop_index
54+
,subexpression => 1));
55+
56+
l_comment := a_comments(l_comment_index);
57+
58+
-- strip everything except the annotation itself (spaces and others)
59+
l_annotation_str := regexp_substr(l_comment, c_annotation_pattern, 1, 1, modifier => 'i');
60+
if l_annotation_str is not null then
61+
62+
l_annotation_params.delete;
63+
64+
-- get the annotation name and it's parameters if present
65+
l_annotation_name := lower(regexp_substr(l_annotation_str
66+
,'%(' || c_rgexp_identifier || ')'
67+
,modifier => 'i'
68+
,subexpression => 1));
69+
l_annotation_params_str := trim(regexp_substr(l_annotation_str, '\((.*?)\)', subexpression => 1));
70+
71+
if l_annotation_params_str is not null then
72+
73+
-- parse the annotation parameters and store them as key-value pair array
74+
for param_ind in 1 .. regexp_count(l_annotation_params_str, '(.+?)(,|$)') loop
75+
declare
76+
l_param_str varchar2(32767);
77+
l_param_item typ_annotation_param;
78+
begin
79+
l_param_str := regexp_substr(srcstr => l_annotation_params_str
80+
,pattern => '(.+?)(,|$)'
81+
,occurrence => param_ind
82+
,subexpression => 1);
83+
84+
l_param_item.key := regexp_substr(srcstr => l_param_str
85+
,pattern => '(' || c_rgexp_identifier || ')\s*='
86+
,modifier => 'i'
87+
,subexpression => 1);
88+
l_param_item.value := regexp_substr(l_param_str, '(.+?=)?(.*$)', subexpression => 2);
89+
90+
l_annotation_params(l_annotation_params.count + 1) := l_param_item;
91+
end;
92+
end loop;
93+
end if;
94+
95+
l_annotations_list(l_annotation_name) := l_annotation_params;
96+
end if;
97+
l_loop_index := l_loop_index + 1;
98+
end loop;
99+
100+
return l_annotations_list;
101+
102+
end get_annotations;
103+
104+
function get_package_annotations(a_source varchar2, a_comments tt_comment_list) return tt_annotations is
105+
l_package_comments varchar2(32767);
106+
begin
107+
l_package_comments := regexp_substr(srcstr => a_source
108+
,pattern => '^\s*(CREATE\s+(OR\s+REPLACE)?(\s+(NON)?EDITIONABLE)?\s+)?PACKAGE .*?\s+(AS|IS)\s+((.*?{COMMENT#\d+}\s?)+)'
109+
,modifier => 'i'
110+
,subexpression => 6);
111+
112+
-- parsing for package annotations
113+
return
114+
case when l_package_comments is not null then
115+
get_annotations(l_package_comments, a_comments)
116+
end;
117+
end;
118+
119+
function get_procedure_annotations(a_source varchar2, a_comments tt_comment_list) return tt_procedure_annotations is
120+
l_proc_comments varchar2(32767);
121+
l_proc_name t_annotation_name;
122+
l_annot_proc_ind number;
123+
l_annot_proc_block varchar2(32767);
124+
l_procedure_annotations tt_procedure_annotations;
125+
begin
126+
-- loop through procedures and functions of the package and get all the comment blocks just before it's declaration
127+
l_annot_proc_ind := 1;
128+
loop
129+
--find annotated procedure index
130+
l_annot_proc_ind := regexp_instr(srcstr => a_source
131+
,pattern => c_annotation_block_pattern
132+
,occurrence => 1
133+
,modifier => 'i'
134+
,position => l_annot_proc_ind);
135+
exit when l_annot_proc_ind = 0;
136+
137+
--get the annotataions with procedure name
138+
l_annot_proc_block := regexp_substr(srcstr => a_source
139+
,pattern => c_annotation_block_pattern
140+
,position => l_annot_proc_ind
141+
,occurrence => 1
142+
,modifier => 'i');
143+
144+
--extract the annotations
145+
l_proc_comments := trim(regexp_substr(srcstr => l_annot_proc_block
146+
,pattern => c_annotation_block_pattern
147+
,modifier => 'i'
148+
,subexpression => 1));
149+
--extract the procedure name
150+
l_proc_name := trim(regexp_substr(srcstr => l_annot_proc_block
151+
,pattern => c_annotation_block_pattern
152+
,modifier => 'i'
153+
,subexpression => 4));
154+
155+
-- parse the comment block for the syntactically correct annotations and store them as an array
156+
l_procedure_annotations(l_proc_name) := get_annotations(l_proc_comments, a_comments);
157+
158+
l_annot_proc_ind := l_annot_proc_ind + length(l_annot_proc_block);
159+
end loop;
160+
return l_procedure_annotations;
161+
end;
162+
163+
function extract_and_replace_comments(a_source in out nocopy clob) return tt_comment_list is
164+
l_comments tt_comment_list;
165+
l_comment_pos pls_integer;
166+
l_comment_replacer varchar2(50);
167+
begin
168+
l_comment_pos := 1;
169+
loop
170+
l_comment_pos := regexp_instr(srcstr => a_source
171+
,pattern => c_singleline_comment_pattern
172+
,occurrence => 1
173+
,modifier => 'm'
174+
,position => l_comment_pos
175+
);
176+
exit when l_comment_pos = 0;
177+
l_comments(l_comments.count + 1) := trim(regexp_substr(srcstr => a_source
178+
,pattern => c_singleline_comment_pattern
179+
,occurrence => 1
180+
,position => l_comment_pos
181+
,modifier => 'm'
182+
,subexpression => 1));
183+
184+
l_comment_replacer := replace(c_comment_replacer_patter, '%N%', l_comments.count);
185+
186+
a_source := regexp_replace(srcstr => a_source
187+
,pattern => c_singleline_comment_pattern
188+
,replacestr => l_comment_replacer
189+
,position => l_comment_pos
190+
,occurrence => 1
191+
,modifier => 'm');
192+
l_comment_pos := l_comment_pos + length(l_comment_replacer);
193+
194+
end loop;
195+
196+
$if $$ut_trace $then
197+
dbms_output.put_line(a_source);
198+
$end
199+
return l_comments;
200+
end extract_and_replace_comments;
201+
202+
$if $$ut_trace $then
203+
procedure print_parse_results(a_annotated_pkg typ_annotated_package) is
204+
l_name t_annotation_name := a_annotated_pkg.package_annotations.first;
205+
l_proc_name t_annotation_name := a_annotated_pkg.procedure_annotations.first;
206+
begin
207+
dbms_output.put_line('Annotations count: ' || a_annotated_pkg.package_annotations.count);
208+
209+
while l_name is not null loop
210+
dbms_output.put_line(' @' || l_name);
211+
if a_annotated_pkg.package_annotations(l_name).count > 0 then
212+
dbms_output.put_line(' Parameters:');
213+
214+
for j in 1 .. a_annotated_pkg.package_annotations(l_name).count loop
215+
dbms_output.put_line(' ' || nvl(a_annotated_pkg.package_annotations(l_name)(j).key, '<Anonimous>') || ' = ' ||
216+
nvl(a_annotated_pkg.package_annotations(l_name)(j).value, 'NULL'));
217+
end loop;
218+
else
219+
dbms_output.put_line(' No parameters.');
220+
end if;
221+
222+
l_name := a_annotated_pkg.package_annotations.next(l_name);
223+
224+
end loop;
225+
226+
dbms_output.put_line('Procedures count: ' || a_annotated_pkg.procedure_annotations.count);
227+
228+
while l_proc_name is not null loop
229+
dbms_output.put_line(rpad('-', 80, '-'));
230+
dbms_output.put_line(' Procedure: ' || l_proc_name);
231+
dbms_output.put_line(' Annotations count: ' || a_annotated_pkg.procedure_annotations(l_proc_name).count);
232+
233+
l_name := a_annotated_pkg.procedure_annotations(l_proc_name).first;
234+
while l_name is not null loop
235+
dbms_output.put_line(' @' || l_name);
236+
if a_annotated_pkg.procedure_annotations(l_proc_name)(l_name).count > 0 then
237+
dbms_output.put_line(' Parameters:');
238+
239+
for j in 1 .. a_annotated_pkg.procedure_annotations(l_proc_name)(l_name).count loop
240+
dbms_output.put_line(' ' ||
241+
nvl(a_annotated_pkg.procedure_annotations(l_proc_name) (l_name)(j).key, '<Anonymous>') ||
242+
' = ' || nvl(a_annotated_pkg.procedure_annotations(l_proc_name) (l_name)(j).value, 'NULL'));
243+
end loop;
244+
else
245+
dbms_output.put_line(' No parameters.');
246+
end if;
247+
248+
l_name := a_annotated_pkg.procedure_annotations(l_proc_name).next(l_name);
249+
end loop;
250+
251+
l_proc_name := a_annotated_pkg.procedure_annotations.next(l_proc_name);
252+
end loop;
253+
254+
end print_parse_results;
255+
$end
256+
257+
function parse_package_annotations(a_source clob) return typ_annotated_package is
258+
l_source clob := a_source;
259+
l_comments tt_comment_list;
260+
l_annotated_pkg typ_annotated_package;
261+
begin
262+
263+
l_source := delete_multiline_comments(l_source);
264+
265+
-- replace all single line comments with {COMMENT#12} element and store it's content for easier processing
266+
-- this call modifies a_source
267+
l_comments := extract_and_replace_comments(l_source);
268+
269+
l_annotated_pkg.package_annotations := get_package_annotations(l_source, l_comments);
270+
271+
l_annotated_pkg.procedure_annotations := get_procedure_annotations(l_source, l_comments);
272+
273+
-- printing out parsed structure for debugging
274+
$if $$ut_trace $then
275+
print_parse_results(l_annotated_pkg);
276+
$end
277+
278+
return l_annotated_pkg;
279+
end parse_package_annotations;
280+
281+
------------------------------
282+
--public definitions
283+
284+
function get_package_annotations(a_owner_name varchar2, a_name varchar2) return typ_annotated_package is
285+
l_source clob;
286+
begin
287+
288+
-- TODO: Add cache of annotations. Cache invalidation should be based on DDL timestamp.
289+
-- Cache garbage collection should be executed once in a while to remove annotations cache for packages that were dropped.
290+
291+
l_source := ut_metadata.get_package_spec_source(a_owner_name, a_name);
292+
293+
if l_source is null then
294+
return null;
295+
else
296+
return parse_package_annotations(l_source);
297+
end if;
298+
end;
299+
300+
function get_annotation_param(a_param_list tt_annotation_params, a_def_index pls_integer) return varchar2 is
301+
l_result varchar2(32767);
302+
begin
303+
if a_param_list.exists(a_def_index) then
304+
l_result := a_param_list(a_def_index).value;
305+
end if;
306+
return l_result;
307+
end get_annotation_param;
308+
309+
end ut_annotations;
310+
/

0 commit comments

Comments
 (0)