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

Skip to content

Commit d7a2711

Browse files
committed
Fixed issue with spaces in column lists.
Updated documentation.
1 parent 62e5d80 commit d7a2711

6 files changed

Lines changed: 163 additions & 97 deletions

File tree

docs/userguide/advanced_data_comparison.md

Lines changed: 145 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Advanced data-comparison options are available for the [`equal`](expectations.md
2828
- `include(a_items ut_varchar2_list)` - table of items to include
2929
- `exclude(a_items ut_varchar2_list)` - table of items to exclude
3030
- `unordered` - perform compare on unordered set of data, return only missing or actual, ***not supported for `include / contain`*** , as alternative `join_by` can be used
31-
- `join_by(a_columns varchar2)` - columns or comma separated list of columns to join two cursors by
31+
- `join_by(a_columns varchar2)` - column or comma separated list of columns to join two cursors by
3232
- `join_by(a_columns ut_varchar2_list)` - table of columns to join two cursors by
3333

3434
Each item in the comma separated list can be:
@@ -58,7 +58,7 @@ procedure test_cur_skip_columns_cn is
5858
l_expected sys_refcursor;
5959
l_actual sys_refcursor;
6060
begin
61-
open l_expected for select 'text' ignore_me, d.* from user_tables d;
61+
open l_expected for select 'text' ignore_me, d.* from user_tables d where rownum = 1;
6262
open l_actual for select sysdate "ADate", d.* from user_tables d;
6363
ut.expect( l_actual ).to_include( l_expected ).exclude( 'IGNORE_ME,ADate' );
6464
end;
@@ -127,99 +127,106 @@ Only the columns 'RN', "A_Column" will be compared. Column 'SOME_COL' is exclude
127127

128128
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.
129129

130-
##Unordered
130+
## Unordered
131131

132132
Unordered option allows for quick comparison of two cursors without need of ordering them in any way.
133133

134134
Result of such comparison will be limited to only information about row existing or not existing in given set without actual information about exact differences.
135135

136-
**This option is not supported for `include / contain` matcher**
137-
138-
139-
140136
```sql
141137
procedure unordered_tst is
142138
l_actual sys_refcursor;
143139
l_expected sys_refcursor;
144140
begin
145-
open l_expected for select username, user_id from all_users
146-
union all
147-
select 'TEST' username, -600 user_id from dual
148-
order by 1 desc;
149-
open l_actual for select username, user_id from all_users
150-
union all
151-
select 'TEST' username, -610 user_id from dual
152-
order by 1 asc;
141+
open l_expected for
142+
select username, user_id from all_users
143+
union all
144+
select 'TEST' username, -600 user_id from dual
145+
order by 1 desc;
146+
open l_actual for
147+
select username, user_id from all_users
148+
union all
149+
select 'TEST' username, -610 user_id from dual
150+
order by 1 asc;
153151
ut.expect( l_actual ).to_equal( l_expected ).unordered;
154152
end;
155153
```
156154

157-
158-
159155
Above test will result in two differences of one row extra and one row missing.
160156

161-
162-
163157
```sql
164158
Diff:
165159
Rows: [ 2 differences ]
166160
Missing: <ROW><USERNAME>TEST</USERNAME><USER_ID>-600</USER_ID></ROW>
167161
Extra: <ROW><USERNAME>TEST</USERNAME><USER_ID>-610</USER_ID></ROW>
168162
```
169163

164+
**Note**
170165

171-
166+
> `include / contain` matcher is not considering order of compared data-sets by default so using `unordered` makes no difference (it's default)
172167
173168

174169
## Join By option
175170

176-
You can now join two cursors by defining a primary key or composite key that will be used to uniquely identify and compare rows. This option allows us to exactly show which rows are missing, extra and which are different without ordering clause. In the situation where the join key is not unique, join will partition set over rows with a same key and join on row number as well as given join key. The extra rows or missing will be presented to user as well as not matching rows.
171+
The `join_by` syntax enables comparison of unordered cursors by joining cursor data using specified columns.
177172

178-
Join by option can be used in conjunction with include or exclude options. However if any of the join keys is part of exclude set, comparison will fail and report to user that sets could not be joined on specific key (excluded).
173+
You can join two cursors by defining join column(s) that will be used to uniquely identify and compare rows.
174+
With this option, framework is able to identify which rows are missing, which are extra and which are different without need to have both cursors uniformly ordered.
175+
When the specified join column(s) are not unique, join will partition set over rows with the same key and join on row number as well as given join key.
176+
The extra or missing rows will be presented to user as well as all non-matching rows.
177+
178+
Join by option can be used in conjunction with include or exclude options.
179+
However if any of the join keys is part of exclude set, comparison will fail and report to user that sets could not be joined on specific key, as the key was excluded.
179180

180181
```sql
181182
procedure join_by_username is
182183
l_actual sys_refcursor;
183184
l_expected sys_refcursor;
184185
begin
185-
open l_expected for select username, user_id from all_users
186-
union all
187-
select 'TEST' username, -600 user_id from dual
188-
order by 1 desc;
189-
open l_actual for select username, user_id from all_users
190-
union all
191-
select 'TEST' username, -610 user_id from dual
192-
order by 1 asc;
186+
open l_expected for
187+
select username, user_id from all_users
188+
union all
189+
select 'TEST' username, -600 user_id from dual
190+
order by 1 desc;
191+
open l_actual for
192+
select username, user_id from all_users
193+
union all
194+
select 'TEST' username, -610 user_id from dual
195+
order by 1 asc;
193196
ut.expect( l_actual ).to_equal( l_expected ).join_by('USERNAME');
194197
end;
195198
```
196-
This will show you difference in row 'TEST' regardless of order.
199+
200+
Above test will result in a difference in row 'TEST' regardless of data order.
197201

198202
```sql
199203
Rows: [ 1 differences ]
200204
PK <USERNAME>TEST</USERNAME> - Expected: <USER_ID>-600</USER_ID>
201205
PK <USERNAME>TEST</USERNAME> - Actual: <USER_ID>-610</USER_ID>
202206
```
203207

204-
Assumption is that join by is made by column name so that what will be displayed as part of results.
208+
**Note**
205209

206-
Consider this example using `contain / include `
210+
> When using `join_by`, the join column(s) are displayed first (as PK) to help you identify the mismatched rows/columns.
211+
212+
You can use `join_by` extended syntax in combination with `contain / include ` matcher.
207213

208214
```sql
209215
procedure join_by_username_cn is
210216
l_actual sys_refcursor;
211217
l_expected sys_refcursor;
212218
begin
213-
open l_actual for select username, user_id from all_users;
214-
open l_expected for select username, user_id from all_users
215-
union all
216-
select 'TEST' username, -610 user_id from dual;
219+
open l_actual for select username, user_id from all_users;
220+
open l_expected for
221+
select username, user_id from all_users
222+
union all
223+
select 'TEST' username, -610 user_id from dual;
217224

218225
ut.expect( l_actual ).to_contain( l_expected ).join_by('USERNAME');
219226
end;
220227
```
221228

222-
This will show you that one value is not included in actual set:
229+
Above test will indicate that in actual data-set
223230

224231
```sql
225232
Actual: refcursor [ count = 43 ] was expected to include: refcursor [ count = 44 ]
@@ -229,59 +236,120 @@ This will show you that one value is not included in actual set:
229236
```
230237

231238

239+
### Joining cursors using multiple columns
232240

233-
Join by options currently doesn't support nested table inside cursor comparison, however is still possible to compare a collection as a whole.
234-
235-
Example.
241+
You can specify multiple columns in `join_by`
236242

237243
```sql
238-
procedure compare_collection_in_rec is
244+
procedure test_join_by_many_columns is
239245
l_actual sys_refcursor;
240246
l_expected sys_refcursor;
241-
l_actual_tab ut3.ut_annotated_object;
242-
l_expected_tab ut3.ut_annotated_object;
243-
l_expected_message varchar2(32767);
244-
l_actual_message varchar2(32767);
245-
begin
246-
select ut3.ut_annotated_object('TEST','TEST','TEST',
247-
ut3.ut_annotations(ut3.ut_annotation(1,'test','test','test'),
248-
ut3.ut_annotation(2,'test','test','test'))
249-
)
250-
into l_actual_tab from dual;
247+
begin
248+
open l_expected for
249+
select username, user_id, created from all_users
250+
order by 1 desc;
251+
open l_actual for
252+
select username, user_id, created from all_users
253+
union all
254+
select 'TEST' username, -610 user_id, sysdate from dual
255+
order by 1 asc;
256+
ut.expect( l_actual ).to_equal( l_expected ).join_by('USERNAME, USER_ID');
257+
end;
258+
```
251259

252-
select ut3.ut_annotated_object('TEST','TEST','TEST',
253-
ut3.ut_annotations(ut3.ut_annotation(1,'test','test','test'),
254-
ut3.ut_annotation(2,'test','test','test'))
255-
)
256-
into l_expected_tab from dual;
257-
258-
--Arrange
259-
open l_actual for select l_actual_tab as nested_table from dual;
260-
261-
open l_expected for select l_expected_tab as nested_table from dual;
262-
263-
--Act
264-
ut3.ut.expect(l_actual).to_equal(l_expected).join_by('NESTED_TABLE/ANNOTATIONS');
260+
### Joining cursors using attributes of object in column list
265261

266-
end;
267-
```
262+
`join_by` allows for joining cursor data by attributes of object from column list of the compared cursors.
263+
264+
To reference attribute as PK, use slash symbol `/` to separate nested elements.
265+
266+
In the below example, cursors are joined using the `NAME` attribute of object in column `SOMEONE`
267+
268+
```sql
269+
create or replace type person as object(
270+
name varchar2(100),
271+
age integer
272+
)
273+
/
274+
create or replace type people as table of person
275+
/
276+
277+
create or replace package test_join_by is
278+
--%suite
279+
280+
--%test
281+
procedure test_join_by_object_attribute;
282+
283+
end;
284+
/
268285

286+
create or replace package body test_join_by is
287+
procedure test_join_by_object_attribute is
288+
l_actual sys_refcursor;
289+
l_expected sys_refcursor;
290+
begin
291+
open l_expected for
292+
select person('Jack',42) someone from dual union all
293+
select person('Pat', 44) someone from dual union all
294+
select person('Matt',45) someone from dual;
295+
open l_actual for
296+
select person('Matt',55) someone from dual union all
297+
select person('Pat', 44) someone from dual;
298+
ut.expect( l_actual ).to_equal( l_expected ).join_by( 'SOMEONE/NAME' );
299+
end;
269300

301+
end;
302+
/
270303

271-
In case when a there is detected collection inside cursor and we cannot join key. Comparison will present a failed joins and also a message about collection being detected.
304+
```
305+
306+
**Note**
307+
> `join_by` does not support joining on individual elements of nested table. You can still use data of the nested table as a PK value.
308+
> When collection is referenced in `join_by`, test will fail with appropriate message, as it cannot perform a join.
272309
273310
```sql
311+
create or replace type person as object(
312+
name varchar2(100),
313+
age integer
314+
)
315+
/
316+
create or replace type people as table of person
317+
/
318+
319+
create or replace package test_join_by is
320+
--%suite
321+
322+
--%test
323+
procedure test_join_by_collection_elem;
324+
325+
end;
326+
/
327+
328+
create or replace package body test_join_by is
329+
procedure test_join_by_collection_elem is
330+
l_actual sys_refcursor;
331+
l_expected sys_refcursor;
332+
begin
333+
open l_expected for select people(person('Matt',45)) persons from dual;
334+
open l_actual for select people(person('Matt',45)) persons from dual;
335+
ut.expect( l_actual ).to_equal( l_expected ).join_by('PERSONS/PERSON/NAME');
336+
end;
337+
338+
end;
339+
/
340+
```
341+
342+
```
274343
Actual: refcursor [ count = 1 ] was expected to equal: refcursor [ count = 1 ]
275344
Diff:
276-
Unable to join sets:
277-
Join key NESTED_TABLE/ANNOTATIONS/TEXT does not exists in expected
278-
Join key NESTED_TABLE/ANNOTATIONS/TEXT does not exists in actual
279-
Please make sure that your join clause is not refferring to collection element
345+
Unable to join sets:
346+
Join key PERSONS/PERSON/NAME does not exists in expected
347+
Join key PERSONS/PERSON/NAME does not exists in actual
348+
Please make sure that your join clause is not refferring to collection element
280349
```
281350

282-
283-
284-
***Please note that .join_by option will take longer to process due to need of parsing via primary keys.***
351+
***Note***
352+
>`join_by` option is slower to process as it needs to perform a cursor join.
285353
286354
## Defining item lists in option
287355
XPath expressions are deprecated. They are currently still supported but in future versions they can be removed completely. Please use a current standard of defining items filter.
@@ -293,21 +361,13 @@ When using item list expression, keep in mind the following:
293361

294362
Example of a valid parameter to include columns: `RN`, `A_Column`, `SOME_COL` in data comparison.
295363
```sql
296-
procedure include_col_list_eq is
364+
procedure include_col_list is
297365
l_actual sys_refcursor;
298366
l_expected sys_refcursor;
299367
begin
300368
open l_expected for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL from dual a connect by level < 4;
301369
open l_actual for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL, a.* from all_objects a where rownum < 4;
302370
ut.expect( l_actual ).to_equal( l_expected ).include( 'RN,A_Column,SOME_COL' );
303-
end;
304-
305-
procedure include_col_list_eq is
306-
l_actual sys_refcursor;
307-
l_expected sys_refcursor;
308-
begin
309-
open l_expected for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL from dual a connect by level < 4;
310-
open l_actual for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL, a.* from all_objects a where rownum < 6;
311-
ut.expect( l_actual ).to_include( l_expected ).include( 'RN,A_Column,SOME_COL' );
371+
ut.expect( l_actual ).to_equal( l_expected ).include( ut_varchar2_list( 'RN', 'A_Column', 'SOME_COL' ) );
312372
end;
313373
```

source/core/ut_utils.pks

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,10 +344,14 @@ create or replace package ut_utils authid definer is
344344
function get_xml_header(a_encoding varchar2) return varchar2;
345345

346346

347-
/*It takes a collection of type ut_varchar2_list and it trims the characters passed as arguments for every element*/
347+
/**
348+
* Takes a collection of type ut_varchar2_list and it trims the characters passed as arguments for every element
349+
*/
348350
function trim_list_elements(a_list IN ut_varchar2_list, a_regexp_to_trim in varchar2 default '[:space:]') return ut_varchar2_list;
349351

350-
/*It takes a collection of type ut_varchar2_list and it only returns the elements which meets the regular expression*/
352+
/**
353+
* Takes a collection of type ut_varchar2_list and it only returns the elements which meets the regular expression
354+
*/
351355
function filter_list(a_list IN ut_varchar2_list, a_regexp_filter in varchar2) return ut_varchar2_list;
352356

353357
-- Generates XMLGEN escaped string

source/expectations/data_values/ut_compound_data_helper.pkb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ create or replace package body ut_compound_data_helper is
413413
end;
414414

415415
function get_rows_diff_by_sql(
416-
a_act_cursor_info ut_cursor_column_tab,a_exp_cursor_info ut_cursor_column_tab,
416+
a_act_cursor_info ut_cursor_column_tab, a_exp_cursor_info ut_cursor_column_tab,
417417
a_expected_dataset_guid raw, a_actual_dataset_guid raw, a_diff_id raw,
418418
a_join_by_list ut_varchar2_list, a_unordered boolean, a_enforce_column_order boolean := false
419419
) return tt_row_diffs is
@@ -832,6 +832,7 @@ create or replace package body ut_compound_data_helper is
832832
begin
833833
g_anytype_name_map(dbms_types.typecode_date) := 'DATE';
834834
g_anytype_name_map(dbms_types.typecode_number) := 'NUMBER';
835+
g_anytype_name_map(3 /*INTEGER in object type*/) := 'NUMBER';
835836
g_anytype_name_map(dbms_types.typecode_raw) := 'RAW';
836837
g_anytype_name_map(dbms_types.typecode_char) := 'CHAR';
837838
g_anytype_name_map(dbms_types.typecode_varchar2) := 'VARCHAR2';

source/expectations/data_values/ut_data_value_refcursor.tpb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,8 @@ create or replace type body ut_data_value_refcursor as
242242
l_results := ut_utils.t_clob_tab();
243243
if l_diff_row_count > 0 then
244244
l_row_diffs := ut_compound_data_helper.get_rows_diff_by_sql(
245-
l_exp_cols,l_act_cols, self.data_id, l_actual.data_id, l_diff_id,a_join_by_list , a_unordered, l_column_order_enforce);
245+
l_exp_cols, l_act_cols, self.data_id, l_actual.data_id, l_diff_id, a_join_by_list , a_unordered, l_column_order_enforce
246+
);
246247
l_message := chr(10)
247248
||'Rows: [ ' || l_diff_row_count ||' differences'
248249
|| case when l_diff_row_count > c_max_rows and l_row_diffs.count > 0 then ', showing first '||c_max_rows end

0 commit comments

Comments
 (0)