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

Skip to content

Commit 6e7f35c

Browse files
authored
Merge pull request #923 from utPLSQL/fix/anydata_join_syntax
Anydata join / exclude / include
2 parents 6918be7 + 0007298 commit 6e7f35c

11 files changed

Lines changed: 196 additions & 35 deletions

docs/userguide/advanced_data_comparison.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,85 @@ end;
126126

127127
```
128128

129+
Example of `include / exclude` for anydata.convertCollection
130+
131+
```plsql
132+
create or replace type person as object(
133+
name varchar2(100),
134+
age integer
135+
)
136+
/
137+
create or replace type people as table of person
138+
/
139+
140+
create or replace package ut_anydata_inc_exc IS
141+
142+
--%suite(Anydata)
143+
144+
--%test(Anydata include)
145+
procedure ut_anydata_test_inc;
146+
147+
--%test(Anydata exclude)
148+
procedure ut_anydata_test_exc;
149+
150+
--%test(Fail on age)
151+
procedure ut_fail_anydata_test;
152+
153+
end ut_anydata_inc_exc;
154+
/
155+
156+
create or replace package body ut_anydata_inc_exc IS
157+
158+
procedure ut_anydata_test_inc IS
159+
l_actual people := people(person('Matt',45));
160+
l_expected people :=people(person('Matt',47));
161+
begin
162+
ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).include('NAME');
163+
end;
164+
165+
procedure ut_anydata_test_exc IS
166+
l_actual people := people(person('Matt',45));
167+
l_expected people :=people(person('Matt',47));
168+
begin
169+
--Arrange
170+
ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).exclude('AGE');
171+
end;
172+
173+
procedure ut_fail_anydata_test IS
174+
l_actual people := people(person('Matt',45));
175+
l_expected people :=people(person('Matt',47));
176+
begin
177+
--Arrange
178+
ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).include('AGE');
179+
end;
180+
181+
end ut_anydata_inc_exc;
182+
/
183+
184+
```
185+
186+
will result in :
187+
188+
```sql
189+
Anydata
190+
Anydata include [.044 sec]
191+
Anydata exclude [.035 sec]
192+
Fail on age [.058 sec] (FAILED - 1)
193+
194+
Failures:
195+
196+
1) ut_fail_anydata_test
197+
Actual: ut3.people [ count = 1 ] was expected to equal: ut3.people [ count = 1 ]
198+
Diff:
199+
Rows: [ 1 differences ]
200+
Row No. 1 - Actual: <AGE>45</AGE>
201+
Row No. 1 - Expected: <AGE>47</AGE>
202+
```
203+
204+
205+
206+
Example of exclude
207+
129208
Only the columns 'RN', "A_Column" will be compared. Column 'SOME_COL' is excluded.
130209

131210
This option can be useful in scenarios where you need to narrow-down the scope of test so that the test is only focused on very specific data.

source/core/ut_utils.pkb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,5 +824,30 @@ create or replace package body ut_utils is
824824
return l_result;
825825
end;
826826

827+
function add_prefix(a_list ut_varchar2_list, a_prefix varchar2, a_connector varchar2 := '/') return ut_varchar2_list is
828+
l_result ut_varchar2_list := ut_varchar2_list();
829+
l_idx binary_integer;
830+
begin
831+
if a_prefix is not null then
832+
l_idx := a_list.first;
833+
while l_idx is not null loop
834+
l_result.extend;
835+
l_result(l_idx) := add_prefix(a_list(l_idx), a_prefix, a_connector);
836+
l_idx := a_list.next(l_idx);
837+
end loop;
838+
end if;
839+
return l_result;
840+
end;
841+
842+
function add_prefix(a_item varchar2, a_prefix varchar2, a_connector varchar2 := '/') return varchar2 is
843+
begin
844+
return a_prefix||a_connector||trim(leading a_connector from a_item);
845+
end;
846+
847+
function strip_prefix(a_item varchar2, a_prefix varchar2, a_connector varchar2 := '/') return varchar2 is
848+
begin
849+
return regexp_replace(a_item,a_prefix||a_connector);
850+
end;
851+
827852
end ut_utils;
828853
/

source/core/ut_utils.pks

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,5 +404,14 @@ create or replace package ut_utils authid definer is
404404
*/
405405
function to_cdata(a_clob clob) return clob;
406406

407+
/**
408+
* Add prefix word to elements of list
409+
*/
410+
function add_prefix(a_list ut_varchar2_list, a_prefix varchar2, a_connector varchar2 := '/') return ut_varchar2_list;
411+
412+
function add_prefix(a_item varchar2, a_prefix varchar2, a_connector varchar2 := '/') return varchar2;
413+
414+
function strip_prefix(a_item varchar2, a_prefix varchar2, a_connector varchar2 := '/') return varchar2;
415+
407416
end ut_utils;
408417
/

source/expectations/data_values/ut_compound_data_helper.pkb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ create or replace package body ut_compound_data_helper is
335335
l_not_equal_stmt clob;
336336
l_where_stmt clob;
337337
l_ut_owner varchar2(250) := ut_utils.ut_owner;
338+
l_join_by_list ut_varchar2_list;
338339

339340
function get_join_type(a_inclusion_compare in boolean,a_negated in boolean) return varchar2 is
340341
begin
@@ -356,12 +357,22 @@ create or replace package body ut_compound_data_helper is
356357
end;
357358

358359
begin
360+
/**
361+
* We already estabilished cursor equality so now we add anydata root if we compare anydata
362+
* to join by.
363+
*/
364+
l_join_by_list :=
365+
case
366+
when a_other is of (ut_data_value_anydata) then ut_utils.add_prefix(a_join_by_list, a_other.cursor_details.get_root)
367+
else a_join_by_list
368+
end;
369+
359370
dbms_lob.createtemporary(l_compare_sql, true);
360371
--Initiate a SQL template with placeholders
361372
ut_utils.append_to_clob(l_compare_sql, g_compare_sql_template);
362373
--Generate a pieceso of dynamic SQL that will substitute placeholders
363374
gen_sql_pieces_out_of_cursor(
364-
a_other.cursor_details.cursor_columns_info, a_join_by_list, a_unordered,
375+
a_other.cursor_details.cursor_columns_info, l_join_by_list, a_unordered,
365376
l_xmltable_stmt, l_select_stmt, l_partition_stmt, l_join_on_stmt,
366377
l_not_equal_stmt
367378
);
@@ -374,7 +385,7 @@ create or replace package body ut_compound_data_helper is
374385
l_compare_sql := replace(l_compare_sql,'{:join_type:}',get_join_type(a_inclusion_type,a_is_negated));
375386
l_compare_sql := replace(l_compare_sql,'{:join_condition:}',l_join_on_stmt);
376387

377-
if l_not_equal_stmt is not null and ((a_join_by_list.count > 0 and not a_is_negated) or (not a_unordered)) then
388+
if l_not_equal_stmt is not null and ((l_join_by_list.count > 0 and not a_is_negated) or (not a_unordered)) then
378389
ut_utils.append_to_clob(l_where_stmt,' ( '||l_not_equal_stmt||' ) or ');
379390
end if;
380391
--If its inclusion we expect a actual set to fully match and have no extra elements over expected

source/expectations/data_values/ut_cursor_column.tpb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ create or replace type body ut_cursor_column as
2626
self.xml_valid_name
2727
else
2828
a_access_path||'/'||self.xml_valid_name
29-
end; --Access path used for incldue exclude eg/ TEST_DUMMY_OBJECT/VARCHAR2
29+
end; --Access path used for XMLTABLE query
30+
self.filter_path := '/'||self.access_path; --Filter path will differ from access path in anydata type
3031
self.transformed_name := case when length(self.xml_valid_name) > 30 then
3132
'"'||ut_compound_data_helper.get_fixed_size_hash(self.parent_name||self.xml_valid_name)||'"'
3233
when self.parent_name is null then

source/expectations/data_values/ut_cursor_column.tps

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
create or replace type ut_cursor_column force authid current_user as object (
1+
create or replace type ut_cursor_column authid current_user as object (
22
/*
33
utPLSQL - Version 3
44
Copyright 2016 - 2018 utPLSQL Project
@@ -17,6 +17,7 @@ create or replace type ut_cursor_column force authid current_user as object (
1717
*/
1818
parent_name varchar2(4000),
1919
access_path varchar2(4000),
20+
filter_path varchar2(4000),
2021
display_path varchar2(4000),
2122
has_nested_col number(1,0),
2223
transformed_name varchar2(2000),

source/expectations/data_values/ut_cursor_details.tpb

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ create or replace type body ut_cursor_details as
9696
l_hierarchy_level integer := 1;
9797
begin
9898
self.cursor_columns_info := ut_cursor_column_tab();
99+
self.is_anydata := 0;
99100
dbms_sql.describe_columns3(a_cursor_number, l_columns_count, l_columns_desc);
100101

101102
/**
@@ -147,12 +148,13 @@ create or replace type body ut_cursor_details as
147148
member function get_missing_join_by_columns( a_expected_columns ut_varchar2_list ) return ut_varchar2_list is
148149
l_result ut_varchar2_list;
149150
begin
151+
--regexp_replace(c.access_path,'^\/?([^\/]+\/){1}')
150152
select fl.column_value
151153
bulk collect into l_result
152154
from table(a_expected_columns) fl
153155
where not exists (
154156
select 1 from table(self.cursor_columns_info) c
155-
where regexp_like(c.access_path, '^'||fl.column_value||'($|/.*)')
157+
where regexp_like(c.filter_path,'^/?'||fl.column_value||'($|/.*)' )
156158
)
157159
order by fl.column_value;
158160
return l_result;
@@ -181,8 +183,9 @@ create or replace type body ut_cursor_details as
181183
bulk collect into l_result.cursor_columns_info
182184
from table(self.cursor_columns_info) x
183185
where exists(
184-
select 1 from included_columns f where regexp_like( x.access_path, '^/?'||f.col_names||'($|/.*)' )
185-
);
186+
select 1 from included_columns f where regexp_like(x.filter_path,'^/?'||f.col_names||'($|/.*)' )
187+
)
188+
or x.hierarchy_level = case when self.is_anydata = 1 then 1 else 0 end ;
186189
end if;
187190
elsif a_match_options.exclude.items.count > 0 then
188191
with excluded_columns as (
@@ -193,7 +196,7 @@ create or replace type body ut_cursor_details as
193196
bulk collect into l_result.cursor_columns_info
194197
from table(self.cursor_columns_info) x
195198
where not exists(
196-
select 1 from excluded_columns f where regexp_like( '/'||x.access_path, '^/?'||f.col_names||'($|/.*)' )
199+
select 1 from excluded_columns f where regexp_like(x.filter_path,'^/?'||f.col_names||'($|/.*)' )
197200
);
198201
end if;
199202

@@ -226,8 +229,28 @@ create or replace type body ut_cursor_details as
226229
from table(self.cursor_columns_info) t
227230
where (a_parent_name is null and parent_name is null and hierarchy_level = 1 and column_name is not null)
228231
having count(*) > 0;
229-
230232
return l_result;
231233
end;
234+
235+
member function get_root return varchar2 is
236+
l_root varchar2(250);
237+
begin
238+
if self.cursor_columns_info.count > 0 then
239+
select x.access_path into l_root from table(self.cursor_columns_info) x
240+
where x.hierarchy_level = 1;
241+
else
242+
l_root := null;
243+
end if;
244+
return l_root;
245+
end;
246+
247+
member procedure strip_root_from_anydata(self in out nocopy ut_cursor_details) is
248+
l_root varchar2(250) := get_root();
249+
begin
250+
self.is_anydata := 1;
251+
for i in 1..cursor_columns_info.count loop
252+
self.cursor_columns_info(i).filter_path := '/'||ut_utils.strip_prefix(self.cursor_columns_info(i).access_path,l_root);
253+
end loop;
254+
end;
232255
end;
233256
/

source/expectations/data_values/ut_cursor_details.tps

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
create or replace type ut_cursor_details force authid current_user as object (
1+
create or replace type ut_cursor_details authid current_user as object (
22
/*
33
utPLSQL - Version 3
44
Copyright 2016 - 2018 utPLSQL Project
@@ -17,6 +17,8 @@ create or replace type ut_cursor_details force authid current_user as object (
1717
*/
1818
cursor_columns_info ut_cursor_column_tab,
1919

20+
/*if type is anydata we need to skip level 1 on joinby / inlude / exclude as its artificial cursor*/
21+
is_anydata number(1,0),
2022
constructor function ut_cursor_details(self in out nocopy ut_cursor_details) return self as result,
2123
constructor function ut_cursor_details(
2224
self in out nocopy ut_cursor_details,a_cursor_number in number
@@ -29,9 +31,11 @@ create or replace type ut_cursor_details force authid current_user as object (
2931
a_level in integer,
3032
a_access_path in varchar2
3133
),
32-
member function contains_collection return boolean,
33-
member function get_missing_join_by_columns( a_expected_columns ut_varchar2_list ) return ut_varchar2_list,
34+
member function contains_collection return boolean,
35+
member function get_missing_join_by_columns( a_expected_columns ut_varchar2_list ) return ut_varchar2_list,
3436
member procedure filter_columns(self in out nocopy ut_cursor_details, a_match_options ut_matcher_options),
35-
member function get_xml_children(a_parent_name varchar2 := null) return xmltype
37+
member function get_xml_children(a_parent_name varchar2 := null) return xmltype,
38+
member function get_root return varchar2,
39+
member procedure strip_root_from_anydata(self in out nocopy ut_cursor_details)
3640
)
3741
/

source/expectations/data_values/ut_data_value_anydata.tpb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ create or replace type body ut_data_value_anydata as
6161

6262
member procedure init(self in out nocopy ut_data_value_anydata, a_value anydata) is
6363
l_refcursor sys_refcursor;
64-
l_ctx number;
65-
l_ut_owner varchar2(250) := ut_utils.ut_owner;
6664
cursor_not_open exception;
6765
l_cursor_number number;
6866
l_anydata_sql varchar2(32767);
@@ -84,6 +82,7 @@ create or replace type body ut_data_value_anydata as
8482
self.extract_cursor(l_refcursor);
8583
l_cursor_number := dbms_sql.to_cursor_number(l_refcursor);
8684
self.cursor_details := ut_cursor_details(l_cursor_number);
85+
self.cursor_details.strip_root_from_anydata;
8786
dbms_sql.close_cursor(l_cursor_number);
8887
elsif not l_refcursor%isopen then
8988
raise cursor_not_open;

source/expectations/data_values/ut_data_value_refcursor.tpb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,14 @@ create or replace type body ut_data_value_refcursor as
250250
if l_diff_row_count > 0 then
251251
l_row_diffs := ut_compound_data_helper.get_rows_diff_by_sql(
252252
l_self_cols, l_other_cols, l_self.data_id, l_other.data_id,
253-
l_diff_id, a_match_options.join_by.items, a_match_options.unordered,
254-
a_match_options.ordered_columns(), self.extract_path
253+
l_diff_id,
254+
case
255+
when
256+
l_self.cursor_details.is_anydata = 1 then ut_utils.add_prefix(a_match_options.join_by.items, l_self.cursor_details.get_root)
257+
else
258+
a_match_options.join_by.items
259+
end,
260+
a_match_options.unordered,a_match_options.ordered_columns(), self.extract_path
255261
);
256262
l_message := chr(10)
257263
||'Rows: [ ' || l_diff_row_count ||' differences'

0 commit comments

Comments
 (0)