@@ -19,29 +19,28 @@ create or replace package body ut_suite_tag_filter is
1919 /**
2020 * Constants use in postfix and infix transformations
2121 */
22- gc_operators constant ut_varchar2_list := ut_varchar2_list('|','&','!');
22+ gc_operators constant ut_varchar2_list := ut_varchar2_list('|','&','!');
2323 gc_unary_operators constant ut_varchar2_list := ut_varchar2_list('!'); -- right side associative operator
2424 gc_binary_operators constant ut_varchar2_list := ut_varchar2_list('|','&'); -- left side associative operator
2525 gc_reserved_tag_words constant ut_varchar2_list := ut_varchar2_list('none','any');
26- gc_tags_column_name constant varchar2(250) := 'tags';
2726 gc_exception_msg constant varchar2(200) := 'Invalid tag expression';
28-
29- type t_precedence_table is table of number index by varchar2(1);
30- g_precedence t_precedence_table;
27+
28+ type t_precedence_table is table of number index by varchar2(1);
29+ g_precedence t_precedence_table;
3130
3231 function tokenize_tags_string(a_tags in varchar2) return ut_varchar2_list is
33- l_tags_tokens ut_varchar2_list := ut_varchar2_list();
32+ l_tags_tokens ut_varchar2_list := ut_varchar2_list();
3433 begin
3534 --Tokenize a string into operators and tags
3635 select regexp_substr(a_tags,'([^!()|&]+)|([!()|&])', 1, level) as string_parts
3736 bulk collect into l_tags_tokens
3837 from dual connect by regexp_substr (a_tags, '([^!()|&]+)|([!()|&])', 1, level) is not null;
39-
38+
4039 return l_tags_tokens;
4140 end;
4241
4342 /*
44- To support a legact tag notation
43+ To support a legact tag notation
4544 , = OR
4645 - = NOT
4746 we will perform a replace of that characters into
@@ -57,36 +56,36 @@ create or replace package body ut_suite_tag_filter is
5756 l_tags_exclude varchar2(4000);
5857 l_return_tag varchar2(4000);
5958 begin
60- if instr(a_tags,',') > 0 or instr(a_tags,'-') > 0 then
59+ if instr(a_tags,',') > 0 or instr(a_tags,'-') > 0 then
6160
6261 select '('||listagg( t.column_value,'|')
63- within group( order by column_value)||')'
62+ within group( order by column_value)||')'
6463 into l_tags_include
6564 from table(l_tags) t
6665 where t.column_value not like '-%';
67-
66+
6867 select '('||listagg( replace(t.column_value,'-','!'),' & ')
6968 within group( order by column_value)||')'
7069 into l_tags_exclude
7170 from table(l_tags) t
72- where t.column_value like '-%';
73-
71+ where t.column_value like '-%';
72+
7473
7574 l_return_tag:=
76- case
75+ case
7776 when l_tags_include <> '()' and l_tags_exclude <> '()'
7877 then l_tags_include || ' & ' || l_tags_exclude
7978 when l_tags_include <> '()'
8079 then l_tags_include
8180 when l_tags_exclude <> '()'
82- then l_tags_exclude
81+ then l_tags_exclude
8382 end;
84- else
83+ else
8584 l_return_tag := a_tags;
86- end if;
85+ end if;
8786 return l_return_tag;
8887 end;
89-
88+
9089 /*
9190 https://stackoverflow.com/questions/29634992/shunting-yard-validate-expression
9291 */
@@ -97,14 +96,14 @@ create or replace package body ut_suite_tag_filter is
9796 l_expect_operand boolean := true;
9897 l_expect_operator boolean := false;
9998 l_idx pls_integer;
100- begin
99+ begin
101100 l_idx := a_tags.first;
102101 --Exuecute modified shunting algorithm
103102 WHILE (l_idx is not null) loop
104103 l_token := a_tags(l_idx);
105104 if (l_token member of gc_operators and l_token member of gc_binary_operators) then
106- if not(l_expect_operator) then
107- raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg);
105+ if not(l_expect_operator) then
106+ raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg);
108107 end if;
109108 while l_operator_stack.top > 0 and (g_precedence(l_operator_stack.peek) > g_precedence(l_token)) loop
110109 l_rnp_tokens.extend;
@@ -113,56 +112,56 @@ create or replace package body ut_suite_tag_filter is
113112 l_operator_stack.push(a_tags(l_idx));
114113 l_expect_operand := true;
115114 l_expect_operator:= false;
116- elsif (l_token member of gc_operators and l_token member of gc_unary_operators) then
117- if not(l_expect_operand) then
118- raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg);
119- end if;
115+ elsif (l_token member of gc_operators and l_token member of gc_unary_operators) then
116+ if not(l_expect_operand) then
117+ raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg);
118+ end if;
120119 l_operator_stack.push(a_tags(l_idx));
121120 l_expect_operand := true;
122- l_expect_operator:= false;
121+ l_expect_operator:= false;
123122 elsif l_token = '(' then
124- if not(l_expect_operand) then
125- raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg);
126- end if;
123+ if not(l_expect_operand) then
124+ raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg);
125+ end if;
127126 l_operator_stack.push(a_tags(l_idx));
128127 l_expect_operand := true;
129- l_expect_operator:= false;
128+ l_expect_operator:= false;
130129 elsif l_token = ')' then
131- if not(l_expect_operator) then
132- raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg);
133- end if;
130+ if not(l_expect_operator) then
131+ raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg);
132+ end if;
134133 while l_operator_stack.peek <> '(' loop
135134 l_rnp_tokens.extend;
136135 l_rnp_tokens(l_rnp_tokens.last) := l_operator_stack.pop;
137136 end loop;
138137 l_operator_stack.pop; --Pop the open bracket and discard it
139138 l_expect_operand := false;
140- l_expect_operator:= true;
139+ l_expect_operator:= true;
141140 else
142- if not(l_expect_operand) then
143- raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg);
141+ if not(l_expect_operand) then
142+ raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg);
144143 end if;
145144 l_rnp_tokens.extend;
146145 l_rnp_tokens(l_rnp_tokens.last) :=l_token;
147146 l_expect_operator := true;
148147 l_expect_operand := false;
149148 end if;
150-
149+
151150 l_idx := a_tags.next(l_idx);
152151 end loop;
153-
152+
154153 while l_operator_stack.peek is not null loop
155- if l_operator_stack.peek in ('(',')') then
156- raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg);
157- end if;
154+ if l_operator_stack.peek in ('(',')') then
155+ raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg);
156+ end if;
158157 l_rnp_tokens.extend;
159- l_rnp_tokens(l_rnp_tokens.last):=l_operator_stack.pop;
158+ l_rnp_tokens(l_rnp_tokens.last):=l_operator_stack.pop;
160159 end loop;
161-
160+
162161 return l_rnp_tokens;
163162 end shunt_logical_expression;
164-
165- function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list,a_tags_column_name in varchar2)
163+
164+ function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list,a_tags_column_name in varchar2)
166165 return varchar2 is
167166 l_infix_stack ut_stack := ut_stack();
168167 l_right_side varchar2(32767);
@@ -175,22 +174,22 @@ create or replace package body ut_suite_tag_filter is
175174 while ( l_idx is not null) loop
176175 --If the token we got is a none or any keyword
177176 if a_postfix_exp(l_idx) member of gc_reserved_tag_words then
178-
179- l_infix_stack.push(
180- case
177+
178+ l_infix_stack.push(
179+ case
181180 when a_postfix_exp(l_idx) = 'none' then '('||a_tags_column_name||' is empty or '||a_tags_column_name||' is null)'
182181 else a_tags_column_name||' is not empty'
183182 end
184183 );
185184 --If token is operand but also single tag
186185 elsif regexp_count(a_postfix_exp(l_idx),'[!()|&]') = 0 then
187186 l_infix_stack.push(q'[']'||a_postfix_exp(l_idx)||q'[']'||l_member_token);
188- --If token is unary operator not
187+ --If token is unary operator not
189188 elsif a_postfix_exp(l_idx) member of gc_unary_operators then
190189 l_right_side := l_infix_stack.pop;
191190 l_infix_exp := a_postfix_exp(l_idx)||'('||l_right_side||')';
192191 l_infix_stack.push(l_infix_exp);
193- --If token is binary operator
192+ --If token is binary operator
194193 elsif a_postfix_exp(l_idx) member of gc_binary_operators then
195194 l_right_side := l_infix_stack.pop;
196195 l_left_side := l_infix_stack.pop;
@@ -199,7 +198,7 @@ create or replace package body ut_suite_tag_filter is
199198 end if;
200199 l_idx := a_postfix_exp.next(l_idx);
201200 end loop;
202-
201+
203202 return l_infix_stack.pop;
204203 end conv_postfix_to_infix_sql;
205204
@@ -208,73 +207,69 @@ create or replace package body ut_suite_tag_filter is
208207 l_tags varchar2(4000);
209208 begin
210209 l_tags := replace(replace_legacy_tag_notation(a_tags),' ');
211- l_tags := conv_postfix_to_infix_sql(shunt_logical_expression(tokenize_tags_string(l_tags)),gc_tags_column_name );
210+ l_tags := conv_postfix_to_infix_sql(shunt_logical_expression(tokenize_tags_string(l_tags)),'tags' );
212211 l_tags := replace(l_tags, '|',' or ');
213212 l_tags := replace(l_tags ,'&',' and ');
214213 l_tags := replace(l_tags ,'!','not');
215- return l_tags;
216- end;
214+ return l_tags;
215+ end;
217216
218217
219218 /*
220219 Having a base set of suites we will do a further filter down if there are
221220 any tags defined.
222- */
221+ */
223222 function get_tags_suites (
224223 a_suite_items ut_suite_cache_rows,
225224 a_tags varchar2
226225 ) return ut_suite_cache_rows is
227- l_suite_tags ut_suite_cache_rows := ut_suite_cache_rows();
226+ l_results ut_suite_cache_rows;
228227 l_sql varchar2(32000);
229- l_tags varchar2(4000):= create_where_filter(a_tags);
228+ l_tags_filter_clause varchar2(4000):= create_where_filter(a_tags);
230229 begin
231230 l_sql :=
232231 q'[
233- with
234- suites_mv as (
235- select c.id,value(c) as obj,c.path as path,c.self_type,c.object_owner,c.tags as ]'||gc_tags_column_name||q'[
236- from table(:suite_items) c
237- ),
238- suites_matching_expr as (
239- select c.id,c.path as path,c.self_type,c.object_owner,c.tags
240- from suites_mv c
241- where c.self_type in ('UT_SUITE','UT_CONTEXT')
242- and ]'||l_tags||q'[
243- ),
244- tests_matching_expr as (
245- select c.id,c.path as path,c.self_type,c.object_owner,c.tags as ]'||gc_tags_column_name||q'[
246- from suites_mv c where c.self_type in ('UT_TEST')
247- and ]'||l_tags||q'[
248- ),
249- tests_with_tags_inh_from_suite as (
250- select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags as ]'||gc_tags_column_name||q'[ ,c.object_owner
251- from suites_mv c join suites_matching_expr t
252- on (c.path||'.' like t.path || '.%' /*all descendants and self*/ and c.object_owner = t.object_owner)
253- ),
254- tests_with_tags_prom_to_suite as (
255- select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags as ]'||gc_tags_column_name||q'[ ,c.object_owner
256- from suites_mv c join tests_matching_expr t
257- on (t.path||'.' like c.path || '.%' /*all ancestors and self*/ and c.object_owner = t.object_owner)
258- )
259- select obj from suites_mv c,
260- (select id,row_number() over (partition by id order by id) r_num from
261- (select id
262- from tests_with_tags_prom_to_suite tst
263- where ]'||l_tags||q'[
264- union all
265- select id from tests_with_tags_inh_from_suite tst
266- where ]'||l_tags||q'[
267- )
268- ) t where c.id = t.id and r_num = 1 ]';
269- execute immediate l_sql bulk collect into l_suite_tags using a_suite_items;
270- return l_suite_tags;
232+ with
233+ suites_mv as (
234+ select c.id,value(c) as obj,c.path as path,c.self_type,c.object_owner,c.tags as tags
235+ from table(:suite_items) c
236+ ),
237+ propagate_tags_from_ancestors as (
238+ select c.id, c.self_type, c.path, c.object_owner, cast(collect(c_tags.tag) as ut_varchar2_rows) as tags
239+ from suites_mv c
240+ left join suites_mv t
241+ on t.self_type in ('UT_SUITE','UT_SUITE_CONTEXT')
242+ and c.path like t.path||'.%' /* all descendants */
243+ and c.object_owner = t.object_owner
244+ outer apply (select column_value tag from table(c.tags) union select column_value tag from table(t.tags) ) c_tags
245+ group by c.id, c.self_type, c.path, c.object_owner
246+ ),
247+ filter_by_tags as (
248+ select *
249+ from propagate_tags_from_ancestors x
250+ where ( ]'||l_tags_filter_clause||q'[ )
251+ ),
252+ only_items_with_tests as (
253+ select item.obj
254+ from suites_mv item
255+ where exists (
256+ select 1
257+ from filter_by_tags tst
258+ where tst.self_type in ('UT_TEST')
259+ and (tst.path||'.' like item.path || '.%' /*all descendants and self*/ and item.object_owner = tst.object_owner)
260+ )
261+ )
262+ select obj from only_items_with_tests]';
263+ execute immediate l_sql bulk collect into l_results using a_suite_items;
264+
265+ return l_results;
271266 end;
272267
273268 function apply(
274269 a_unfiltered_rows ut_suite_cache_rows,
275270 a_tags varchar2 := null
276271 ) return ut_suite_cache_rows is
277- l_suite_items ut_suite_cache_rows := a_unfiltered_rows;
272+ l_suite_items ut_suite_cache_rows := a_unfiltered_rows;
278273 begin
279274 if length(a_tags) > 0 then
280275 l_suite_items := get_tags_suites(l_suite_items,a_tags);
0 commit comments