diff --git a/.travis/run_sonar_scanner.sh b/.travis/run_sonar_scanner.sh index f5ea43750..4685eb3f3 100755 --- a/.travis/run_sonar_scanner.sh +++ b/.travis/run_sonar_scanner.sh @@ -64,4 +64,5 @@ add_sonar_property "${DB_DRIVER_PATH}" "${OJDBC_HOME}/ojdbc8.jar" #Execute Sonar scanner -sonar-scanner \ No newline at end of file +echo "Executing sonar scanner" +sonar-scanner diff --git a/docs/about/authors.md b/docs/about/authors.md index 55f85642d..76bb24e5f 100644 --- a/docs/about/authors.md +++ b/docs/about/authors.md @@ -7,11 +7,11 @@ | ---------------- | -------------- | David Pyke | [Shoelace](https://github.com/Shoelace) | Jacek Gebal | [jgebal](https://github.com/jgebal) +| Lukasz Wasylow | [lwasylow](https://github.com/lwasylow/) | Pavel Kaplya | [Pazus](https://github.com/Pazus) | Robert Love | [rlove](https://github.com/rlove) -| Vinicius Avellar | [viniciusam](https://github.com/viniciusam/) | Samuel Nitsche | [pesse](https://github.com/pesse/) -| Lukasz Wasylow | [lwasylow](https://github.com/lwasylow/) +| Vinicius Avellar | [viniciusam](https://github.com/viniciusam/) diff --git a/docs/images/venn21.gif b/docs/images/venn21.gif new file mode 100644 index 000000000..0efecae07 Binary files /dev/null and b/docs/images/venn21.gif differ diff --git a/docs/images/venn22.gif b/docs/images/venn22.gif new file mode 100644 index 000000000..52768b71f Binary files /dev/null and b/docs/images/venn22.gif differ diff --git a/docs/userguide/advanced_data_comparison.md b/docs/userguide/advanced_data_comparison.md index 3eb45fe50..e1858a6fb 100644 --- a/docs/userguide/advanced_data_comparison.md +++ b/docs/userguide/advanced_data_comparison.md @@ -6,7 +6,7 @@ utPLSQL expectations incorporates advanced data comparison options when comparin - object type - nested table and varray -Advanced data-comparison options are available for the [`equal`](expectations.md#equal) matcher. +Advanced data-comparison options are available for the [`equal`](expectations.md#equal) and [`contain`](expectations.md#include--contain) matcher. ## Syntax @@ -15,6 +15,10 @@ Advanced data-comparison options are available for the [`equal`](expectations.md ut.expect( a_actual {data-type} ).not_to( equal( a_expected {data-type})[.extendend_option()[.extendend_option()[...]]]) ); ut.expect( a_actual {data-type} ).to_equal( a_expected {data-type})[.extendend_option()[.extendend_option()[...]]]); ut.expect( a_actual {data-type} ).not_to_equal( a_expected {data-type})[.extendend_option()[.extendend_option()[...]]] ); + ut.expect( a_actual {data-type} ).to_( contain( a_expected {data-type})[.extendend_option()[.extendend_option()[...]]]); + ut.expect( a_actual {data-type} ).not_to( contain( a_expected {data-type})[.extendend_option()[.extendend_option()[...]]]) ); + ut.expect( a_actual {data-type} ).to_contain( a_expected {data-type})[.extendend_option()[.extendend_option()[...]]]); + ut.expect( a_actual {data-type} ).not_to_contain( a_expected {data-type})[.extendend_option()[.extendend_option()[...]]]); ``` `extended_option` can be one of: @@ -23,28 +27,26 @@ Advanced data-comparison options are available for the [`equal`](expectations.md - `exclude(a_items varchar2)` - item or comma separated list of items to exclude - `include(a_items ut_varchar2_list)` - table of items to include - `exclude(a_items ut_varchar2_list)` - table of items to exclude - - `unordered` - perform compare on unordered set of data, return only missing or actual - - `join_by(a_columns varchar2)` - columns or comma seperated list of columns to join two cursors by + - `unordered` - ignore order of data sets when comparing data. Default when comparing data-sets with `to_contain` + - `join_by(a_columns varchar2)` - column or comma separated list of columns to join two cursors by - `join_by(a_columns ut_varchar2_list)` - table of columns to join two cursors by + - `unordered_columns` / `uc` - ignore the ordering of columns / attributes in compared data-sets. Column/attribute names will be used to identify data to be compared and the position will be ignored. Each item in the comma separated list can be: - a column name of cursor to be compared - an attribute name of object type to be compared - an attribute name of object type within a table of objects to be compared -- an [XPath](http://zvon.org/xxl/XPathTutorial/Output/example1.html) expression representing column/attribute - Include and exclude option will not support implicit colum names that starts with single quota, or in fact any other special characters e.g. <, >, & Each element in `ut_varchar2_list` nested table can be an item or a comma separated list of items. When specifying column/attribute names, keep in mind that the names are **case sensitive**. -**XPath expressions with comma are not supported.** - ## Excluding elements from data comparison -Consider the following example +Consider the following examples ```sql -procedure test_cursors_skip_columns is +procedure test_cur_skip_columns_eq is l_expected sys_refcursor; l_actual sys_refcursor; begin @@ -52,10 +54,19 @@ begin open l_actual for select sysdate "ADate", d.* from user_tables d; ut.expect( l_actual ).to_equal( l_expected ).exclude( 'IGNORE_ME,ADate' ); end; + +procedure test_cur_skip_columns_cn is + l_expected sys_refcursor; + l_actual sys_refcursor; +begin + open l_expected for select 'text' ignore_me, d.* from user_tables d where rownum = 1; + open l_actual for select sysdate "ADate", d.* from user_tables d; + ut.expect( l_actual ).to_contain( l_expected ).exclude( 'IGNORE_ME,ADate' ); +end; ``` Columns 'ignore_me' and "ADate" will get excluded from cursor comparison. -The cursor data is equal, when those columns are excluded. +The cursor data is equal or includes expected, when those columns are excluded. This option is useful in scenarios, when you need to exclude incomparable/unpredictable column data like CREATE_DATE of a record that is maintained by default value on a table column. @@ -63,7 +74,7 @@ This option is useful in scenarios, when you need to exclude incomparable/unpred Consider the following example ```sql -procedure include_columns_as_csv is +procedure include_col_as_csv_eq is l_actual sys_refcursor; l_expected sys_refcursor; begin @@ -71,14 +82,23 @@ begin open l_actual for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL, a.* from all_objects a where rownum < 4; ut.expect( l_actual ).to_equal( l_expected ).include( 'RN,A_Column,SOME_COL' ); end; + +procedure include_col_as_csv_cn is + l_actual sys_refcursor; + l_expected sys_refcursor; +begin + open l_expected for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL from dual a connect by level < 4; + open l_actual for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL, a.* from all_objects a where rownum < 6; + ut.expect( l_actual ).to_contain( l_expected ).include( 'RN,A_Column,SOME_COL' ); +end; ``` ## Combining include/exclude options You can chain the advanced options in an expectation and mix the `varchar2` with `ut_varchar2_list` arguments. -When doing so, the fianl list of items to include/exclude will be a concatenation of all items. +When doing so, the final list of items to include/exclude will be a concatenation of all items. ```sql -procedure include_columns_as_csv is +procedure include_col_as_csv_eq is l_actual sys_refcursor; l_expected sys_refcursor; begin @@ -89,43 +109,52 @@ begin .include( ut_varchar2_list( 'A_Column', 'SOME_COL' ) ) .exclude( 'SOME_COL' ); end; + +procedure include_col_as_csv_cn is + l_actual sys_refcursor; + l_expected sys_refcursor; +begin + open l_expected for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL from dual a connect by level < 4; + open l_actual for select rownum as rn, 'a' as "A_Column", 'Y' SOME_COL, a.* from all_objects a where rownum < 6; + ut.expect( l_actual ).to_contain( l_expected ) + .include( 'RN') + .include( ut_varchar2_list( 'A_Column', 'SOME_COL' ) ) + .exclude( 'SOME_COL' ); +end; + ``` Only the columns 'RN', "A_Column" will be compared. Column 'SOME_COL' is excluded. 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. -##Unordered +## Unordered -Unordered option allows for quick comparison of two cursors without need of ordering them in any way. +Unordered option allows for quick comparison of two compound data types without need of ordering them in any way. 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. - - ```sql procedure unordered_tst is l_actual sys_refcursor; l_expected sys_refcursor; begin - open l_expected for select username, user_id from all_users - union all - select 'TEST' username, -600 user_id from dual - order by 1 desc; - open l_actual for select username, user_id from all_users - union all - select 'TEST' username, -610 user_id from dual - order by 1 asc; + open l_expected for + select username, user_id from all_users + union all + select 'TEST' username, -600 user_id from dual + order by 1 desc; + open l_actual for + select username, user_id from all_users + union all + select 'TEST' username, -610 user_id from dual + order by 1 asc; ut.expect( l_actual ).to_equal( l_expected ).unordered; end; ``` - - Above test will result in two differences of one row extra and one row missing. - - ```sql Diff: Rows: [ 2 differences ] @@ -133,33 +162,43 @@ Above test will result in two differences of one row extra and one row missing. Extra: TEST-610 ``` +**Note** - +> `contain` matcher is not considering order of compared data-sets. Using `unordered` makes no difference (it's default) ## Join By option -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. +The `join_by` syntax enables comparison of unordered compound data types by joining data using specified columns. -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). +You can join two compound data types by defining join column(s) that will be used to uniquely identify and compare data rows. +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. +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. +The extra or missing rows will be presented to user as well as all non-matching rows. + +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, as the key was excluded. ```sql procedure join_by_username is l_actual sys_refcursor; l_expected sys_refcursor; begin - open l_expected for select username, user_id from all_users - union all - select 'TEST' username, -600 user_id from dual - order by 1 desc; - open l_actual for select username, user_id from all_users - union all - select 'TEST' username, -610 user_id from dual - order by 1 asc; + open l_expected for + select username, user_id from all_users + union all + select 'TEST' username, -600 user_id from dual + order by 1 desc; + open l_actual for + select username, user_id from all_users + union all + select 'TEST' username, -610 user_id from dual + order by 1 asc; ut.expect( l_actual ).to_equal( l_expected ).join_by('USERNAME'); end; ``` -This will show you difference in row 'TEST' regardless of order. + +Above test will result in a difference in row 'TEST' regardless of data order. ```sql Rows: [ 1 differences ] @@ -167,78 +206,219 @@ This will show you difference in row 'TEST' regardless of order. PK TEST - Actual: -610 ``` -Assumption is that join by is made by column name so that what will be displayed as part of results. +**Note** -Join by options currently doesn't support nested table inside cursor comparison, however is still possible to compare a collection as a whole. +> When using `join_by`, the join column(s) are displayed first (as PK) to help you identify the mismatched rows/columns. -Example. +You can use `join_by` extended syntax in combination with `contain / include ` matcher. ```sql - procedure compare_collection_in_rec is +procedure join_by_username_cn is l_actual sys_refcursor; l_expected sys_refcursor; - l_actual_tab ut3.ut_annotated_object; - l_expected_tab ut3.ut_annotated_object; - l_expected_message varchar2(32767); - l_actual_message varchar2(32767); - begin - select ut3.ut_annotated_object('TEST','TEST','TEST', - ut3.ut_annotations(ut3.ut_annotation(1,'test','test','test'), - ut3.ut_annotation(2,'test','test','test')) - ) - into l_actual_tab from dual; - - select ut3.ut_annotated_object('TEST','TEST','TEST', - ut3.ut_annotations(ut3.ut_annotation(1,'test','test','test'), - ut3.ut_annotation(2,'test','test','test')) - ) - into l_expected_tab from dual; - - --Arrange - open l_actual for select l_actual_tab as nested_table from dual; - - open l_expected for select l_expected_tab as nested_table from dual; +begin + open l_actual for select username, user_id from all_users; + open l_expected for + select username, user_id from all_users + union all + select 'TEST' username, -610 user_id from dual; - --Act - ut3.ut.expect(l_actual).to_equal(l_expected).join_by('NESTED_TABLE/ANNOTATIONS'); + ut.expect( l_actual ).to_contain( l_expected ).join_by('USERNAME'); +end; +``` - end; +Above test will indicate that in actual data-set + +```sql + Actual: refcursor [ count = 43 ] was expected to contain: refcursor [ count = 44 ] + Diff: + Rows: [ 1 differences ] + PK TEST - Missing -610 ``` +### Joining using multiple columns -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. +You can specify multiple columns in `join_by` ```sql -Actual: refcursor [ count = 1 ] was expected to equal: refcursor [ count = 1 ] -Diff: - Unable to join sets: - Join key NESTED_TABLE/ANNOTATIONS/TEXT does not exists in expected - Join key NESTED_TABLE/ANNOTATIONS/TEXT does not exists in actual - Please make sure that your join clause is not refferring to collection element +procedure test_join_by_many_columns is + l_actual sys_refcursor; + l_expected sys_refcursor; +begin + open l_expected for + select username, user_id, created from all_users + order by 1 desc; + open l_actual for + select username, user_id, created from all_users + union all + select 'TEST' username, -610 user_id, sysdate from dual + order by 1 asc; + ut.expect( l_actual ).to_equal( l_expected ).join_by('USERNAME, USER_ID'); +end; +``` + +### Joining using attributes of object in column list + +`join_by` allows for joining data by attributes of object from column list of the compared compound data types. + +To reference attribute as PK, use slash symbol `/` to separate nested elements. + +In the below example, cursors are joined using the `NAME` attribute of object in column `SOMEONE` + +```sql +create or replace type person as object( + name varchar2(100), + age integer +) +/ +create or replace type people as table of person +/ + +create or replace package test_join_by is +--%suite + +--%test +procedure test_join_by_object_attribute; + +end; +/ + +create or replace package body test_join_by is + procedure test_join_by_object_attribute is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + open l_expected for + select person('Jack',42) someone from dual union all + select person('Pat', 44) someone from dual union all + select person('Matt',45) someone from dual; + open l_actual for + select person('Matt',55) someone from dual union all + select person('Pat', 44) someone from dual; + ut.expect( l_actual ).to_equal( l_expected ).join_by( 'SOMEONE/NAME' ); + end; + +end; +/ + ``` +**Note** +> `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. +> When collection is referenced in `join_by`, test will fail with appropriate message, as it cannot perform a join. +```sql +create or replace type person as object( + name varchar2(100), + age integer +) +/ +create or replace type people as table of person +/ +create or replace package test_join_by is +--%suite +--%test +procedure test_join_by_collection_elem; -**Please note that .join_by option will take longer to process due to need of parsing via primary keys.** +end; +/ + +create or replace package body test_join_by is + procedure test_join_by_collection_elem is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + open l_expected for select people(person('Matt',45)) persons from dual; + open l_actual for select people(person('Matt',45)) persons from dual; + ut.expect( l_actual ).to_equal( l_expected ).join_by('PERSONS/PERSON/NAME'); + end; + +end; +/ +``` + +``` +Actual: refcursor [ count = 1 ] was expected to equal: refcursor [ count = 1 ] +Diff: +Unable to join sets: + Join key PERSONS/PERSON/NAME does not exists in expected + Join key PERSONS/PERSON/NAME does not exists in actual + Please make sure that your join clause is not refferring to collection element +``` -## Defining item as XPath -When using XPath expression, keep in mind the following: +***Note*** +>`join_by` option is slower to process as it needs to perform a cursor join. + +## Defining item lists in option +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. + +When using item list expression, keep in mind the following: -- cursor columns are nested under `` element - object type attributes are nested under `` element - nested table and varray items type attributes are nested under `` elements -Example of a valid XPath parameter to include columns: `RN`, `A_Column`, `SOME_COL` in data comparison. +Example of a valid parameter to include columns: `RN`, `A_Column`, `SOME_COL` in data comparison. ```sql -procedure include_columns_as_xpath is +procedure include_col_list is l_actual sys_refcursor; l_expected sys_refcursor; begin open l_expected for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL from dual a connect by level < 4; open l_actual for select rownum as rn, 'a' as "A_Column", 'x' SOME_COL, a.* from all_objects a where rownum < 4; - ut.expect( l_actual ).to_equal( l_expected ).include( '/ROW/RN|/ROW/A_Column|/ROW/SOME_COL' ); + ut.expect( l_actual ).to_equal( l_expected ).include( 'RN,A_Column,SOME_COL' ); + ut.expect( l_actual ).to_equal( l_expected ).include( ut_varchar2_list( 'RN', 'A_Column', 'SOME_COL' ) ); +end; +``` + +## Unordered columns / uc option + +If you need to perform data comparison of compound data types without strictly depending on column order in the returned result-set, use the `unordered_columns` option. +Shortcut name `uc` is also available for that option. + +Expectations that compare compound data type data with `unordered_columns` option, will not fail when columns are ordered differently. + +This option can be useful whn we have no control over the ordering of the column or the column order is not of importance from testing perspective. + +```sql +create or replace package test_unordered_columns as + --%suite + + --%test + procedure cursor_include_unordered_cols; +end; +/ + +create or replace package body test_unordered_columns as + + procedure cursor_include_unordered_cols is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select owner, object_name,object_type from all_objects where owner = user + order by 1,2,3 asc; + open l_expected for select object_type, owner, object_name from all_objects where owner = user + and rownum < 20; + + --Assert + ut.expect(l_actual).to_contain(l_expected).unordered_columns(); + end; end; +/ + +exec ut.run('test_unordered_columns'); ``` + +The above test is successful despite the fact that column ordering in cursor is different. + +``` +test_unordered_columns + cursor_include_unordered_cols [.042 sec] + +Finished in .046193 seconds +1 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s) +``` + diff --git a/docs/userguide/expectations.md b/docs/userguide/expectations.md index b02605605..31bc9aef1 100644 --- a/docs/userguide/expectations.md +++ b/docs/userguide/expectations.md @@ -112,7 +112,7 @@ create or replace package body test_divide is procedure divides_numbers is begin - ut3.ut.expect(divide(6,2)).to_equal(3); + ut.expect(divide(6,2)).to_equal(3); end; procedure raises_divisor_exception is @@ -124,7 +124,7 @@ create or replace package body test_divide is end; / -exec ut3.ut.run('test_divide'); +exec ut.run('test_divide'); ``` For details see documentation of the [`--%throws` annotation.](annotations.md#throws-annotation) @@ -145,6 +145,7 @@ utPLSQL provides the following matchers to perform checks on the expected and ac - `be_null` - `be_true` - `equal` +- `contain` - `have_count` - `match` @@ -363,68 +364,68 @@ end; create or replace package test_animals_getter is - --%suite(Animals getter tests) - - --%test(get_animal - returns a dog) - procedure test_variant_1_get_animal; - --%test(get_animal - returns a dog) - procedure test_variant_2_get_animal; - --%test(get_animal - returns a dog) - procedure test_variant_3_get_animal; - --%test(get_animal - returns a dog) - procedure test_variant_4_get_animal; - --%test(get_animal - returns a dog) - procedure test_variant_5_get_animal; + --%suite(Animals getter tests) + + --%test(get_animal - returns a dog) + procedure test_variant_1_get_animal; + --%test(get_animal - returns a dog) + procedure test_variant_2_get_animal; + --%test(get_animal - returns a dog) + procedure test_variant_3_get_animal; + --%test(get_animal - returns a dog) + procedure test_variant_4_get_animal; + --%test(get_animal - returns a dog) + procedure test_variant_5_get_animal; end; / create or replace package body test_animals_getter is - --The below tests perform exactly the same check. - --They use different syntax to achieve the goal. - procedure test_variant_1_get_animal is - l_actual varchar2(100) := 'a dog'; - l_expected varchar2(100); - begin - --Arrange - l_actual := 'a dog'; - --Act - l_expected := get_animal(); - --Assert - ut.expect( l_actual ).to_equal( l_expected ); - end; - - procedure test_variant_2_get_animal is - l_expected varchar2(100); - begin - --Act - l_expected := get_animal(); - --Assert - ut.expect( l_expected ).to_equal( 'a dog' ); - end; - - procedure test_variant_3_get_animal is - begin - --Act / Assert - ut.expect( get_animal() ).to_equal( 'a dog' ); - end; - - procedure test_variant_4_get_animal is - begin - --Act / Assert - ut.expect( get_animal() ).to_equal( 'a dog', a_nulls_are_equal => true ); - end; - - procedure test_variant_5_get_animal is - begin - --Act / Assert - ut.expect( get_animal() ).to_( equal( 'a dog' ) ); - end; - - procedure test_variant_6_get_animal is - begin - --Act / Assert - ut.expect( get_animal() ).to_( equal( 'a dog', a_nulls_are_equal => true ) ); - end; + --The below tests perform exactly the same check. + --They use different syntax to achieve the goal. + procedure test_variant_1_get_animal is + l_actual varchar2(100) := 'a dog'; + l_expected varchar2(100); + begin + --Arrange + l_actual := 'a dog'; + --Act + l_expected := get_animal(); + --Assert + ut.expect( l_actual ).to_equal( l_expected ); + end; + + procedure test_variant_2_get_animal is + l_expected varchar2(100); + begin + --Act + l_expected := get_animal(); + --Assert + ut.expect( l_expected ).to_equal( 'a dog' ); + end; + + procedure test_variant_3_get_animal is + begin + --Act / Assert + ut.expect( get_animal() ).to_equal( 'a dog' ); + end; + + procedure test_variant_4_get_animal is + begin + --Act / Assert + ut.expect( get_animal() ).to_equal( 'a dog', a_nulls_are_equal => true ); + end; + + procedure test_variant_5_get_animal is + begin + --Act / Assert + ut.expect( get_animal() ).to_( equal( 'a dog' ) ); + end; + + procedure test_variant_6_get_animal is + begin + --Act / Assert + ut.expect( get_animal() ).to_( equal( 'a dog', a_nulls_are_equal => true ) ); + end; end; ``` @@ -432,6 +433,277 @@ end; The `a_nulls_are_equal` parameter controls the behavior of a `null = null` comparison. To change the behavior of `NULL = NULL` comparison pass the `a_nulls_are_equal => false` to the `equal` matcher. +## contain + +This matcher supports only compound data comparison. It check if the give set contain all values from given subset. + +When comparing data using `contain` matcher, the data-types of columns for compared compound types must be exactly the same. + +The matcher supports all advanced comparison options as `equal` like: `include` , `exclude`, `join_by` etc.. + +The matcher is successful when actual data set contains all of the values from expected results. + +The matcher will cause a test to fail if actual data set does not contain any of expected values. + +![included_set](../images/venn21.gif) + +*Example 1*. + +```sql + procedure ut_refcursors is + l_actual SYS_REFCURSOR; + l_expected SYS_REFCURSOR; + begin + --Arrange + open l_actual for select rownum as rn from dual a connect by level < 10; + open l_expected for select rownum as rn from dual a connect by level < 4 + union all select rownum as rn from dual a connect by level < 4; + + --Act + ut.expect(l_actual).to_contain(l_expected); + end; +``` + +Will result in failure message + +```sql + 1) ut_refcursors + Actual: refcursor [ count = 9 ] was expected to contain: refcursor [ count = 6 ] + Diff: + Rows: [ 3 differences ] + Missing: 3 + Missing: 2 + Missing: 1 +``` + +When duplicate rows are present in expected data set, actual data set must also include the same amount of duplicates. + +*Example 2.* + + + +```sql +create or replace package ut_duplicate_test is + + --%suite(Sample Test Suite) + + --%test(Ref Cursor contain duplicates) + procedure ut_duplicate_contain; + +end ut_duplicate_test; +/ + +create or replace package body ut_duplicate_test is + procedure ut_duplicate_contain is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + open l_expected for select mod(level,2) as rn from dual connect by level < 5; + open l_actual for select mod(level,8) as rn from dual connect by level < 9; + ut.expect(l_actual).to_contain(l_expected); + end; + +end ut_duplicate_test; +``` + +Will result in failure test message + +```sql + 1) ut_duplicate_contain + Actual: refcursor [ count = 8 ] was expected to contain: refcursor [ count = 4 ] + Diff: + Rows: [ 2 differences ] + Missing: 0 + Missing: 1 +``` + + + +The negated version of `contain` ( `not_to_contain` ) is successful only when all values from expected set are not part of actual (they are disjoint and there is no overlap). + + + +![not_overlapping_set](../images/venn22.gif) + +*Example 3.* + +Set 1 is defined as [ A , B , C ] + +*Set 2 is defined as [A , D , E ]* + +*Result : This will fail both of options to `to_contain` and `not_to_contain`* + + + +*Example 4.* + +Set 1 is defined as [ A , B , C , D ] + +*Set 2 is defined as [A , B , D ]* + +*Result : This will be success on option `to_contain` and fail `not_to_contain`* + + + +*Example 5. + +Set 1 is defined as [ A , B , C ] + +*Set 2 is defined as [D, E , F ]* + +*Result : This will be success on options `not_to_contain` and fail `to_contain`* + + + +Example usage + +```sql +create or replace package example_contain is + --%suite(Contain test) + + --%test( Cursor contains data from another cursor) + procedure cursor_to_contain; + + --%test( Cursor contains data from another cursor) + procedure cursor_not_to_contain; + + --%test( Cursor fail on to_contain) + procedure cursor_fail_contain; + + --%test( Cursor fail not_to_contain) + procedure cursor_fail_not_contain; +end; +/ + +create or replace package body example_contain is + + procedure cursor_to_contain is + l_actual SYS_REFCURSOR; + l_expected SYS_REFCURSOR; + begin + --Arrange + open l_actual for + select 'a' as name from dual union all + select 'b' as name from dual union all + select 'c' as name from dual union all + select 'd' as name from dual; + + open l_expected for + select 'a' as name from dual union all + select 'b' as name from dual union all + select 'c' as name from dual; + + --Act + ut.expect(l_actual).to_contain(l_expected); + end; + + procedure cursor_not_to_contain is + l_actual SYS_REFCURSOR; + l_expected SYS_REFCURSOR; + begin + --Arrange + open l_actual for + select 'a' as name from dual union all + select 'b' as name from dual union all + select 'c' as name from dual; + + open l_expected for + select 'd' as name from dual union all + select 'e' as name from dual union all + select 'f' as name from dual; + + --Act + ut.expect(l_actual).not_to_contain(l_expected); + end; + + procedure cursor_fail_contain is + l_actual SYS_REFCURSOR; + l_expected SYS_REFCURSOR; + begin + --Arrange + open l_actual for + select 'a' as name from dual union all + select 'b' as name from dual union all + select 'c' as name from dual; + + open l_expected for + select 'a' as name from dual union all + select 'd' as name from dual union all + select 'e' as name from dual; + + --Act + ut.expect(l_actual).to_contain(l_expected); + end; + + procedure cursor_fail_not_contain is + l_actual SYS_REFCURSOR; + l_expected SYS_REFCURSOR; + begin + --Arrange + open l_actual for + select 'a' as name from dual union all + select 'b' as name from dual union all + select 'c' as name from dual; + + open l_expected for + select 'a' as name from dual union all + select 'd' as name from dual union all + select 'e' as name from dual; + + --Act + ut.expect(l_actual).not_to_contain(l_expected); + end; +end; +/ +``` + + + +Above execution will provide results as follow: +Cursor contains data from another cursor +Cursor contains data from another cursor +Cursor fail on to_contain +Cursor fail not_to_contain + +```sql +Contain test + Cursor contains data from another cursor [.045 sec] + Cursor contains data from another cursor [.039 sec] + Cursor fail on to_contain [.046 sec] (FAILED - 1) + Cursor fail not_to_contain [.043 sec] (FAILED - 2) + +Failures: + + 1) cursor_fail_contain + Actual: refcursor [ count = 3 ] was expected to contain: refcursor [ count = 3 ] + Diff: + Rows: [ 2 differences ] + Missing: d + Missing: e + at "UT3.EXAMPLE_CONTAIN.CURSOR_FAIL_CONTAIN", line 71 ut.expect(l_actual).to_contain(l_expected); + + + 2) cursor_fail_not_contain + Actual: (refcursor [ count = 3 ]) + Data-types: + CHAR + + Data: + a + b + c + was expected not to contain:(refcursor [ count = 3 ]) + Data-types: + CHAR + + Data: + a + d + e + at "UT3.EXAMPLE_CONTAIN.CURSOR_FAIL_NOT_CONTAIN", line 94 ut.expect(l_actual).not_to_contain(l_expected); +``` + + ## Comparing cursors, object types, nested tables and varrays @@ -441,23 +713,24 @@ utPLSQL is capable of comparing compound data-types including: - nested table/varray types ### Notes on comparison of compound data -- Compound data can contain elements of any data-type. This includes blob, clob, object type, nested table, varray or even a nested-cursor within a cursor. -- Cursors, nested table and varray types are compared as **ordered lists of elements**. If order of elements differ, expectation will fail. + +- Compound data can contain elements of any data-type. This includes blob, clob, object type, nested table, varray or even a nested-cursor within a cursor. +- Attributes in nested table and array types are compared as **ordered lists of elements**. If order of attributes in nested table and array differ, expectation will fail. +- Columns in compound data are compared as **ordered list of elements** by default. Use `unordered_columns` option when order of columns in cursor is not relevant - Comparison of compound data is data-type aware. So a column `ID NUMBER` in a cursor is not the same as `ID VARCHAR2(100)`, even if they both hold the same numeric values. - Comparison of cursor columns containing `DATE` will only compare date part **and ignore time** by default. See [Comparing cursor data containing DATE fields](#comparing-cursor-data-containing-date-fields) to check how to enable date-time comparison in cursors. -- Comparison of cursor returning `TIMESTAMP` **columns** against cursor returning `TIMESTAMP` **bind variables** requires variables to be casted to proper precision. This is an Oracle SQL - PLSQL compatibility issue and usage of CAST is the only known workaround for now. -See [Comparing cursor data containing TIMESTAMP bind variables](#comparing-cursor-data-containing-timestamp-bind-variables) for examples. +- Comparison of cursor returning `TIMESTAMP` **columns** against cursor returning `TIMESTAMP` **bind variables** requires variables to be casted to proper precision. This is an Oracle SQL - PLSQL compatibility issue and usage of CAST is the only known workaround for now. See [Comparing cursor data containing TIMESTAMP bind variables](#comparing-cursor-data-containing-timestamp-bind-variables) for examples. - To compare nested table/varray type you need to convert it to `anydata` by using `anydata.convertCollection()` - To compare object type you need to convert it to `anydata` by using `anydata.convertObject()` - It is possible to compare PL/SQL records, collections, varrays and associative arrays. To compare this types of data, use cursor comparison feature of utPLSQL and TABLE operator in SQL query - On Oracle 11g Release 2 - pipelined table functions are needed (see section [Implicit (Shadow) Types in this artcile](https://oracle-base.com/articles/misc/pipelined-table-functions)) - On Oracle 12c and above - use [TABLE function on nested tables/varrays/associative arrays of PL/SQL records](https://oracle-base.com/articles/12c/using-the-table-operator-with-locally-defined-types-in-plsql-12cr1) - + utPLSQL offers advanced data-comparison options, for comparing compound data-types. The options allow you to: - define columns/attributes to exclude from comparison - define columns/attributes to include in comparison -- and more +- and more ... For details on available options and how to use them, read the [advanced data comparison](advanced_data_comparison.md) guide. @@ -488,7 +761,7 @@ And the actual cursor data: | M | LUKE | SKYWALKER | 1000 | 2 | -The two datasets above have the following differences: +The two data-sets above have the following differences: - column ID is misplaced (should be first column but is last) - column SALARY has data-type VARCHAR2 but should be NUMBER - column GENDER exists in actual but not in the expected (it is an Extra column) @@ -668,6 +941,60 @@ drop type departments; drop type department; ``` +Some of the possible combinations of the anydata and their results: + +```sql +create or replace type t_tab_varchar is table of varchar2(1) +/ + +create or replace type dummy_obj as object ( + id number, + "name" varchar2(30), + "Value" varchar2(30) +) +/ + +create or replace type dummy_obj_lst as table of dummy_obj +/ + +create or replace type t_varray is varray(1) of number +/ + +``` + + + + + +| Type A | Comparisoon | Type B | Result | +| :------------------------------------- | :-----------: | :------------------------------------ | -----: | +| t_tab_varchar('A') | equal | t_tab_varchar('A') | Pass | +| t_tab_varchar('A') | equal | t_tab_varchar('B') | Fail | +| t_tab_varchar | is_null | | Pass | +| t_tab_varchar | equal | t_tab_varchar | Pass | +| t_tab_varchar | equal | t_tab_varchar('A') | Fail | +| t_tab_varchar() | have_count(0) | | Pass | +| t_tab_varchar() | equal | t_tab_varchar() | Pass | +| t_tab_varchar() | equal | t_tab_varchar('A') | Fail | +| dummy_obj_lst (dummy_obj(1, 'A', '0')) | equal | dummy_obj_lst(dummy_obj(1, 'A', '0')) | Pass | +| dummy_obj_lst (dummy_obj(1, 'A', '0')) | equal | dummy_obj_lst(dummy_obj(2, 'A', '0')) | Fail | +| dummy_obj_lst | equal | dummy_obj_lst(dummy_obj(1, 'A', '0')) | Fail | +| dummy_obj_lst | is_null | | Pass | +| dummy_obj_lst | equal | dummy_obj_lst | Pass | +| dummy_obj_lst() | have_count(0) | | Pass | +| dummy_obj_lst() | equal | dummy_obj_lst(dummy_obj(1, 'A', '0')) | Fail | +| dummy_obj_lst() | equal | dummy_obj_lst() | Pass | +| t_varray | is null | | Pass | +| t_varray | equal | t_varray | Pass | +| t_varray | equal | t_varray(1) | Fail | +| t_varray() | have_count(0) | | Pass | +| t_varray() | equal | t_varray() | Pass | +| t_varray() | equal | t_varray(1) | Fail | +| t_varray(1) | equal | t_varray(1) | Pass | +| t_varray(1) | equal | t_varray(2) | Fail | + + + ### Comparing cursor data containing DATE fields **Important note** @@ -677,7 +1004,7 @@ Due to the way Oracle handles DATE data type when converting from cursor data to The NLS_DATE_FORMAT setting from the moment the cursor was opened determines the formatting of dates used for cursor data comparison. By default, Oracle NLS_DATE_FORMAT is timeless, so data of DATE datatype, will be compared ignoring the time component. -You should use procedures `ut.set_nls`, `ut.reset_nls` around cursors that you want to compare in your tests. +You should surround cursors and expectations with procedures `ut.set_nls`, `ut.reset_nls`. This way, the DATE data in cursors will be properly formatted for comparison using date-time format. The example below makes use of `ut.set_nls`, `ut.reset_nls`, so that the date in `l_expected` and `l_actual` is compared using date-time formatting. @@ -721,9 +1048,9 @@ create or replace package body test_get_events is open l_expected_bad_date for select gc_description as description, gc_event_date + gc_second as event_date from dual; --Act l_actual := get_events(); - ut.reset_nls(); -- Change the NLS settings after cursors were opened --Assert ut.expect( l_actual ).not_to_equal( l_expected_bad_date ); + ut.reset_nls(); -- Change the NLS settings after cursors were opened end; procedure bad_test is @@ -761,9 +1088,9 @@ Example below illustrates usage of `cast` operator to assure appropriate precisi ```sql drop table timestamps; create table timestamps ( - ts3 timestamp (3), - ts6 timestamp (6), - ts9 timestamp (9) + ts3 timestamp (3), + ts6 timestamp (6), + ts9 timestamp (9) ); create or replace package timestamps_api is @@ -880,23 +1207,21 @@ Since NULL is neither *true* nor *false*, both expectations will report failure. The matrix below illustrates the data types supported by different matchers. -| Matcher |blob |boolean|clob |date |number|timestamp|timestamp
with
timezone|timestamp
with
local
timezone|varchar2|interval
year
to
month|interval
day
to
second|cursor|nested
table
/ varray|object| -|:----------------------|:---:|:-----:|:---:|:---:|:----:|:-------:|:---------------------------:|:------------------------------------:|:------:|:-----------------------------:|:-----------------------------:|:----:|:-------------------------:|:----:| -|**be_not_null** | X | X | X | X | X | X | X | X | X | X | X | X | X | X | -|**be_null** | X | X | X | X | X | X | X | X | X | X | X | X | X | X | -|**be_false** | | X | | | | | | | | | | | | | -|**be_true** | | X | | | | | | | | | | | | | -|**be_greater_than** | | | | X | X | X | X | X | | X | X | | | | -|**be_greater_or_equal**| | | | X | X | X | X | X | | X | X | | | | -|**be_less_or_equal** | | | | X | X | X | X | X | | X | X | | | | -|**be_less_than** | | | | X | X | X | X | X | | X | X | | | | -|**be_between** | | | | X | X | X | X | X | X | X | X | | | | -|**equal** | X | X | X | X | X | X | X | X | X | X | X | X | X | X | -|**match** | | | X | | | | | | X | | | | | | -|**be_like** | | | X | | | | | | X | | | | | | -|**be_empty** | X | | X | | | | | | | | | X | X | | -|**have_count** | | | | | | | | | | | | X | X | | - - - +| Matcher | blob | boolean | clob | date | number | timestamp | timestamp
with
timezone | timestamp
with
local
timezone | varchar2 | interval
year
to
month | interval
day
to
second | cursor | nested
table
/ varray | object | +| :---------------------- | :--: | :-----: | :--: | :--: | :----: | :-------: | :---------------------------: | :------------------------------------: | :------: | :-----------------------------: | :-----------------------------: | :----: | :-------------------------: | :----: | +| **be_not_null** | X | X | X | X | X | X | X | X | X | X | X | X | X | X | +| **be_null** | X | X | X | X | X | X | X | X | X | X | X | X | X | X | +| **be_false** | | X | | | | | | | | | | | | | +| **be_true** | | X | | | | | | | | | | | | | +| **be_greater_than** | | | | X | X | X | X | X | | X | X | | | | +| **be_greater_or_equal** | | | | X | X | X | X | X | | X | X | | | | +| **be_less_or_equal** | | | | X | X | X | X | X | | X | X | | | | +| **be_less_than** | | | | X | X | X | X | X | | X | X | | | | +| **be_between** | | | | X | X | X | X | X | X | X | X | | | | +| **equal** | X | X | X | X | X | X | X | X | X | X | X | X | X | X | +| **contain** | | | | | | | | | | | | X | X | X | +| **match** | | | X | | | | | | X | | | | | | +| **be_like** | | | X | | | | | | X | | | | | | +| **be_empty** | X | | X | | | | | | | | | X | X | | +| **have_count** | | | | | | | | | | | | X | X | | diff --git a/source/api/contain.syn b/source/api/contain.syn new file mode 100644 index 000000000..5bce7d1d2 --- /dev/null +++ b/source/api/contain.syn @@ -0,0 +1 @@ +create synonym contain for ut_contain; diff --git a/source/api/ut.pkb b/source/api/ut.pkb index 5710f78c4..eb4a06bc3 100644 --- a/source/api/ut.pkb +++ b/source/api/ut.pkb @@ -30,7 +30,7 @@ create or replace package body ut is function expect(a_actual in anydata, a_message varchar2 := null) return ut_expectation_compound is begin - return ut_expectation_compound(ut_data_value_anydata.get_instance(a_actual), a_message); + return ut_expectation_compound(ut_data_value_anydata(a_actual), a_message); end; function expect(a_actual in blob, a_message varchar2 := null) return ut_expectation is diff --git a/source/api/ut_runner.pkb b/source/api/ut_runner.pkb index dcc8e59f3..d7f1295b2 100644 --- a/source/api/ut_runner.pkb +++ b/source/api/ut_runner.pkb @@ -49,6 +49,7 @@ create or replace package body ut_runner is ut_metadata.reset_source_definition_cache; ut_utils.read_cache_to_dbms_output(); ut_coverage_helper.cleanup_tmp_table(); + ut_compound_data_helper.cleanup_diff(); if not a_force_manual_rollback then rollback; end if; diff --git a/source/check_object_grants.sql b/source/check_object_grants.sql index 130818040..d499ced9c 100644 --- a/source/check_object_grants.sql +++ b/source/check_object_grants.sql @@ -9,7 +9,7 @@ declare l_owner_column varchar2(128); function get_view(a_dba_view_name varchar2) return varchar2 is - l_invalid_object_name exception; + l_invalid_object_name exception; l_result varchar2(128) := lower(a_dba_view_name); pragma exception_init(l_invalid_object_name,-44002); begin diff --git a/source/core/ut_expectation_processor.pkb b/source/core/ut_expectation_processor.pkb index 170846125..8d6205111 100644 --- a/source/core/ut_expectation_processor.pkb +++ b/source/core/ut_expectation_processor.pkb @@ -96,7 +96,8 @@ create or replace package body ut_expectation_processor as bulk collect into l_session_params from nls_session_parameters nsp where parameter - in ( 'NLS_DATE_FORMAT', 'NLS_TIMESTAMP_FORMAT', 'NLS_TIMESTAMP_TZ_FORMAT'); + in ( 'NLS_DATE_FORMAT', 'NLS_TIMESTAMP_FORMAT', 'NLS_TIMESTAMP_TZ_FORMAT') + order by 1; return l_session_params; end; @@ -113,7 +114,6 @@ create or replace package body ut_expectation_processor as when insuf_privs then NULL; end; - execute immediate 'alter session set nls_date_format = '''||ut_utils.gc_date_format||''''; execute immediate 'alter session set nls_timestamp_format = '''||ut_utils.gc_timestamp_format||''''; execute immediate 'alter session set nls_timestamp_tz_format = '''||ut_utils.gc_timestamp_tz_format||''''; end; diff --git a/source/core/ut_metadata.pkb b/source/core/ut_metadata.pkb index c905ebefe..a254529f8 100644 --- a/source/core/ut_metadata.pkb +++ b/source/core/ut_metadata.pkb @@ -25,23 +25,27 @@ create or replace package body ut_metadata as procedure do_resolve(a_owner in out nocopy varchar2, a_object in out nocopy varchar2, a_procedure_name in out nocopy varchar2) is l_name varchar2(200); l_context integer := 1; --plsql + begin + l_name := form_name(a_owner, a_object, a_procedure_name); + do_resolve(l_name,l_context,a_owner,a_object, a_procedure_name); + end do_resolve; + + procedure do_resolve(a_fully_qualified_name in varchar2,a_context in integer,a_owner out nocopy varchar2, a_object out nocopy varchar2, + a_procedure_name out nocopy varchar2) is l_dblink varchar2(200); l_part1_type number; l_object_number number; begin - l_name := form_name(a_owner, a_object, a_procedure_name); - - dbms_utility.name_resolve(name => l_name - ,context => l_context + dbms_utility.name_resolve(name => a_fully_qualified_name + ,context => a_context ,schema => a_owner ,part1 => a_object ,part2 => a_procedure_name ,dblink => l_dblink ,part1_type => l_part1_type ,object_number => l_object_number); - - end do_resolve; - + end; + function form_name(a_owner_name varchar2, a_object varchar2, a_subprogram varchar2 default null) return varchar2 is l_name varchar2(200); begin @@ -189,6 +193,164 @@ create or replace package body ut_metadata as return l_cnt > 0; end; + function is_collection (a_anytype_code in integer) return boolean is + begin + return coalesce(a_anytype_code in (dbms_types.typecode_varray,dbms_types.typecode_table,dbms_types.typecode_namedcollection),false); + end; + + function is_collection (a_owner varchar2, a_type_name varchar2) return boolean is + begin + return is_collection( + get_anytype_members_info( + get_user_defined_type(a_owner, a_type_name) + ).type_code + ); + end; + + function get_attr_elem_info( a_anytype anytype, a_pos pls_integer := null ) + return t_anytype_elem_info_rec is + l_result t_anytype_elem_info_rec; + begin + if a_anytype is not null then + l_result.type_code := a_anytype.getattreleminfo( + pos => a_pos, + prec => l_result.precision, + scale => l_result.scale, + len => l_result.length, + csid => l_result.char_set_id, + csfrm => l_result.char_set_frm, + attr_elt_type => l_result.attr_elt_type, + aname => l_result.attribute_name + ); + end if; + return l_result; + end; + + function get_anytype_members_info( a_anytype anytype ) + return t_anytype_members_rec is + l_result t_anytype_members_rec; + begin + if a_anytype is not null then + l_result.type_code := a_anytype.getinfo( + prec => l_result.precision, + scale => l_result.scale, + len => l_result.length, + csid => l_result.char_set_id, + csfrm => l_result.char_set_frm, + schema_name => l_result.schema_name, + type_name => l_result.type_name, + version => l_result.version, + numelems => l_result.elements_count + ); + end if; + return l_result; + end; + + function get_user_defined_type(a_owner varchar2, a_type_name varchar2) return anytype is + l_anytype anytype; + not_found exception; + pragma exception_init(not_found,-22303); + begin + if a_type_name is not null then + begin + if ut_metadata.is_object_visible('GETANYTYPEFROMPERSISTENT') then + execute immediate 'begin :l_anytype := getanytypefrompersistent( :a_owner, :a_type_name ); end;' + using out l_anytype, in nvl(a_owner,sys_context('userenv','current_schema')), in a_type_name; + else + execute immediate 'begin :l_anytype := anytype.getpersistent( :a_owner, :a_type_name ); end;' + using out l_anytype, in nvl(a_owner,sys_context('userenv','current_schema')), in a_type_name; + end if; + exception + when not_found then + null; + end; + end if; + return l_anytype; + end; + + function get_collection_element(a_anydata in anydata) return varchar2 + is + l_anytype anytype; + l_nested_type t_anytype_members_rec; + l_elements_rec t_anytype_elem_info_rec; + l_type_code integer; + begin + l_type_code := a_anydata.gettype(l_anytype); + if is_collection(l_type_code) then + l_elements_rec := get_attr_elem_info(l_anytype); + if l_elements_rec.attr_elt_type is null then + l_nested_type := get_anytype_members_info(l_anytype); + else + l_nested_type := get_anytype_members_info(l_elements_rec.attr_elt_type); + end if; + end if; + return l_nested_type.schema_name || '.' ||l_nested_type.type_name; + end; + + function has_collection_members (a_anydata in anydata) return boolean is + l_anytype anytype; + l_elements_rec t_anytype_elem_info_rec; + l_type_code integer; + begin + l_type_code := a_anydata.gettype(l_anytype); + l_elements_rec := get_attr_elem_info(l_anytype); + return l_elements_rec.attr_elt_type is not null; + end; + + function get_anydata_typename(a_data_value anydata) return varchar2 + is + begin + return case when a_data_value is not null then lower(a_data_value.gettypename()) else 'undefined' end; + end; + + function is_anytype_null(a_value in anydata, a_compound_type in varchar2) return number is + l_result integer := 0; + l_anydata_sql varchar2(4000); + begin + if a_value is not null then + l_anydata_sql := ' + declare + l_data '||get_anydata_typename(a_value)||'; + l_value anydata := :a_value; + l_status integer; + begin + l_status := l_value.get'||a_compound_type||'(l_data); + :l_data_is_null := case when l_data is null then 1 else 0 end; + end;'; + execute immediate l_anydata_sql using in a_value, out l_result; + else + l_result := 1; + end if; + return l_result; + end; + + function get_object_name(a_full_object_name in varchar2) return varchar2 is + l_schema varchar2(250); + l_object varchar2(250); + l_procedure_name varchar2(250); + begin + ut_metadata.do_resolve(a_full_object_name,7,l_schema,l_object, l_procedure_name); + return l_object; + end; + + function get_anydata_compound_type(a_data_value anydata) return varchar2 is + l_result varchar2(30); + l_type anytype; + l_type_code integer; + begin + if a_data_value is not null then + l_type_code := a_data_value.gettype(l_type); + if l_type_code in (dbms_types.typecode_table, dbms_types.typecode_varray, dbms_types.typecode_namedcollection, + dbms_types.typecode_object) then + if l_type_code = dbms_types.typecode_object then + l_result := 'object'; + else + l_result := 'collection'; + end if; + end if; + end if; + return l_result; + end; end; / diff --git a/source/core/ut_metadata.pks b/source/core/ut_metadata.pks index 9a0ac2b00..184943f2a 100644 --- a/source/core/ut_metadata.pks +++ b/source/core/ut_metadata.pks @@ -20,6 +20,31 @@ create or replace package ut_metadata authid current_user as * Common package for all code that reads from the system tables. */ + type t_anytype_members_rec is record ( + type_code pls_integer, + schema_name varchar2(128), + type_name varchar2(128), + length pls_integer, + elements_count pls_integer, + version varchar2(32767), + precision pls_integer, + scale pls_integer, + char_set_id pls_integer, + char_set_frm pls_integer + ); + + type t_anytype_elem_info_rec is record ( + type_code pls_integer, + attribute_name varchar2(260), + length pls_integer, + version varchar2(32767), + precision pls_integer, + scale pls_integer, + char_set_id pls_integer, + char_set_frm pls_integer, + attr_elt_type anytype + ); + /** * Forms correct object/subprogram name to call as owner.object[.subprogram] * @@ -45,6 +70,12 @@ create or replace package ut_metadata authid current_user as */ procedure do_resolve(a_owner in out nocopy varchar2, a_object in out nocopy varchar2, a_procedure_name in out nocopy varchar2); + /** + * Resolves single string [owner.]object[.procedure] using dbms_utility.name_resolve and returns parts [owner] [object] [procedure] + */ + procedure do_resolve(a_fully_qualified_name in varchar2,a_context in integer,a_owner out nocopy varchar2, + a_object out nocopy varchar2, a_procedure_name out nocopy varchar2); + /** * Return the text of the source line for a given object (body). It excludes package spec and type spec */ @@ -91,5 +122,60 @@ create or replace package ut_metadata authid current_user as */ function package_exists_in_cur_schema(a_object_name varchar2) return boolean; + /** + * Returns true if given typecode is a collection typecode + */ + function is_collection(a_anytype_code in integer) return boolean; + + /** + * Returns true if given object is a collection + */ + function is_collection(a_owner varchar2, a_type_name varchar2) return boolean; + + /** + * Returns a descriptor of anytype + */ + function get_anytype_members_info( a_anytype anytype ) return t_anytype_members_rec; + + /** + * Returns a descriptor of anytype attribute + */ + function get_attr_elem_info( a_anytype anytype, a_pos pls_integer := null ) return t_anytype_elem_info_rec; + + /** + * Returns ANYTYPE descriptor of an object type + */ + function get_user_defined_type(a_owner varchar2, a_type_name varchar2) return anytype; + + /** + * Return fully qualified name of the object from collection, if not collection returns null + */ + function get_collection_element(a_anydata in anydata) return varchar2; + + /** + * Check if collection got elements + */ + function has_collection_members (a_anydata in anydata) return boolean; + + /** + * Get typename from anydata + */ + function get_anydata_typename(a_data_value anydata) return varchar2; + + /** + * Is anydata object/collection is null + */ + function is_anytype_null(a_value in anydata, a_compound_type in varchar2) return number; + + /** + * Get object name from fully qualified name e.g ut3.test -> test + */ + function get_object_name(a_full_object_name in varchar2) return varchar2; + + /** + * Based on anydata decide if its a object or collection + */ + function get_anydata_compound_type(a_data_value anydata) return varchar2; + end ut_metadata; / diff --git a/source/core/ut_utils.pks b/source/core/ut_utils.pks index d8d2ccf82..c3752eb3f 100644 --- a/source/core/ut_utils.pks +++ b/source/core/ut_utils.pks @@ -97,6 +97,14 @@ create or replace package ut_utils authid definer is gc_invalid_package constant pls_integer := -6550; pragma exception_init(ex_invalid_package, -6550); + ex_failure_for_all exception; + gc_failure_for_all constant pls_integer := -24381; + pragma exception_init (ex_failure_for_all, -24381); + + ex_dml_for_all exception; + gc_dml_for_all constant pls_integer := -20215; + pragma exception_init (ex_dml_for_all, -20215); + gc_max_storage_varchar2_len constant integer := 4000; gc_max_output_string_length constant integer := 4000; gc_max_input_string_length constant integer := gc_max_output_string_length - 2; --we need to remove 2 chars for quotes around string @@ -109,6 +117,9 @@ create or replace package ut_utils authid definer is gc_null_string constant varchar2(4) := 'NULL'; gc_empty_string constant varchar2(5) := 'EMPTY'; + gc_bc_fetch_limit constant integer := 1000; + gc_diff_max_rows constant integer := 20; + type t_version is record( major natural, minor natural, @@ -320,10 +331,14 @@ create or replace package ut_utils authid definer is function get_xml_header(a_encoding varchar2) return varchar2; - /*It takes a collection of type ut_varchar2_list and it trims the characters passed as arguments for every element*/ + /** + * Takes a collection of type ut_varchar2_list and it trims the characters passed as arguments for every element + */ function trim_list_elements(a_list IN ut_varchar2_list, a_regexp_to_trim in varchar2 default '[:space:]') return ut_varchar2_list; - /*It takes a collection of type ut_varchar2_list and it only returns the elements which meets the regular expression*/ + /** + * Takes a collection of type ut_varchar2_list and it only returns the elements which meets the regular expression + */ function filter_list(a_list IN ut_varchar2_list, a_regexp_filter in varchar2) return ut_varchar2_list; -- Generates XMLGEN escaped string diff --git a/source/create_synonyms_and_grants_for_public.sql b/source/create_synonyms_and_grants_for_public.sql index fdc60ec11..632925cec 100644 --- a/source/create_synonyms_and_grants_for_public.sql +++ b/source/create_synonyms_and_grants_for_public.sql @@ -103,25 +103,30 @@ grant execute on &&ut3_owner..ut_realtime_reporter to public; grant select, insert, delete, update on &&ut3_owner..dbmspcc_blocks to public; grant select, insert, delete, update on &&ut3_owner..dbmspcc_runs to public; grant select, insert, delete, update on &&ut3_owner..dbmspcc_units to public; +grant execute on &&ut3_owner..ut_matcher_options to public; +grant execute on &&ut3_owner..ut_matcher_options_items to public; prompt Creating synonyms for UTPLSQL objects in &&ut3_owner schema to PUBLIC create public synonym ut_expectation for &&ut3_owner..ut_expectation; create public synonym ut_expectation_compound for &&ut3_owner..ut_expectation_compound; -create public synonym be_between for &&ut3_owner..ut_be_between; -create public synonym be_empty for &&ut3_owner..ut_be_empty; -create public synonym be_false for &&ut3_owner..ut_be_false; -create public synonym be_greater_or_equal for &&ut3_owner..ut_be_greater_or_equal; -create public synonym be_greater_than for &&ut3_owner..ut_be_greater_than; -create public synonym be_less_or_equal for &&ut3_owner..ut_be_less_or_equal; -create public synonym be_less_than for &&ut3_owner..ut_be_less_than; -create public synonym be_like for &&ut3_owner..ut_be_like; -create public synonym be_not_null for &&ut3_owner..ut_be_not_null; -create public synonym be_null for &&ut3_owner..ut_be_null; -create public synonym be_true for &&ut3_owner..ut_be_true; -create public synonym equal for &&ut3_owner..ut_equal; + +create public synonym be_between for &&ut3_owner..be_between; +create public synonym be_empty for &&ut3_owner..be_empty; +create public synonym be_false for &&ut3_owner..be_false; +create public synonym be_greater_or_equal for &&ut3_owner..be_greater_or_equal; +create public synonym be_greater_than for &&ut3_owner..be_greater_than; +create public synonym be_less_or_equal for &&ut3_owner..be_less_or_equal; +create public synonym be_less_than for &&ut3_owner..be_less_than; +create public synonym be_like for &&ut3_owner..be_like; +create public synonym be_not_null for &&ut3_owner..be_not_null; +create public synonym be_null for &&ut3_owner..be_null; +create public synonym be_true for &&ut3_owner..be_true; +create public synonym contain for &&ut3_owner..contain; +create public synonym equal for &&ut3_owner..equal; create public synonym have_count for &&ut3_owner..have_count; -create public synonym match for &&ut3_owner..ut_match; +create public synonym match for &&ut3_owner..match; + create public synonym ut for &&ut3_owner..ut; create public synonym ut_runner for &&ut3_owner..ut_runner; create public synonym ut_teamcity_reporter for &&ut3_owner..ut_teamcity_reporter; diff --git a/source/create_user_grants.sql b/source/create_user_grants.sql index 9ad3c6549..e62f6ccc2 100644 --- a/source/create_user_grants.sql +++ b/source/create_user_grants.sql @@ -53,6 +53,7 @@ alter session set current_schema = &&ut3_owner; grant execute on &&ut3_owner..ut_expectation to &ut3_user; grant execute on &&ut3_owner..ut_expectation_compound to &ut3_user; + grant execute on &&ut3_owner..ut_be_between to &ut3_user; grant execute on &&ut3_owner..ut_be_empty to &ut3_user; grant execute on &&ut3_owner..ut_be_false to &ut3_user; @@ -121,3 +122,6 @@ grant execute on &&ut3_owner..ut_realtime_reporter to &ut3_user; grant select, insert, delete, update on &&ut3_owner..dbmspcc_blocks to &ut3_user; grant select, insert, delete, update on &&ut3_owner..dbmspcc_runs to &ut3_user; grant select, insert, delete, update on &&ut3_owner..dbmspcc_units to &ut3_user; +grant execute on &&ut3_owner..ut_matcher_options to &ut3_user; +grant execute on &&ut3_owner..ut_matcher_options_items to &ut3_user; + diff --git a/source/create_user_synonyms.sql b/source/create_user_synonyms.sql index 746354b9e..6ea06a0fd 100644 --- a/source/create_user_synonyms.sql +++ b/source/create_user_synonyms.sql @@ -55,6 +55,7 @@ prompt Creating synonyms for UTPLSQL objects in &&ut3_owner schema to user &&ut3 create or replace synonym &ut3_user..ut_expectation for &&ut3_owner..ut_expectation; create or replace synonym &ut3_user..ut_expectation_compound for &&ut3_owner..ut_expectation_compound; + create or replace synonym &ut3_user..be_between for &&ut3_owner..be_between; create or replace synonym &ut3_user..be_empty for &&ut3_owner..be_empty; create or replace synonym &ut3_user..be_false for &&ut3_owner..be_false; @@ -66,9 +67,11 @@ create or replace synonym &ut3_user..be_like for &&ut3_owner..be_like; create or replace synonym &ut3_user..be_not_null for &&ut3_owner..be_not_null; create or replace synonym &ut3_user..be_null for &&ut3_owner..be_null; create or replace synonym &ut3_user..be_true for &&ut3_owner..be_true; +create or replace synonym &ut3_user..contain for &&ut3_owner..contain; create or replace synonym &ut3_user..equal for &&ut3_owner..equal; create or replace synonym &ut3_user..have_count for &&ut3_owner..have_count; create or replace synonym &ut3_user..match for &&ut3_owner..match; + create or replace synonym &ut3_user..ut for &&ut3_owner..ut; create or replace synonym &ut3_user..ut_runner for &&ut3_owner..ut_runner; create or replace synonym &ut3_user..ut_teamcity_reporter for &&ut3_owner..ut_teamcity_reporter; diff --git a/source/expectations/data_values/ut_compound_data_diff_tmp.sql b/source/expectations/data_values/ut_compound_data_diff_tmp.sql index a7959d4aa..242d6c2fc 100644 --- a/source/expectations/data_values/ut_compound_data_diff_tmp.sql +++ b/source/expectations/data_values/ut_compound_data_diff_tmp.sql @@ -12,15 +12,15 @@ create global temporary table ut_compound_data_diff_tmp( See the License for the specific language governing permissions and limitations under the License. */ - diff_id raw(128), - item_no integer, - pk_hash raw(128), - item_hash raw(128), + diff_id raw(128), + act_data_id raw(32), + exp_data_id raw(32), + act_item_data xmltype, + exp_item_data xmltype, + item_no integer, duplicate_no integer, - constraint ut_compound_data_diff_tmp_uk1 unique (diff_id,duplicate_no,item_no,item_hash, pk_hash), + constraint ut_compound_data_diff_tmp_uk1 unique (diff_id,duplicate_no,item_no), constraint ut_compound_data_diff_tmp_chk check( - item_no is not null and pk_hash is null and duplicate_no is null - or item_no is null and item_hash is not null and duplicate_no is not null - or item_no is null and pk_hash is not null and duplicate_no is not null + item_no is not null ) ) on commit preserve rows; diff --git a/source/expectations/data_values/ut_compound_data_helper.pkb b/source/expectations/data_values/ut_compound_data_helper.pkb index 0fba5b0e7..dab3d4af1 100644 --- a/source/expectations/data_values/ut_compound_data_helper.pkb +++ b/source/expectations/data_values/ut_compound_data_helper.pkb @@ -16,583 +16,621 @@ create or replace package body ut_compound_data_helper is limitations under the License. */ - g_user_defined_type pls_integer := dbms_sql.user_defined_type; + g_diff_count integer; + type t_type_name_map is table of varchar2(128) index by binary_integer; + g_type_name_map t_type_name_map; + g_anytype_name_map t_type_name_map; + + g_compare_sql_template varchar2(4000) := + q'[ + with exp as ( + select + ucd.*, + {:duplicate_number:} dup_no + from ( + select + ucd.item_data + ,x.data_id data_id + ,position + x.item_no item_no + {:columns:} + from {:ut3_owner:}.ut_compound_data_tmp x, + xmltable('/ROWSET/ROW' passing x.item_data columns + item_data xmltype path '*' + ,position for ordinality + {:xml_to_columns:} ) ucd + where data_id = :exp_guid + ) ucd + ) + , act as ( + select + ucd.*, + {:duplicate_number:} dup_no + from ( + select + ucd.item_data + ,x.data_id data_id + ,position + x.item_no item_no + {:columns:} + from {:ut3_owner:}.ut_compound_data_tmp x, + xmltable('/ROWSET/ROW' passing x.item_data columns + item_data xmltype path '*' + ,position for ordinality + {:xml_to_columns:} ) ucd + where data_id = :act_guid + ) ucd + ) + select + a.item_data as act_item_data, + a.data_id act_data_id, + e.item_data as exp_item_data, + e.data_id exp_data_id, + {:item_no:} as item_no, + nvl(e.dup_no,a.dup_no) dup_no + from act a {:join_type:} exp e on ( {:join_condition:} ) + where {:where_condition:}]'; + + function get_columns_diff( + a_expected ut_cursor_column_tab, + a_actual ut_cursor_column_tab, + a_order_enforced boolean := false + ) return tt_column_diffs is + l_results tt_column_diffs; + begin + execute immediate q'[with + expected_cols as ( + select access_path exp_column_name,column_position exp_col_pos, + replace(column_type,'VARCHAR2','CHAR') exp_col_type_compare, column_type exp_col_type + from table(:a_expected) + ), + actual_cols as ( + select access_path act_column_name,column_position act_col_pos, + replace(column_type,'VARCHAR2','CHAR') act_col_type_compare, column_type act_col_type + from table(:a_actual)), + joined_cols as ( + select e.*,a.*]' + || case when a_order_enforced then ', + row_number() over(partition by case when a.act_col_pos + e.exp_col_pos is not null then 1 end order by a.act_col_pos) a_pos_nn, + row_number() over(partition by case when a.act_col_pos + e.exp_col_pos is not null then 1 end order by e.exp_col_pos) e_pos_nn' + else + null + end ||q'[ + from expected_cols e + full outer join actual_cols a + on e.exp_column_name = a.act_column_name + ) + select case + when exp_col_pos is null and act_col_pos is not null then '+' + when exp_col_pos is not null and act_col_pos is null then '-' + when exp_col_type_compare != act_col_type_compare then 't' + else 'p' + end as diff_type, + exp_column_name, exp_col_type, exp_col_pos, + act_column_name, act_col_type, act_col_pos + from joined_cols + --column is unexpected (extra) or missing + where act_col_pos is null or exp_col_pos is null + --column type is not matching (except CHAR/VARCHAR2) + or act_col_type_compare != exp_col_type_compare]' + || case when a_order_enforced then q'[ + --column position is not matching (both when excluded extra/missing columns as well as when they are included) + or (a_pos_nn != e_pos_nn and exp_col_pos != act_col_pos)]' + else + null + end ||q'[ + order by exp_col_pos, act_col_pos]' + bulk collect into l_results using a_expected, a_actual; + return l_results; + end; + + function generate_not_equal_stmt( + a_data_info ut_cursor_column, a_pk_table ut_varchar2_list + ) return varchar2 + is + l_pk_tab ut_varchar2_list := coalesce(a_pk_table,ut_varchar2_list()); + l_index integer; + l_sql_stmt varchar2(32767); + l_exists boolean := false; + begin + l_index := l_pk_tab.first; + if l_pk_tab.count > 0 then + loop + if a_data_info.access_path = l_pk_tab(l_index) then + l_exists := true; + end if; + exit when l_index = l_pk_tab.count or (a_data_info.access_path = l_pk_tab(l_index)); + l_index := a_pk_table.next(l_index); + end loop; + end if; + if not(l_exists) then + l_sql_stmt := ' (decode(a.'||a_data_info.transformed_name||','||' e.'||a_data_info.transformed_name||',1,0) = 0)'; + end if; + return l_sql_stmt; + end; + + function generate_join_by_stmt( + a_data_info ut_cursor_column, a_pk_table ut_varchar2_list + ) return varchar2 + is + l_pk_tab ut_varchar2_list := coalesce(a_pk_table,ut_varchar2_list()); + l_index integer; + l_sql_stmt varchar2(32767); + begin + if l_pk_tab.count <> 0 then + l_index:= l_pk_tab.first; + loop + if l_pk_tab(l_index) in (a_data_info.access_path, a_data_info.parent_name) then + --When then table is nested and join is on whole table + l_sql_stmt := l_sql_stmt ||' a.'||a_data_info.transformed_name||q'[ = ]'||' e.'||a_data_info.transformed_name; + end if; + exit when (a_data_info.access_path = l_pk_tab(l_index)) or l_index = l_pk_tab.count; + l_index := l_pk_tab.next(l_index); + end loop; + end if; + return l_sql_stmt; + end; - function get_column_info_xml(a_column_details ut_key_anyval_pair) return xmltype is - l_result varchar2(4000); - l_res xmltype; - l_data ut_data_value := a_column_details.value; - l_key varchar2(4000) := ut_utils.xmlgen_escaped_string(a_column_details.KEY); + function generate_equal_sql(a_col_name in varchar2) return varchar2 is begin - l_result := '<'||l_key||' xml_valid_name="'||l_key||'">'; - if l_data is of(ut_data_value_xmltype) then - l_result := l_result || (treat(l_data as ut_data_value_xmltype).to_string); + return ' a.'||a_col_name||q'[ = ]'||' e.'||a_col_name; + end; + + function generate_partition_stmt( + a_data_info ut_cursor_column, a_pk_table in ut_varchar2_list, a_alias varchar2 := 'ucd.' + ) return varchar2 + is + l_index integer; + l_sql_stmt varchar2(32767); + begin + if a_pk_table is not empty then + l_index:= a_pk_table.first; + loop + if a_pk_table(l_index) in (a_data_info.access_path, a_data_info.parent_name) then + --When then table is nested and join is on whole table + l_sql_stmt := l_sql_stmt ||a_alias||a_data_info.transformed_name; + end if; + exit when (a_data_info.access_path = a_pk_table(l_index)) or l_index = a_pk_table.count; + l_index := a_pk_table.next(l_index); + end loop; else - l_result := l_result || ut_utils.xmlgen_escaped_string((treat(l_data as ut_data_value_varchar2).data_value)); + l_sql_stmt := a_alias||a_data_info.transformed_name; end if; - - l_result := l_result ||''; - return xmltype(l_result); - end; + return l_sql_stmt; + end; - function get_columns_filter( - a_exclude_xpath varchar2, a_include_xpath varchar2, - a_table_alias varchar2 := 'ucd', a_column_alias varchar2 := 'item_data' - ) return varchar2 is - l_filter varchar2(32767); - l_source_column varchar2(500) := a_table_alias||'.'||a_column_alias; + function generate_select_stmt(a_data_info ut_cursor_column, a_alias varchar2 := 'ucd.') + return varchar2 + is + l_alias varchar2(10) := a_alias; + l_col_syntax varchar2(4000); + l_ut_owner varchar2(250) := ut_utils.ut_owner; + begin + if a_data_info.is_sql_diffable = 0 then + l_col_syntax := l_ut_owner ||'.ut_compound_data_helper.get_hash('||l_alias||a_data_info.transformed_name||'.getClobVal()) as '||a_data_info.transformed_name ; + elsif a_data_info.is_sql_diffable = 1 and a_data_info.column_type = 'DATE' then + l_col_syntax := 'to_date('||l_alias||a_data_info.transformed_name||') as '|| a_data_info.transformed_name; + elsif a_data_info.is_sql_diffable = 1 and a_data_info.column_type in ('TIMESTAMP') then + l_col_syntax := 'to_timestamp('||l_alias||a_data_info.transformed_name||','''||ut_utils.gc_timestamp_format||''') as '|| a_data_info.transformed_name; + elsif a_data_info.is_sql_diffable = 1 and a_data_info.column_type in ('TIMESTAMP WITH TIME ZONE') then + l_col_syntax := 'to_timestamp_tz('||l_alias||a_data_info.transformed_name||','''||ut_utils.gc_timestamp_tz_format||''') as '|| a_data_info.transformed_name; + elsif a_data_info.is_sql_diffable = 1 and a_data_info.column_type in ('TIMESTAMP WITH LOCAL TIME ZONE') then + l_col_syntax := ' cast( to_timestamp_tz('||l_alias||a_data_info.transformed_name||','''||ut_utils.gc_timestamp_tz_format||''') AS TIMESTAMP WITH LOCAL TIME ZONE) as '|| a_data_info.transformed_name; + else + l_col_syntax := l_alias||a_data_info.transformed_name||' as '|| a_data_info.transformed_name; + end if; + return l_col_syntax; + end; + + function generate_xmltab_stmt(a_data_info ut_cursor_column) return varchar2 is + l_col_type varchar2(4000); begin - -- this SQL statement is constructed in a way that we always get the same number and ordering of substitution variables - -- That is, we always get: l_exclude_xpath, l_include_xpath - -- regardless if the variables are NULL (not to be used) or NOT NULL and will be used for filtering - if a_exclude_xpath is null and a_include_xpath is null then - l_filter := ':l_exclude_xpath, :l_include_xpath, '||l_source_column||' as '||a_column_alias; - elsif a_exclude_xpath is not null and a_include_xpath is null then - l_filter := 'deletexml( '||l_source_column||', :l_exclude_xpath ) as '||a_column_alias||', :l_include_xpath'; - elsif a_exclude_xpath is null and a_include_xpath is not null then - l_filter := ':l_exclude_xpath, extract( '||l_source_column||', :l_include_xpath ) as '||a_column_alias; - elsif a_exclude_xpath is not null and a_include_xpath is not null then - l_filter := 'extract( deletexml( '||l_source_column||', :l_exclude_xpath ), :l_include_xpath ) as '||a_column_alias; + if a_data_info.is_sql_diffable = 0 then + l_col_type := 'XMLTYPE'; + elsif a_data_info.is_sql_diffable = 1 and a_data_info.column_type in ('DATE','TIMESTAMP','TIMESTAMP WITH TIME ZONE', + 'TIMESTAMP WITH LOCAL TIME ZONE') then + l_col_type := 'VARCHAR2(50)'; + elsif a_data_info.is_sql_diffable = 1 and a_data_info.column_type in ('INTERVAL DAY TO SECOND','INTERVAL YEAR TO MONTH') then + l_col_type := a_data_info.column_type; + else + l_col_type := a_data_info.column_type + ||case when a_data_info.column_len is not null + then '('||a_data_info.column_len||')' + else null + end; end if; - return l_filter; + return a_data_info.transformed_name||' '||l_col_type||q'[ PATH ']'||a_data_info.access_path||q'[']'; end; - - /** - * Current get column filter shaving off ROW tag during extract, this not working well with include and XMLTABLE option - * so when there is extract we artificially inject removed tag - **/ - function get_columns_row_filter( - a_exclude_xpath varchar2, a_include_xpath varchar2, - a_table_alias varchar2 := 'ucd', a_column_alias varchar2 := 'item_data' - ) return varchar2 is - l_filter varchar2(32767); - l_source_column varchar2(500) := a_table_alias||'.'||a_column_alias; + + procedure gen_sql_pieces_out_of_cursor( + a_data_info ut_cursor_column_tab, + a_pk_table ut_varchar2_list, + a_unordered boolean, + a_xml_stmt out nocopy clob, + a_select_stmt out nocopy clob, + a_partition_stmt out nocopy clob, + a_join_by_stmt out nocopy clob, + a_not_equal_stmt out nocopy clob + ) is + l_partition_tmp clob; + l_xmltab_list ut_varchar2_list := ut_varchar2_list(); + l_select_list ut_varchar2_list := ut_varchar2_list(); + l_partition_list ut_varchar2_list := ut_varchar2_list(); + l_equal_list ut_varchar2_list := ut_varchar2_list(); + l_join_by_list ut_varchar2_list := ut_varchar2_list(); + l_not_equal_list ut_varchar2_list := ut_varchar2_list(); + + procedure add_element_to_list(a_list in out ut_varchar2_list, a_list_element in varchar2) + is + begin + if a_list_element is not null then + a_list.extend; + a_list(a_list.last) := a_list_element; + end if; + end; + begin - -- this SQL statement is constructed in a way that we always get the same number and ordering of substitution variables - -- That is, we always get: l_exclude_xpath, l_include_xpath - -- regardless if the variables are NULL (not to be used) or NOT NULL and will be used for filtering - if a_exclude_xpath is null and a_include_xpath is null then - l_filter := ':l_exclude_xpath, :l_include_xpath, '||l_source_column||' as '||a_column_alias; - elsif a_exclude_xpath is not null and a_include_xpath is null then - l_filter := 'deletexml( '||l_source_column||', :l_exclude_xpath ) as '||a_column_alias||', :l_include_xpath'; - elsif a_exclude_xpath is null and a_include_xpath is not null then - l_filter := ':l_exclude_xpath, xmlelement("ROW",extract( '||l_source_column||', :l_include_xpath )) as '||a_column_alias; - elsif a_exclude_xpath is not null and a_include_xpath is not null then - l_filter := 'xmlelement("ROW",extract( deletexml( '||l_source_column||', :l_exclude_xpath ), :l_include_xpath )) as '||a_column_alias; + if a_data_info is not empty then + for i in 1..a_data_info.count loop + if a_data_info(i).has_nested_col = 0 then + --Get XMLTABLE column list + add_element_to_list(l_xmltab_list,generate_xmltab_stmt(a_data_info(i))); + --Get Select statment list of columns + add_element_to_list(l_select_list, generate_select_stmt(a_data_info(i))); + --Get columns by which we partition + add_element_to_list(l_partition_list,generate_partition_stmt(a_data_info(i), a_pk_table)); + --Get equal statement + add_element_to_list(l_equal_list,generate_equal_sql(a_data_info(i).transformed_name)); + --Generate join by stmt + add_element_to_list(l_join_by_list,generate_join_by_stmt(a_data_info(i), a_pk_table)); + --Generate not equal stmt + add_element_to_list(l_not_equal_list,generate_not_equal_stmt(a_data_info(i), a_pk_table)); + end if; + end loop; + + a_xml_stmt := nullif(','||ut_utils.table_to_clob(l_xmltab_list, ' , '),','); + a_select_stmt := nullif(','||ut_utils.table_to_clob(l_select_list, ' , '),','); + l_partition_tmp := ut_utils.table_to_clob(l_partition_list, ' , '); + ut_utils.append_to_clob(a_partition_stmt,' row_number() over (partition by '||l_partition_tmp||' order by '||l_partition_tmp||' ) '); + + if a_pk_table.count > 0 then + -- If key defined do the join or these and where on diffrences + a_join_by_stmt := ut_utils.table_to_clob(l_join_by_list, ' and '); + elsif a_unordered then + -- If no key defined do the join on all columns + a_join_by_stmt := ' e.dup_no = a.dup_no and '||ut_utils.table_to_clob(l_equal_list, ' and '); + else + -- Else join on rownumber + a_join_by_stmt := 'a.item_no = e.item_no '; + end if; + a_not_equal_stmt := ut_utils.table_to_clob(l_not_equal_list, ' or '); + else + --Partition by piece when no data + ut_utils.append_to_clob(a_partition_stmt,' 1 '); + a_join_by_stmt := 'a.item_no = e.item_no '; end if; - return l_filter; end; + + function gen_compare_sql( + a_other ut_data_value_refcursor, + a_join_by_list ut_varchar2_list, + a_unordered boolean, + a_inclusion_type boolean, + a_is_negated boolean + ) return clob is + l_compare_sql clob; + l_xmltable_stmt clob; + l_select_stmt clob; + l_partition_stmt clob; + l_join_on_stmt clob; + l_not_equal_stmt clob; + l_where_stmt clob; + l_ut_owner varchar2(250) := ut_utils.ut_owner; + + function get_join_type(a_inclusion_compare in boolean,a_negated in boolean) return varchar2 is + begin + return + case + when a_inclusion_compare and not(a_negated) then ' right outer join ' + when a_inclusion_compare and a_negated then ' inner join ' + else ' full outer join ' + end; + end; + + function get_item_no(a_unordered boolean) return varchar2 is + begin + return + case + when a_unordered then 'row_number() over ( order by nvl(e.item_no,a.item_no))' + else 'nvl(e.item_no,a.item_no) ' + end; + end; - function get_columns_diff( - a_expected xmltype, a_actual xmltype, a_exclude_xpath varchar2, a_include_xpath varchar2 - ) return tt_column_diffs is - l_column_filter varchar2(32767); - l_sql varchar2(32767); - l_results tt_column_diffs; begin - l_column_filter := get_columns_row_filter(a_exclude_xpath, a_include_xpath); - --CARDINALITY hints added to address issue: https://github.com/utPLSQL/utPLSQL/issues/752 - l_sql := q'[ - with - expected_cols as ( select :a_expected as item_data from dual ), - actual_cols as ( select :a_actual as item_data from dual ), - expected_cols_info as ( - select e.*, - replace(expected_type,'VARCHAR2','CHAR') expected_type_compare - from ( - select /*+ CARDINALITY(xt 100) */ - rownum expected_pos, - xt.name expected_name, - xt.type expected_type - from (select ]'||l_column_filter||q'[ from expected_cols ucd) x, - xmltable( - '/ROW/*' - passing x.item_data - columns - name varchar2(4000) PATH '@xml_valid_name', - type varchar2(4000) PATH '/' - ) xt - ) e - ), - actual_cols_info as ( - select a.*, - replace(actual_type,'VARCHAR2','CHAR') actual_type_compare - from (select /*+ CARDINALITY(xt 100) */ - rownum actual_pos, - xt.name actual_name, - xt.type actual_type - from (select ]'||l_column_filter||q'[ from actual_cols ucd) x, - xmltable('/ROW/*' - passing x.item_data - columns - name varchar2(4000) path '@xml_valid_name', - type varchar2(4000) path '/' - ) xt - ) a - ), - joined_cols as ( - select e.*, a.*, - row_number() over(partition by case when actual_pos + expected_pos is not null then 1 end order by actual_pos) a_pos_nn, - row_number() over(partition by case when actual_pos + expected_pos is not null then 1 end order by expected_pos) e_pos_nn - from expected_cols_info e - full outer join actual_cols_info a on e.expected_name = a.actual_name - ) - select case - when expected_pos is null and actual_pos is not null then '+' - when expected_pos is not null and actual_pos is null then '-' - when expected_type_compare != actual_type_compare then 't' - else 'p' - end as diff_type, - expected_name, expected_type, expected_pos, - actual_name, actual_type, actual_pos - from joined_cols - --column is unexpected (extra) or missing - where actual_pos is null or expected_pos is null - --column type is not matching (except CHAR/VARCHAR2) - or actual_type_compare != expected_type_compare - --column position is not matching (both when excluded extra/missing columns as well as when they are included) - or (a_pos_nn != e_pos_nn and expected_pos != actual_pos) - order by expected_pos, actual_pos]'; - execute immediate l_sql - bulk collect into l_results - using a_expected, a_actual, a_exclude_xpath, a_include_xpath, a_exclude_xpath, a_include_xpath; + dbms_lob.createtemporary(l_compare_sql, true); + --Initiate a SQL template with placeholders + ut_utils.append_to_clob(l_compare_sql, g_compare_sql_template); + --Generate a pieceso of dynamic SQL that will substitute placeholders + gen_sql_pieces_out_of_cursor( + a_other.cursor_details.cursor_columns_info, a_join_by_list, a_unordered, + l_xmltable_stmt, l_select_stmt, l_partition_stmt, l_join_on_stmt, + l_not_equal_stmt + ); + + l_compare_sql := replace(l_compare_sql,'{:duplicate_number:}',l_partition_stmt); + l_compare_sql := replace(l_compare_sql,'{:columns:}',l_select_stmt); + l_compare_sql := replace(l_compare_sql,'{:ut3_owner:}',l_ut_owner); + l_compare_sql := replace(l_compare_sql,'{:xml_to_columns:}',l_xmltable_stmt); + l_compare_sql := replace(l_compare_sql,'{:item_no:}',get_item_no(a_unordered)); + l_compare_sql := replace(l_compare_sql,'{:join_type:}',get_join_type(a_inclusion_type,a_is_negated)); + l_compare_sql := replace(l_compare_sql,'{:join_condition:}',l_join_on_stmt); - return l_results; + 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 + ut_utils.append_to_clob(l_where_stmt,' ( '||l_not_equal_stmt||' ) or '); + end if; + --If its inclusion we expect a actual set to fully match and have no extra elements over expected + if a_inclusion_type then + ut_utils.append_to_clob(l_where_stmt,case when a_is_negated then ' 1 = 1 ' else ' ( a.data_id is null ) ' end); + else + ut_utils.append_to_clob(l_where_stmt,' (a.data_id is null or e.data_id is null) '); + end if; + + l_compare_sql := replace(l_compare_sql,'{:where_condition:}',l_where_stmt); + return l_compare_sql; end; - - function get_pk_value (a_join_by_xpath varchar2,a_item_data xmltype) return clob is - l_pk_value clob; + + function get_column_extract_path(a_cursor_info ut_cursor_column_tab) return ut_varchar2_list is + l_column_list ut_varchar2_list := ut_varchar2_list(); begin - select replace((extract(a_item_data,a_join_by_xpath).getclobval()),chr(10)) into l_pk_value from dual; - return l_pk_value; + for i in 1..a_cursor_info.count loop + l_column_list.extend; + l_column_list(l_column_list.last) := a_cursor_info(i).access_path; + end loop; + return l_column_list; end; - - function get_rows_diff( + + function get_rows_diff_by_sql( + a_act_cursor_info ut_cursor_column_tab, a_exp_cursor_info ut_cursor_column_tab, a_expected_dataset_guid raw, a_actual_dataset_guid raw, a_diff_id raw, - a_max_rows integer, a_exclude_xpath varchar2, a_include_xpath varchar2, - a_join_by_xpath varchar2 + a_join_by_list ut_varchar2_list, a_unordered boolean, a_enforce_column_order boolean := false, + a_extract_path varchar2 ) return tt_row_diffs is - l_column_filter varchar2(32767); - l_results tt_row_diffs; + l_act_extract_xpath varchar2(32767):= ut_utils.to_xpath(get_column_extract_path(a_act_cursor_info)); + l_exp_extract_xpath varchar2(32767):= ut_utils.to_xpath(get_column_extract_path(a_exp_cursor_info)); + l_join_xpath varchar2(32767) := ut_utils.to_xpath(a_join_by_list); + l_results tt_row_diffs; + l_sql varchar2(32767); begin - l_column_filter := get_columns_row_filter(a_exclude_xpath,a_include_xpath); - - /** - * Since its unordered search we cannot select max rows from diffs as we miss some comparision records - * We will restrict output on higher level of select - * NO_MERGE hint was introduced to prevent optimizer from merging views and rewriting query which in some cases - * lead to second value being null depend on execution plan that been chosen - **/ - execute immediate q'[ - with diff_info as (select item_hash,pk_hash,duplicate_no from ut_compound_data_diff_tmp ucdc where diff_id = :diff_guid) - select rn,diff_type,diffed_row,pk_value from - ( - select - diff_type, diffed_row, - dense_rank() over (order by case when diff_type in ('Extra','Missing') then diff_type end, - case when diff_type in ('Actual','Expected') then pk_hash end, - case when diff_type in ('Extra','Missing') then pk_hash end, - case when diff_type in ('Actual','Expected') then diff_type end) rn, - pk_value, pk_hash - from - ( - select diff_type,diffed_row,pk_hash,pk_value from - (select diff_type,data_item diffed_row,pk_hash,pk_value - from - (select /*+NO_MERGE*/ nvl(exp.pk_hash, act.pk_hash) pk_hash,nvl(exp.pk_value, act.pk_value) pk_value, - xmlserialize(content exp.row_data no indent) exp_item, - xmlserialize(content act.row_data no indent) act_item - from - (select ucd.* - from - (select ucd.column_value row_data, - r.item_hash row_hash, - r.pk_hash , - r.duplicate_no, - ucd.column_value.getclobval() col_val, - ucd.column_value.getRootElement() col_name, - ut_compound_data_helper.get_pk_value(:join_xpath,r.item_data) pk_value - from - (select ]'||l_column_filter||q'[, ucd.item_no, ucd.item_hash, i.pk_hash, i.duplicate_no - from ut_compound_data_tmp ucd, - diff_info i - where ucd.data_id = :self_guid - and ucd.item_hash = i.item_hash - ) r, - table( xmlsequence( extract(r.item_data,'/*/*') ) ) ucd - ) ucd - ) exp - join ( - select ucd.* - from - (select ucd.column_value row_data, - r.item_hash row_hash, - r.pk_hash , - r.duplicate_no, - ucd.column_value.getclobval() col_val, - ucd.column_value.getRootElement() col_name, - ut_compound_data_helper.get_pk_value(:join_xpath,r.item_data) pk_value - from - (select ]'||l_column_filter||q'[, ucd.item_no, ucd.item_hash, i.pk_hash, i.duplicate_no - from ut_compound_data_tmp ucd, - diff_info i - where ucd.data_id = :other_guid - and ucd.item_hash = i.item_hash - ) r, - table( xmlsequence( extract(r.item_data,'/*/*') ) ) ucd - ) ucd - ) act - on exp.pk_hash = act.pk_hash and exp.col_name = act.col_name - and exp.duplicate_no = act.duplicate_no + l_sql := q'[ + with exp as ( + select + exp_item_data, exp_data_id, item_no rn, rownum col_no, pk_value, + s.column_value col, s.column_value.getRootElement() col_name, + nvl(s.column_value.getclobval(),empty_clob()) col_val + from ( + select + exp_data_id, extract( ucd.exp_item_data, :column_path ) exp_item_data, item_no, + replace( extract( ucd.exp_item_data, :join_by ).getclobval(), chr(10) ) pk_value + from ut_compound_data_diff_tmp ucd + where diff_id = :diff_id + and ucd.exp_data_id = :self_guid + ) i, + table( xmlsequence( extract(i.exp_item_data,:extract_path) ) ) s + ), + act as ( + select + act_item_data, act_data_id, item_no rn, rownum col_no, pk_value, + s.column_value col, s.column_value.getRootElement() col_name, + nvl(s.column_value.getclobval(),empty_clob()) col_val + from ( + select + act_data_id, extract( ucd.act_item_data, :column_path ) act_item_data, item_no, + replace( extract( ucd.act_item_data, :join_by ).getclobval(), chr(10) ) pk_value + from ut_compound_data_diff_tmp ucd + where diff_id = :diff_id + and ucd.act_data_id = :other_guid + ) i, + table( xmlsequence( extract(i.act_item_data,:extract_path) ) ) s + ) + select rn, diff_type, diffed_row, pk_value pk_value + from ( + select rn, diff_type, diffed_row, pk_value, + case when diff_type = 'Actual:' then 1 else 2 end rnk, + 1 final_order, + col_name + from ( ]' + || case when a_unordered then q'[ + select rn, diff_type, xmlserialize(content data_item no indent) diffed_row, pk_value, col_name + from ( + select nvl(exp.rn, act.rn) rn, + nvl(exp.pk_value, act.pk_value) pk_value, + exp.col exp_item, + act.col act_item, + nvl(exp.col_name,act.col_name) col_name + from exp + join act + on exp.rn = act.rn and exp.col_name = act.col_name + where dbms_lob.compare(exp.col_val, act.col_val) != 0 + ) + unpivot ( data_item for diff_type in (exp_item as 'Expected:', act_item as 'Actual:') ) ]' + else q'[ + select rn, diff_type, xmlserialize(content data_item no indent) diffed_row, null pk_value, col_name + from ( + select nvl(exp.rn, act.rn) rn, + xmlagg(exp.col order by exp.col_no) exp_item, + xmlagg(act.col order by act.col_no) act_item, + max(nvl(exp.col_name,act.col_name)) col_name + from exp exp + join act act + on exp.rn = act.rn and exp.col_name = act.col_name where dbms_lob.compare(exp.col_val, act.col_val) != 0 - ) - unpivot ( data_item for diff_type in (exp_item as 'Expected:', act_item as 'Actual:') ) - ) - union all - select case when exp.pk_hash is null then 'Extra' else 'Missing' end as diff_type, - xmlserialize(content nvl(exp.item_data, act.item_data) no indent) diffed_row, - coalesce(exp.pk_hash,act.pk_hash) pk_hash, - coalesce(exp.pk_value,act.pk_value) pk_value - from (select extract(deletexml(ucd.item_data, :join_by),'/*/*') item_data,i.pk_hash, - ut_compound_data_helper.get_pk_value(:join_by,item_data) pk_value - from ut_compound_data_tmp ucd, - diff_info i - where ucd.data_id = :self_guid - and ucd.item_hash = i.item_hash - ) exp - full outer join ( - select extract(deletexml(ucd.item_data, :join_by),'/*/*') item_data,i.pk_hash, - ut_compound_data_helper.get_pk_value(:join_by,item_data) pk_value - from ut_compound_data_tmp ucd, - diff_info i - where ucd.data_id = :other_guid - and ucd.item_hash = i.item_hash - )act - on exp.pk_hash = act.pk_hash - where exp.pk_hash is null or act.pk_hash is null - ) - ) where rn <= :max_rows - order by rn, pk_hash, diff_type - ]' - bulk collect into l_results - using a_diff_id, - a_join_by_xpath, - a_exclude_xpath, a_include_xpath, a_expected_dataset_guid, - a_join_by_xpath, - a_exclude_xpath, a_include_xpath, a_actual_dataset_guid, - a_join_by_xpath,a_join_by_xpath,a_expected_dataset_guid,a_join_by_xpath,a_join_by_xpath, a_actual_dataset_guid, - a_max_rows; - + group by (exp.rn, act.rn) + ) + unpivot ( data_item for diff_type in (exp_item as 'Expected:', act_item as 'Actual:') ) ]' + end ||q'[ + ) + union all + select + item_no as rn, + case when exp_data_id is null then 'Extra:' else 'Missing:' end as diff_type, + xmlserialize( + content ( + extract( (case when exp_data_id is null then act_item_data else exp_item_data end),'/*/*') + ) no indent + ) diffed_row, + nvl2( + :join_by, + replace( + extract( case when exp_data_id is null then act_item_data else exp_item_data end, :join_by ).getclobval(), + chr(10) + ), + null + ) pk_value, + case when exp_data_id is null then 1 else 2 end rnk, + 2 final_order, + null col_name + from ut_compound_data_diff_tmp i + where diff_id = :diff_id + and act_data_id is null or exp_data_id is null + ) + order by final_order,]' + ||case when a_enforce_column_order or (not(a_enforce_column_order) and not(a_unordered)) then + q'[ + case when final_order = 1 then rn else rnk end, + case when final_order = 1 then rnk else rn end + ]' + when a_unordered then + q'[ + case when final_order = 1 then col_name else to_char(rnk) end, + case when final_order = 1 then to_char(rn) else col_name end, + case when final_order = 1 then to_char(rnk) else col_name end + ]' + else + null + end; + execute immediate l_sql + bulk collect into l_results + using l_exp_extract_xpath, l_join_xpath, a_diff_id, a_expected_dataset_guid,a_extract_path, + l_act_extract_xpath, l_join_xpath, a_diff_id, a_actual_dataset_guid,a_extract_path, + l_join_xpath, l_join_xpath, a_diff_id; return l_results; end; + + function get_hash(a_data raw, a_hash_type binary_integer := dbms_crypto.hash_sh1) return t_hash is + begin + return dbms_crypto.hash(a_data, a_hash_type); + end; - function get_rows_diff( - a_expected_dataset_guid raw, a_actual_dataset_guid raw, a_diff_id raw, - a_max_rows integer, a_exclude_xpath varchar2, a_include_xpath varchar2 - ) return tt_row_diffs is - l_column_filter varchar2(32767); - l_results tt_row_diffs; + function get_hash(a_data clob, a_hash_type binary_integer := dbms_crypto.hash_sh1) return t_hash is begin - l_column_filter := get_columns_filter(a_exclude_xpath,a_include_xpath); - execute immediate q'[ - with - diff_info as ( select item_no - from - (select item_no from ut_compound_data_diff_tmp ucdc where diff_id = :diff_guid order by item_no asc) - where rownum <= :max_rows) - select * - from (select rn, diff_type, xmlserialize(content data_item no indent) diffed_row, null pk_value - from (select nvl(exp.rn, act.rn) rn, - xmlagg(exp.col order by exp.col_no) exp_item, - xmlagg(act.col order by act.col_no) act_item - from (select r.item_no as rn, rownum col_no, s.column_value col, - s.column_value.getRootElement() col_name, - s.column_value.getclobval() col_val - from (select ]'||l_column_filter||q'[, ucd.item_no, ucd.item_data item_data_no_filter - from ut_compound_data_tmp ucd, - diff_info i - where ucd.data_id = :self_guid - and ucd.item_no = i.item_no - ) r, - table( xmlsequence( extract(r.item_data,'/*/*') ) ) s - ) exp - join ( - select item_no as rn, rownum col_no, s.column_value col, - s.column_value.getRootElement() col_name, - s.column_value.getclobval() col_val - from (select ]'||l_column_filter||q'[, ucd.item_no, ucd.item_data item_data_no_filter - from ut_compound_data_tmp ucd, - diff_info i - where ucd.data_id = :other_guid - and ucd.item_no = i.item_no - ) r, - table( xmlsequence( extract(r.item_data,'/*/*') ) ) s - ) act - on exp.rn = act.rn and exp.col_name = act.col_name - where dbms_lob.compare(exp.col_val, act.col_val) != 0 - group by exp.rn, act.rn - ) - unpivot ( data_item for diff_type in (exp_item as 'Expected:', act_item as 'Actual:') ) - ) - union all - select nvl(exp.item_no, act.item_no) rn, - case when exp.item_no is null then 'Extra:' else 'Missing:' end as diff_type, - xmlserialize(content nvl(exp.item_data, act.item_data) no indent) diffed_row, - null pk_value - from (select ucd.item_no, extract(ucd.item_data,'/*/*') item_data - from ut_compound_data_tmp ucd - where ucd.data_id = :self_guid - and ucd.item_no in (select i.item_no from diff_info i) - ) exp - full outer join ( - select ucd.item_no, extract(ucd.item_data,'/*/*') item_data - from ut_compound_data_tmp ucd - where ucd.data_id = :other_guid - and ucd.item_no in (select i.item_no from diff_info i) - )act - on exp.item_no = act.item_no - where exp.item_no is null or act.item_no is null - order by 1, 2]' - bulk collect into l_results - using a_diff_id, a_max_rows, - a_exclude_xpath, a_include_xpath, a_expected_dataset_guid, - a_exclude_xpath, a_include_xpath, a_actual_dataset_guid, - a_expected_dataset_guid, a_actual_dataset_guid; - return l_results; + return dbms_crypto.hash(a_data, a_hash_type); end; - function get_rows_diff_unordered( - a_expected_dataset_guid raw, a_actual_dataset_guid raw, a_diff_id raw, - a_max_rows integer, a_exclude_xpath varchar2, a_include_xpath varchar2 - ) return tt_row_diffs is - l_column_filter varchar2(32767); - l_results tt_row_diffs; + function get_fixed_size_hash(a_string varchar2, a_base integer :=0,a_size integer := 9999999) return number is begin - l_column_filter := get_columns_filter(a_exclude_xpath,a_include_xpath); - - /** - * Since its unordered search we cannot select max rows from diffs as we miss some comparision records - * We will restrict output on higher level of select - */ - execute immediate q'[with - diff_info as (select item_hash,duplicate_no from ut_compound_data_diff_tmp ucdc where diff_id = :diff_guid) - select duplicate_no, - diffed_type, - diffed_row, - null pk_value - from - (select - coalesce(exp.duplicate_no,act.duplicate_no) duplicate_no, - case - when act.row_hash is null then - 'Missing:' - else 'Extra:' - end diffed_type, - case when exp.row_hash is null then - xmlserialize(content act.row_data no indent) - when act.row_hash is null then - xmlserialize(content exp.row_data no indent) - end diffed_row - from (select ucd.* - from (select ucd.column_value row_data, - r.item_hash row_hash, - r.duplicate_no - from (select ]'||l_column_filter||q'[, ucd.item_no, i.item_hash, i.duplicate_no - from ut_compound_data_tmp ucd, - diff_info i - where ucd.data_id = :self_guid - and ucd.item_hash = i.item_hash - and ucd.duplicate_no = i.duplicate_no - ) r, - table( xmlsequence( extract(r.item_data,'/*') ) ) ucd - ) ucd - ) exp - full outer join - (select ucd.* - from (select ucd.column_value row_data, - r.item_hash row_hash, - r.duplicate_no - from (select ]'||l_column_filter||q'[, ucd.item_no, i.item_hash, i.duplicate_no - from ut_compound_data_tmp ucd, - diff_info i - where ucd.data_id = :other_guid - and ucd.item_hash = i.item_hash - and ucd.duplicate_no = i.duplicate_no - ) r, - table( xmlsequence( extract(r.item_data,'/*') ) ) ucd - ) ucd - ) act - on exp.row_hash = act.row_hash - and exp.duplicate_no = act.duplicate_no - where exp.row_hash is null or act.row_hash is null - order by diffed_type, coalesce(exp.row_hash,act.row_hash), duplicate_no - ) - where rownum < :max_rows ]' - bulk collect into l_results - using a_diff_id, - a_exclude_xpath, a_include_xpath, a_expected_dataset_guid, - a_exclude_xpath, a_include_xpath, a_actual_dataset_guid, - a_max_rows; - - return l_results; + return dbms_utility.get_hash_value(a_string,a_base,a_size); + end; + procedure insert_diffs_result(a_diff_tab t_diff_tab, a_diff_id raw) is + begin + forall idx in 1..a_diff_tab.count save exceptions + insert into ut_compound_data_diff_tmp + ( diff_id, act_item_data, act_data_id, exp_item_data, exp_data_id, item_no, duplicate_no ) + values + (a_diff_id, + xmlelement( name "ROW", a_diff_tab(idx).act_item_data), a_diff_tab(idx).act_data_id, + xmlelement( name "ROW", a_diff_tab(idx).exp_item_data), a_diff_tab(idx).exp_data_id, + a_diff_tab(idx).item_no, a_diff_tab(idx).dup_no); + exception + when ut_utils.ex_failure_for_all then + raise_application_error(ut_utils.gc_dml_for_all,'Failure to insert a diff tmp data.'); end; - function compare_type(a_join_by_xpath in varchar2,a_unordered boolean) return varchar2 is - begin - case - when a_join_by_xpath is not null then - return gc_compare_join_by; - when a_unordered then - return gc_compare_unordered; - else - return gc_compare_normal; - end case; - end; - - function get_rows_diff( - a_expected_dataset_guid raw, a_actual_dataset_guid raw, a_diff_id raw, - a_max_rows integer, a_exclude_xpath varchar2, a_include_xpath varchar2, - a_join_by_xpath varchar2,a_unorderdered boolean - ) return tt_row_diffs is - l_results tt_row_diffs; - l_compare_type varchar2(10):= compare_type(a_join_by_xpath,a_unorderdered); + procedure set_rows_diff(a_rows_diff integer) is begin - case - when l_compare_type = gc_compare_join_by then - return get_rows_diff(a_expected_dataset_guid, a_actual_dataset_guid, a_diff_id, - a_max_rows, a_exclude_xpath, a_include_xpath ,a_join_by_xpath); - when l_compare_type = gc_compare_unordered then - return get_rows_diff_unordered(a_expected_dataset_guid, a_actual_dataset_guid, a_diff_id, - a_max_rows, a_exclude_xpath, a_include_xpath); - else - return get_rows_diff(a_expected_dataset_guid, a_actual_dataset_guid, a_diff_id, - a_max_rows, a_exclude_xpath, a_include_xpath); - end case; - + g_diff_count := a_rows_diff; end; - - function get_hash(a_data raw, a_hash_type binary_integer := dbms_crypto.hash_sh1) return t_hash is + + procedure cleanup_diff is begin - return dbms_crypto.hash(a_data, a_hash_type); + g_diff_count := 0; end; - - function get_hash(a_data clob, a_hash_type binary_integer := dbms_crypto.hash_sh1) return t_hash is + + function get_rows_diff_count return integer is begin - return dbms_crypto.hash(a_data, a_hash_type); + return g_diff_count; end; - function columns_hash( - a_data_value_cursor ut_data_value_refcursor, a_exclude_xpath varchar2, a_include_xpath varchar2, - a_hash_type binary_integer := dbms_crypto.hash_sh1 - ) return t_hash is - l_cols_hash t_hash; - begin - if not a_data_value_cursor.is_null then - execute immediate - q'[select dbms_crypto.hash(replace(x.item_data.getclobval(),'>CHAR<','>VARCHAR2<'),]'||a_hash_type||') ' || - ' from ( select '||get_columns_filter(a_exclude_xpath, a_include_xpath)|| - ' from (select :columns_info as item_data from dual ) ucd' || - ' ) x' - into l_cols_hash using a_exclude_xpath,a_include_xpath, a_data_value_cursor.columns_info; + function is_sql_compare_allowed(a_type_name varchar2) + return boolean is + l_assert boolean; + begin + --clob/blob/xmltype/object/nestedcursor/nestedtable + if a_type_name IN (g_type_name_map(dbms_sql.blob_type), + g_type_name_map(dbms_sql.clob_type), + g_type_name_map(dbms_sql.bfile_type), + g_anytype_name_map(dbms_types.typecode_namedcollection)) + then + l_assert := false; + else + l_assert := true; end if; - return l_cols_hash; + return l_assert; end; - function is_pk_exists(a_expected_cursor xmltype,a_actual_cursor xmltype, a_exclude_xpath varchar2, a_include_xpath varchar2,a_join_by_xpath varchar2) - return tt_missing_pk is - l_pk_xpath_tabs ut_varchar2_list := ut_varchar2_list(); - l_column_filter varchar2(32767); - l_no_missing_keys tt_missing_pk := tt_missing_pk(); - + function get_column_type_desc(a_type_code in integer, a_dbms_sql_desc in boolean) + return varchar2 is begin - if a_join_by_xpath is not null then - l_pk_xpath_tabs := ut_utils.string_to_table(a_join_by_xpath,'|'); - l_column_filter := get_columns_row_filter(a_exclude_xpath, a_include_xpath); - - execute immediate q'[ - with xpaths_tab as (select column_value xpath from table(:xpath_tabs)), - expected_column_info as ( select :expected as item_data from dual ), - actual_column_info as ( select :actual as item_data from dual ) - select REGEXP_SUBSTR (xpath,'[^(/\*/)](.+)$'),diif_type from - ( - (select xpath,'e' diif_type from xpaths_tab - minus - select xpath,'e' diif_type - from ( select ]'||l_column_filter||q'[ from expected_column_info ucd) x - ,xpaths_tab - where xmlexists (xpaths_tab.xpath passing x.item_data) - ) - union all - (select xpath,'a' diif_type from xpaths_tab - minus - select xpath,'a' diif_type - from ( select ]'||l_column_filter||q'[ from actual_column_info ucd) x - ,xpaths_tab - where xmlexists (xpaths_tab.xpath passing x.item_data) - ) - )]' bulk collect into l_no_missing_keys - using l_pk_xpath_tabs,a_expected_cursor,a_actual_cursor, - a_exclude_xpath, a_include_xpath, - a_exclude_xpath, a_include_xpath; - - end if; - - return l_no_missing_keys; + return + case + when a_dbms_sql_desc then g_type_name_map(a_type_code) + else g_anytype_name_map(a_type_code) + end; end; - - procedure update_row_and_pk_hash(a_self_data_id in raw, a_other_data_id in raw, a_exclude_xpath varchar2, - a_include_xpath varchar2, a_join_by_xpath varchar2) is - l_ut_owner varchar2(250) := ut_utils.ut_owner; - l_column_filter varchar2(32767); - l_pk_hash_sql varchar2(32767); - - function get_column_pk_hash(a_join_by_xpath varchar2) return varchar2 is - l_column varchar2(32767); - begin - /* due to possibility of key being to columns we cannot use xmlextractvalue - usage of xmlagg is possible however it greatly complicates code and performance is impacted. - xpath to be looked at or regex - */ - if a_join_by_xpath is not null then - l_column := l_ut_owner ||'.ut_compound_data_helper.get_hash(extract(ucd.item_data,:join_by_xpath).GetClobVal()) pk_hash'; - else - l_column := ':join_by_xpath pk_hash'; - end if; - return l_column; - end; - - begin - l_column_filter := ut_compound_data_helper.get_columns_filter(a_exclude_xpath, a_include_xpath); - l_pk_hash_sql := get_column_pk_hash(a_join_by_xpath); - - execute immediate 'merge into ' || l_ut_owner || '.ut_compound_data_tmp tgt - using ( - select ucd_out.item_hash, - ucd_out.pk_hash, - ucd_out.item_no, - ucd_out.data_id, - row_number() over (partition by ucd_out.pk_hash,ucd_out.item_hash,ucd_out.data_id order by 1,2) duplicate_no - from - ( - select '||l_ut_owner ||'.ut_compound_data_helper.get_hash(ucd.item_data.getclobval()) item_hash, - pk_hash, ucd.item_no, ucd.data_id - from - ( - select '||l_column_filter||','||l_pk_hash_sql||', item_no, data_id - from ' || l_ut_owner || q'[.ut_compound_data_tmp ucd - where data_id = :self_guid or data_id = :other_guid - ) ucd - )ucd_out - ) src - on (tgt.item_no = src.item_no and tgt.data_id = src.data_id) - when matched then update - set tgt.item_hash = src.item_hash, - tgt.pk_hash = src.pk_hash, - tgt.duplicate_no = src.duplicate_no]' - using a_exclude_xpath, a_include_xpath,a_join_by_xpath,a_self_data_id, a_other_data_id; - end; - +begin + g_anytype_name_map(dbms_types.typecode_date) := 'DATE'; + g_anytype_name_map(dbms_types.typecode_number) := 'NUMBER'; + g_anytype_name_map(3 /*INTEGER in object type*/) := 'NUMBER'; + g_anytype_name_map(dbms_types.typecode_raw) := 'RAW'; + g_anytype_name_map(dbms_types.typecode_char) := 'CHAR'; + g_anytype_name_map(dbms_types.typecode_varchar2) := 'VARCHAR2'; + g_anytype_name_map(dbms_types.typecode_varchar) := 'VARCHAR'; + g_anytype_name_map(dbms_types.typecode_blob) := 'BLOB'; + g_anytype_name_map(dbms_types.typecode_bfile) := 'BFILE'; + g_anytype_name_map(dbms_types.typecode_clob) := 'CLOB'; + g_anytype_name_map(dbms_types.typecode_timestamp) := 'TIMESTAMP'; + g_anytype_name_map(dbms_types.typecode_timestamp_tz) := 'TIMESTAMP WITH TIME ZONE'; + g_anytype_name_map(dbms_types.typecode_timestamp_ltz) := 'TIMESTAMP WITH LOCAL TIME ZONE'; + g_anytype_name_map(dbms_types.typecode_interval_ym) := 'INTERVAL YEAR TO MONTH'; + g_anytype_name_map(dbms_types.typecode_interval_ds) := 'INTERVAL DAY TO SECOND'; + g_anytype_name_map(dbms_types.typecode_bfloat) := 'BINARY_FLOAT'; + g_anytype_name_map(dbms_types.typecode_bdouble) := 'BINARY_DOUBLE'; + g_anytype_name_map(dbms_types.typecode_urowid) := 'UROWID'; + g_anytype_name_map(dbms_types.typecode_varray) := 'VARRRAY'; + g_anytype_name_map(dbms_types.typecode_table) := 'TABLE'; + g_anytype_name_map(dbms_types.typecode_namedcollection) := 'NAMEDCOLLECTION'; + g_anytype_name_map(dbms_types.typecode_object) := 'OBJECT'; + + g_type_name_map( dbms_sql.binary_bouble_type ) := 'BINARY_DOUBLE'; + g_type_name_map( dbms_sql.bfile_type ) := 'BFILE'; + g_type_name_map( dbms_sql.binary_float_type ) := 'BINARY_FLOAT'; + g_type_name_map( dbms_sql.blob_type ) := 'BLOB'; + g_type_name_map( dbms_sql.long_raw_type ) := 'LONG RAW'; + g_type_name_map( dbms_sql.char_type ) := 'CHAR'; + g_type_name_map( dbms_sql.clob_type ) := 'CLOB'; + g_type_name_map( dbms_sql.long_type ) := 'LONG'; + g_type_name_map( dbms_sql.date_type ) := 'DATE'; + g_type_name_map( dbms_sql.interval_day_to_second_type ) := 'INTERVAL DAY TO SECOND'; + g_type_name_map( dbms_sql.interval_year_to_month_type ) := 'INTERVAL YEAR TO MONTH'; + g_type_name_map( dbms_sql.raw_type ) := 'RAW'; + g_type_name_map( dbms_sql.timestamp_type ) := 'TIMESTAMP'; + g_type_name_map( dbms_sql.timestamp_with_tz_type ) := 'TIMESTAMP WITH TIME ZONE'; + g_type_name_map( dbms_sql.timestamp_with_local_tz_type ) := 'TIMESTAMP WITH LOCAL TIME ZONE'; + g_type_name_map( dbms_sql.varchar2_type ) := 'VARCHAR2'; + g_type_name_map( dbms_sql.number_type ) := 'NUMBER'; + g_type_name_map( dbms_sql.rowid_type ) := 'ROWID'; + g_type_name_map( dbms_sql.urowid_type ) := 'UROWID'; + g_type_name_map( dbms_sql.user_defined_type ) := 'USER_DEFINED_TYPE'; + g_type_name_map( dbms_sql.ref_type ) := 'REF_TYPE'; + end; / diff --git a/source/expectations/data_values/ut_compound_data_helper.pks b/source/expectations/data_values/ut_compound_data_helper.pks index a18c20952..679c13692 100644 --- a/source/expectations/data_values/ut_compound_data_helper.pks +++ b/source/expectations/data_values/ut_compound_data_helper.pks @@ -16,7 +16,6 @@ create or replace package ut_compound_data_helper authid definer is limitations under the License. */ - gc_compare_join_by constant varchar2(10):='join_by'; gc_compare_unordered constant varchar2(10):='unordered'; gc_compare_normal constant varchar2(10):='normal'; @@ -32,13 +31,6 @@ create or replace package ut_compound_data_helper authid definer is type tt_column_diffs is table of t_column_diffs; - type t_missing_pk is record( - missingxpath varchar2(250), - diff_type varchar2(1) - ); - - type tt_missing_pk is table of t_missing_pk; - type t_row_diffs is record( rn integer, diff_type varchar2(250), @@ -48,41 +40,55 @@ create or replace package ut_compound_data_helper authid definer is type tt_row_diffs is table of t_row_diffs; - function get_column_info_xml(a_column_details ut_key_anyval_pair) return xmltype; - - function get_columns_filter( - a_exclude_xpath varchar2, a_include_xpath varchar2, - a_table_alias varchar2 := 'ucd', a_column_alias varchar2 := 'item_data' - ) return varchar2; + type t_diff_rec is record ( + act_item_data xmltype, + act_data_id raw(32), + exp_item_data xmltype, + exp_data_id raw(32), + item_no number, + dup_no number + ); + type t_diff_tab is table of t_diff_rec; + function get_columns_diff( - a_expected xmltype, a_actual xmltype, a_exclude_xpath varchar2, a_include_xpath varchar2 + a_expected ut_cursor_column_tab, a_actual ut_cursor_column_tab,a_order_enforced boolean := false ) return tt_column_diffs; - function get_pk_value (a_join_by_xpath varchar2,a_item_data xmltype) return clob; - - function compare_type(a_join_by_xpath in varchar2,a_unordered boolean) return varchar2; - - function get_rows_diff( + function get_rows_diff_by_sql( + a_act_cursor_info ut_cursor_column_tab,a_exp_cursor_info ut_cursor_column_tab, a_expected_dataset_guid raw, a_actual_dataset_guid raw, a_diff_id raw, - a_max_rows integer, a_exclude_xpath varchar2, a_include_xpath varchar2, - a_join_by_xpath varchar2,a_unorderdered boolean + a_join_by_list ut_varchar2_list, a_unordered boolean, a_enforce_column_order boolean := false, + a_extract_path varchar2 ) return tt_row_diffs; subtype t_hash is raw(128); function get_hash(a_data raw, a_hash_type binary_integer := dbms_crypto.hash_sh1) return t_hash; + function get_hash(a_data clob, a_hash_type binary_integer := dbms_crypto.hash_sh1) return t_hash; - function columns_hash( - a_data_value_cursor ut_data_value_refcursor, a_exclude_xpath varchar2, a_include_xpath varchar2, - a_hash_type binary_integer := dbms_crypto.hash_sh1 - ) return t_hash; - function is_pk_exists(a_expected_cursor xmltype, a_actual_cursor xmltype, a_exclude_xpath varchar2, a_include_xpath varchar2,a_join_by_xpath varchar2) - return tt_missing_pk; + function get_fixed_size_hash(a_string varchar2, a_base integer :=0,a_size integer :=9999999) return number; + + function gen_compare_sql( + a_other ut_data_value_refcursor, + a_join_by_list ut_varchar2_list, + a_unordered boolean, + a_inclusion_type boolean, + a_is_negated boolean + ) return clob; + + procedure insert_diffs_result(a_diff_tab t_diff_tab, a_diff_id raw); + + procedure set_rows_diff(a_rows_diff integer); + + procedure cleanup_diff; + + function get_rows_diff_count return integer; - procedure update_row_and_pk_hash(a_self_data_id in raw, a_other_data_id in raw, a_exclude_xpath varchar2, - a_include_xpath varchar2, a_join_by_xpath varchar2); + function is_sql_compare_allowed(a_type_name varchar2) return boolean; + + function get_column_type_desc(a_type_code in integer, a_dbms_sql_desc in boolean) return varchar2; end; / diff --git a/source/expectations/data_values/ut_compound_data_tmp.sql b/source/expectations/data_values/ut_compound_data_tmp.sql index a4c6516af..0fb5f9544 100644 --- a/source/expectations/data_values/ut_compound_data_tmp.sql +++ b/source/expectations/data_values/ut_compound_data_tmp.sql @@ -18,5 +18,6 @@ create global temporary table ut_compound_data_tmp( item_hash raw(128), pk_hash raw(128), duplicate_no integer, - constraint ut_cmp_data_tmp_hash_pk unique (data_id,item_no, item_hash , duplicate_no) + constraint ut_cmp_data_tmp_hash_pk unique (data_id, item_no, duplicate_no) ) on commit preserve rows; +--xmltype column item_data store as binary xml; \ No newline at end of file diff --git a/source/expectations/data_values/ut_compound_data_value.tpb b/source/expectations/data_values/ut_compound_data_value.tpb index e5a766890..4aecdb81f 100644 --- a/source/expectations/data_values/ut_compound_data_value.tpb +++ b/source/expectations/data_values/ut_compound_data_value.tpb @@ -36,14 +36,8 @@ create or replace type body ut_compound_data_value as return not self.is_null(); end; - overriding member function compare_implementation(a_other ut_data_value) return integer is - begin - return compare_implementation( a_other, null, null); - end; - overriding member function to_string return varchar2 is l_results ut_utils.t_clob_tab; - c_max_rows constant integer := 20; l_result clob; l_result_string varchar2(32767); begin @@ -53,10 +47,13 @@ create or replace type body ut_compound_data_value as --return first c_max_rows rows execute immediate ' select xmlserialize( content ucd.item_data no indent) - from '|| ut_utils.ut_owner ||'.ut_compound_data_tmp ucd - where ucd.data_id = :data_id - and ucd.item_no <= :max_rows' - bulk collect into l_results using self.data_id, c_max_rows; + from '|| ut_utils.ut_owner ||q'[.ut_compound_data_tmp tmp + ,xmltable ( '/ROWSET' passing tmp.item_data + columns item_data xmltype PATH '*' + ) ucd + where tmp.data_id = :data_id + and rownum <= :max_rows]' + bulk collect into l_results using self.data_id, ut_utils.gc_diff_max_rows; ut_utils.append_to_clob(l_result,l_results); @@ -66,198 +63,5 @@ create or replace type body ut_compound_data_value as return l_result_string; end; - overriding member function diff( a_other ut_data_value, a_exclude_xpath varchar2, a_include_xpath varchar2, a_join_by_xpath varchar2, a_unordered boolean := false ) return varchar2 is - l_result clob; - l_result_string varchar2(32767); - begin - l_result := get_data_diff(a_other, a_exclude_xpath, a_include_xpath, a_join_by_xpath,a_unordered); - l_result_string := ut_utils.to_string(l_result,null); - dbms_lob.freetemporary(l_result); - return l_result_string; - end; - - member function get_data_diff(a_other ut_data_value, a_exclude_xpath varchar2, a_include_xpath varchar2, - a_join_by_xpath varchar2, a_unordered boolean) return clob is - c_max_rows constant integer := 20; - l_result clob; - l_results ut_utils.t_clob_tab := ut_utils.t_clob_tab(); - l_message varchar2(32767); - l_ut_owner varchar2(250) := ut_utils.ut_owner; - l_diff_row_count integer; - l_actual ut_compound_data_value; - l_diff_id ut_compound_data_helper.t_hash; - l_row_diffs ut_compound_data_helper.tt_row_diffs; - l_compare_type varchar2(10); - - function get_diff_message (a_row_diff ut_compound_data_helper.t_row_diffs,a_compare_type varchar2) return varchar2 is - begin - if a_compare_type = ut_compound_data_helper.gc_compare_join_by and a_row_diff.pk_value is not null then - return ' PK '||a_row_diff.pk_value||' - '||rpad(a_row_diff.diff_type,10)||a_row_diff.diffed_row; - elsif a_compare_type = ut_compound_data_helper.gc_compare_join_by or a_compare_type = ut_compound_data_helper.gc_compare_normal then - return ' Row No. '||a_row_diff.rn||' - '||rpad(a_row_diff.diff_type,10)||a_row_diff.diffed_row; - elsif a_compare_type = ut_compound_data_helper.gc_compare_unordered then - return rpad(a_row_diff.diff_type,10)||a_row_diff.diffed_row; - end if; - end; - - begin - if not a_other is of (ut_compound_data_value) then - raise value_error; - end if; - l_actual := treat(a_other as ut_compound_data_value); - - dbms_lob.createtemporary(l_result,true); - - --diff rows and row elements - l_diff_id := ut_compound_data_helper.get_hash(self.data_id||l_actual.data_id); - - -- First tell how many rows are different - execute immediate 'select count('||case when a_join_by_xpath is not null then 'distinct pk_hash' else '*' end||') from ' - || l_ut_owner || '.ut_compound_data_diff_tmp - where diff_id = :diff_id' into l_diff_row_count using l_diff_id; - - if l_diff_row_count > 0 then - l_compare_type := ut_compound_data_helper.compare_type(a_join_by_xpath,a_unordered); - l_row_diffs := ut_compound_data_helper.get_rows_diff( - self.data_id, l_actual.data_id, l_diff_id, c_max_rows, a_exclude_xpath, a_include_xpath, a_join_by_xpath, a_unordered); - l_message := chr(10) - ||'Rows: [ ' || l_diff_row_count ||' differences' - || case when l_diff_row_count > c_max_rows and l_row_diffs.count > 0 then ', showing first '||c_max_rows end - ||' ]' || chr(10) - || case when l_row_diffs.count = 0 - then ' All rows are different as the columns are not matching.' end; - ut_utils.append_to_clob( l_result, l_message ); - for i in 1 .. l_row_diffs.count loop - l_results.extend; - l_results(l_results.last) := get_diff_message(l_row_diffs(i),l_compare_type); - end loop; - ut_utils.append_to_clob(l_result,l_results); - end if; - return l_result; - end; - - - member function compare_implementation(a_other ut_data_value, a_exclude_xpath varchar2, a_include_xpath varchar2) return integer is - l_other ut_compound_data_value; - l_ut_owner varchar2(250) := ut_utils.ut_owner; - l_column_filter varchar2(32767); - l_diff_id ut_compound_data_helper.t_hash; - l_result integer; - --the XML stylesheet is applied on XML representation of data to exclude column names from comparison - --column names and data-types are compared separately - --user CHR(38) instead of ampersand to eliminate define request when installing through some IDEs - l_xml_data_fmt constant xmltype := xmltype( - q'[ - - - - - - '||CHR(38)||'#xD; - , - - - - ]'); - begin - if not a_other is of (ut_compound_data_value) then - raise value_error; - end if; - - l_other := treat(a_other as ut_compound_data_value); - - l_diff_id := ut_compound_data_helper.get_hash(self.data_id||l_other.data_id); - l_column_filter := ut_compound_data_helper.get_columns_filter(a_exclude_xpath, a_include_xpath); - -- Find differences - execute immediate 'insert into ' || l_ut_owner || '.ut_compound_data_diff_tmp ( diff_id, item_no ) - select :diff_id, nvl(exp.item_no, act.item_no) - from (select '||l_column_filter||', ucd.item_no - from ' || l_ut_owner || '.ut_compound_data_tmp ucd where ucd.data_id = :self_guid) exp - full outer join - (select '||l_column_filter||', ucd.item_no - from ' || l_ut_owner || '.ut_compound_data_tmp ucd where ucd.data_id = :l_other_guid) act - on exp.item_no = act.item_no '|| - 'where nvl( dbms_lob.compare(' || - /*the xmltransform removes column names and leaves column data to be compared only*/ - ' xmltransform(exp.item_data, :l_xml_data_fmt).getclobval()' || - ', xmltransform(act.item_data, :l_xml_data_fmt).getclobval())' || - ',1' || - ') != 0' - using in l_diff_id, a_exclude_xpath, a_include_xpath, self.data_id, - a_exclude_xpath, a_include_xpath, l_other.data_id, l_xml_data_fmt, l_xml_data_fmt; - --result is OK only if both are same - if sql%rowcount = 0 and self.elements_count = l_other.elements_count then - l_result := 0; - else - l_result := 1; - end if; - return l_result; - end; - - member function compare_implementation(a_other ut_data_value, a_exclude_xpath varchar2, a_include_xpath varchar2, a_join_by_xpath varchar2, a_unordered boolean ) return integer is - l_other ut_compound_data_value; - l_ut_owner varchar2(250) := ut_utils.ut_owner; - l_diff_id ut_compound_data_helper.t_hash; - l_result integer; - l_row_diffs ut_compound_data_helper.tt_row_diffs; - c_max_rows constant integer := 20; - - begin - if not a_other is of (ut_compound_data_value) then - raise value_error; - end if; - - l_other := treat(a_other as ut_compound_data_value); - - l_diff_id := ut_compound_data_helper.get_hash(self.data_id||l_other.data_id); - - /** - * Due to incompatibility issues in XML between 11 and 12.2 and 12.1 versions we will prepopulate pk_hash upfront to - * avoid optimizer incorrectly rewrite and causing NULL error or ORA-600 - **/ - ut_compound_data_helper.update_row_and_pk_hash(self.data_id, l_other.data_id, a_exclude_xpath,a_include_xpath,a_join_by_xpath); - - /* Peform minus on two sets two get diffrences that will be used later on to print results */ - execute immediate 'insert into ' || l_ut_owner || '.ut_compound_data_diff_tmp ( diff_id,item_hash,pk_hash,duplicate_no) - with source_data as - ( select t.data_id,t.item_hash,t.duplicate_no, - pk_hash - from ' || l_ut_owner || '.ut_compound_data_tmp t - where data_id = :self_guid or data_id = :other_guid - ) - select distinct :diff_id,tmp.item_hash,tmp.pk_hash,tmp.duplicate_no - from( - ( - select t.item_hash,t.duplicate_no,t.pk_hash - from source_data t - where t.data_id = :self_guid - minus - select t.item_hash,t.duplicate_no,t.pk_hash - from source_data t - where t.data_id = :other_guid - ) - union all - ( - select t.item_hash,t.duplicate_no,t.pk_hash - from source_data t - where t.data_id = :other_guid - minus - select t.item_hash,t.duplicate_no,t.pk_hash - from source_data t - where t.data_id = :self_guid - ))tmp' - using self.data_id, l_other.data_id, - l_diff_id, - self.data_id, l_other.data_id, - l_other.data_id,self.data_id; - --result is OK only if both are same - if sql%rowcount = 0 and self.elements_count = l_other.elements_count then - l_result := 0; - else - l_result := 1; - end if; - return l_result; - end; - end; / diff --git a/source/expectations/data_values/ut_compound_data_value.tps b/source/expectations/data_values/ut_compound_data_value.tps index bf942c92e..8e42765f5 100644 --- a/source/expectations/data_values/ut_compound_data_value.tps +++ b/source/expectations/data_values/ut_compound_data_value.tps @@ -34,16 +34,16 @@ create or replace type ut_compound_data_value force under ut_data_value( * Holds unique id for retrieving the data from ut_compound_data_tmp temp table */ data_id raw(16), - + + /** + * Holds name for the type of compound + */ + compound_type varchar2(50), + overriding member function get_object_info return varchar2, overriding member function is_null return boolean, overriding member function is_diffable return boolean, overriding member function to_string return varchar2, - overriding member function is_multi_line return boolean, - overriding member function compare_implementation(a_other ut_data_value) return integer, - overriding member function diff( a_other ut_data_value, a_exclude_xpath varchar2, a_include_xpath varchar2, a_join_by_xpath varchar2, a_unordered boolean := false ) return varchar2, - member function get_data_diff(a_other ut_data_value, a_exclude_xpath varchar2, a_include_xpath varchar2, a_join_by_xpath varchar2, a_unordered boolean) return clob, - member function compare_implementation(a_other ut_data_value, a_exclude_xpath varchar2, a_include_xpath varchar2) return integer, - member function compare_implementation(a_other ut_data_value, a_exclude_xpath varchar2, a_include_xpath varchar2, a_join_by_xpath varchar2, a_unordered boolean ) return integer + overriding member function is_multi_line return boolean ) not final not instantiable / diff --git a/source/expectations/data_values/ut_curr_usr_compound_helper.pkb b/source/expectations/data_values/ut_curr_usr_compound_helper.pkb deleted file mode 100644 index b610ee13c..000000000 --- a/source/expectations/data_values/ut_curr_usr_compound_helper.pkb +++ /dev/null @@ -1,244 +0,0 @@ -create or replace package body ut_curr_usr_compound_helper is - - type t_type_name_map is table of varchar2(100) index by binary_integer; - g_type_name_map t_type_name_map; - g_anytype_name_map t_type_name_map; - g_anytype_collection_name t_type_name_map; - g_user_defined_type pls_integer := dbms_sql.user_defined_type; - g_is_collection boolean := false; - - procedure set_collection_state(a_is_collection boolean) is - begin - --Make sure that we set a g_is_collection only once so we dont reset from true to false. - if not g_is_collection then - g_is_collection := a_is_collection; - end if; - end; - - function get_column_type(a_desc_rec dbms_sql.desc_rec3, a_desc_user_types boolean := false) return ut_key_anyval_pair is - l_data ut_data_value; - l_result ut_key_anyval_pair; - l_data_type varchar2(500) := 'unknown datatype'; - - function is_collection (a_owner varchar2,a_type_name varchar2) return boolean is - l_type_view varchar2(200) := ut_metadata.get_dba_view('dba_types'); - l_typecode varchar2(100); - begin - execute immediate 'select typecode from '||l_type_view ||' - where owner = :owner and type_name = :typename' - into l_typecode using a_owner,a_type_name; - - return l_typecode = 'COLLECTION'; - end; - - begin - if g_type_name_map.exists(a_desc_rec.col_type) then - l_data := ut_data_value_varchar2(g_type_name_map(a_desc_rec.col_type)); - /*If its a collection regardless is we want to describe user defined types we will return just a name - and capture that name */ - elsif a_desc_rec.col_type = g_user_defined_type and is_collection(a_desc_rec.col_schema_name,a_desc_rec.col_type_name) then - l_data := ut_data_value_varchar2(a_desc_rec.col_schema_name||'.'||a_desc_rec.col_type_name); - set_collection_state(true); - elsif a_desc_rec.col_type = g_user_defined_type and a_desc_user_types then - l_data :=ut_data_value_xmltype(get_user_defined_type(a_desc_rec.col_schema_name,a_desc_rec.col_type_name)); - elsif a_desc_rec.col_schema_name is not null and a_desc_rec.col_type_name is not null then - l_data := ut_data_value_varchar2(a_desc_rec.col_schema_name||'.'||a_desc_rec.col_type_name); - end if; - return ut_key_anyval_pair(a_desc_rec.col_name,l_data); - end; - - function get_columns_info( - a_columns_tab dbms_sql.desc_tab3, a_columns_count integer, a_desc_user_types boolean := false - ) return ut_key_anyval_pairs is - l_result ut_key_anyval_pairs := ut_key_anyval_pairs(); - begin - for i in 1 .. a_columns_count loop - l_result.extend; - l_result(l_result.last) := get_column_type(a_columns_tab(i),a_desc_user_types); - end loop; - return l_result; - end; - - procedure get_descr_cursor( - a_cursor in out nocopy sys_refcursor, - a_columns_tab in out nocopy ut_key_anyval_pairs, - a_join_by_tab in out nocopy ut_key_anyval_pairs - ) is - l_cursor_number integer; - l_columns_count pls_integer; - l_columns_desc dbms_sql.desc_tab3; - begin - if a_cursor is null or not a_cursor%isopen then - a_columns_tab := null; - a_join_by_tab := null; - end if; - l_cursor_number := dbms_sql.to_cursor_number( a_cursor ); - dbms_sql.describe_columns3( l_cursor_number, l_columns_count, l_columns_desc ); - a_cursor := dbms_sql.to_refcursor( l_cursor_number ); - a_columns_tab := get_columns_info( l_columns_desc, l_columns_count, false); - a_join_by_tab := get_columns_info( l_columns_desc, l_columns_count, true); - end; - - procedure get_columns_info( - a_cursor in out nocopy sys_refcursor, - a_columns_info out nocopy xmltype, - a_join_by_info out nocopy xmltype, - a_contains_collection out nocopy number - ) is - l_columns_info xmltype; - l_join_by_info xmltype; - l_result_tmp xmltype; - l_columns_tab ut_key_anyval_pairs; - l_join_by_tab ut_key_anyval_pairs; - begin - - get_descr_cursor(a_cursor,l_columns_tab,l_join_by_tab); - - for i in 1..l_columns_tab.COUNT - loop - l_result_tmp := ut_compound_data_helper.get_column_info_xml(l_columns_tab(i)); - select xmlconcat(l_columns_info,l_result_tmp) into l_columns_info from dual; - end loop; - - for i in 1..l_join_by_tab.COUNT - loop - l_result_tmp := ut_compound_data_helper.get_column_info_xml(l_join_by_tab(i)); - select xmlconcat(l_join_by_info,l_result_tmp) into l_join_by_info from dual; - end loop; - - select XMLELEMENT("ROW",l_columns_info ),XMLELEMENT("ROW",l_join_by_info ) - into a_columns_info,a_join_by_info from dual; - - a_contains_collection := ut_utils.boolean_to_int(g_is_collection); - end; - - function get_anytype_attribute_count (a_anytype anytype) return pls_integer is - l_attribute_typecode pls_integer; - l_schema_name varchar2(32767); - l_version varchar2(32767); - l_type_name varchar2(32767); - l_attributes pls_integer; - l_prec pls_integer; - l_scale pls_integer; - l_len pls_integer; - l_csid pls_integer; - l_csfrm pls_integer; - begin - l_attribute_typecode := a_anytype.getinfo( - prec => l_prec, - scale => l_scale, - len => l_len, - csid => l_csid, - csfrm => l_csfrm, - schema_name => l_schema_name, - type_name => l_type_name, - version => l_version, - numelems => l_attributes); - return l_attributes; - end; - - function get_anytype_attributes_info (a_anytype anytype) return ut_key_value_pairs is - l_result ut_key_value_pairs := ut_key_value_pairs(); - l_attribute_typecode pls_integer; - l_aname varchar2(32767); - l_prec pls_integer; - l_scale pls_integer; - l_len pls_integer; - l_csid pls_integer; - l_csfrm pls_integer; - l_attr_elt_type anytype; - begin - for i in 1..get_anytype_attribute_count(a_anytype) loop - l_attribute_typecode := a_anytype.getAttrElemInfo( - pos => i, --First attribute - prec => l_prec, - scale => l_scale, - len => l_len, - csid => l_csid, - csfrm => l_csfrm, - attr_elt_type => l_attr_elt_type, - aname => l_aname); - - l_result.extend; - l_result(l_result.last) := ut_key_value_pair(l_aname, g_anytype_name_map(l_attribute_typecode)); - --check for collection - if g_anytype_collection_name.exists(l_attribute_typecode) then - set_collection_state(true); - end if; - end loop; - return l_result; - end; - - function get_user_defined_type(a_owner varchar2,a_type_name varchar2) return xmltype is - l_anydata anydata; - l_anytype anytype; - l_typecode pls_integer; - l_result xmltype; - l_columns_tab ut_key_value_pairs := ut_key_value_pairs(); - - begin - execute immediate 'declare - l_v '||a_owner||'.'||a_type_name||'; - begin - :anydata := anydata.convertobject(l_v); - end;' USING IN OUT l_anydata; - - l_typecode := l_anydata.gettype(l_anytype); - l_columns_tab := get_anytype_attributes_info(l_anytype); - - select xmlagg(xmlelement(evalname key,value)) - into l_result from table(l_columns_tab); - - return l_result; - - end; - - begin - g_anytype_name_map(dbms_types.typecode_date) :=' DATE'; - g_anytype_name_map(dbms_types.typecode_number) := 'NUMBER'; - g_anytype_name_map(dbms_types.typecode_raw) := 'RAW'; - g_anytype_name_map(dbms_types.typecode_char) := 'CHAR'; - g_anytype_name_map(dbms_types.typecode_varchar2) := 'VARCHAR2'; - g_anytype_name_map(dbms_types.typecode_varchar) := 'VARCHAR'; - g_anytype_name_map(dbms_types.typecode_blob) := 'BLOB'; - g_anytype_name_map(dbms_types.typecode_bfile) := 'BFILE'; - g_anytype_name_map(dbms_types.typecode_clob) := 'CLOB'; - g_anytype_name_map(dbms_types.typecode_timestamp) := 'TIMESTAMP'; - g_anytype_name_map(dbms_types.typecode_timestamp_tz) := 'TIMESTAMP WITH TIME ZONE'; - g_anytype_name_map(dbms_types.typecode_timestamp_ltz) := 'TIMESTAMP WITH LOCAL TIME ZONE'; - g_anytype_name_map(dbms_types.typecode_interval_ym) := 'INTERVAL YEAR TO MONTH'; - g_anytype_name_map(dbms_types.typecode_interval_ds) := 'INTERVAL DAY TO SECOND'; - g_anytype_name_map(dbms_types.typecode_bfloat) := 'BINARY_FLOAT'; - g_anytype_name_map(dbms_types.typecode_bdouble) := 'BINARY_DOUBLE'; - g_anytype_name_map(dbms_types.typecode_urowid) := 'UROWID'; - g_anytype_name_map(dbms_types.typecode_varray) := 'VARRRAY'; - g_anytype_name_map(dbms_types.typecode_table) := 'TABLE'; - g_anytype_name_map(dbms_types.typecode_namedcollection) := 'NAMEDCOLLECTION'; - - g_anytype_collection_name(dbms_types.typecode_varray) := 'VARRRAY'; - g_anytype_collection_name(dbms_types.typecode_table) := 'TABLE'; - g_anytype_collection_name(dbms_types.typecode_namedcollection) := 'NAMEDCOLLECTION'; - - - g_type_name_map( dbms_sql.binary_bouble_type ) := 'BINARY_DOUBLE'; - g_type_name_map( dbms_sql.bfile_type ) := 'BFILE'; - g_type_name_map( dbms_sql.binary_float_type ) := 'BINARY_FLOAT'; - g_type_name_map( dbms_sql.blob_type ) := 'BLOB'; - g_type_name_map( dbms_sql.long_raw_type ) := 'LONG RAW'; - g_type_name_map( dbms_sql.char_type ) := 'CHAR'; - g_type_name_map( dbms_sql.clob_type ) := 'CLOB'; - g_type_name_map( dbms_sql.long_type ) := 'LONG'; - g_type_name_map( dbms_sql.date_type ) := 'DATE'; - g_type_name_map( dbms_sql.interval_day_to_second_type ) := 'INTERVAL DAY TO SECOND'; - g_type_name_map( dbms_sql.interval_year_to_month_type ) := 'INTERVAL YEAR TO MONTH'; - g_type_name_map( dbms_sql.raw_type ) := 'RAW'; - g_type_name_map( dbms_sql.timestamp_type ) := 'TIMESTAMP'; - g_type_name_map( dbms_sql.timestamp_with_tz_type ) := 'TIMESTAMP WITH TIME ZONE'; - g_type_name_map( dbms_sql.timestamp_with_local_tz_type ) := 'TIMESTAMP WITH LOCAL TIME ZONE'; - g_type_name_map( dbms_sql.varchar2_type ) := 'VARCHAR2'; - g_type_name_map( dbms_sql.number_type ) := 'NUMBER'; - g_type_name_map( dbms_sql.rowid_type ) := 'ROWID'; - g_type_name_map( dbms_sql.urowid_type ) := 'UROWID'; - -end; -/ diff --git a/source/expectations/data_values/ut_curr_usr_compound_helper.pks b/source/expectations/data_values/ut_curr_usr_compound_helper.pks deleted file mode 100644 index d662f51f9..000000000 --- a/source/expectations/data_values/ut_curr_usr_compound_helper.pks +++ /dev/null @@ -1,13 +0,0 @@ -create or replace package ut_curr_usr_compound_helper authid current_user is - - procedure get_columns_info( - a_cursor in out nocopy sys_refcursor, - a_columns_info out nocopy xmltype, - a_join_by_info out nocopy xmltype, - a_contains_collection out nocopy number - ); - - function get_user_defined_type(a_owner varchar2, a_type_name varchar2) return xmltype; - -end; -/ diff --git a/source/expectations/data_values/ut_cursor_column.tpb b/source/expectations/data_values/ut_cursor_column.tpb new file mode 100644 index 000000000..ab83ff339 --- /dev/null +++ b/source/expectations/data_values/ut_cursor_column.tpb @@ -0,0 +1,57 @@ +create or replace type body ut_cursor_column as + + member procedure init( + self in out nocopy ut_cursor_column, + a_col_name varchar2, a_col_schema_name varchar2, + a_col_type_name varchar2, a_col_max_len integer, a_parent_name varchar2 := null, a_hierarchy_level integer := 1, + a_col_position integer, a_col_type varchar2, a_collection integer,a_access_path in varchar2 + ) is + begin + self.parent_name := a_parent_name; --Name of the parent if its nested + self.hierarchy_level := a_hierarchy_level; --Hierarchy level + self.column_position := a_col_position; --Position of the column in cursor/ type + self.column_len := a_col_max_len; --length of column + self.column_name := TRIM( BOTH '''' FROM a_col_name); --name of the column + self.column_type_name := a_col_type_name; --type name e.g. test_dummy_object or varchar2 + self.access_path := case when a_access_path is null then + self.column_name + else + a_access_path||'/'||self.column_name + end; --Access path used for incldue exclude eg/ TEST_DUMMY_OBJECT/VARCHAR2 + self.xml_valid_name := '"'||self.column_name||'"'; --User friendly column name + self.transformed_name := case when self.parent_name is null then + self.xml_valid_name + else + '"'||ut_compound_data_helper.get_fixed_size_hash(self.parent_name||self.column_name)||'"' + end; --when is nestd we need to hash name to make sure we dont exceed 30 char + self.column_type := a_col_type; --column type e.g. user_defined , varchar2 + self.column_schema := a_col_schema_name; -- schema name + self.is_sql_diffable := case + when lower(self.column_type) = 'user_defined_type' then + 0 + -- Due to bug in 11g/12.1 collection fails on varchar 4000+ + when (lower(self.column_type) in ('varchar2','char')) and (self.column_len > 4000) then + 0 + else + ut_utils.boolean_to_int(ut_compound_data_helper.is_sql_compare_allowed(self.column_type)) + end; --can we directly compare or do we need to hash value + self.is_collection := a_collection; + self.has_nested_col := case when lower(self.column_type) = 'user_defined_type' and self.is_collection = 0 then 1 else 0 end; + end; + + constructor function ut_cursor_column( self in out nocopy ut_cursor_column, + a_col_name varchar2, a_col_schema_name varchar2, + a_col_type_name varchar2, a_col_max_len integer, a_parent_name varchar2 := null, a_hierarchy_level integer := 1, + a_col_position integer, a_col_type in varchar2, a_collection integer,a_access_path in varchar2 + ) return self as result is + begin + init(a_col_name, a_col_schema_name, a_col_type_name, a_col_max_len, a_parent_name,a_hierarchy_level, a_col_position, a_col_type, a_collection,a_access_path); + return; + end; + + constructor function ut_cursor_column( self in out nocopy ut_cursor_column) return self as result is + begin + return; + end; +end; +/ diff --git a/source/expectations/data_values/ut_cursor_column.tps b/source/expectations/data_values/ut_cursor_column.tps new file mode 100644 index 000000000..4b436051a --- /dev/null +++ b/source/expectations/data_values/ut_cursor_column.tps @@ -0,0 +1,46 @@ +create or replace type ut_cursor_column force authid current_user as object ( + /* + utPLSQL - Version 3 + Copyright 2016 - 2018 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + parent_name varchar2(4000), + access_path varchar2(4000), + has_nested_col number(1,0), + transformed_name varchar2(32), + hierarchy_level number, + column_position number, + xml_valid_name varchar2(128), + column_name varchar2(128), + column_type varchar2(128), + column_type_name varchar2(128), + column_schema varchar2(128), + column_len integer, + is_sql_diffable number(1, 0), + is_collection number(1, 0), + + member procedure init(self in out nocopy ut_cursor_column, + a_col_name varchar2, a_col_schema_name varchar2, + a_col_type_name varchar2, a_col_max_len integer, a_parent_name varchar2 := null, a_hierarchy_level integer := 1, + a_col_position integer, a_col_type in varchar2, a_collection integer,a_access_path in varchar2), + + constructor function ut_cursor_column( self in out nocopy ut_cursor_column, + a_col_name varchar2, a_col_schema_name varchar2, + a_col_type_name varchar2, a_col_max_len integer, a_parent_name varchar2 := null, a_hierarchy_level integer := 1, + a_col_position integer, a_col_type in varchar2, a_collection integer, a_access_path in varchar2) + return self as result, + + constructor function ut_cursor_column( self in out nocopy ut_cursor_column) return self as result +) +/ diff --git a/source/expectations/data_values/ut_data_value_object.tps b/source/expectations/data_values/ut_cursor_column_tab.tps similarity index 70% rename from source/expectations/data_values/ut_data_value_object.tps rename to source/expectations/data_values/ut_cursor_column_tab.tps index cf1dc631d..1b7a1698f 100644 --- a/source/expectations/data_values/ut_data_value_object.tps +++ b/source/expectations/data_values/ut_cursor_column_tab.tps @@ -1,4 +1,4 @@ -create or replace type ut_data_value_object under ut_data_value_anydata( +create or replace type ut_cursor_column_tab as /* utPLSQL - Version 3 Copyright 2016 - 2018 utPLSQL Project @@ -15,7 +15,5 @@ create or replace type ut_data_value_object under ut_data_value_anydata( See the License for the specific language governing permissions and limitations under the License. */ - constructor function ut_data_value_object(self in out nocopy ut_data_value_object, a_value anydata) return self as result, - overriding member function get_object_info return varchar2 -) -/ +table of ut_cursor_column +/ \ No newline at end of file diff --git a/source/expectations/data_values/ut_cursor_details.tpb b/source/expectations/data_values/ut_cursor_details.tpb new file mode 100644 index 000000000..f6162aac2 --- /dev/null +++ b/source/expectations/data_values/ut_cursor_details.tpb @@ -0,0 +1,209 @@ +create or replace type body ut_cursor_details as + + member function equals( a_other ut_cursor_details, a_match_options ut_matcher_options ) return boolean is + l_diffs integer; + begin + select count(1) into l_diffs + from table(self.cursor_columns_info) a + full outer join table(a_other.cursor_columns_info) e + on decode(a.parent_name,e.parent_name,1,0)= 1 + and a.column_name = e.column_name + and replace(a.column_type,'VARCHAR2','CHAR') = replace(e.column_type,'VARCHAR2','CHAR') + and ( a.column_position = e.column_position or a_match_options.columns_are_unordered_flag = 1 ) + where a.column_name is null or e.column_name is null; + return l_diffs = 0; + end; + + member procedure desc_compound_data( + self in out nocopy ut_cursor_details, a_compound_data anytype, + a_parent_name in varchar2, a_level in integer, a_access_path in varchar2 + ) is + l_idx pls_integer := 1; + l_elements_info ut_metadata.t_anytype_members_rec; + l_element_info ut_metadata.t_anytype_elem_info_rec; + l_is_collection boolean; + begin + l_elements_info := ut_metadata.get_anytype_members_info( a_compound_data ); + l_is_collection := ut_metadata.is_collection(l_elements_info.type_code); + if l_elements_info.elements_count is null then + l_element_info := ut_metadata.get_attr_elem_info( a_compound_data ); + self.cursor_columns_info.extend; + self.cursor_columns_info(cursor_columns_info.last) := + ut_cursor_column( + l_elements_info.type_name, + l_elements_info.schema_name, + null, + l_elements_info.length, + a_parent_name, + a_level, + l_idx, + ut_compound_data_helper.get_column_type_desc(l_elements_info.type_code,false), + ut_utils.boolean_to_int(l_is_collection), + a_access_path + ); + if l_element_info.attr_elt_type is not null then + desc_compound_data( + l_element_info.attr_elt_type, l_elements_info.type_name, + a_level + 1, a_access_path || '/' || l_elements_info.type_name + ); + end if; + else + while l_idx <= l_elements_info.elements_count loop + l_element_info := ut_metadata.get_attr_elem_info( a_compound_data, l_idx ); + + self.cursor_columns_info.extend; + self.cursor_columns_info(cursor_columns_info.last) := + ut_cursor_column( + l_element_info.attribute_name, + l_elements_info.schema_name, + null, + l_element_info.length, + a_parent_name, + a_level, + l_idx, + ut_compound_data_helper.get_column_type_desc(l_element_info.type_code,false), + ut_utils.boolean_to_int(l_is_collection), + a_access_path + ); + if l_element_info.attr_elt_type is not null then + desc_compound_data( + l_element_info.attr_elt_type, l_element_info.attribute_name, + a_level + 1, a_access_path || '/' || l_element_info.attribute_name + ); + end if; + l_idx := l_idx + 1; + end loop; + end if; + end; + + constructor function ut_cursor_details(self in out nocopy ut_cursor_details) return self as result is + begin + self.cursor_columns_info := ut_cursor_column_tab(); + return; + end; + + constructor function ut_cursor_details( + self in out nocopy ut_cursor_details, + a_cursor_number in number + ) return self as result is + l_columns_count pls_integer; + l_columns_desc dbms_sql.desc_tab3; + l_is_collection boolean; + l_hierarchy_level integer := 1; + begin + self.cursor_columns_info := ut_cursor_column_tab(); + dbms_sql.describe_columns3(a_cursor_number, l_columns_count, l_columns_desc); + + /** + * Due to a bug with object being part of cursor in ANYDATA scenario + * oracle fails to revert number to cursor. We ar using dbms_sql.close cursor to close it + * to avoid leaving open cursors behind. + * a_cursor := dbms_sql.to_refcursor(l_cursor_number); + **/ + for pos in 1 .. l_columns_count loop + l_is_collection := ut_metadata.is_collection( l_columns_desc(pos).col_schema_name, l_columns_desc(pos).col_type_name ); + self.cursor_columns_info.extend; + self.cursor_columns_info(self.cursor_columns_info.last) := + ut_cursor_column( + l_columns_desc(pos).col_name, + l_columns_desc(pos).col_schema_name, + l_columns_desc(pos).col_type_name, + l_columns_desc(pos).col_max_len, + null, + l_hierarchy_level, + pos, + ut_compound_data_helper.get_column_type_desc(l_columns_desc(pos).col_type,true), + ut_utils.boolean_to_int(l_is_collection), + null + ); + + if l_columns_desc(pos).col_type = dbms_sql.user_defined_type or l_is_collection then + desc_compound_data( + ut_metadata.get_user_defined_type( l_columns_desc(pos).col_schema_name, l_columns_desc(pos).col_type_name ), + l_columns_desc(pos).col_name, + l_hierarchy_level + 1, + l_columns_desc(pos).col_name + ); + end if; + end loop; + return; + end; + + member function contains_collection return boolean is + l_collection_elements number; + begin + select count(1) into l_collection_elements + from table(cursor_columns_info) c + where c.is_collection = 1 and rownum = 1; + return l_collection_elements > 0; + end; + + member function get_missing_join_by_columns( a_expected_columns ut_varchar2_list ) return ut_varchar2_list is + l_result ut_varchar2_list; + begin + select fl.column_value + bulk collect into l_result + from table(a_expected_columns) fl + where not exists ( + select 1 from table(self.cursor_columns_info) c + where regexp_like(c.access_path, '^'||fl.column_value||'($|/.*)') + ) + order by fl.column_value; + return l_result; + end; + + member procedure filter_columns(self in out nocopy ut_cursor_details, a_match_options ut_matcher_options) is + l_result ut_cursor_details := self; + c_xpath_extract_reg constant varchar2(50) := '^((/ROW/)|^(//)|^(/\*/))?(.*)'; + begin + if l_result.cursor_columns_info is not null then + + --limit columns to those on the include items minus exclude items + if a_match_options.include.items.count > 0 then + -- if include - exclude = 0 then keep all columns + if a_match_options.include.items != a_match_options.exclude.items then + with included_columns as ( + select regexp_replace( column_value, c_xpath_extract_reg, '\5' ) col_names + from table(a_match_options.include.items) + minus + select regexp_replace( column_value, c_xpath_extract_reg, '\5' ) col_names + from table(a_match_options.exclude.items) + ) + select value(x) + bulk collect into l_result.cursor_columns_info + from table(self.cursor_columns_info) x + where exists( + select 1 from included_columns f where regexp_like( x.access_path, '^/?'||f.col_names||'($|/.*)' ) + ); + end if; + elsif a_match_options.exclude.items.count > 0 then + with excluded_columns as ( + select regexp_replace( column_value, c_xpath_extract_reg, '\5' ) col_names + from table(a_match_options.exclude.items) + ) + select value(x) + bulk collect into l_result.cursor_columns_info + from table(self.cursor_columns_info) x + where not exists( + select 1 from excluded_columns f where regexp_like( '/'||x.access_path, '^/?'||f.col_names||'($|/.*)' ) + ); + end if; + self := l_result; + end if; + end; + + member function get_xml_children(a_parent_name varchar2 := null) return xmltype is + l_result xmltype; + begin + select xmlagg(xmlelement(evalname t.column_name,t.column_type, + self.get_xml_children(t.column_name))) + into l_result + from table(self.cursor_columns_info) t + where (a_parent_name is not null and parent_name = a_parent_name and hierarchy_level > 1 and column_name is not null) + or (a_parent_name is null and parent_name is null and hierarchy_level = 1 and column_name is not null) + having count(*) > 0; + + return l_result; + end; +end; +/ diff --git a/source/expectations/data_values/ut_cursor_details.tps b/source/expectations/data_values/ut_cursor_details.tps new file mode 100644 index 000000000..c2aa98066 --- /dev/null +++ b/source/expectations/data_values/ut_cursor_details.tps @@ -0,0 +1,37 @@ +create or replace type ut_cursor_details force authid current_user as object ( + /* + utPLSQL - Version 3 + Copyright 2016 - 2018 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + cursor_columns_info ut_cursor_column_tab, + + constructor function ut_cursor_details(self in out nocopy ut_cursor_details) return self as result, + constructor function ut_cursor_details( + self in out nocopy ut_cursor_details,a_cursor_number in number + ) return self as result, + member function equals(a_other ut_cursor_details, a_match_options ut_matcher_options) return boolean, + member procedure desc_compound_data( + self in out nocopy ut_cursor_details, + a_compound_data anytype, + a_parent_name in varchar2, + a_level in integer, + a_access_path in varchar2 + ), + member function contains_collection return boolean, + member function get_missing_join_by_columns( a_expected_columns ut_varchar2_list ) return ut_varchar2_list, + member procedure filter_columns(self in out nocopy ut_cursor_details, a_match_options ut_matcher_options), + member function get_xml_children(a_parent_name varchar2 := null) return xmltype +) +/ diff --git a/source/expectations/data_values/ut_data_value.tpb b/source/expectations/data_values/ut_data_value.tpb index 4237ba040..8c8e677fb 100644 --- a/source/expectations/data_values/ut_data_value.tpb +++ b/source/expectations/data_values/ut_data_value.tpb @@ -30,7 +30,7 @@ create or replace type body ut_data_value as raise value_error; end; - member function diff( a_other ut_data_value, a_exclude_xpath varchar2, a_include_xpath varchar2, a_join_by_xpath varchar2, a_unordered boolean :=false ) return varchar2 is + member function diff( a_other ut_data_value, a_match_options ut_matcher_options ) return varchar2 is begin return null; end; diff --git a/source/expectations/data_values/ut_data_value.tps b/source/expectations/data_values/ut_data_value.tps index 793a25942..425fbeb54 100644 --- a/source/expectations/data_values/ut_data_value.tps +++ b/source/expectations/data_values/ut_data_value.tps @@ -25,7 +25,7 @@ create or replace type ut_data_value force authid current_user as object ( order member function compare( a_other ut_data_value ) return integer, member function is_diffable return boolean, member function is_empty return boolean, - member function diff( a_other ut_data_value, a_exclude_xpath varchar2, a_include_xpath varchar2, a_join_by_xpath varchar2, a_unordered boolean := false ) return varchar2, + member function diff( a_other ut_data_value, a_match_options ut_matcher_options ) return varchar2, not instantiable member function compare_implementation( a_other ut_data_value ) return integer ) not final not instantiable / diff --git a/source/expectations/data_values/ut_data_value_anydata.tpb b/source/expectations/data_values/ut_data_value_anydata.tpb index 7d5322cca..19917633a 100644 --- a/source/expectations/data_values/ut_data_value_anydata.tpb +++ b/source/expectations/data_values/ut_data_value_anydata.tpb @@ -14,65 +14,130 @@ create or replace type body ut_data_value_anydata as WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - */ - - final member procedure init(self in out nocopy ut_data_value_anydata, a_value anydata, a_data_object_type varchar2, a_extract_path varchar2) is - l_query sys_refcursor; - l_ctx number; - l_ut_owner varchar2(250) := ut_utils.ut_owner; + */ + + overriding member function get_object_info return varchar2 is begin - self.data_type := case when a_value is not null then lower(a_value.gettypename) else 'undefined' end; - self.data_id := sys_guid(); - if a_value is not null then - execute immediate ' + return self.data_type || case when self.compound_type = 'collection' then ' [ count = '||self.elements_count||' ]' else null end; + end; + + member function get_extract_path(a_data_value anydata) return varchar2 is + l_path varchar2(10); + begin + if self.compound_type = 'object' then + l_path := '/*/*'; + else + case when ut_metadata.has_collection_members(a_data_value) then + l_path := '/*/*'; + else + l_path := '/*'; + end case; + end if; + return l_path; + end; + + member function get_cursor_sql_from_anydata(a_data_value anydata) return varchar2 is + l_cursor_sql varchar2(32767); + begin + l_cursor_sql := ' declare l_data '||self.data_type||'; l_value anydata := :a_value; l_status integer; + l_tmp_refcursor sys_refcursor; begin - l_status := l_value.get'||a_data_object_type||'(l_data); - :l_data_is_null := case when l_data is null then 1 else 0 end; - end;' using in a_value, out self.is_data_null; - else - self.is_data_null := 1; - end if; + l_status := l_value.get'||self.compound_type||'(l_data); '|| + case when self.compound_type = 'collection' then + q'[ open :l_tmp_refcursor for select value(x) as "]'|| + ut_metadata.get_object_name(ut_metadata.get_collection_element(a_data_value))|| + q'[" from table(l_data) x;]' + else + q'[ open :l_tmp_refcursor for select l_data as "]'||ut_metadata.get_object_name(self.data_type)|| + q'[" from dual;]' + end || + 'end;'; + return l_cursor_sql; + end; + + member procedure init(self in out nocopy ut_data_value_anydata, a_value anydata) is + l_refcursor sys_refcursor; + l_ctx number; + l_ut_owner varchar2(250) := ut_utils.ut_owner; + cursor_not_open exception; + l_cursor_number number; + l_anydata_sql varchar2(32767); + begin + self.data_type := ut_metadata.get_anydata_typename(a_value); + self.compound_type := get_instance(a_value); + self.is_data_null := ut_metadata.is_anytype_null(a_value,self.compound_type); + self.data_id := sys_guid(); + self.self_type := $$plsql_unit; + self.cursor_details := ut_cursor_details(); + + ut_compound_data_helper.cleanup_diff; + if not self.is_null() then - ut_expectation_processor.set_xml_nls_params(); - open l_query for select a_value val from dual; - l_ctx := sys.dbms_xmlgen.newcontext( l_query ); - dbms_xmlgen.setrowtag(l_ctx, ''); - dbms_xmlgen.setrowsettag(l_ctx, ''); - dbms_xmlgen.setnullhandling(l_ctx,2); - execute immediate - 'insert into ' || l_ut_owner || '.ut_compound_data_tmp(data_id, item_no, item_data) ' || - 'select :self_guid, rownum, value(a) ' || - ' from table( xmlsequence( extract(:l_xml, :xpath ) ) ) a' - using in self.data_id, dbms_xmlgen.getXMLtype(l_ctx), a_extract_path; - self.elements_count := sql%rowcount; - dbms_xmlgen.closecontext (l_ctx); - ut_expectation_processor.reset_nls_params(); + self.extract_path := get_extract_path(a_value); + l_anydata_sql := get_cursor_sql_from_anydata(a_value); + execute immediate l_anydata_sql using in a_value, in out l_refcursor; + if l_refcursor%isopen then + self.extract_cursor(l_refcursor); + l_cursor_number := dbms_sql.to_cursor_number(l_refcursor); + self.cursor_details := ut_cursor_details(l_cursor_number); + dbms_sql.close_cursor(l_cursor_number); + elsif not l_refcursor%isopen then + raise cursor_not_open; + end if; end if; + exception + when cursor_not_open then + raise_application_error(-20155, 'Cursor is not open'); + when others then + if l_refcursor%isopen then + close l_refcursor; + end if; + raise; end; - static function get_instance(a_data_value anydata) return ut_data_value_anydata is - l_result ut_data_value_anydata := ut_data_value_object(null); - l_type anytype; - l_type_code integer; + member function get_instance(a_data_value anydata) return varchar2 is + l_result varchar2(30); begin - if a_data_value is not null then - l_type_code := a_data_value.gettype(l_type); - if l_type_code in (dbms_types.typecode_table, dbms_types.typecode_varray, dbms_types.typecode_namedcollection, dbms_types.typecode_object) then - if l_type_code = dbms_types.typecode_object then - l_result := ut_data_value_object(a_data_value); - else - l_result := ut_data_value_collection(a_data_value); - end if; - else - raise_application_error(-20000, 'Data type '||a_data_value.gettypename||' in ANYDATA is not supported by utPLSQL'); - end if; + l_result := ut_metadata.get_anydata_compound_type(a_data_value); + if l_result not in ('object','collection') then + raise_application_error(-20000, 'Data type '||a_data_value.gettypename||' in ANYDATA is not supported by utPLSQL'); end if; return l_result; end; + constructor function ut_data_value_anydata(self in out nocopy ut_data_value_anydata, a_value anydata) return self as result + is + begin + init(a_value); + return; + end; + + overriding member function compare_implementation( + a_other ut_data_value, + a_match_options ut_matcher_options, + a_inclusion_compare boolean := false, + a_is_negated boolean := false + ) return integer is + l_result integer := 0; + begin + if not a_other is of (ut_data_value_anydata) then + raise value_error; + end if; + l_result := l_result + (self as ut_data_value_refcursor).compare_implementation(a_other,a_match_options,a_inclusion_compare,a_is_negated); + return l_result; + end; + + overriding member function is_empty return boolean is + begin + if self.compound_type = 'collection' then + return self.elements_count = 0; + else + raise value_error; + end if; + end; end; / diff --git a/source/expectations/data_values/ut_data_value_anydata.tps b/source/expectations/data_values/ut_data_value_anydata.tps index caabe36b6..c0ee6137b 100644 --- a/source/expectations/data_values/ut_data_value_anydata.tps +++ b/source/expectations/data_values/ut_data_value_anydata.tps @@ -1,4 +1,4 @@ -create or replace type ut_data_value_anydata under ut_compound_data_value( +create or replace type ut_data_value_anydata under ut_data_value_refcursor( /* utPLSQL - Version 3 Copyright 2016 - 2018 utPLSQL Project @@ -15,8 +15,19 @@ create or replace type ut_data_value_anydata under ut_compound_data_value( See the License for the specific language governing permissions and limitations under the License. */ - - final member procedure init(self in out nocopy ut_data_value_anydata, a_value anydata, a_data_object_type varchar2, a_extract_path varchar2), - static function get_instance(a_data_value anydata) return ut_data_value_anydata -) not final not instantiable + + overriding member function get_object_info return varchar2, + member function get_extract_path(a_data_value anydata) return varchar2, + member function get_cursor_sql_from_anydata(a_data_value anydata) return varchar2, + member procedure init(self in out nocopy ut_data_value_anydata, a_value anydata), + member function get_instance(a_data_value anydata) return varchar2, + constructor function ut_data_value_anydata(self in out nocopy ut_data_value_anydata, a_value anydata) return self as result, + overriding member function compare_implementation( + a_other ut_data_value, + a_match_options ut_matcher_options, + a_inclusion_compare boolean := false, + a_is_negated boolean := false + ) return integer, + overriding member function is_empty return boolean +) / diff --git a/source/expectations/data_values/ut_data_value_collection.tpb b/source/expectations/data_values/ut_data_value_collection.tpb deleted file mode 100644 index 15298c801..000000000 --- a/source/expectations/data_values/ut_data_value_collection.tpb +++ /dev/null @@ -1,45 +0,0 @@ -create or replace type body ut_data_value_collection as - /* - utPLSQL - Version 3 - Copyright 2016 - 2018 utPLSQL Project - - Licensed under the Apache License, Version 2.0 (the "License"): - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - - constructor function ut_data_value_collection(self in out nocopy ut_data_value_collection, a_value anydata) return self as result is - begin - self.self_type := $$plsql_unit; - self.init(a_value, 'collection', '/*/*/*'); - if a_value is not null then - execute immediate ' - declare - l_data '||a_value.gettypename()||'; - l_value anydata := :a_value; - l_status integer; - begin - l_status := l_value.getCollection(l_data); - if l_data is not null then - :l_count := l_data.count; - end if; - end;' using in a_value, out self.elements_count; - end if; - return; - end; - - overriding member function is_empty return boolean is - begin - return self.elements_count = 0; - end; - -end; -/ diff --git a/source/expectations/data_values/ut_data_value_object.tpb b/source/expectations/data_values/ut_data_value_object.tpb deleted file mode 100644 index 89e49abc1..000000000 --- a/source/expectations/data_values/ut_data_value_object.tpb +++ /dev/null @@ -1,32 +0,0 @@ -create or replace type body ut_data_value_object as - /* - utPLSQL - Version 3 - Copyright 2016 - 2018 utPLSQL Project - - Licensed under the Apache License, Version 2.0 (the "License"): - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - - constructor function ut_data_value_object(self in out nocopy ut_data_value_object, a_value anydata) return self as result is - begin - self.self_type := $$plsql_unit; - self.init(a_value, 'object', '/*/*'); - return; - end; - - overriding member function get_object_info return varchar2 is - begin - return self.data_type; - end; - -end; -/ diff --git a/source/expectations/data_values/ut_data_value_refcursor.tpb b/source/expectations/data_values/ut_data_value_refcursor.tpb index 1c7619be0..584ca6426 100644 --- a/source/expectations/data_values/ut_data_value_refcursor.tpb +++ b/source/expectations/data_values/ut_data_value_refcursor.tpb @@ -15,87 +15,94 @@ create or replace type body ut_data_value_refcursor as See the License for the specific language governing permissions and limitations under the License. */ - - constructor function ut_data_value_refcursor(self in out nocopy ut_data_value_refcursor, a_value sys_refcursor) return self as result is + + constructor function ut_data_value_refcursor(self in out nocopy ut_data_value_refcursor, a_value sys_refcursor) + return self as result is begin init(a_value); return; end; - member procedure init(self in out nocopy ut_data_value_refcursor, a_value sys_refcursor) is - c_bulk_rows constant integer := 1000; + member procedure extract_cursor(self in out nocopy ut_data_value_refcursor, a_value sys_refcursor) + is + c_bulk_rows constant integer := 10000; l_cursor sys_refcursor := a_value; - l_ctx number; - l_xml xmltype; - l_current_date_format varchar2(4000); - cursor_not_open exception; - l_ut_owner varchar2(250) := ut_utils.ut_owner; + l_ctx number; + l_xml xmltype; + l_ut_owner varchar2(250) := ut_utils.ut_owner; + l_set_id integer := 0; + l_elements_count number := 0; + begin + -- We use DBMS_XMLGEN in order to: + -- 1) be able to process data in bulks (set of rows) + -- 2) be able to influence the ROWSET/ROW tags + -- 3) be able to influence the way NULL values are handled (empty TAG) + -- 4) be able to influence the way TIMESTAMP is formatted. + -- Due to Oracle feature/bug, it is not possible to change the DATE formatting of cursor data + -- AFTER the cursor was opened. + -- The only solution for this is to change NLS settings before opening the cursor. + -- + -- This would work fine if we could use DBMS_XMLGEN.restartQuery. + -- The restartQuery fails however if PLSQL variables of TIMESTAMP/INTERVAL or CLOB/BLOB are used. + ut_expectation_processor.set_xml_nls_params(); + l_ctx := dbms_xmlgen.newContext(l_cursor); + dbms_xmlgen.setNullHandling(l_ctx, dbms_xmlgen.empty_tag); + dbms_xmlgen.setMaxRows(l_ctx, c_bulk_rows); + loop + l_xml := dbms_xmlgen.getxmltype(l_ctx); + exit when dbms_xmlgen.getNumRowsProcessed(l_ctx) = 0; + l_elements_count := l_elements_count + dbms_xmlgen.getNumRowsProcessed(l_ctx); + execute immediate + 'insert into ' || l_ut_owner || '.ut_compound_data_tmp(data_id, item_no, item_data) ' || + 'values (:self_guid, :self_row_count, :l_xml)' + using in self.data_id, l_set_id, l_xml; + l_set_id := l_set_id + c_bulk_rows; + end loop; + ut_expectation_processor.reset_nls_params(); + dbms_xmlgen.closeContext(l_ctx); + self.elements_count := l_elements_count; + exception + when others then + ut_expectation_processor.reset_nls_params(); + dbms_xmlgen.closeContext(l_ctx); + raise; + end; + + member procedure init(self in out nocopy ut_data_value_refcursor, a_value sys_refcursor) is + l_cursor sys_refcursor := a_value; + cursor_not_open exception; + l_cursor_number number; begin - self.is_data_null := ut_utils.boolean_to_int(a_value is null); + self.is_data_null := ut_utils.boolean_to_int(l_cursor is null); self.self_type := $$plsql_unit; self.data_id := sys_guid(); self.data_type := 'refcursor'; + self.compound_type := 'refcursor'; + self.extract_path := '/*'; + ut_compound_data_helper.cleanup_diff; + self.cursor_details := ut_cursor_details(); + if l_cursor is not null then if l_cursor%isopen then - --Get some more info regarding cursor, including if it containts collection columns and what is their name - - ut_curr_usr_compound_helper.get_columns_info(l_cursor,self.columns_info,self.key_info, - self.contain_collection); - - self.elements_count := 0; - -- We use DBMS_XMLGEN in order to: - -- 1) be able to process data in bulks (set of rows) - -- 2) be able to influence the ROWSET/ROW tags - -- 3) be able to influence the way NULL values are handled (empty TAG) - -- 4) be able to influence the way TIMESTAMP is formatted. - -- Due to Oracle feature/bug, it is not possible to change the DATE formatting of cursor data - -- AFTER the cursor was opened. - -- The only solution for this is to change NLS settings before opening the cursor. - -- - -- This would work fine if we could use DBMS_XMLGEN.restartQuery. - -- The restartQuery fails however if PLSQL variables of TIMESTAMP/INTERVAL or CLOB/BLOB are used. - - ut_expectation_processor.set_xml_nls_params(); - l_ctx := dbms_xmlgen.newContext(l_cursor); - dbms_xmlgen.setNullHandling(l_ctx, dbms_xmlgen.empty_tag); - dbms_xmlgen.setMaxRows(l_ctx, c_bulk_rows); - - loop - l_xml := dbms_xmlgen.getxmltype(l_ctx); - - execute immediate - 'insert into ' || l_ut_owner || '.ut_compound_data_tmp(data_id, item_no, item_data) ' || - 'select :self_guid, :self_row_count + rownum, value(a) ' || - ' from table( xmlsequence( extract(:l_xml,''ROWSET/*'') ) ) a' - using in self.data_id, self.elements_count, l_xml; - - exit when sql%rowcount = 0; - - self.elements_count := self.elements_count + sql%rowcount; - end loop; - - ut_expectation_processor.reset_nls_params(); - if l_cursor%isopen then - close l_cursor; - end if; - dbms_xmlgen.closeContext(l_ctx); - + --Get some more info regarding cursor, including if it containts collection columns and what is their name + extract_cursor(l_cursor); + l_cursor_number := dbms_sql.to_cursor_number(l_cursor); + self.cursor_details := ut_cursor_details(l_cursor_number); + dbms_sql.close_cursor(l_cursor_number); elsif not l_cursor%isopen then - raise cursor_not_open; + raise cursor_not_open; end if; end if; exception when cursor_not_open then raise_application_error(-20155, 'Cursor is not open'); when others then - ut_expectation_processor.reset_nls_params(); if l_cursor%isopen then close l_cursor; end if; - dbms_xmlgen.closeContext(l_ctx); raise; end; - + overriding member function to_string return varchar2 is l_result clob; l_result_string varchar2(32767); @@ -103,8 +110,8 @@ create or replace type body ut_data_value_refcursor as if not self.is_null() then dbms_lob.createtemporary(l_result, true); ut_utils.append_to_clob(l_result, 'Data-types:'||chr(10)); - ut_utils.append_to_clob(l_result, self.columns_info.getclobval()); + ut_utils.append_to_clob( l_result, self.cursor_details.get_xml_children().getclobval() ); ut_utils.append_to_clob(l_result,chr(10)||(self as ut_compound_data_value).to_string()); l_result_string := ut_utils.to_string(l_result,null); dbms_lob.freetemporary(l_result); @@ -112,14 +119,25 @@ create or replace type body ut_data_value_refcursor as return l_result_string; end; - overriding member function diff( a_other ut_data_value, a_exclude_xpath varchar2, a_include_xpath varchar2, a_join_by_xpath varchar2, a_unordered boolean := false ) return varchar2 is + overriding member function diff( a_other ut_data_value, a_match_options ut_matcher_options ) return varchar2 is l_result clob; l_results ut_utils.t_clob_tab := ut_utils.t_clob_tab(); l_result_string varchar2(32767); - l_actual ut_data_value_refcursor; - l_column_diffs ut_compound_data_helper.tt_column_diffs := ut_compound_data_helper.tt_column_diffs(); - l_exclude_xpath varchar2(32767) := a_exclude_xpath; - l_missing_pk ut_compound_data_helper.tt_missing_pk := ut_compound_data_helper.tt_missing_pk(); + l_other ut_data_value_refcursor; + l_self ut_data_value_refcursor := self; + l_column_diffs ut_compound_data_helper.tt_column_diffs; + + l_other_cols ut_cursor_column_tab; + l_self_cols ut_cursor_column_tab; + + l_act_missing_pk ut_varchar2_list := ut_varchar2_list(); + l_exp_missing_pk ut_varchar2_list := ut_varchar2_list(); + + c_max_rows integer := ut_utils.gc_diff_max_rows; + l_diff_id ut_compound_data_helper.t_hash; + l_diff_row_count integer; + l_row_diffs ut_compound_data_helper.tt_row_diffs; + l_message varchar2(32767); function get_col_diff_text(a_col ut_compound_data_helper.t_column_diffs) return varchar2 is begin @@ -135,83 +153,120 @@ create or replace type body ut_data_value_refcursor as ' Column <'||a_col.actual_name||'> is misplaced. Expected position: '||a_col.expected_pos||',' ||' actual position: '||a_col.actual_pos||'.' end; end; - - function get_missing_key_message(a_missing_keys ut_compound_data_helper.t_missing_pk) return varchar2 is - l_message varchar2(200); - begin - - if a_missing_keys.diff_type = 'a' then - l_message := ' Join key '||a_missing_keys.missingxpath||' does not exists in actual'; - elsif a_missing_keys.diff_type = 'e' then - l_message := ' Join key '||a_missing_keys.missingxpath||' does not exists in expected'; - end if; - return l_message; - end; - - function add_incomparable_cols_to_xpath( - a_column_diffs ut_compound_data_helper.tt_column_diffs, a_exclude_xpath varchar2 - ) return varchar2 is - l_incomparable_cols ut_varchar2_list := ut_varchar2_list(); - l_result varchar2(32767); + function remove_incomparable_cols( + a_cursor_details ut_cursor_column_tab, a_column_diffs ut_compound_data_helper.tt_column_diffs + ) return ut_cursor_column_tab is + l_missing_cols ut_varchar2_list := ut_varchar2_list(); + l_result ut_cursor_column_tab; begin for i in 1 .. a_column_diffs.count loop if a_column_diffs(i).diff_type in ('-','+') then - l_incomparable_cols.extend; - l_incomparable_cols(l_incomparable_cols.last) := ut_utils.xmlgen_escaped_string(coalesce(a_column_diffs(i).expected_name,a_column_diffs(i).actual_name)); - end if; + l_missing_cols.extend; + l_missing_cols(l_missing_cols.last) := coalesce(a_column_diffs(i).expected_name, a_column_diffs(i).actual_name); + end if; end loop; - l_result := ut_utils.to_xpath(l_incomparable_cols); - if a_exclude_xpath is not null and l_result is not null then - l_result := l_result ||'|'||a_exclude_xpath; - else - l_result := coalesce(a_exclude_xpath, l_result); - end if; + select value(i) bulk collect into l_result + from table(a_cursor_details) i + where i.access_path not in ( + select c.column_value + from table(l_missing_cols) c + ); return l_result; end; + function get_diff_message (a_row_diff ut_compound_data_helper.t_row_diffs, a_is_unordered boolean) return varchar2 is + begin + if a_is_unordered then + if a_row_diff.pk_value is not null then + return ' PK '||a_row_diff.pk_value||' - '||rpad(a_row_diff.diff_type,10)||a_row_diff.diffed_row; + else + return rpad(a_row_diff.diff_type,10)||a_row_diff.diffed_row; + end if; + else + return ' Row No. '||a_row_diff.rn||' - '||rpad(a_row_diff.diff_type,10)||a_row_diff.diffed_row; + end if; + end; + begin if not a_other is of (ut_data_value_refcursor) then raise value_error; end if; - l_actual := treat(a_other as ut_data_value_refcursor); + l_other := treat(a_other as ut_data_value_refcursor); + l_other.cursor_details.filter_columns(a_match_options); + l_self.cursor_details.filter_columns(a_match_options); - dbms_lob.createtemporary(l_result,true); + l_other_cols := l_other.cursor_details.cursor_columns_info; + l_self_cols := l_self.cursor_details.cursor_columns_info; + dbms_lob.createtemporary(l_result,true); --diff columns - if not self.is_null and not l_actual.is_null then - l_column_diffs := ut_compound_data_helper.get_columns_diff(self.columns_info, l_actual.columns_info, a_exclude_xpath, a_include_xpath); - + if not l_self.is_null and not l_other.is_null then + l_column_diffs := ut_compound_data_helper.get_columns_diff( + l_self.cursor_details.cursor_columns_info, + l_other.cursor_details.cursor_columns_info, + a_match_options.ordered_columns() + ); + if l_column_diffs.count > 0 then ut_utils.append_to_clob(l_result,chr(10) || 'Columns:' || chr(10)); + l_other_cols := remove_incomparable_cols( l_other_cols, l_column_diffs ); + l_self_cols := remove_incomparable_cols( l_self_cols, l_column_diffs ); + for i in 1 .. l_column_diffs.count loop + l_results.extend; + l_results(l_results.last) := get_col_diff_text(l_column_diffs(i)); + end loop; + ut_utils.append_to_clob(l_result, l_results); end if; - - for i in 1 .. l_column_diffs.count loop - l_results.extend; - l_results(l_results.last) := get_col_diff_text(l_column_diffs(i)); - end loop; - ut_utils.append_to_clob(l_result, l_results); - l_exclude_xpath := add_incomparable_cols_to_xpath(l_column_diffs, a_exclude_xpath); end if; --check for missing pk - if (a_join_by_xpath is not null) then - l_missing_pk := ut_compound_data_helper.is_pk_exists(self.key_info, l_actual.key_info, a_exclude_xpath, a_include_xpath,a_join_by_xpath); + if a_match_options.join_by.items.count > 0 then + l_act_missing_pk := l_other.cursor_details.get_missing_join_by_columns( a_match_options.join_by.items ); + l_exp_missing_pk := l_self.cursor_details.get_missing_join_by_columns( a_match_options.join_by.items ); end if; --diff rows and row elements if the pk is not missing - if l_missing_pk.count = 0 then - ut_utils.append_to_clob(l_result, self.get_data_diff(a_other, a_exclude_xpath, a_include_xpath, a_join_by_xpath, a_unordered)); - else - ut_utils.append_to_clob(l_result,chr(10) || 'Unable to join sets:' || chr(10)); - for i in 1 .. l_missing_pk.count loop + if l_act_missing_pk.count + l_exp_missing_pk.count = 0 then + l_diff_id := ut_compound_data_helper.get_hash( l_self.data_id || l_other.data_id ); + + -- First tell how many rows are different + l_diff_row_count := ut_compound_data_helper.get_rows_diff_count; + if l_diff_row_count > 0 then + l_row_diffs := ut_compound_data_helper.get_rows_diff_by_sql( + l_self_cols, l_other_cols, l_self.data_id, l_other.data_id, + l_diff_id, a_match_options.join_by.items, a_match_options.unordered, + a_match_options.ordered_columns(), self.extract_path + ); + l_message := chr(10) + ||'Rows: [ ' || l_diff_row_count ||' differences' + || case when l_diff_row_count > c_max_rows and l_row_diffs.count > 0 then ', showing first '||c_max_rows end + ||' ]'||chr(10)|| case when l_row_diffs.count = 0 then ' All rows are different as the columns are not matching.' else null end; + ut_utils.append_to_clob( l_result, l_message ); + l_results := ut_utils.t_clob_tab(); + for i in 1 .. l_row_diffs.count loop l_results.extend; - ut_utils.append_to_clob(l_result, get_missing_key_message(l_missing_pk(i))|| chr(10)); + l_results(l_results.last) := get_diff_message(l_row_diffs(i),a_match_options.unordered); end loop; - - if ut_utils.int_to_boolean(self.contain_collection) or ut_utils.int_to_boolean(l_actual.contain_collection) then - ut_utils.append_to_clob(l_result,' Please make sure that your join clause is not refferring to collection element'|| chr(10)); - end if; + ut_utils.append_to_clob(l_result,l_results); + else + l_message:= chr(10)||'Rows: [ all different ]'||chr(10)||' All rows are different as the columns position is not matching.'; + ut_utils.append_to_clob( l_result, l_message ); + end if; + else + ut_utils.append_to_clob(l_result,chr(10) || 'Unable to join sets:' || chr(10)); + + for i in 1 .. l_exp_missing_pk.count loop + ut_utils.append_to_clob(l_result, ' Join key '||l_exp_missing_pk(i)||' does not exists in expected'||chr(10)); + end loop; + + for i in 1 .. l_act_missing_pk.count loop + ut_utils.append_to_clob(l_result, ' Join key '||l_act_missing_pk(i)||' does not exists in actual'||chr(10)); + end loop; + + if l_self.cursor_details.contains_collection() or l_other.cursor_details.contains_collection() then + ut_utils.append_to_clob(l_result,' Please make sure that your join clause is not refferring to collection element'|| chr(10)); + end if; end if; @@ -220,40 +275,95 @@ create or replace type body ut_data_value_refcursor as return l_result_string; end; - overriding member function compare_implementation (a_other ut_data_value, a_exclude_xpath varchar2, a_include_xpath varchar2, a_join_by_xpath varchar2, a_unordered boolean) return integer is - l_result integer := 0; - l_other ut_data_value_refcursor; - function is_pk_missing (a_pk_missing_tab ut_compound_data_helper.tt_missing_pk) return integer is + overriding member function compare_implementation(a_other ut_data_value) return integer is + begin + return compare_implementation( a_other, null ); + end; + + member function compare_implementation( + a_other ut_data_value, + a_match_options ut_matcher_options, + a_inclusion_compare boolean := false, + a_is_negated boolean := false + ) return integer is + l_result integer := 0; + l_self ut_data_value_refcursor := self; + l_other ut_data_value_refcursor; + l_diff_cursor_text clob; + + function compare_data( + a_self ut_data_value_refcursor, + a_other ut_data_value_refcursor, + a_diff_cursor_text clob + ) return integer is + l_diff_id ut_compound_data_helper.t_hash; + l_result integer; + --We will start with number od differences being displayed. + l_cursor sys_refcursor; + l_diff_tab ut_compound_data_helper.t_diff_tab; + l_diif_rowcount integer :=0; begin - return case when a_pk_missing_tab.count > 0 then 1 else 0 end; + l_diff_id := ut_compound_data_helper.get_hash(a_self.data_id||a_other.data_id); + + begin + open l_cursor for a_diff_cursor_text using a_self.data_id, a_other.data_id; + --fetch and save rows for display of diff + fetch l_cursor bulk collect into l_diff_tab limit ut_utils.gc_diff_max_rows; + + exception when others then + if l_cursor%isopen then + close l_cursor; + end if; + raise; + end; + + ut_compound_data_helper.insert_diffs_result( l_diff_tab, l_diff_id ); + --fetch rows for count only + loop + exit when l_diff_tab.count = 0; + l_diif_rowcount := l_diif_rowcount + l_diff_tab.count; + fetch l_cursor bulk collect into l_diff_tab limit ut_utils.gc_bc_fetch_limit; + end loop; + + ut_compound_data_helper.set_rows_diff(l_diif_rowcount); + + --result is OK only if both are same + if l_diif_rowcount = 0 and a_self.is_null = a_other.is_null then + l_result := 0; + else + l_result := 1; + end if; + close l_cursor; + return l_result; end; begin if not a_other is of (ut_data_value_refcursor) then raise value_error; end if; - l_other := treat(a_other as ut_data_value_refcursor); - - --if we join by key and key is missing fail and report error - if a_join_by_xpath is not null then - l_result := is_pk_missing(ut_compound_data_helper.is_pk_exists(self.key_info, l_other.key_info, a_exclude_xpath, a_include_xpath,a_join_by_xpath)); + l_other := treat(a_other as ut_data_value_refcursor); + l_other.cursor_details.filter_columns( a_match_options ); + l_self.cursor_details.filter_columns( a_match_options ); + + if a_match_options.join_by.items.count > 0 then + l_result := + l_self.cursor_details.get_missing_join_by_columns( a_match_options.join_by.items ).count + + l_other.cursor_details.get_missing_join_by_columns( a_match_options.join_by.items ).count; end if; - + if l_result = 0 then - --if column names/types are not equal - build a diff of column names and types - if ut_compound_data_helper.columns_hash( self, a_exclude_xpath, a_include_xpath ) - != ut_compound_data_helper.columns_hash( l_other, a_exclude_xpath, a_include_xpath ) - then + if not l_self.is_null() and not l_other.is_null() and not l_self.cursor_details.equals( l_other.cursor_details, a_match_options ) then l_result := 1; end if; - - if a_unordered then - l_result := l_result + (self as ut_compound_data_value).compare_implementation(a_other, a_exclude_xpath, a_include_xpath, a_join_by_xpath, a_unordered); - else - l_result := l_result + (self as ut_compound_data_value).compare_implementation(a_other, a_exclude_xpath, a_include_xpath); - end if; + l_diff_cursor_text := ut_compound_data_helper.gen_compare_sql( + l_other, + a_match_options.join_by.items, + a_match_options.unordered(), + a_inclusion_compare, + a_is_negated + ); + l_result := l_result + compare_data( l_self, l_other, l_diff_cursor_text ); end if; - return l_result; end; @@ -262,6 +372,5 @@ create or replace type body ut_data_value_refcursor as return self.elements_count = 0; end; - end; / diff --git a/source/expectations/data_values/ut_data_value_refcursor.tps b/source/expectations/data_values/ut_data_value_refcursor.tps index 02ded0b39..7556ec3de 100644 --- a/source/expectations/data_values/ut_data_value_refcursor.tps +++ b/source/expectations/data_values/ut_data_value_refcursor.tps @@ -24,28 +24,29 @@ create or replace type ut_data_value_refcursor under ut_compound_data_value( * Determines if the cursor is null */ is_cursor_null integer, + + /* + *columns info + */ + cursor_details ut_cursor_details, - /** - * hold information if the cursor contains collection object - */ - contain_collection number(1,0), - - /** - * Holds information about column names and column data-types - */ - columns_info xmltype, - - /** - * Holds more detailed information regarding the pk joins + /* + * extract path of elements, important for collectiosn and objects */ - key_info xmltype, + extract_path varchar2(10), constructor function ut_data_value_refcursor(self in out nocopy ut_data_value_refcursor, a_value sys_refcursor) return self as result, + member procedure extract_cursor(self in out nocopy ut_data_value_refcursor, a_value sys_refcursor), member procedure init(self in out nocopy ut_data_value_refcursor, a_value sys_refcursor), overriding member function to_string return varchar2, - overriding member function diff( a_other ut_data_value, a_exclude_xpath varchar2, a_include_xpath varchar2, a_join_by_xpath varchar2, a_unordered boolean := false ) return varchar2, - overriding member function compare_implementation(a_other ut_data_value, a_exclude_xpath varchar2, a_include_xpath varchar2, a_join_by_xpath varchar2, a_unordered boolean) return integer, + overriding member function diff( a_other ut_data_value, a_match_options ut_matcher_options ) return varchar2, + overriding member function compare_implementation(a_other ut_data_value) return integer, + member function compare_implementation( + a_other ut_data_value, + a_match_options ut_matcher_options, + a_inclusion_compare boolean := false, + a_is_negated boolean := false + ) return integer, overriding member function is_empty return boolean - -) +) not final / diff --git a/source/expectations/matchers/ut_contain.tpb b/source/expectations/matchers/ut_contain.tpb new file mode 100644 index 000000000..bb6e78d50 --- /dev/null +++ b/source/expectations/matchers/ut_contain.tpb @@ -0,0 +1,74 @@ +create or replace type body ut_contain as + /* + utPLSQL - Version 3 + Copyright 2016 - 2018 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + constructor function ut_contain(self in out nocopy ut_contain, a_expected sys_refcursor) return self as result is + begin + self.init(ut_data_value_refcursor(a_expected), null, $$plsql_unit); + self.options.unordered(); + return; + end; + + constructor function ut_contain(self in out nocopy ut_contain, a_expected anydata) return self as result is + begin + self.init(ut_data_value_anydata(a_expected), null, $$plsql_unit); + self.options.unordered(); + return; + end; + + overriding member function run_matcher(self in out nocopy ut_contain, a_actual ut_data_value) return boolean is + l_result boolean; + begin + if self.expected.data_type = a_actual.data_type then + l_result := + ( 0 + = treat( self.expected as ut_data_value_refcursor ) + .compare_implementation( a_actual, self.options, true, self.is_negated() ) + ); + else + l_result := (self as ut_matcher).run_matcher(a_actual); + end if; + return l_result; + end; + + overriding member function run_matcher_negated(self in out nocopy ut_contain, a_actual ut_data_value) return boolean is + begin + return run_matcher(a_actual); + end; + + overriding member function failure_message(a_actual ut_data_value) return varchar2 is + l_result varchar2(32767); + begin + if self.expected.data_type = a_actual.data_type and self.expected.is_diffable then + l_result := + 'Actual: '||a_actual.get_object_info()||' '||self.description()||': '||self.expected.get_object_info() + || chr(10) || 'Diff:' + || treat(expected as ut_data_value_refcursor).diff( a_actual, self.options ); + else + l_result := (self as ut_matcher).failure_message(a_actual) || ': '|| self.expected.to_string_report(); + end if; + return l_result; + end; + + overriding member function failure_message_when_negated(a_actual ut_data_value) return varchar2 is + l_result varchar2(32767); + begin + return (self as ut_matcher).failure_message_when_negated(a_actual) || ':'|| expected.to_string_report(); + end; + +end; +/ diff --git a/source/expectations/matchers/ut_contain.tps b/source/expectations/matchers/ut_contain.tps new file mode 100644 index 000000000..bb4e1b40a --- /dev/null +++ b/source/expectations/matchers/ut_contain.tps @@ -0,0 +1,33 @@ +create or replace type ut_contain under ut_equal( + /* + utPLSQL - Version 3 + Copyright 2016 - 2018 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + /** + * Due to nature of inclusion compare the not is bit diffrente than standard. + * Result is false when even one element belongs which can cause overlap. + * e.g. set can fail at same time not_include and include. By that we mean + * that false include not necessary mean true not include. + */ + + constructor function ut_contain(self in out nocopy ut_contain, a_expected sys_refcursor) return self as result, + constructor function ut_contain(self in out nocopy ut_contain, a_expected anydata) return self as result, + overriding member function run_matcher(self in out nocopy ut_contain, a_actual ut_data_value) return boolean, + overriding member function run_matcher_negated(self in out nocopy ut_contain, a_actual ut_data_value) return boolean, + overriding member function failure_message(a_actual ut_data_value) return varchar2, + overriding member function failure_message_when_negated(a_actual ut_data_value) return varchar2 +) +/ diff --git a/source/expectations/matchers/ut_equal.tpb b/source/expectations/matchers/ut_equal.tpb index 611b1cd1c..a666caaa3 100644 --- a/source/expectations/matchers/ut_equal.tpb +++ b/source/expectations/matchers/ut_equal.tpb @@ -16,25 +16,22 @@ create or replace type body ut_equal as limitations under the License. */ - member procedure init(self in out nocopy ut_equal, a_expected ut_data_value, a_nulls_are_equal boolean) is + member procedure init(self in out nocopy ut_equal, a_expected ut_data_value, a_nulls_are_equal boolean, a_self_type varchar2 := null) is begin - self.nulls_are_equal_flag := ut_utils.boolean_to_int( coalesce(a_nulls_are_equal, ut_expectation_processor.nulls_are_equal()) ); - self.self_type := $$plsql_unit; self.expected := a_expected; - self.include_list := ut_varchar2_list(); - self.exclude_list := ut_varchar2_list(); - self.join_columns := ut_varchar2_list(); + self.options := ut_matcher_options( a_nulls_are_equal ); + self.self_type := nvl( a_self_type, $$plsql_unit ); end; - + member function equal_with_nulls(a_assert_result boolean, a_actual ut_data_value) return boolean is begin ut_utils.debug_log('ut_equal.equal_with_nulls :' || ut_utils.to_test_result(a_assert_result) || ':'); - return ( a_assert_result or ( self.expected.is_null() and a_actual.is_null() and ut_utils.int_to_boolean( nulls_are_equal_flag ) ) ); + return ( a_assert_result or ( self.expected.is_null() and a_actual.is_null() and options.nulls_are_equal ) ); end; constructor function ut_equal(self in out nocopy ut_equal, a_expected anydata, a_nulls_are_equal boolean := null) return self as result is begin - init(ut_data_value_anydata.get_instance(a_expected), a_nulls_are_equal); + init(ut_data_value_anydata(a_expected), a_nulls_are_equal); return; end; @@ -45,8 +42,8 @@ create or replace type body ut_equal as 'equal( a_expected anydata, a_exclude varchar2 )', 'equal( a_expected anydata ).exclude( a_exclude varchar2 )' ); - init(ut_data_value_anydata.get_instance(a_expected), a_nulls_are_equal); - exclude_list := ut_varchar2_list(a_exclude); + init(ut_data_value_anydata(a_expected), a_nulls_are_equal); + self.options.exclude.add_items(a_exclude); return; end; @@ -56,8 +53,8 @@ create or replace type body ut_equal as 'equal( a_expected anydata, a_exclude ut_varchar2_list )', 'equal( a_expected anydata ).exclude( a_exclude ut_varchar2_list )' ); - init(ut_data_value_anydata.get_instance(a_expected), a_nulls_are_equal); - exclude_list := coalesce(a_exclude, ut_varchar2_list()); + init(ut_data_value_anydata(a_expected), a_nulls_are_equal); + self.options.exclude.add_items(a_exclude); return; end; @@ -104,7 +101,7 @@ create or replace type body ut_equal as 'equal( a_expected sys_refcursor ).exclude( a_exclude varchar2 )' ); init(ut_data_value_refcursor(a_expected), a_nulls_are_equal); - exclude_list := ut_varchar2_list(a_exclude); + self.options.exclude.add_items(a_exclude); return; end; @@ -115,7 +112,7 @@ create or replace type body ut_equal as 'equal( a_expected sys_refcursor ).exclude( a_exclude ut_varchar2_list )' ); init(ut_data_value_refcursor(a_expected), a_nulls_are_equal); - exclude_list := coalesce(a_exclude, ut_varchar2_list()); + self.options.exclude.add_items(a_exclude); return; end; @@ -158,82 +155,74 @@ create or replace type body ut_equal as member function include(a_items varchar2) return ut_equal is l_result ut_equal := self; begin - ut_utils.append_to_list(l_result.include_list, a_items); + l_result.options.include.add_items(a_items); return l_result; end; member function include(a_items ut_varchar2_list) return ut_equal is l_result ut_equal := self; begin - l_result.include_list := l_result.include_list multiset union all coalesce(a_items,ut_varchar2_list()); + l_result.options.include.add_items(a_items); return l_result; end; member function exclude(a_items varchar2) return ut_equal is l_result ut_equal := self; begin - ut_utils.append_to_list(l_result.exclude_list, a_items); + l_result.options.exclude.add_items(a_items); return l_result; end; member function exclude(a_items ut_varchar2_list) return ut_equal is l_result ut_equal := self; begin - l_result.exclude_list := l_result.exclude_list multiset union all coalesce(a_items,ut_varchar2_list()); + l_result.options.exclude.add_items(a_items); return l_result; end; member function unordered return ut_equal is l_result ut_equal := self; begin - l_result.is_unordered := ut_utils.boolean_to_int(true); + l_result.options.unordered(); return l_result; end; member function join_by(a_columns varchar2) return ut_equal is l_result ut_equal := self; begin - l_result.is_unordered := ut_utils.boolean_to_int(true); - ut_utils.append_to_list(l_result.join_columns, a_columns); + l_result.options.unordered(); + l_result.options.join_by.add_items(a_columns); return l_result; end; member function join_by(a_columns ut_varchar2_list) return ut_equal is l_result ut_equal := self; begin - l_result.is_unordered := ut_utils.boolean_to_int(true); - l_result.join_columns := l_result.join_columns multiset union all coalesce(a_columns,ut_varchar2_list()); + l_result.options.unordered(); + l_result.options.join_by.add_items(a_columns); return l_result; end; - member function get_include_xpath return varchar2 is - begin - return ut_utils.to_xpath( coalesce(include_list, ut_varchar2_list()) ); - end; - - member function get_exclude_xpath return varchar2 is - begin - return ut_utils.to_xpath( coalesce(exclude_list, ut_varchar2_list()) ); - end; - - member function get_unordered return boolean is + member function uc return ut_equal is begin - return ut_utils.int_to_boolean(nvl(is_unordered,0)); + return unordered_columns; end; - member function get_join_by_xpath return varchar2 is + member function unordered_columns return ut_equal is + l_result ut_equal := self; begin - return ut_utils.to_xpath( coalesce(join_columns, ut_varchar2_list()) ); + l_result.options.unordered_columns(); + return l_result; end; - + overriding member function run_matcher(self in out nocopy ut_equal, a_actual ut_data_value) return boolean is l_result boolean; begin if self.expected.data_type = a_actual.data_type then if self.expected is of (ut_data_value_anydata) then - l_result := 0 = treat(self.expected as ut_data_value_anydata).compare_implementation(a_actual, get_exclude_xpath(), get_include_xpath()); + l_result := 0 = treat(self.expected as ut_data_value_anydata).compare_implementation( a_actual, options ); elsif self.expected is of (ut_data_value_refcursor) then - l_result := 0 = treat(self.expected as ut_data_value_refcursor).compare_implementation(a_actual, get_exclude_xpath(), get_include_xpath(), get_join_by_xpath(), get_unordered()); + l_result := 0 = treat(self.expected as ut_data_value_refcursor).compare_implementation( a_actual, options ); else l_result := equal_with_nulls((self.expected = a_actual), a_actual); end if; @@ -248,9 +237,17 @@ create or replace type body ut_equal as l_result varchar2(32767); begin if self.expected.data_type = a_actual.data_type and self.expected.is_diffable then - l_result := - 'Actual: '||a_actual.get_object_info()||' '||self.description()||': '||self.expected.get_object_info() - || chr(10) || 'Diff:' || expected.diff(a_actual, get_exclude_xpath(), get_include_xpath(), get_join_by_xpath(), get_unordered()); + if self.expected is of (ut_data_value_refcursor) then + l_result := + 'Actual: '||a_actual.get_object_info()||' '||self.description()||': '||self.expected.get_object_info() + || chr(10) || 'Diff:' || + treat(expected as ut_data_value_refcursor).diff( a_actual, options ); + else + l_result := + 'Actual: '||a_actual.get_object_info()||' '||self.description()||': '||self.expected.get_object_info() + || chr(10) || 'Diff:' || + expected.diff( a_actual, options ); + end if; else l_result := (self as ut_matcher).failure_message(a_actual) || ': '|| self.expected.to_string_report(); end if; diff --git a/source/expectations/matchers/ut_equal.tps b/source/expectations/matchers/ut_equal.tps index 9968dba7f..68997fbcc 100644 --- a/source/expectations/matchers/ut_equal.tps +++ b/source/expectations/matchers/ut_equal.tps @@ -1,4 +1,4 @@ -create or replace type ut_equal under ut_comparison_matcher( +create or replace type ut_equal force under ut_comparison_matcher( /* utPLSQL - Version 3 Copyright 2016 - 2018 utPLSQL Project @@ -15,28 +15,14 @@ create or replace type ut_equal under ut_comparison_matcher( See the License for the specific language governing permissions and limitations under the License. */ - nulls_are_equal_flag number(1,0), - /** - * Holds (list of columns/attributes) to exclude when comparing compound types - */ - exclude_list ut_varchar2_list, - /** - * Holds (list of columns/attributes) to incude when comparing compound types - */ - include_list ut_varchar2_list, - - /** - * Holds value if comparision on refcursor to be performed as unordered set - */ - is_unordered number(1,0), /** - * Holds list of columns to be used as a join PK on sys_refcursor comparision - */ - join_columns ut_varchar2_list, - - member procedure init(self in out nocopy ut_equal, a_expected ut_data_value, a_nulls_are_equal boolean), + * Holds information about mather options + */ + options ut_matcher_options, + + member procedure init(self in out nocopy ut_equal, a_expected ut_data_value, a_nulls_are_equal boolean, a_self_type varchar2 := null), member function equal_with_nulls( self in ut_equal, a_assert_result boolean, a_actual ut_data_value) return boolean, constructor function ut_equal(self in out nocopy ut_equal, a_expected anydata, a_nulls_are_equal boolean := null) return self as result, constructor function ut_equal(self in out nocopy ut_equal, a_expected anydata, a_exclude varchar2, a_nulls_are_equal boolean := null) return self as result, @@ -62,12 +48,11 @@ create or replace type ut_equal under ut_comparison_matcher( member function unordered return ut_equal, member function join_by(a_columns varchar2) return ut_equal, member function join_by(a_columns ut_varchar2_list) return ut_equal, - member function get_include_xpath return varchar2, - member function get_exclude_xpath return varchar2, - member function get_unordered return boolean, - member function get_join_by_xpath return varchar2, overriding member function run_matcher(self in out nocopy ut_equal, a_actual ut_data_value) return boolean, overriding member function failure_message(a_actual ut_data_value) return varchar2, - overriding member function failure_message_when_negated(a_actual ut_data_value) return varchar2 + overriding member function failure_message_when_negated(a_actual ut_data_value) return varchar2, + member function unordered_columns return ut_equal, + member function uc return ut_equal ) +not final / diff --git a/source/expectations/matchers/ut_have_count.tpb b/source/expectations/matchers/ut_have_count.tpb index de2d64d1c..135a948d5 100644 --- a/source/expectations/matchers/ut_have_count.tpb +++ b/source/expectations/matchers/ut_have_count.tpb @@ -26,8 +26,8 @@ create or replace type body ut_have_count as overriding member function run_matcher(self in out nocopy ut_have_count, a_actual ut_data_value) return boolean is l_result boolean; begin - if a_actual is of(ut_data_value_refcursor, ut_data_value_collection) then - l_result := ( self.expected = treat(a_actual as ut_compound_data_value).elements_count ); + if a_actual is of(ut_data_value_refcursor) and ( treat (a_actual as ut_data_value_refcursor).compound_type != 'object') then + l_result := ( self.expected = treat(a_actual as ut_data_value_refcursor).elements_count ); else l_result := (self as ut_matcher).run_matcher(a_actual); end if; diff --git a/source/expectations/matchers/ut_matcher.tpb b/source/expectations/matchers/ut_matcher.tpb index 78b05a827..4f3b324b4 100644 --- a/source/expectations/matchers/ut_matcher.tpb +++ b/source/expectations/matchers/ut_matcher.tpb @@ -67,5 +67,22 @@ create or replace type body ut_matcher as return 'Actual: ' || a_actual.to_string_report(true) || description_when_negated(); end; + member procedure negated is + begin + is_negated_flag := ut_utils.boolean_to_int(true); + end; + + member function negated return ut_matcher is + l_result ut_matcher := self; + begin + l_result.negated(); + return l_result; + end; + + member function is_negated return boolean is + begin + return coalesce(ut_utils.int_to_boolean(is_negated_flag), false); + end; + end; / diff --git a/source/expectations/matchers/ut_matcher.tps b/source/expectations/matchers/ut_matcher.tps index 7aefb8ad9..f4c082af0 100644 --- a/source/expectations/matchers/ut_matcher.tps +++ b/source/expectations/matchers/ut_matcher.tps @@ -17,6 +17,7 @@ create or replace type ut_matcher authid current_user as object( */ self_type varchar2(250), is_errored integer, + is_negated_flag number(1,0), /* function: run_matcher @@ -35,6 +36,9 @@ create or replace type ut_matcher authid current_user as object( member function description_when_negated return varchar2, member function error_message(a_actual ut_data_value) return varchar2, member function failure_message(a_actual ut_data_value) return varchar2, - member function failure_message_when_negated(a_actual ut_data_value) return varchar2 + member function failure_message_when_negated(a_actual ut_data_value) return varchar2, + member procedure negated, + member function negated return ut_matcher, + member function is_negated return boolean ) not final not instantiable / diff --git a/source/expectations/matchers/ut_matcher_options.tpb b/source/expectations/matchers/ut_matcher_options.tpb new file mode 100644 index 000000000..7741747d5 --- /dev/null +++ b/source/expectations/matchers/ut_matcher_options.tpb @@ -0,0 +1,60 @@ +create or replace type body ut_matcher_options as + /* + utPLSQL - Version 3 + Copyright 2016 - 2018 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + constructor function ut_matcher_options(self in out nocopy ut_matcher_options, a_nulls_are_equal in boolean := null) return self as result is + begin + nulls_are_equal_flag := ut_utils.boolean_to_int( coalesce(a_nulls_are_equal, ut_expectation_processor.nulls_are_equal()) ); + is_unordered := ut_utils.boolean_to_int(false); + columns_are_unordered_flag := ut_utils.boolean_to_int(false); + include := ut_matcher_options_items(); + exclude := ut_matcher_options_items(); + join_by := ut_matcher_options_items(); + return; + end; + + member procedure nulls_are_equal(self in out nocopy ut_matcher_options) is + begin + self.nulls_are_equal_flag := ut_utils.boolean_to_int(true); + end; + + member function nulls_are_equal return boolean is + begin + return ut_utils.int_to_boolean(self.nulls_are_equal_flag); + end; + + member procedure unordered_columns(self in out nocopy ut_matcher_options) is + begin + columns_are_unordered_flag := ut_utils.boolean_to_int(true); + end; + + member function ordered_columns return boolean is + begin + return not ut_utils.int_to_boolean(columns_are_unordered_flag); + end; + + member procedure unordered(self in out nocopy ut_matcher_options) is + begin + is_unordered := ut_utils.boolean_to_int(true); + end; + + member function unordered return boolean is + begin + return ut_utils.int_to_boolean(is_unordered); + end; +end; +/ diff --git a/source/expectations/matchers/ut_matcher_options.tps b/source/expectations/matchers/ut_matcher_options.tps new file mode 100644 index 000000000..cae062bf1 --- /dev/null +++ b/source/expectations/matchers/ut_matcher_options.tps @@ -0,0 +1,57 @@ +create or replace type ut_matcher_options authid current_user as object( + /* + utPLSQL - Version 3 + Copyright 2016 - 2018 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + /** + * Flag indicating that columns order is to be ignored + */ + columns_are_unordered_flag number(1,0), + + /** + * Flag indicating that rows/items order is to be ignored + */ + is_unordered number(1,0), + + /** + * Flag determining how to react to null values + */ + nulls_are_equal_flag number(1,0), + + /** + * Holds (list of columns/attributes) to exclude when comparing compound types + */ + exclude ut_matcher_options_items, + + /** + * Holds (list of columns/attributes) to incude when comparing compound types + */ + include ut_matcher_options_items, + + /** + * Holds list of columns to be used as a join PK on sys_refcursor comparision + */ + join_by ut_matcher_options_items, + + constructor function ut_matcher_options(self in out nocopy ut_matcher_options, a_nulls_are_equal in boolean := null) return self as result, + member procedure nulls_are_equal(self in out nocopy ut_matcher_options), + member function nulls_are_equal return boolean, + member procedure unordered_columns(self in out nocopy ut_matcher_options), + member function ordered_columns return boolean, + member procedure unordered(self in out nocopy ut_matcher_options), + member function unordered return boolean +) +/ diff --git a/source/expectations/matchers/ut_matcher_options_items.tpb b/source/expectations/matchers/ut_matcher_options_items.tpb new file mode 100644 index 000000000..575537417 --- /dev/null +++ b/source/expectations/matchers/ut_matcher_options_items.tpb @@ -0,0 +1,56 @@ +create or replace type body ut_matcher_options_items is + /* + utPLSQL - Version 3 + Copyright 2016 - 2018 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + constructor function ut_matcher_options_items(self in out nocopy ut_matcher_options_items) return self as result is + begin + items := ut_varchar2_list(); + return; + end; + + member procedure add_items(self in out nocopy ut_matcher_options_items, a_items varchar2) is + begin + items := + items + multiset union all + ut_utils.filter_list( + ut_utils.trim_list_elements( + ut_utils.string_to_table( replace( a_items , '|', ',' ), ',' ) + ) + , '.+' + ); + end; + + member procedure add_items(self in out nocopy ut_matcher_options_items, a_items ut_varchar2_list) is + l_idx binary_integer; + begin + if a_items is not null then + l_idx := a_items.first; + while l_idx is not null loop + add_items( a_items(l_idx) ); + l_idx := a_items.next(l_idx); + end loop; + end if; + end; + + member function to_xpath return varchar2 is + begin + return ut_utils.to_xpath(items); + end; + +end; +/ \ No newline at end of file diff --git a/source/expectations/data_values/ut_data_value_collection.tps b/source/expectations/matchers/ut_matcher_options_items.tps similarity index 55% rename from source/expectations/data_values/ut_data_value_collection.tps rename to source/expectations/matchers/ut_matcher_options_items.tps index 7c5d5285c..81ee17897 100644 --- a/source/expectations/data_values/ut_data_value_collection.tps +++ b/source/expectations/matchers/ut_matcher_options_items.tps @@ -1,4 +1,4 @@ -create or replace type ut_data_value_collection under ut_data_value_anydata( +create or replace type ut_matcher_options_items authid current_user as object( /* utPLSQL - Version 3 Copyright 2016 - 2018 utPLSQL Project @@ -16,8 +16,14 @@ create or replace type ut_data_value_collection under ut_data_value_anydata( limitations under the License. */ - constructor function ut_data_value_collection(self in out nocopy ut_data_value_collection, a_value anydata) return self as result, - overriding member function is_empty return boolean + /** + * Attributes / columns list + */ + items ut_varchar2_list, + constructor function ut_matcher_options_items(self in out nocopy ut_matcher_options_items) return self as result, + member procedure add_items(self in out nocopy ut_matcher_options_items, a_items varchar2), + member procedure add_items(self in out nocopy ut_matcher_options_items, a_items ut_varchar2_list), + member function to_xpath return varchar2 ) / diff --git a/source/expectations/ut_expectation.tpb b/source/expectations/ut_expectation.tpb index 26e0c1092..54de82b47 100644 --- a/source/expectations/ut_expectation.tpb +++ b/source/expectations/ut_expectation.tpb @@ -20,11 +20,14 @@ create or replace type body ut_expectation as l_matcher ut_matcher := a_matcher; l_message varchar2(32767); begin - - l_expectation_result := l_matcher.run_matcher( self.actual_data ); - l_expectation_result := coalesce(l_expectation_result,false); - l_message := coalesce( l_matcher.error_message( self.actual_data ), l_matcher.failure_message( self.actual_data ) ); - ut_expectation_processor.add_expectation_result( ut_expectation_result( ut_utils.to_test_result( l_expectation_result ), self.description, l_message ) ); + if a_matcher.is_negated() then + self.not_to( a_matcher ); + else + l_expectation_result := l_matcher.run_matcher( self.actual_data ); + l_expectation_result := coalesce(l_expectation_result,false); + l_message := coalesce( l_matcher.error_message( self.actual_data ), l_matcher.failure_message( self.actual_data ) ); + ut_expectation_processor.add_expectation_result( ut_expectation_result( ut_utils.to_test_result( l_expectation_result ), self.description, l_message ) ); + end if; end; member procedure not_to(self in ut_expectation, a_matcher ut_matcher) is @@ -32,9 +35,8 @@ create or replace type body ut_expectation as l_matcher ut_matcher := a_matcher; l_message varchar2(32767); begin - - l_expectation_result := l_matcher.run_matcher_negated( self.actual_data ); - l_expectation_result := coalesce(l_expectation_result,false); + l_expectation_result := coalesce( l_matcher.run_matcher_negated( self.actual_data ), false ); + l_message := coalesce( l_matcher.error_message( self.actual_data ), l_matcher.failure_message_when_negated( self.actual_data ) ); ut_expectation_processor.add_expectation_result( ut_expectation_result( ut_utils.to_test_result( l_expectation_result ), self.description, l_message ) ); end; @@ -679,5 +681,25 @@ create or replace type body ut_expectation as self.not_to( ut_be_less_than (a_expected) ); end; + member procedure to_contain(self in ut_expectation, a_expected sys_refcursor) is + begin + self.to_( ut_contain(a_expected) ); + end; + + member procedure not_to_contain(self in ut_expectation, a_expected sys_refcursor) is + begin + self.not_to( ut_contain(a_expected).negated() ); + end; + + member procedure to_contain(self in ut_expectation, a_expected anydata) is + begin + self.to_( ut_contain(a_expected) ); + end; + + member procedure not_to_contain(self in ut_expectation, a_expected anydata) is + begin + self.not_to( ut_contain(a_expected).negated() ); + end; + end; / diff --git a/source/expectations/ut_expectation.tps b/source/expectations/ut_expectation.tps index d27a18377..eae74202e 100644 --- a/source/expectations/ut_expectation.tps +++ b/source/expectations/ut_expectation.tps @@ -21,7 +21,7 @@ create or replace type ut_expectation authid current_user as object( --base matcher executors member procedure to_(self in ut_expectation, a_matcher ut_matcher), member procedure not_to(self in ut_expectation, a_matcher ut_matcher), - + --shortcuts member procedure to_be_null(self in ut_expectation), member procedure to_be_not_null(self in ut_expectation), @@ -158,7 +158,13 @@ create or replace type ut_expectation authid current_user as object( member procedure not_to_be_less_than(self in ut_expectation, a_expected timestamp_unconstrained), member procedure not_to_be_less_than(self in ut_expectation, a_expected timestamp_ltz_unconstrained), member procedure not_to_be_less_than(self in ut_expectation, a_expected timestamp_tz_unconstrained), - member procedure not_to_be_less_than(self in ut_expectation, a_expected yminterval_unconstrained) + member procedure not_to_be_less_than(self in ut_expectation, a_expected yminterval_unconstrained), + + member procedure to_contain(self in ut_expectation, a_expected sys_refcursor), + member procedure not_to_contain(self in ut_expectation, a_expected sys_refcursor), + member procedure to_contain(self in ut_expectation, a_expected anydata), + member procedure not_to_contain(self in ut_expectation, a_expected anydata) + ) not final / diff --git a/source/expectations/ut_expectation_compound.tpb b/source/expectations/ut_expectation_compound.tpb index c620ec39c..603513e78 100644 --- a/source/expectations/ut_expectation_compound.tpb +++ b/source/expectations/ut_expectation_compound.tpb @@ -20,7 +20,6 @@ create or replace type body ut_expectation_compound as begin self.actual_data := a_actual_data; self.description := a_description; - negated := ut_utils.boolean_to_int(false); return; end; @@ -34,7 +33,6 @@ create or replace type body ut_expectation_compound as self.not_to( ut_be_empty() ); end; - member procedure to_have_count(self in ut_expectation_compound, a_expected integer) is begin self.to_( ut_have_count(a_expected) ); @@ -53,119 +51,117 @@ create or replace type body ut_expectation_compound as return l_result; end; - member function to_equal(a_expected sys_refcursor, a_nulls_are_equal boolean := null) return ut_expectation_compound is + member function not_to_equal(a_expected anydata, a_nulls_are_equal boolean := null) return ut_expectation_compound is l_result ut_expectation_compound := self; begin - l_result.matcher := ut_equal(a_expected, a_nulls_are_equal); + l_result.matcher := ut_equal(a_expected, a_nulls_are_equal).negated(); return l_result; end; - member function not_to_equal(a_expected anydata, a_nulls_are_equal boolean := null) return ut_expectation_compound is + member function to_equal(a_expected sys_refcursor, a_nulls_are_equal boolean := null) return ut_expectation_compound is l_result ut_expectation_compound := self; begin l_result.matcher := ut_equal(a_expected, a_nulls_are_equal); - l_result.negated := ut_utils.boolean_to_int(true); return l_result; end; member function not_to_equal(a_expected sys_refcursor, a_nulls_are_equal boolean := null) return ut_expectation_compound is l_result ut_expectation_compound := self; begin - l_result.matcher := ut_equal(a_expected, a_nulls_are_equal); - l_result.negated := ut_utils.boolean_to_int(true); + l_result.matcher := ut_equal(a_expected, a_nulls_are_equal).negated(); return l_result; end; - member function include(a_items varchar2) return ut_expectation_compound is - l_result ut_expectation_compound; + member function to_contain(a_expected sys_refcursor) return ut_expectation_compound is + l_result ut_expectation_compound := self; begin - l_result := self; - l_result.matcher := treat(l_result.matcher as ut_equal).include(a_items); + l_result.matcher := ut_contain(a_expected); + return l_result; + end; + + member function not_to_contain(a_expected sys_refcursor) return ut_expectation_compound is + l_result ut_expectation_compound := self; + begin + l_result.matcher := ut_contain(a_expected).negated(); return l_result; end; + member function to_contain(a_expected anydata) return ut_expectation_compound is + l_result ut_expectation_compound := self; + begin + l_result.matcher := ut_contain(a_expected); + return l_result; + end; + + member function not_to_contain(a_expected anydata) return ut_expectation_compound is + l_result ut_expectation_compound := self; + begin + l_result.matcher := ut_contain(a_expected).negated(); + return l_result; + end; + + member function include(a_items varchar2) return ut_expectation_compound is + begin + return include( ut_varchar2_list( a_items ) ); + end; + member function include(a_items ut_varchar2_list) return ut_expectation_compound is - l_result ut_expectation_compound; + l_result ut_expectation_compound := self; begin - l_result := self; l_result.matcher := treat(l_result.matcher as ut_equal).include(a_items); return l_result; end; member procedure include(self in ut_expectation_compound, a_items varchar2) is begin - if ut_utils.int_to_boolean(negated) then - self.not_to( treat(matcher as ut_equal).include(a_items) ); - else - self.to_( treat(matcher as ut_equal).include(a_items) ); - end if; + include( ut_varchar2_list( a_items ) ); end; member procedure include(self in ut_expectation_compound, a_items ut_varchar2_list) is begin - - if ut_utils.int_to_boolean(negated) then - self.not_to( treat(matcher as ut_equal).include(a_items) ); - else - self.to_( treat(matcher as ut_equal).include(a_items) ); - end if; + self.to_( treat(matcher as ut_equal).include(a_items) ); end; - member function exclude(a_items varchar2) return ut_expectation_compound is - l_result ut_expectation_compound; - begin - l_result := self; - l_result.matcher := treat(l_result.matcher as ut_equal).exclude(a_items); - return l_result; - end; + begin + return exclude( ut_varchar2_list( a_items ) ); + end; member function exclude(a_items ut_varchar2_list) return ut_expectation_compound is - l_result ut_expectation_compound; - begin - l_result := self; - l_result.matcher := treat(l_result.matcher as ut_equal).exclude(a_items); - return l_result; - end; + l_result ut_expectation_compound := self; + begin + l_result.matcher := treat(l_result.matcher as ut_equal).exclude(a_items); + return l_result; + end; member procedure exclude(self in ut_expectation_compound, a_items varchar2) is - begin - if ut_utils.int_to_boolean(negated) then - self.not_to( treat(matcher as ut_equal).exclude(a_items) ); - else - self.to_( treat(matcher as ut_equal).exclude(a_items) ); - end if; - end; + begin + exclude( ut_varchar2_list( a_items ) ); + end; member procedure exclude(self in ut_expectation_compound, a_items ut_varchar2_list) is - begin - - if ut_utils.int_to_boolean(negated) then - self.not_to( treat(matcher as ut_equal).exclude(a_items) ); - else - self.to_( treat(matcher as ut_equal).exclude(a_items) ); - end if; - end; + begin + self.to_( treat(matcher as ut_equal).exclude(a_items) ); + end; member function unordered return ut_expectation_compound is - l_result ut_expectation_compound; + l_result ut_expectation_compound := self; begin - l_result := self; - l_result.matcher := treat(l_result.matcher as ut_equal).unordered; + l_result.matcher := treat(l_result.matcher as ut_equal).unordered(); return l_result; end; member procedure unordered(self in ut_expectation_compound) is begin + self.to_( treat(matcher as ut_equal).unordered() ); + end; - if ut_utils.int_to_boolean(negated) then - self.not_to( treat(matcher as ut_equal).unordered ); - else - self.to_( treat(matcher as ut_equal).unordered ); - end if; + member function join_by(a_columns varchar2) return ut_expectation_compound is + begin + return join_by( ut_varchar2_list( a_columns ) ); end; - member function join_by(a_columns varchar2) return ut_expectation_compound is + member function join_by(a_columns ut_varchar2_list) return ut_expectation_compound is l_result ut_expectation_compound; begin l_result := self; @@ -173,31 +169,37 @@ create or replace type body ut_expectation_compound as return l_result; end; - member function join_by(a_columns ut_varchar2_list) return ut_expectation_compound is + member procedure join_by(self in ut_expectation_compound, a_columns varchar2) is + begin + join_by( ut_varchar2_list( a_columns ) ); + end; + + member procedure join_by(self in ut_expectation_compound, a_columns ut_varchar2_list) is + begin + self.to_( treat(matcher as ut_equal).join_by(a_columns) ); + end; + + member function unordered_columns return ut_expectation_compound is l_result ut_expectation_compound; begin l_result := self; - l_result.matcher := treat(l_result.matcher as ut_equal).join_by(a_columns); + l_result.matcher := treat(l_result.matcher as ut_equal).unordered_columns; return l_result; end; - member procedure join_by(self in ut_expectation_compound, a_columns varchar2) is + member procedure unordered_columns(self in ut_expectation_compound) is begin - if ut_utils.int_to_boolean(negated) then - self.not_to( treat(matcher as ut_equal).join_by(a_columns) ); - else - self.to_( treat(matcher as ut_equal).join_by(a_columns) ); - end if; + self.to_( treat(matcher as ut_equal).unordered_columns ); end; - member procedure join_by(self in ut_expectation_compound, a_columns ut_varchar2_list) is + member function uc return ut_expectation_compound is begin + return unordered_columns; + end; - if ut_utils.int_to_boolean(negated) then - self.not_to( treat(matcher as ut_equal).join_by(a_columns) ); - else - self.to_( treat(matcher as ut_equal).join_by(a_columns) ); - end if; + member procedure uc(self in ut_expectation_compound) is + begin + unordered_columns; end; end; diff --git a/source/expectations/ut_expectation_compound.tps b/source/expectations/ut_expectation_compound.tps index 529b8875d..a0f935c5c 100644 --- a/source/expectations/ut_expectation_compound.tps +++ b/source/expectations/ut_expectation_compound.tps @@ -1,4 +1,4 @@ -create or replace type ut_expectation_compound under ut_expectation( +create or replace type ut_expectation_compound force under ut_expectation( /* utPLSQL - Version 3 Copyright 2016 - 2018 utPLSQL Project @@ -16,7 +16,6 @@ create or replace type ut_expectation_compound under ut_expectation( limitations under the License. */ matcher ut_matcher, - negated integer, constructor function ut_expectation_compound(self in out nocopy ut_expectation_compound, a_actual_data ut_data_value, a_description varchar2) return self as result, @@ -26,9 +25,14 @@ create or replace type ut_expectation_compound under ut_expectation( member procedure not_to_have_count(self in ut_expectation_compound, a_expected integer), member function to_equal(a_expected anydata, a_nulls_are_equal boolean := null) return ut_expectation_compound, - member function to_equal(a_expected sys_refcursor, a_nulls_are_equal boolean := null) return ut_expectation_compound, member function not_to_equal(a_expected anydata, a_nulls_are_equal boolean := null) return ut_expectation_compound, + member function to_equal(a_expected sys_refcursor, a_nulls_are_equal boolean := null) return ut_expectation_compound, member function not_to_equal(a_expected sys_refcursor, a_nulls_are_equal boolean := null) return ut_expectation_compound, + member function to_contain(a_expected sys_refcursor) return ut_expectation_compound, + member function not_to_contain(a_expected sys_refcursor) return ut_expectation_compound, + member function to_contain(a_expected anydata) return ut_expectation_compound, + member function not_to_contain(a_expected anydata) return ut_expectation_compound, + member function include(a_items varchar2) return ut_expectation_compound, member function include(a_items ut_varchar2_list) return ut_expectation_compound, member procedure include(self in ut_expectation_compound, a_items varchar2), @@ -42,7 +46,11 @@ create or replace type ut_expectation_compound under ut_expectation( member function join_by(a_columns varchar2) return ut_expectation_compound, member function join_by(a_columns ut_varchar2_list) return ut_expectation_compound, member procedure join_by(self in ut_expectation_compound, a_columns varchar2), - member procedure join_by(self in ut_expectation_compound, a_columns ut_varchar2_list) + member procedure join_by(self in ut_expectation_compound, a_columns ut_varchar2_list), + + member function unordered_columns return ut_expectation_compound, + member procedure unordered_columns(self in ut_expectation_compound), + member function uc return ut_expectation_compound, + member procedure uc(self in ut_expectation_compound) ) -final -/ \ No newline at end of file +/ diff --git a/source/install.sql b/source/install.sql index 4d24b1d34..fc7de30ed 100644 --- a/source/install.sql +++ b/source/install.sql @@ -96,7 +96,7 @@ alter session set current_schema = &&ut3_owner; @@install_component.sql 'core/types/ut_output_reporter_base.tps' ---annoations +--annotations @@install_component.sql 'core/annotations/ut_annotation.tps' @@install_component.sql 'core/annotations/ut_annotations.tps' @@install_component.sql 'core/annotations/ut_annotated_object.tps' @@ -180,11 +180,13 @@ prompt Installing DBMSPLSQL Tables objects into &&ut3_owner schema --expectations and matchers @@install_component.sql 'expectations/data_values/ut_compound_data_tmp.sql' @@install_component.sql 'expectations/data_values/ut_compound_data_diff_tmp.sql' +@@install_component.sql 'expectations/data_values/ut_cursor_column.tps' +@@install_component.sql 'expectations/data_values/ut_cursor_column_tab.tps' +@@install_component.sql 'expectations/data_values/ut_cursor_details.tps' +@@install_component.sql 'expectations/matchers/ut_matcher_options_items.tps' +@@install_component.sql 'expectations/matchers/ut_matcher_options.tps' @@install_component.sql 'expectations/data_values/ut_data_value.tps' @@install_component.sql 'expectations/data_values/ut_compound_data_value.tps' -@@install_component.sql 'expectations/data_values/ut_data_value_anydata.tps' -@@install_component.sql 'expectations/data_values/ut_data_value_collection.tps' -@@install_component.sql 'expectations/data_values/ut_data_value_object.tps' @@install_component.sql 'expectations/data_values/ut_data_value_blob.tps' @@install_component.sql 'expectations/data_values/ut_data_value_boolean.tps' @@install_component.sql 'expectations/data_values/ut_data_value_clob.tps' @@ -192,6 +194,7 @@ prompt Installing DBMSPLSQL Tables objects into &&ut3_owner schema @@install_component.sql 'expectations/data_values/ut_data_value_dsinterval.tps' @@install_component.sql 'expectations/data_values/ut_data_value_number.tps' @@install_component.sql 'expectations/data_values/ut_data_value_refcursor.tps' +@@install_component.sql 'expectations/data_values/ut_data_value_anydata.tps' @@install_component.sql 'expectations/data_values/ut_data_value_timestamp.tps' @@install_component.sql 'expectations/data_values/ut_data_value_timestamp_tz.tps' @@install_component.sql 'expectations/data_values/ut_data_value_timestamp_ltz.tps' @@ -201,7 +204,6 @@ prompt Installing DBMSPLSQL Tables objects into &&ut3_owner schema @@install_component.sql 'expectations/data_values/ut_key_anyval_pair.tps' @@install_component.sql 'expectations/data_values/ut_key_anyval_pairs.tps' @@install_component.sql 'expectations/data_values/ut_compound_data_helper.pks' -@@install_component.sql 'expectations/data_values/ut_curr_usr_compound_helper.pks' @@install_component.sql 'expectations/matchers/ut_matcher.tps' @@install_component.sql 'expectations/matchers/ut_comparison_matcher.tps' @@install_component.sql 'expectations/matchers/ut_be_false.tps' @@ -214,19 +216,21 @@ prompt Installing DBMSPLSQL Tables objects into &&ut3_owner schema @@install_component.sql 'expectations/matchers/ut_be_null.tps' @@install_component.sql 'expectations/matchers/ut_be_true.tps' @@install_component.sql 'expectations/matchers/ut_equal.tps' +@@install_component.sql 'expectations/matchers/ut_contain.tps' @@install_component.sql 'expectations/matchers/ut_have_count.tps' @@install_component.sql 'expectations/matchers/ut_be_between.tps' @@install_component.sql 'expectations/matchers/ut_be_empty.tps' @@install_component.sql 'expectations/matchers/ut_match.tps' @@install_component.sql 'expectations/ut_expectation.tps' +@@install_component.sql 'expectations/data_values/ut_cursor_column.tpb' +@@install_component.sql 'expectations/data_values/ut_cursor_details.tpb' @@install_component.sql 'expectations/ut_expectation_compound.tps' + +@@install_component.sql 'expectations/matchers/ut_matcher_options_items.tpb' +@@install_component.sql 'expectations/matchers/ut_matcher_options.tpb' @@install_component.sql 'expectations/data_values/ut_data_value.tpb' @@install_component.sql 'expectations/data_values/ut_compound_data_value.tpb' @@install_component.sql 'expectations/data_values/ut_compound_data_helper.pkb' -@@install_component.sql 'expectations/data_values/ut_curr_usr_compound_helper.pkb' -@@install_component.sql 'expectations/data_values/ut_data_value_anydata.tpb' -@@install_component.sql 'expectations/data_values/ut_data_value_object.tpb' -@@install_component.sql 'expectations/data_values/ut_data_value_collection.tpb' @@install_component.sql 'expectations/data_values/ut_data_value_blob.tpb' @@install_component.sql 'expectations/data_values/ut_data_value_boolean.tpb' @@install_component.sql 'expectations/data_values/ut_data_value_clob.tpb' @@ -234,6 +238,7 @@ prompt Installing DBMSPLSQL Tables objects into &&ut3_owner schema @@install_component.sql 'expectations/data_values/ut_data_value_dsinterval.tpb' @@install_component.sql 'expectations/data_values/ut_data_value_number.tpb' @@install_component.sql 'expectations/data_values/ut_data_value_refcursor.tpb' +@@install_component.sql 'expectations/data_values/ut_data_value_anydata.tpb' @@install_component.sql 'expectations/data_values/ut_data_value_timestamp.tpb' @@install_component.sql 'expectations/data_values/ut_data_value_timestamp_tz.tpb' @@install_component.sql 'expectations/data_values/ut_data_value_timestamp_ltz.tpb' @@ -252,6 +257,7 @@ prompt Installing DBMSPLSQL Tables objects into &&ut3_owner schema @@install_component.sql 'expectations/matchers/ut_be_null.tpb' @@install_component.sql 'expectations/matchers/ut_be_true.tpb' @@install_component.sql 'expectations/matchers/ut_equal.tpb' +@@install_component.sql 'expectations/matchers/ut_contain.tpb' @@install_component.sql 'expectations/matchers/ut_have_count.tpb' @@install_component.sql 'expectations/matchers/ut_be_between.tpb' @@install_component.sql 'expectations/matchers/ut_be_empty.tpb' @@ -314,6 +320,7 @@ prompt Installing DBMSPLSQL Tables objects into &&ut3_owner schema @@install_component.sql 'api/equal.syn' @@install_component.sql 'api/have_count.syn' @@install_component.sql 'api/match.syn' +@@install_component.sql 'api/contain.syn' set linesize 200 set define on diff --git a/source/uninstall_objects.sql b/source/uninstall_objects.sql index 6048a832f..b31d03520 100644 --- a/source/uninstall_objects.sql +++ b/source/uninstall_objects.sql @@ -5,6 +5,8 @@ drop synonym have_count; drop synonym match; +drop synonym contain; + drop synonym be_false; drop synonym be_empty; @@ -83,6 +85,8 @@ drop type ut_match force; drop type ut_be_between force; +drop type ut_contain force; + drop type ut_equal force; drop type ut_be_true force; @@ -143,6 +147,16 @@ drop type ut_data_value_xmltype force; drop type ut_data_value force; +drop type ut_matcher_options force; + +drop type ut_matcher_options_items force; + +drop type ut_cursor_details force; + +drop type ut_cursor_column_tab force; + +drop type ut_cursor_column force; + drop table ut_compound_data_tmp; drop table ut_compound_data_diff_tmp; @@ -283,8 +297,6 @@ drop package ut_coverage_profiler; drop package ut_compound_data_helper; -drop package ut_curr_usr_compound_helper; - drop package ut_coverage_helper_profiler; drop type ut_have_count; diff --git a/test/core/expectations/test_expectation_anydata.pkb b/test/core/expectations/test_expectation_anydata.pkb index bade26652..0b3a93261 100644 --- a/test/core/expectations/test_expectation_anydata.pkb +++ b/test/core/expectations/test_expectation_anydata.pkb @@ -16,6 +16,8 @@ create or replace package body test_expectation_anydata is end; procedure fail_on_different_type_null is + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); begin --Arrange g_test_expected := anydata.convertObject( cast(null as test_dummy_object) ); @@ -23,10 +25,15 @@ create or replace package body test_expectation_anydata is --Act ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); --Assert - ut.expect(expectations.failed_expectations_data()).not_to_be_empty(); + l_expected_message := q'[%Actual (ut3_tester.other_dummy_object) cannot be compared to Expected (ut3_tester.test_dummy_object) using matcher 'equal'.]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); end; procedure fail_on_different_type is + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); begin --Arrange g_test_expected := anydata.convertObject( test_dummy_object(1, 'A', '0') ); @@ -34,7 +41,10 @@ create or replace package body test_expectation_anydata is --Act ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); --Assert - ut.expect(expectations.failed_expectations_data()).not_to_be_empty(); + l_expected_message := q'[%Actual (ut3_tester.other_dummy_object) cannot be compared to Expected (ut3_tester.test_dummy_object) using matcher 'equal'.]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); end; procedure fail_on_different_object_data is @@ -49,6 +59,8 @@ create or replace package body test_expectation_anydata is end; procedure fail_on_one_object_null is + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); begin --Arrange g_test_expected := anydata.convertObject( test_dummy_object(1, 'A', '0') ); @@ -56,10 +68,19 @@ create or replace package body test_expectation_anydata is --Act ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); --Assert - ut.expect(expectations.failed_expectations_data()).not_to_be_empty(); + l_expected_message := q'[%Actual: ut3_tester.test_dummy_object was expected to equal: ut3_tester.test_dummy_object +%Diff: +%Rows: [ 1 differences ] +%Row No. 1 - Missing: 1A0]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; procedure fail_on_collection_vs_object is + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); begin --Arrange g_test_expected := anydata.convertObject( test_dummy_object(1, 'A', '0') ); @@ -67,11 +88,16 @@ create or replace package body test_expectation_anydata is --Act ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); --Assert - ut.expect(expectations.failed_expectations_data()).not_to_be_empty(); + l_expected_message := q'[%Actual (ut3_tester.test_dummy_object_list) cannot be compared to Expected (ut3_tester.test_dummy_object) using matcher 'equal'.]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); end; procedure fail_on_null_vs_empty_coll is l_null_list test_dummy_object_list; + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); begin --Arrange g_test_expected := anydata.convertCollection( test_dummy_object_list() ); @@ -79,11 +105,20 @@ create or replace package body test_expectation_anydata is --Act ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); --Assert - ut.expect(expectations.failed_expectations_data()).not_to_be_empty(); + l_expected_message := q'[%Actual: ut3_tester.test_dummy_object_list [ count = ] was expected to equal: ut3_tester.test_dummy_object_list [ count = 0 ] +%Diff: +%Rows: [ all different ] +%All rows are different as the columns position is not matching.]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; procedure fail_on_one_collection_null is l_null_list test_dummy_object_list; + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); begin --Arrange g_test_expected := anydata.convertCollection( test_dummy_object_list(test_dummy_object(1, 'A', '0')) ); @@ -91,10 +126,18 @@ create or replace package body test_expectation_anydata is --Act ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); --Assert - ut.expect(expectations.failed_expectations_data()).not_to_be_empty(); + l_expected_message := q'[%Actual: ut3_tester.test_dummy_object_list [ count = ] was expected to equal: ut3_tester.test_dummy_object_list [ count = 1 ] +%Diff: +%Rows: [ 1 differences ] +%Row No. 1 - Missing: 1A0]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); end; procedure fail_on_one_collection_empty is + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); begin --Arrange g_test_expected := anydata.convertCollection( test_dummy_object_list(test_dummy_object(1, 'A', '0')) ); @@ -102,10 +145,19 @@ create or replace package body test_expectation_anydata is --Act ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); --Assert - ut.expect(expectations.failed_expectations_data()).not_to_be_empty(); + l_expected_message := q'[%Actual: ut3_tester.test_dummy_object_list [ count = 0 ] was expected to equal: ut3_tester.test_dummy_object_list [ count = 1 ] +%Diff: +%Rows: [ 1 differences ] +%Row No. 1 - Missing: 1A0]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; procedure fail_on_different_coll_data is + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); l_obj test_dummy_object := test_dummy_object(1, 'A', '0'); begin --Arrange @@ -114,7 +166,13 @@ create or replace package body test_expectation_anydata is --Act ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); --Assert - ut.expect(expectations.failed_expectations_data()).not_to_be_empty(); + l_expected_message := q'[%Actual: ut3_tester.test_dummy_object_list [ count = 2 ] was expected to equal: ut3_tester.test_dummy_object_list [ count = 1 ] +%Diff: +%Rows: [ 1 differences ] +%Row No. 2 - Extra: 1A0]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); end; --%test(Gives success when both anydata are NULL) @@ -162,6 +220,8 @@ create or replace package body test_expectation_anydata is end; procedure fail_on_coll_different_order is + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); l_first_obj test_dummy_object := test_dummy_object(1, 'A', '0'); l_second_obj test_dummy_object := test_dummy_object(2, 'b', '1'); begin @@ -189,7 +249,7 @@ create or replace package body test_expectation_anydata is l_list ut3.ut_varchar2_list; begin --Arrange - l_list := ut3.ut_varchar2_list('Value','/TEST_DUMMY_OBJECT/ID'); + l_list := ut3.ut_varchar2_list('TEST_DUMMY_OBJECT/Value','/TEST_DUMMY_OBJECT/ID'); g_test_expected := anydata.convertObject( test_dummy_object(id=>1, "name"=>'A',"Value"=>'0') ); g_test_actual := anydata.convertObject( test_dummy_object(id=>3, "name"=>'A',"Value"=>'1') ); --Act @@ -202,7 +262,7 @@ create or replace package body test_expectation_anydata is l_list varchar2(100); begin --Arrange - l_list := 'Value,ID'; + l_list := 'TEST_DUMMY_OBJECT/Value,TEST_DUMMY_OBJECT/ID'; g_test_expected := anydata.convertObject( test_dummy_object(id=>1, "name"=>'A',"Value"=>'0') ); g_test_actual := anydata.convertObject( test_dummy_object(id=>2, "name"=>'A',"Value"=>'1') ); --Act @@ -211,27 +271,11 @@ create or replace package body test_expectation_anydata is ut.expect(expectations.failed_expectations_data()).to_be_empty(); end; - procedure exclude_attrib_xpath_invalid is - l_anydata_object anydata; - l_xpath varchar2(100); - begin - --Arrange - l_xpath := '//KEY,\\//Value'; - l_anydata_object := anydata.convertObject( test_dummy_object(id=>1, "name"=>'A',"Value"=>'0') ); - --Act - ut3.ut.expect( l_anydata_object ).to_equal( l_anydata_object, a_exclude=> l_xpath ); - --Assert - ut.fail('Expected exception -31011 but nothing was raised'); - exception - when others then - ut.expect(sqlcode).to_equal(-31011); - end; - procedure exclude_attributes_xpath is l_xpath varchar2(100); begin --Arrange - l_xpath := '//Value|//ID'; + l_xpath := '//TEST_DUMMY_OBJECT/Value|//TEST_DUMMY_OBJECT/ID'; g_test_expected := anydata.convertObject( test_dummy_object(id=>1, "name"=>'A',"Value"=>'0') ); g_test_actual := anydata.convertObject( test_dummy_object(id=>2, "name"=>'A',"Value"=>'1') ); --Act @@ -257,7 +301,7 @@ create or replace package body test_expectation_anydata is l_list ut3.ut_varchar2_list; begin --Arrange - l_list := ut3.ut_varchar2_list('Value','ID'); + l_list := ut3.ut_varchar2_list('TEST_DUMMY_OBJECT/Value','TEST_DUMMY_OBJECT/ID'); g_test_expected := anydata.convertObject( test_dummy_object(id=>1, "name"=>'A',"Value"=>'0') ); g_test_actual := anydata.convertObject( test_dummy_object(id=>1, "name"=>'b',"Value"=>'0') ); --Act @@ -270,7 +314,7 @@ create or replace package body test_expectation_anydata is l_xpath varchar2(100); begin --Arrange - l_xpath := 'key,ID'; + l_xpath := 'TEST_DUMMY_OBJECT/key,TEST_DUMMY_OBJECT/ID'; g_test_expected := anydata.convertObject( test_dummy_object(id=>1, "name"=>'A',"Value"=>'0') ); g_test_actual := anydata.convertObject( test_dummy_object(id=>1, "name"=>'A',"Value"=>'1') ); --Act @@ -279,27 +323,11 @@ create or replace package body test_expectation_anydata is ut.expect(expectations.failed_expectations_data()).to_be_empty(); end; - procedure include_attrib_xpath_invalid is - l_anydata_object anydata; - l_xpath varchar2(100); - begin - --Arrange - l_xpath := '//KEY,\\//Value'; - l_anydata_object := anydata.convertObject( test_dummy_object(id=>1, "name"=>'A',"Value"=>'0') ); - --Act - ut3.ut.expect( l_anydata_object ).to_equal( l_anydata_object ).include( l_xpath ); - --Assert - ut.fail('Expected exception -31011 but nothing was raised'); - exception - when others then - ut.expect(sqlcode).to_be_between(-31013,-31011); - end; - procedure include_attributes_xpath is l_xpath varchar2(100); begin --Arrange - l_xpath := '//key|//ID'; + l_xpath := '//TEST_DUMMY_OBJECT/key|//TEST_DUMMY_OBJECT/ID'; g_test_expected := anydata.convertObject( test_dummy_object(id=>1, "name"=>'A',"Value"=>'0') ); g_test_actual := anydata.convertObject( test_dummy_object(id=>1, "name"=>'A',"Value"=>'1') ); --Act @@ -312,7 +340,7 @@ create or replace package body test_expectation_anydata is l_include varchar2(100); begin --Arrange - l_include := ' BadAttributeName, ID '; + l_include := ' BadAttributeName, TEST_DUMMY_OBJECT/ID '; g_test_expected := anydata.convertObject( test_dummy_object(id=>1, "name"=>'B',"Value"=>'0') ); g_test_actual := anydata.convertObject( test_dummy_object(id=>1, "name"=>'A',"Value"=>'1') ); --Act @@ -326,8 +354,8 @@ create or replace package body test_expectation_anydata is l_include varchar2(100); begin --Arrange - l_include := 'key,ID,Value'; - l_exclude := '//key|//Value'; + l_include := 'TEST_DUMMY_OBJECT/key,TEST_DUMMY_OBJECT/ID,TEST_DUMMY_OBJECT/Value'; + l_exclude := '//TEST_DUMMY_OBJECT/key|//TEST_DUMMY_OBJECT/Value'; g_test_expected := anydata.convertObject( test_dummy_object(id=>1, "name"=>'B',"Value"=>'0') ); g_test_actual := anydata.convertObject( test_dummy_object(id=>1, "name"=>'A',"Value"=>'1') ); --Act @@ -339,10 +367,12 @@ create or replace package body test_expectation_anydata is procedure include_exclude_attrib_list is l_exclude ut3.ut_varchar2_list; l_include ut3.ut_varchar2_list; + l_expected varchar2(32767); + l_actual varchar2(32767); begin --Arrange - l_include := ut3.ut_varchar2_list('key','ID','Value'); - l_exclude := ut3.ut_varchar2_list('key','Value'); + l_include := ut3.ut_varchar2_list('TEST_DUMMY_OBJECT/key','TEST_DUMMY_OBJECT/ID','TEST_DUMMY_OBJECT/Value'); + l_exclude := ut3.ut_varchar2_list('TEST_DUMMY_OBJECT/key','TEST_DUMMY_OBJECT/Value'); g_test_expected := anydata.convertObject( test_dummy_object(id=>1, "name"=>'B',"Value"=>'0') ); g_test_actual := anydata.convertObject( test_dummy_object(id=>1, "name"=>'A',"Value"=>'1') ); --Act @@ -382,7 +412,7 @@ Rows: [ 1 differences ] l_expected := q'[Actual: ut3_tester.test_dummy_object_list [ count = 2 ] was expected to equal: ut3_tester.test_dummy_object_list [ count = 1 ] Diff: Rows: [ 1 differences ] - Row No. 2 - Extra: 1A0]'; + Row No. 2 - Extra: 1A0]'; --Act ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); --Assert @@ -449,7 +479,6 @@ Rows: [ 1 differences ] ut.expect(ut3.ut_expectation_processor.get_warnings()(1)).to_be_like('The syntax: "%" is deprecated.%'); end; - --%test(Reports only mismatched columns on column data mismatch) procedure data_diff_on_atr_data_mismatch is l_actual test_dummy_object_list; l_expected test_dummy_object_list; @@ -514,5 +543,432 @@ Rows: [ 60 differences, showing first 20 ] ut.expect(l_actual_message).to_be_like(l_expected_message); end; + procedure collection_include_list is + l_actual test_dummy_object_list; + l_expected test_dummy_object_list; + l_list ut3.ut_varchar2_list; + begin + l_list := ut3.ut_varchar2_list('TEST_DUMMY_OBJECT/Value','TEST_DUMMY_OBJECT/ID'); + --Arrange + select test_dummy_object( rownum, 'SomethingsDifferent '||rownum, rownum) + bulk collect into l_actual + from dual connect by level <=2; + select test_dummy_object( rownum, 'Something '||rownum, rownum) + bulk collect into l_expected + from dual connect by level <=2; + --Act + ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).include( l_list ); + + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure collection_exclude_list is + l_actual test_dummy_object_list; + l_expected test_dummy_object_list; + l_list ut3.ut_varchar2_list; + begin + l_list := ut3.ut_varchar2_list('TEST_DUMMY_OBJECT/Value','TEST_DUMMY_OBJECT/ID'); + --Arrange + select test_dummy_object( rownum*2, 'Something '||rownum, rownum*2) + bulk collect into l_actual + from dual connect by level <=2; + select test_dummy_object( rownum, 'Something '||rownum, rownum) + bulk collect into l_expected + from dual connect by level <=2; + --Act + ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).exclude( l_list ); + + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure collection_include_list_fail is + l_actual test_dummy_object_list; + l_expected test_dummy_object_list; + l_list ut3.ut_varchar2_list; + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + begin + l_list := ut3.ut_varchar2_list('TEST_DUMMY_OBJECT/name'); + --Arrange + select test_dummy_object( rownum, 'SomethingsDifferent '||rownum, rownum) + bulk collect into l_actual + from dual connect by level <=2; + select test_dummy_object( rownum, 'Something '||rownum, rownum) + bulk collect into l_expected + from dual connect by level <=2; + --Act + ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).include( l_list ); + + l_expected_message := q'[%Actual: ut3_tester.test_dummy_object_list [ count = 2 ] was expected to equal: ut3_tester.test_dummy_object_list [ count = 2 ] +%Diff: +%Rows: [ 2 differences ] +%All rows are different as the columns are not matching.]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + + procedure array_same_data is + begin + --Arrange + g_test_expected := anydata.convertCollection( t_tab_varchar('A') ); + g_test_actual := anydata.convertCollection( t_tab_varchar('A') ); + --Act + ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure array_diff_data is + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + begin + --Arrange + g_test_expected := anydata.convertCollection( t_tab_varchar('A') ); + g_test_actual := anydata.convertCollection( t_tab_varchar('B') ); + --Act + ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); + l_expected_message := q'[%Actual: ut3_tester.t_tab_varchar [ count = 1 ] was expected to equal: ut3_tester.t_tab_varchar [ count = 1 ] +%Diff: +%Rows: [ 1 differences ] +%Row No. 1 - Actual: B +%Row No. 1 - Expected: A]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + + procedure array_is_null is + l_is_null t_tab_varchar ; + begin + ut3.ut.expect( anydata.convertCollection( l_is_null ) ).to_be_null; + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure array_null_equal_array_null is + l_is_null t_tab_varchar ; + l_is_null_bis t_tab_varchar ; + begin + ut3.ut.expect( anydata.convertCollection( l_is_null ) ).to_equal(anydata.convertCollection( l_is_null_bis )); + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure array_null_equal_array_notnull is + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + l_is_null t_tab_varchar ; + begin + --Arrange + g_test_expected := anydata.convertCollection( l_is_null ); + g_test_actual := anydata.convertCollection( t_tab_varchar('A') ); + --Act + ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); + l_expected_message := q'[%Actual: ut3_tester.t_tab_varchar [ count = 1 ] was expected to equal: ut3_tester.t_tab_varchar [ count = ] +%Diff: +%Rows: [ 1 differences ] +%Row No. 1 - Extra: A]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + + procedure empty_array_have_zero_elem is + begin + ut3.ut.expect( anydata.convertCollection(t_tab_varchar())).to_have_count(0); + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure array_empty_equal_array_empty is + begin + --Arrange + g_test_expected := anydata.convertCollection(t_tab_varchar()); + g_test_actual := anydata.convertCollection(t_tab_varchar()); + --Act + ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure arr_empty_equal_arr_notempty is + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + l_is_null t_tab_varchar ; + begin + --Arrange + g_test_expected := anydata.convertCollection( t_tab_varchar() ); + g_test_actual := anydata.convertCollection( t_tab_varchar('A') ); + --Act + ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); + l_expected_message := q'[%Actual: ut3_tester.t_tab_varchar [ count = 1 ] was expected to equal: ut3_tester.t_tab_varchar [ count = 0 ] +%Diff: +%Rows: [ 1 differences ] +%Row No. 1 - Extra: A]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + + procedure collection_is_null is + l_null_list test_dummy_object_list; + begin + --Arrange + g_test_actual := anydata.convertCollection( l_null_list ); + --Act + ut3.ut.expect( g_test_actual ).to_be_null; + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure collection_is_empty is + begin + --Arrange + g_test_actual := anydata.convertCollection( test_dummy_object_list() ); + --Act + ut3.ut.expect( g_test_actual ).to_have_count(0); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + + end; + + procedure varray_same_data is + begin + --Arrange + g_test_expected := anydata.convertCollection( t_varray(1) ); + g_test_actual := anydata.convertCollection( t_varray(1) ); + --Act + ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure varray_diff_data is + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + begin + --Arrange + g_test_expected := anydata.convertCollection( t_varray(1) ); + g_test_actual := anydata.convertCollection( t_varray(2) ); + --Act + ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); + l_expected_message := q'[%Actual: ut3_tester.t_varray [ count = 1 ] was expected to equal: ut3_tester.t_varray [ count = 1 ] +%Diff: +%Rows: [ 1 differences ] +%Row No. 1 - Actual: 2 +%Row No. 1 - Expected: 1]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + + procedure varray_is_null is + l_is_null t_varray ; + begin + ut3.ut.expect( anydata.convertCollection( l_is_null ) ).to_be_null; + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure varray_null_equal_varray_null is + l_is_null t_varray ; + l_is_null_bis t_varray ; + begin + ut3.ut.expect( anydata.convertCollection( l_is_null ) ).to_equal(anydata.convertCollection( l_is_null_bis )); + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure varr_null_equal_varr_notnull is + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + l_is_null t_varray ; + begin + --Arrange + g_test_expected := anydata.convertCollection( l_is_null ); + g_test_actual := anydata.convertCollection( t_varray(1) ); + --Act + ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); + l_expected_message := q'[%Actual: ut3_tester.t_varray [ count = 1 ] was expected to equal: ut3_tester.t_varray [ count = ] +%Diff: +%Rows: [ 1 differences ] +%Row No. 1 - Extra: 1]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + + procedure empty_varray_have_zero_elem is + begin + ut3.ut.expect( anydata.convertCollection(t_varray())).to_have_count(0); + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure varr_empty_equal_varr_empty is + begin + --Arrange + g_test_expected := anydata.convertCollection(t_varray()); + g_test_actual := anydata.convertCollection(t_varray()); + --Act + ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure varr_empty_equal_varr_notempty is + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + l_is_null t_varray ; + begin + --Arrange + g_test_expected := anydata.convertCollection( t_varray() ); + g_test_actual := anydata.convertCollection( t_varray(1) ); + --Act + ut3.ut.expect( g_test_actual ).to_equal( g_test_expected ); + l_expected_message := q'[%Actual: ut3_tester.t_varray [ count = 1 ] was expected to equal: ut3_tester.t_varray [ count = 0 ] +%Diff: +%Rows: [ 1 differences ] +%Row No. 1 - Extra: 1]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + + procedure collection_join_by is + l_actual test_dummy_object_list; + l_expected test_dummy_object_list; + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + begin + --Arrange + select test_dummy_object( rownum, 'Something '||rownum, rownum) + bulk collect into l_actual + from dual connect by level <=2; + select test_dummy_object( rownum, 'Something '||rownum, rownum) + bulk collect into l_expected + from dual connect by level <=2 + order by rownum desc; + --Act + ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).join_by('TEST_DUMMY_OBJECT/ID'); + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure collection_join_by_fail is + l_actual test_dummy_object_list; + l_expected test_dummy_object_list; + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + begin + --Arrange + select test_dummy_object( rownum, 'Something '||rownum, rownum) + bulk collect into l_actual + from dual connect by level <=2; + select test_dummy_object( rownum * 2, 'Something '||rownum, rownum) + bulk collect into l_expected + from dual connect by level <=2 + order by rownum desc; + --Act + ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).join_by('TEST_DUMMY_OBJECT/ID'); + l_expected_message := q'[%Actual: ut3_tester.test_dummy_object_list [ count = 2 ] was expected to equal: ut3_tester.test_dummy_object_list [ count = 2 ] +%Diff: +%Rows: [ 3 differences ] +%PK 2 - Actual: Something 2 +%PK 2 - Actual: 2 +%PK 2 - Expected: Something 1 +%PK 2 - Expected: 1 +%PK 1 - Extra: 1Something 11 +%PK 4 - Missing: 4Something 22]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + + procedure collection_unordered is + l_actual test_dummy_object_list; + l_expected test_dummy_object_list; + begin + --Arrange + select test_dummy_object( rownum, 'Something '||rownum, rownum) + bulk collect into l_actual + from dual connect by level <=3; + select test_dummy_object( rownum, 'Something '||rownum, rownum) + bulk collect into l_expected + from dual connect by level <=3 + order by rownum desc; + --Act + ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).unordered; + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure collection_unordered_fail is + l_actual test_dummy_object_list; + l_expected test_dummy_object_list; + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + begin + --Arrange + select test_dummy_object( rownum, 'Something '||rownum, rownum) + bulk collect into l_actual + from dual connect by level <=2; + select test_dummy_object( rownum * 2, 'Something '||rownum, rownum) + bulk collect into l_expected + from dual connect by level <=2 + order by rownum desc; + + l_expected_message := q'[%Actual: ut3_tester.test_dummy_object_list [ count = 2 ] was expected to equal: ut3_tester.test_dummy_object_list [ count = 2 ] +%Diff: +%Rows: [ 4 differences ] +%Extra: 1Something 11 +%Extra: 2Something 22 +%Missing: 4Something 22 +%Missing: 2Something 11]'; + + ut3.ut.expect(anydata.convertCollection(l_actual)).to_equal(anydata.convertCollection(l_expected)).unordered; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + + procedure object_join_by is + begin + --Arrange + g_test_expected := anydata.convertObject( test_dummy_object(1, 'A', '0') ); + g_test_actual := anydata.convertObject( test_dummy_object(1, 'A', '0') ); + + --Act + ut3.ut.expect(g_test_actual).to_equal(g_test_expected).join_by('TEST_DUMMY_OBJECT/ID'); + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure object_unordered is + begin + g_test_expected := anydata.convertObject( test_dummy_object(1, 'A', '0') ); + g_test_actual := anydata.convertObject( test_dummy_object(1, 'A', '0') ); + + --Act + ut3.ut.expect(g_test_actual).to_equal(g_test_expected).unordered; + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure collection_to_contain is + l_actual test_dummy_object_list; + l_expected test_dummy_object_list; + begin + --Arrange + select test_dummy_object( rownum, 'Something '||rownum, rownum) + bulk collect into l_actual + from dual connect by level <=4; + select test_dummy_object( rownum, 'Something '||rownum, rownum) + bulk collect into l_expected + from dual connect by level <=2 + order by rownum desc; + --Act + ut3.ut.expect(anydata.convertCollection(l_actual)).to_contain(anydata.convertCollection(l_expected)); + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure object_to_contain is + begin + --Arrange + g_test_expected := anydata.convertObject( test_dummy_object(1, 'A', '0') ); + g_test_actual := anydata.convertObject( test_dummy_object(1, 'A', '0') ); + + --Act + ut3.ut.expect(g_test_actual).to_contain(g_test_expected); + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + end; / \ No newline at end of file diff --git a/test/core/expectations/test_expectation_anydata.pks b/test/core/expectations/test_expectation_anydata.pks index fedec586c..b9fbda93b 100644 --- a/test/core/expectations/test_expectation_anydata.pks +++ b/test/core/expectations/test_expectation_anydata.pks @@ -57,9 +57,6 @@ create or replace package test_expectation_anydata is --%test(Comma separated list of attributes to exclude is case sensitive) procedure exclude_attributes_as_csv; - --%test(Exclude attributes fails on invalid XPath) - procedure exclude_attrib_xpath_invalid; - --%test(Exclude attributes by XPath is case sensitive) procedure exclude_attributes_xpath; @@ -72,9 +69,6 @@ create or replace package test_expectation_anydata is --%test(Comma separated list of attributes to include is case sensitive) procedure include_attributes_as_csv; - --%test(Include attributes fails on invalid XPath) - procedure include_attrib_xpath_invalid; - --%test(Include attributes by XPath is case sensitive) procedure include_attributes_xpath; @@ -117,5 +111,92 @@ create or replace package test_expectation_anydata is --%test(Reports only first 20 rows of diff and gives a full diff count) procedure data_diff_on_20_rows_only; + --%test(Validate include list on collections of objects) + procedure collection_include_list; + + --%test(Validate exclude list on collections of objects) + procedure collection_exclude_list; + + --%test(Validate include list on collections of objects fail) + procedure collection_include_list_fail; + + --%test(Two ARRAYS with same data) + procedure array_same_data; + + --%test(Two ARRAYS with different data) + procedure array_diff_data; + + --%test(ARRAY is atomically null) + procedure array_is_null; + + --%test(Compare two null ARRAYs) + procedure array_null_equal_array_null; + + --%test(Compare null ARRAY to ARRAY with data) + procedure array_null_equal_array_notnull; + + --%test(Empty ARRAY have count of 0) + procedure empty_array_have_zero_elem; + + --%test(Compare two empty ARRAYs) + procedure array_empty_equal_array_empty; + + --%test(Compare empty ARRAY to ARRAY with data) + procedure arr_empty_equal_arr_notempty; + + --%test(Collection is atomically NULL) + procedure collection_is_null; + + --%test(Collection is empty) + procedure collection_is_empty; + + --%test(Two VARRAYS with same data) + procedure varray_same_data; + + --%test(Two VARRAYS with different data) + procedure varray_diff_data; + + --%test(VARRAY is atomically null) + procedure varray_is_null; + + --%test(Compare two null VARRAYs) + procedure varray_null_equal_varray_null; + + --%test(Compare null VARRAY to VARRAY with data) + procedure varr_null_equal_varr_notnull; + + --%test(Empty VARRAY have count of 0) + procedure empty_varray_have_zero_elem; + + --%test(Compare two empty VARRAYs) + procedure varr_empty_equal_varr_empty; + + --%test(Compare empty VARRAY to VARRAY with data) + procedure varr_empty_equal_varr_notempty; + + --%test( Anydata collection using joinby ) + procedure collection_join_by; + + --%test( Anydata collection using joinby fail) + procedure collection_join_by_fail; + + --%test( Anydata collection unordered ) + procedure collection_unordered; + + --%test( Anydata collection unordered fail ) + procedure collection_unordered_fail; + + --%test( Anydata object using joinby ) + procedure object_join_by; + + --%test( Anydata object unordered ) + procedure object_unordered; + + --%test( Success when anydata collection contains data from another anydata collection) + procedure collection_to_contain; + + --%test( Success when anydata object contains data from another anydata) + procedure object_to_contain; + end; / diff --git a/test/core/expectations/test_expectations_cursor.pkb b/test/core/expectations/test_expectations_cursor.pkb index ac4fc9acf..f0d9ae36e 100644 --- a/test/core/expectations/test_expectations_cursor.pkb +++ b/test/core/expectations/test_expectations_cursor.pkb @@ -62,6 +62,7 @@ create or replace package body test_expectations_cursor is l_actual sys_refcursor; begin -- Arrange + ut3.ut.set_nls; open l_expected for select 1 as my_num, 'This is my test string' as my_string, @@ -78,6 +79,7 @@ create or replace package body test_expectations_cursor is ut3.ut.expect( l_actual ).to_equal( l_expected ); --Assert ut.expect(expectations.failed_expectations_data()).to_be_empty(); + ut3.ut.reset_nls; end; procedure success_on_empty @@ -303,6 +305,80 @@ create or replace package body test_expectations_cursor is ut.expect(expectations.failed_expectations_data()).not_to_be_empty(); end; + procedure pass_on_different_column_order + as + l_expected sys_refcursor; + l_actual sys_refcursor; + begin + --Arrange + open l_expected for select 1 as col_1, 2 as col_2 from dual; + open l_actual for select 2 as col_2, 1 as col_1 from dual; + --Act + ut3.ut.expect( l_actual ).to_equal( l_expected ).unordered_columns; + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure pass_on_diff_column_ord_uc + as + l_expected sys_refcursor; + l_actual sys_refcursor; + begin + --Arrange + open l_expected for select 1 as col_1, 2 as col_2 from dual; + open l_actual for select 2 as col_2, 1 as col_1 from dual; + --Act + ut3.ut.expect( l_actual ).to_equal( l_expected ).uc; + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure fail_on_multi_diff_col_order + as + l_expected sys_refcursor; + l_actual sys_refcursor; + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + begin + --Arrange + open l_expected for select 1 as col_1, 2 as col_2,3 as col_3, 4 as col_4,5 col_5 from dual; + open l_actual for select 2 as col_2, 1 as col_1,40 as col_4, 5 as col_5, 30 col_3 from dual; + --Act + ut3.ut.expect( l_actual ).to_equal( l_expected ).unordered_columns; + --Assert + l_expected_message := q'[Actual: refcursor [ count = 1 ] was expected to equal: refcursor [ count = 1 ] +%Diff: +%Rows: [ 1 differences ] +%Row No. 1 - Actual: 4030 +%Row No. 1 - Expected: 34]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + + procedure fail_on_multi_diff_col_ord_uc + as + l_expected sys_refcursor; + l_actual sys_refcursor; + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + begin + --Arrange + open l_expected for select 1 as col_1, 2 as col_2,3 as col_3, 4 as col_4,5 col_5 from dual; + open l_actual for select 2 as col_2, 1 as col_1,40 as col_4, 5 as col_5, 30 col_3 from dual; + --Act + ut3.ut.expect( l_actual ).to_equal( l_expected ).uc; + --Assert + l_expected_message := q'[Actual: refcursor [ count = 1 ] was expected to equal: refcursor [ count = 1 ] +%Diff: +%Rows: [ 1 differences ] +%Row No. 1 - Actual: 4030 +%Row No. 1 - Expected: 34]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + procedure fail_on_different_row_order as l_expected sys_refcursor; @@ -328,11 +404,11 @@ create or replace package body test_expectations_cursor is ut3.ut.set_nls; open l_actual for select l_date as some_date from dual; open l_expected for select l_date-l_second some_date from dual; - ut3.ut.reset_nls; --Act ut3.ut.expect( l_actual ).to_equal( l_expected ); --Assert ut.expect(expectations.failed_expectations_data()).not_to_be_empty(); + ut3.ut.reset_nls; end; procedure uses_default_nls_for_date @@ -405,22 +481,29 @@ create or replace package body test_expectations_cursor is procedure exclude_columns_xpath_invalid as - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; - l_error_code integer := -31011; --xpath_error + l_actual sys_refcursor; + l_expected sys_refcursor; + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); begin --Arrange open l_actual for select rownum as rn, 'a' as "A_Column", 'c' as A_COLUMN, 'x' SOME_COL, 'd' "Some_Col" from dual a connect by level < 4; open l_expected for select rownum as rn, 'a' as "A_Column", 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; - begin --Act - ut3.ut.expect(l_actual).to_equal(l_expected, a_exclude=>'/ROW/A_COLUMN,\\//Some_Col'); + ut3.ut.expect(l_actual).to_equal(l_expected, a_exclude=>'/ROW/A_COLUMN,\\//Some_Col'); --Assert - ut.fail('Expected '||l_error_code||' but nothing was raised'); - exception - when others then - ut.expect(sqlcode).to_equal(l_error_code); - end; + l_expected_message := q'[Actual: refcursor [ count = 3 ] was expected to equal: refcursor [ count = 3 ] +%Diff: +%Rows: [ 3 differences ] +%Row No. 1 - Actual: d +%Row No. 1 - Expected: c +%Row No. 2 - Actual: d +%Row No. 2 - Expected: c +%Row No. 3 - Actual: d +%Row No. 3 - Expected: c]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); end; procedure exclude_columns_xpath @@ -474,30 +557,11 @@ create or replace package body test_expectations_cursor is open l_actual for select rownum as rn, 'a' as "A_Column", 'c' as A_COLUMN, 'x' SOME_COL, 'd' "Some_Col" from dual a connect by level < 4; open l_expected for select rownum as rn, 'a' as "A_Column", 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; --Act - ut3.ut.expect(l_actual).to_equal(l_expected).include('RN,//A_Column,SOME_COL'); + ut3.ut.expect(l_actual).to_equal(l_expected).include('RN,//A_Column, SOME_COL'); --Assert ut.expect(expectations.failed_expectations_data()).to_be_empty(); end; - procedure include_columns_xpath_invalid - as - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; - begin - --Arrange - open l_actual for select rownum as rn, 'a' as "A_Column", 'c' as A_COLUMN, 'x' SOME_COL, 'd' "Some_Col" from dual a connect by level < 4; - open l_expected for select rownum as rn, 'a' as "A_Column", 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; - begin - --Act - ut3.ut.expect(l_actual).to_equal(l_expected).include('/ROW/RN,\\//A_Column,//SOME_COL'); - --Assert - ut.fail('Expected exception but nothing was raised'); - exception - when others then - ut.expect(sqlcode).to_be_between(-31013,-31011); - end; - end; - procedure include_columns_xpath as l_actual sys_refcursor; @@ -521,7 +585,7 @@ create or replace package body test_expectations_cursor is open l_actual for select rownum as rn, 'c' as A_COLUMN from dual a connect by level < 4; open l_expected for select rownum as rn, 'd' as A_COLUMN from dual a connect by level < 4; --Act - ut3.ut.expect(l_actual).to_equal(l_expected).include(ut3.ut_varchar2_list('RN','non_existing_column')); + ut3.ut.expect(l_actual).to_equal(l_expected).include(ut3.ut_varchar2_list(' RN ',' non_existing_column ')); --Assert ut.expect(expectations.failed_expectations_data()).to_be_empty(); end; @@ -615,7 +679,9 @@ Rows: [ 1 differences ] l_expected_message := q'[Actual: refcursor [ count = 2 ] was expected to equal: refcursor [ count = 2 ] Diff: Columns: - Column data-type is invalid. Expected: NUMBER, actual: VARCHAR2.]'; + Column data-type is invalid. Expected: NUMBER, actual: VARCHAR2. +Rows: [ all different ] + All rows are different as the columns position is not matching.]'; l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; --Assert ut.expect(l_actual_message).to_be_like(l_expected_message); @@ -643,7 +709,6 @@ Columns:% ut.expect(l_actual_message).to_be_like(l_expected_message); end; - --%test(Reports column diff on cusror with different column positions) procedure column_diff_on_col_position is l_actual sys_refcursor; l_expected sys_refcursor; @@ -662,13 +727,27 @@ Columns: Column is misplaced. Expected position: 2, actual position: 4. Column is misplaced. Expected position: 3, actual position: 2. Column is misplaced. Expected position: 4, actual position: 3. -Rows: [ 2 differences ] - All rows are different as the columns are not matching.]'; +Rows: [ all different ] + All rows are different as the columns position is not matching.]'; l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; --Assert ut.expect(l_actual_message).to_be_like(l_expected_message); end; + procedure column_diff_on_col_pos_unord is + l_actual sys_refcursor; + l_expected sys_refcursor; + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + begin + --Arrange + open l_actual for select rownum+1 col_1, rownum+2 col_2, rownum+3 col_3, rownum+4 col_4 from dual connect by level <=2; + open l_expected for select rownum+1 col_1, rownum+4 col_4, rownum+2 col_2, rownum+3 col_3 from dual connect by level <=2; + --Act + ut3.ut.expect(l_actual).to_equal(l_expected).unordered_columns; + + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; --%test(Reports only mismatched columns on column data mismatch) procedure data_diff_on_col_data_mismatch is @@ -769,6 +848,42 @@ Rows: [ 4 differences ] ut.expect(l_actual_message).to_be_like(l_expected_message); end; + procedure col_and_data_diff_not_ordered is + l_actual sys_refcursor; + l_expected sys_refcursor; + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + begin + --Arrange + open l_expected for + select 1 as ID, 'JACK' as FIRST_NAME, 'SPARROW' AS LAST_NAME, 10000 AS SALARY from dual union all + select 2 as ID, 'LUKE' as FIRST_NAME, 'SKYWALKER' AS LAST_NAME, 1000 AS SALARY from dual union all + select 3 as ID, 'TONY' as FIRST_NAME, 'STARK' AS LAST_NAME, 100000 AS SALARY from dual; + open l_actual for + select 'M' AS GENDER, 'JACK' as FIRST_NAME, 'SPARROW' AS LAST_NAME, 1 as ID, '25000' AS SALARY from dual union all + select 'M' AS GENDER, 'TONY' as FIRST_NAME, 'STARK' AS LAST_NAME, 3 as ID, '100000' AS SALARY from dual union all + select 'F' AS GENDER, 'JESSICA' as FIRST_NAME, 'JONES' AS LAST_NAME, 4 as ID, '2345' AS SALARY from dual union all + select 'M' AS GENDER, 'LUKE' as FIRST_NAME, 'SKYWALKER' AS LAST_NAME, 2 as ID, '1000' AS SALARY from dual; + --Act + ut3.ut.expect(l_actual).to_equal(l_expected).unordered_columns; + l_expected_message := q'[Actual: refcursor [ count = 4 ] was expected to equal: refcursor [ count = 3 ] +Diff: +Columns: + Column data-type is invalid. Expected: NUMBER, actual: VARCHAR2. + Column [position: 1, data-type: CHAR] is not expected in results. +Rows: [ 4 differences ] + Row No. 1 - Actual: 25000 + Row No. 1 - Expected: 10000 + Row No. 2 - Actual: TONYSTARK3100000 + Row No. 2 - Expected: 2LUKESKYWALKER1000 + Row No. 3 - Actual: JESSICAJONES42345 + Row No. 3 - Expected: 3TONYSTARK100000 + Row No. 4 - Extra: MLUKESKYWALKER21000]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + procedure prepare_table as pragma autonomous_transaction; @@ -893,15 +1008,15 @@ Rows: [ 4 differences ] l_expected sys_refcursor; begin --Arrange - open l_actual for select object_name from all_objects where rownum <=1100; - open l_expected for select object_name from all_objects where rownum <=1100; + open l_actual for select object_name from all_objects where rownum <=1100 order by object_id; + open l_expected for select object_name from all_objects where rownum <=1100 order by object_id; --Act ut3.ut.expect(l_actual).to_equal(l_expected); --Assert ut.expect(expectations.failed_expectations_data()).to_be_empty(); end; - + function get_cursor return sys_refcursor is l_cursor sys_refcursor; begin @@ -964,8 +1079,8 @@ Rows: [ 4 differences ] end; procedure col_diff_on_col_name_implicit is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; l_actual_message varchar2(32767); l_expected_message varchar2(32767); begin @@ -975,23 +1090,23 @@ Rows: [ 4 differences ] --Act ut3.ut.expect(l_actual).to_equal(l_expected); - l_expected_message := q'[Actual: refcursor [ count = 2 ] was expected to equal: refcursor [ count = 2 ]% -Diff:% -Columns:% - Column [data-type: NUMBER] is missing. Expected column position: 1.% - Column [data-type: NUMBER] is missing. Expected column position: 2.% - Column <%1%> [position: 1, data-type: CHAR] is not expected in results.% - Column <%2%> [position: 2, data-type: CHAR] is not expected in results.% -Rows: [ 2 differences ]% - All rows are different as the columns are not matching.%]'; + l_expected_message := q'[%Actual: refcursor [ count = 2 ] was expected to equal: refcursor [ count = 2 ] +%Diff: +%Columns: +%Column [data-type: NUMBER] is missing. Expected column position: 1. +%Column [data-type: NUMBER] is missing. Expected column position: 2. +%Column <1> [position: 1, data-type: CHAR] is not expected in results. +%Column <2> [position: 2, data-type: CHAR] is not expected in results. +%Rows: [ all different ] +%All rows are different as the columns position is not matching.]'; l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; --Assert ut.expect(l_actual_message).to_be_like(l_expected_message); end; procedure col_mtch_on_col_name_implicit is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; l_actual_message varchar2(32767); l_expected_message varchar2(32767); begin @@ -1003,47 +1118,10 @@ Rows: [ 2 differences ]% --Assert ut.expect(expectations.failed_expectations_data()).to_be_empty(); end; - - - procedure include_col_name_implicit is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; - begin - --Arrange - open l_actual for select rownum as rn, 'a', 'c' as A_COLUMN, 'x' SOME_COL, 'd' "Some_Col" from dual a connect by level < 4; - open l_expected for select rownum as rn, 'a', 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; - begin - --Act - ut3.ut.expect(l_actual).to_equal(l_expected).include(q'!/ROW/RN,'a',//SOME_COL!'); - --Assert - ut.fail('Expected exception but nothing was raised'); - exception - when others then - ut.expect(sqlcode).to_be_between(-31013,-31011); - end; - end; - - procedure exclude_col_name_implicit is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; - begin - --Arrange - open l_actual for select rownum as rn, 'a', 'c' as A_COLUMN, 'x' SOME_COL, 'd' "Some_Col" from dual a connect by level < 4; - open l_expected for select rownum as rn, 'a', 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; - begin - --Act - ut3.ut.expect(l_actual).to_equal(l_expected).exclude(q'!/ROW/RN,'a',//SOME_COL!'); - --Assert - ut.fail('Expected exception but nothing was raised'); - exception - when others then - ut.expect(sqlcode).to_be_between(-31013,-31011); - end; - end; - + procedure cursor_unorderd_compr_success is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; begin --Arrange open l_actual for select username , user_id from all_users order by username asc; @@ -1054,9 +1132,22 @@ Rows: [ 2 differences ]% ut.expect(expectations.failed_expectations_data()).to_be_empty(); end; + procedure cursor_unord_compr_success_uc is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select user_id, username from all_users order by username asc; + open l_expected for select username , user_id from all_users order by username desc; + --Act + ut3.ut.expect(l_actual).to_equal(l_expected).unordered().uc(); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + procedure cursor_unordered_compare_fail is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; l_actual_message varchar2(32767); l_expected_message varchar2(32767); begin @@ -1075,48 +1166,83 @@ Rows: [ 2 differences ]% l_expected_message := q'[%Actual: refcursor [ count = 2 ] was expected to equal: refcursor [ count = 2 ]% %Diff:% %Rows: [ 2 differences ]% -%Extra: test-666% -%Missing: test-667%]'; +%Extra: test-666% +%Missing: test-667%]'; l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; --Assert ut.expect(l_actual_message).to_be_like(l_expected_message); end; + + procedure cursor_joinby_compare_uc is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select owner, object_id, object_name,object_type from all_objects where owner = user; + open l_expected for select object_id, owner, object_name,object_type from all_objects where owner = user; + + --Act + ut3.ut.expect(l_actual).to_equal(l_expected).join_by('OBJECT_ID').uc(); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; procedure cursor_joinby_compare is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; begin --Arrange - open l_actual for select owner, object_name,object_type from all_objects where owner = user - order by 1,2,3 asc; - open l_expected for select owner, object_name,object_type from all_objects where owner = user - order by 1,2,3 desc; + open l_actual for select object_id, owner, object_name,object_type from all_objects where owner = user; + open l_expected for select object_id, owner, object_name,object_type from all_objects where owner = user; --Act - ut3.ut.expect(l_actual).to_equal(l_expected).join_by('OWNER'); + ut3.ut.expect(l_actual).to_equal(l_expected).join_by('OBJECT_ID'); --Assert ut.expect(expectations.failed_expectations_data()).to_be_empty(); end; + procedure cursor_joinby_col_not_ord + as + l_expected sys_refcursor; + l_actual sys_refcursor; + l_actual_message varchar2(32767); + l_expected_message varchar2(32767); + begin + --Arrange + open l_expected for select 1 as col_1, 2 as col_2,3 as col_3, 4 as col_4,5 col_5 from dual; + open l_actual for select 2 as col_2, 1 as col_1,40 as col_4, 5 as col_5, 30 col_3 from dual; + --Act + ut3.ut.expect( l_actual ).to_equal( l_expected ).join_by('COL_1').unordered_columns; + --Assert + l_expected_message := q'[Actual: refcursor [ count = 1 ] was expected to equal: refcursor [ count = 1 ] +%Diff: +%Rows: [ 1 differences ] +%PK 1 - Actual: 30 +%PK 1 - Expected: 3 +%PK 1 - Actual: 40 +%PK 1 - Expected: 4]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + procedure cursor_joinby_compare_twocols is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; begin --Arrange - open l_actual for select owner, object_name,object_type from all_objects where owner = user - order by 1,2,3 asc; - open l_expected for select owner, object_name,object_type from all_objects where owner = user - order by 1,2,3 desc; + open l_actual for select object_id, owner, object_name,object_type from all_objects where owner = user; + open l_expected for select object_id, owner, object_name,object_type from all_objects where owner = user; --Act - ut3.ut.expect(l_actual).to_equal(l_expected).join_by(ut3.ut_varchar2_list('OWNER,OBJECT_NAME')); + ut3.ut.expect(l_actual).to_equal(l_expected).join_by(ut3.ut_varchar2_list('OBJECT_ID,OBJECT_NAME')); --Assert ut.expect(expectations.failed_expectations_data()).to_be_empty(); end; procedure cursor_joinby_compare_nokey is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; l_actual_message varchar2(32767); l_expected_message varchar2(32767); begin @@ -1138,8 +1264,8 @@ Diff:% end; procedure cur_joinby_comp_twocols_nokey is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; l_actual_message varchar2(32767); l_expected_message varchar2(32767); begin @@ -1163,8 +1289,8 @@ Diff:% end; procedure cursor_joinby_compare_exkey is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; l_actual_message varchar2(32767); l_expected_message varchar2(32767); begin @@ -1186,8 +1312,8 @@ Diff:% end; procedure cur_joinby_comp_twocols_exkey is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; l_actual_message varchar2(32767); l_expected_message varchar2(32767); begin @@ -1209,8 +1335,8 @@ Diff:% end; procedure cursor_joinby_comp_nokey_ex is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; l_actual_message varchar2(32767); l_expected_message varchar2(32767); begin @@ -1231,8 +1357,8 @@ Diff:% end; procedure cursor_joinby_comp_nokey_ac is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; l_actual_message varchar2(32767); l_expected_message varchar2(32767); begin @@ -1253,21 +1379,34 @@ Diff:% end; procedure cursor_joinby_compare_1000 is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; begin --Arrange - open l_actual for select object_name from all_objects where rownum <=1100; - open l_expected for select object_name from all_objects where rownum <=1100; + open l_actual for select level object_id, level || '_TEST' object_name from dual connect by level <=1100; + open l_expected for select level object_id, level || '_TEST' object_name from dual connect by level <=1100; --Act - ut3.ut.expect(l_actual).to_equal(l_expected).join_by('OBJECT_NAME'); + ut3.ut.expect(l_actual).to_equal(l_expected).join_by('OBJECT_ID'); --Assert ut.expect(expectations.failed_expectations_data()).to_be_empty(); end; + + procedure cursor_unorder_compare_1000 is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select level object_id, level || '_TEST' object_name from dual connect by level <=1100; + open l_expected for select level object_id, level || '_TEST' object_name from dual connect by level <=1100; + --Act + ut3.ut.expect(l_actual).to_equal(l_expected).unordered; + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; procedure cursor_joinby_compare_fail is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; l_actual_message varchar2(32767); l_expected_message varchar2(32767); begin @@ -1292,8 +1431,8 @@ Diff:% end; procedure cursor_joinby_cmp_twocol_fail is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; l_actual_message varchar2(32767); l_expected_message varchar2(32767); begin @@ -1310,16 +1449,16 @@ Diff:% l_expected_message := q'[%Actual: refcursor [ count = % ] was expected to equal: refcursor [ count = % ] %Diff:% %Rows: [ 2 differences ]% -%PK TEST-610 - Extra% -%PK TEST-600 - Missing%]'; +%PK TEST-610 - Extra: TEST-610% +%PK TEST-600 - Missing: TEST-600%]'; l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; --Assert ut.expect(l_actual_message).to_be_like(l_expected_message); end; procedure cur_joinby_cmp_threcol_fail is - l_actual SYS_REFCURSOR; - l_expected SYS_REFCURSOR; + l_actual sys_refcursor; + l_expected sys_refcursor; l_actual_message varchar2(32767); l_expected_message varchar2(32767); begin @@ -1366,7 +1505,7 @@ Diff:% open l_actual for select rownum as rn, 'a' as "A_Column", 'c' as A_COLUMN, 'x' SOME_COL, 'd' "Some_Col" from dual a connect by level < 4; open l_expected for select rownum as rn, 'a' as "A_Column", 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; --Act - ut3.ut.expect(l_actual).to_equal(l_expected).include(ut3.ut_varchar2_list('RN','//A_Column','SOME_COL')).join_by('SOME_COL'); + ut3.ut.expect(l_actual).to_equal(l_expected).include(ut3.ut_varchar2_list('RN','//A_Column','SOME_COL')).join_by('RN'); --Assert ut.expect(expectations.failed_expectations_data()).to_be_empty(); end; @@ -1380,7 +1519,7 @@ Diff:% open l_actual for select rownum as rn, 'a' as "A_Column", 'c' as A_COLUMN, 'x' SOME_COL, 'd' "Some_Col" from dual a connect by level < 4; open l_expected for select rownum as rn, 'a' as "A_Column", 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; --Act - ut3.ut.expect(l_actual).to_equal(l_expected).exclude(ut3.ut_varchar2_list('//Some_Col','A_COLUMN')).join_by('SOME_COL'); + ut3.ut.expect(l_actual).to_equal(l_expected).exclude(ut3.ut_varchar2_list('//Some_Col','A_COLUMN')).join_by('RN'); --Assert ut.expect(expectations.failed_expectations_data()).to_be_empty(); end; @@ -1492,12 +1631,12 @@ Diff:% ut3.ut.expect(l_actual).to_equal(l_expected).unordered; l_expected_message := q'[%Actual: refcursor [ count = 2 ] was expected to equal: refcursor [ count = 3 ]% Diff:% -Rows: [ 5 differences ] -%Extra: 2Something 22% -%Extra: 1Something 11% -%Missing: 1Somethings 11% -%Missing: 2Somethings 22% -%Missing: 3Somethings 33%]'; +Rows: [ 5 differences% +%Extra: 1Something 11% +%Extra: 2Something 22% +%Missing: 3Somethings 33% +%Missing: 2Somethings 22% +%Missing: 1Somethings 11%]'; l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; --Assert ut.expect(l_actual_message).to_be_like(l_expected_message); @@ -1726,7 +1865,7 @@ Diff:% --Assert l_expected_message := q'[%Actual: refcursor [ count = 2 ] was expected to equal: refcursor [ count = 2 ]% %Diff:% -%Rows: [ 2 differences ]% +%Rows: [ 4 differences ]% %PK %%%%%%%%%%%%%Extra%%% %PK %%%%%%%%%%%%%Extra%%% %PK %%%%%%%%%%%%%Missing%%% @@ -1775,11 +1914,11 @@ Diff:% l_expected_message varchar2(32767); l_actual_message varchar2(32767); begin - select ut3.ut_key_value_pair(rownum,'Something '||rownum) + select ut3.ut_key_value_pair(rownum,'Apples '||rownum) bulk collect into l_actual_tab from dual connect by level <=2; - select ut3.ut_key_value_pair(rownum,'Somethings '||rownum) + select ut3.ut_key_value_pair(rownum,'Peaches '||rownum) bulk collect into l_expected_tab from dual connect by level <=2; @@ -1791,15 +1930,16 @@ Diff:% from dual connect by level <=2; --Act - ut3.ut.expect(l_actual).to_equal(l_expected).join_by('NESTED_TABLE/UT_KEY_VALUE_PAIR'); + ut3.ut.expect(l_actual).to_equal(l_expected).join_by('NESTED_TABLE/UT_KEY_VALUE_PAIRS'); --Assert l_expected_message := q'[%Actual: refcursor [ count = 2 ] was expected to equal: refcursor [ count = 2 ] %Diff: -%Unable to join sets: -%Join key NESTED_TABLE/UT_KEY_VALUE_PAIR does not exists in expected% -%Join key NESTED_TABLE/UT_KEY_VALUE_PAIR does not exists in actual% -%Please make sure that your join clause is not refferring to collection element%]'; +%Rows: [ 4 differences ] +%Extra: 11Apples 12Apples 2 +%Extra: 21Apples 12Apples 2 +%Missing: 11Peaches 12Peaches 2 +%Missing: 21Peaches 12Peaches 2%]'; l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; --Assert ut.expect(l_actual_message).to_be_like(l_expected_message); @@ -2013,12 +2153,317 @@ Diff:% l_expected_message := q'[%Actual: refcursor [ count = 2 ] was expected to equal: refcursor [ count = 3 ] %Diff: %Rows: [ 1 differences ] -%Missing: Table%]'; +%Missing: Table%]'; l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; --Assert ut.expect(l_actual_message).to_be_like(l_expected_message); end; + procedure cursor_to_contain is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select owner, object_name,object_type from all_objects where owner = user + order by 1,2,3 asc; + open l_expected for select owner, object_name,object_type from all_objects where owner = user + and rownum < 20; + + --Act + ut3.ut.expect(l_actual).to_( ut3.contain(l_expected) ); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure cursor_to_contain_uc is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select owner, object_name, object_type from all_objects where owner = user + order by 1,2,3 asc; + open l_expected for select object_type, owner, object_name from all_objects where owner = user + and rownum < 20; + + --Act + ut3.ut.expect(l_actual).to_( ut3.contain(l_expected).uc() ); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure cursor_to_contain_unordered is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for + select owner, object_name,object_type from all_objects where owner = user + order by 1,2,3 asc; + open l_expected for + select owner, object_name,object_type from all_objects where owner = user and rownum < 20; + + --Act + ut3.ut.expect(l_actual).to_( ut3.contain(l_expected).unordered() ); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure cursor_to_contain_fail is + l_actual sys_refcursor; + l_expected sys_refcursor; + l_expected_message varchar2(32767); + l_actual_message varchar2(32767); + begin + --Arrange + open l_actual for select owner, object_name,object_type from all_objects where owner = user + and rownum < 5; + open l_expected for select owner, object_name,object_type from all_objects where owner = user + and rownum < 10; + + --Act + ut3.ut.expect(l_actual).to_contain(l_expected); + --Assert + l_expected_message := q'[%Actual: refcursor [ count = 4 ] was expected to contain: refcursor [ count = 9 ] +%Diff: +%Rows: [ 5 differences ] +%Missing: %%%% +%Missing: %%%% +%Missing: %%%% +%Missing: %%%% +%Missing: %%%%]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + + procedure cursor_contain_joinby is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select username,user_id from all_users; + open l_expected for select username ,user_id from all_users where rownum < 5; + + --Act + ut3.ut.expect(l_actual).to_contain(l_expected).join_by('USERNAME'); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure cursor_contain_joinby_fail is + l_actual sys_refcursor; + l_expected sys_refcursor; + l_expected_message varchar2(32767); + l_actual_message varchar2(32767); + begin + --Arrange + open l_actual for select username, user_id from all_users + union all + select 'TEST' username, -600 user_id from dual + order by 1 desc; + open l_expected for select username, user_id from all_users + union all + select 'TEST' username, -601 user_id from dual + order by 1 asc; + + --Act + ut3.ut.expect(l_actual).to_contain(l_expected).join_by('USERNAME'); + --Assert + l_expected_message := q'[%Actual: refcursor [ count = % ] was expected to contain: refcursor [ count = % ] +%Diff: +%Rows: [ 1 differences ] +%PK TEST - Actual: -600 +%PK TEST - Expected: -601%]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + + end; + + procedure to_contain_incl_cols_as_list + as + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select rownum as rn, 'a' as "A_Column", 'c' as A_COLUMN, 'x' SOME_COL, 'd' "Some_Col" from dual a connect by level < 6; + open l_expected for select rownum as rn, 'a' as "A_Column", 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; + --Act + ut3.ut.expect(l_actual).to_contain(l_expected).include(ut3.ut_varchar2_list('RN','//A_Column','SOME_COL')); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure to_cont_join_incl_cols_as_lst + as + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select rownum as rn, 'a' as "A_Column", 'c' as A_COLUMN, 'x' SOME_COL, 'd' "Some_Col" from dual a connect by level < 10; + open l_expected for select rownum as rn, 'a' as "A_Column", 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; + --Act + ut3.ut.expect(l_actual).to_contain(l_expected).include(ut3.ut_varchar2_list('RN','//A_Column','SOME_COL')).join_by('RN'); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure contain_join_excl_cols_as_lst + as + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select rownum as rn, 'a' as "A_Column", 'c' as A_COLUMN, 'x' SOME_COL, 'd' "Some_Col" from dual a connect by level < 10; + open l_expected for select rownum as rn, 'a' as "A_Column", 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; + --Act + ut3.ut.expect(l_actual).to_contain(l_expected).exclude(ut3.ut_varchar2_list('//Some_Col','A_COLUMN')).join_by('RN'); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure contain_excl_cols_as_list + as + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select rownum as rn, 'a' as "A_Column", 'c' as A_COLUMN, 'x' SOME_COL, 'd' "Some_Col" from dual a connect by level < 10; + open l_expected for select rownum as rn, 'a' as "A_Column", 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; + --Act + ut3.ut.expect(l_actual).to_contain(l_expected).exclude(ut3.ut_varchar2_list('A_COLUMN|//Some_Col')); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure cursor_not_to_contain + as + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + open l_expected for select 'TEST' username, -600 user_id from dual; + + open l_actual for select username, user_id from all_users + union all + select 'TEST1' username, -601 user_id from dual; + + --Act + ut3.ut.expect(l_actual).not_to_contain(l_expected); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure cursor_not_to_contain_fail is + l_actual sys_refcursor; + l_expected sys_refcursor; + l_expected_message varchar2(32767); + l_actual_message varchar2(32767); + begin + --Arrange + open l_expected for select 'TEST' username, -600 user_id from dual; + + open l_actual for select username, user_id from all_users + union all + select 'TEST' username, -600 user_id from dual; + + --Act + ut3.ut.expect(l_actual).not_to_contain(l_expected); + --Assert + l_expected_message := q'[%Actual: (refcursor [ count = % ])% +%Data-types:% +%VARCHAR2NUMBER% +%Data:% +%was expected not to contain:(refcursor [ count = 1 ])% +%Data-types:% +%CHARNUMBER% +%Data:% +%TEST-600%]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + + procedure cursor_not_to_contain_joinby is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select username,rownum * 10 user_id from all_users where rownum < 5; + open l_expected for select username||to_char(rownum) username ,rownum user_id from all_users where rownum < 5; + + --Act + ut3.ut.expect(l_actual).not_to_contain(l_expected).join_by('USER_ID'); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure not_cont_join_incl_cols_as_lst is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select rownum as rn, 'b' as "A_Column", 'c' as A_COLUMN, 'x' SOME_COL, 'd' "Some_Col" from dual a connect by level < 10; + open l_expected for select rownum * 20 rn, 'a' as "A_Column", 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; + --Act + ut3.ut.expect(l_actual).not_to_contain(l_expected).include(ut3.ut_varchar2_list('RN','//A_Column','SOME_COL')).join_by('RN'); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure not_cont_join_excl_cols_as_lst is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select rownum as rn, 'a' as "A_Column", 'c' as A_COLUMN, 'y' SOME_COL, 'd' "Some_Col" from dual a connect by level < 10; + open l_expected for select rownum * 20 as rn, 'a' as "A_Column", 'd' as A_COLUMN, 'x' SOME_COL, 'c' "Some_Col" from dual a connect by level < 4; + --Act + ut3.ut.expect(l_actual).not_to_contain(l_expected).exclude(ut3.ut_varchar2_list('//Some_Col','A_COLUMN')).join_by('RN'); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure to_contain_duplicates is + l_actual sys_refcursor; + l_expected sys_refcursor; + begin + --Arrange + open l_actual for select rownum as rn from dual a connect by level < 10 + union all + select rownum as rn from dual a connect by level < 4; + open l_expected for select rownum as rn from dual a connect by level < 4; + + --Act + ut3.ut.expect(l_actual).to_contain(l_expected); + --Assert + ut.expect(expectations.failed_expectations_data()).to_be_empty(); + end; + + procedure to_contain_duplicates_fail is + l_actual sys_refcursor; + l_expected sys_refcursor; + l_expected_message varchar2(32767); + l_actual_message varchar2(32767); + begin + --Arrange + open l_actual for select rownum as rn from dual a connect by level < 10; + open l_expected for select rownum as rn from dual a connect by level < 4 + union all select rownum as rn from dual a connect by level < 4; + + --Act + ut3.ut.expect(l_actual).to_contain(l_expected); + --Assert + l_expected_message := q'[%Actual: refcursor [ count = 9 ] was expected to contain: refcursor [ count = 6 ] +%Diff: +%Rows: [ 3 differences ] +%Missing: % +%Missing: % +%Missing: %]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); + end; + end; / diff --git a/test/core/expectations/test_expectations_cursor.pks b/test/core/expectations/test_expectations_cursor.pks index 88d59e1a3..c0195393e 100644 --- a/test/core/expectations/test_expectations_cursor.pks +++ b/test/core/expectations/test_expectations_cursor.pks @@ -68,9 +68,21 @@ create or replace package test_expectations_cursor is --%test(Gives failure when different column name is used in cursors) procedure fail_on_different_column_name; - --%test(Gives failure when different column ordering is used in cursors) + --%test(Gives failure when different column ordering is used in cursors when enforced column order) procedure fail_on_different_column_order; + --%test(Pass when different column ordering is used in cursors) + procedure pass_on_different_column_order; + + --%test(Pass when different column ordering is used in cursors - shortname) + procedure pass_on_diff_column_ord_uc; + + --%test(Fail and highlight diffrence between columns when columns are unordered and different value) + procedure fail_on_multi_diff_col_order; + + --%test(Fail and highlight diffrence between columns when columns are unordered and different value - shortname) + procedure fail_on_multi_diff_col_ord_uc; + --%test(Gives failure when different row ordering is used in cursors) procedure fail_on_different_row_order; @@ -92,7 +104,7 @@ create or replace package test_expectations_cursor is --%test(Excludes comma separated list of mixed columns and XPath) procedure exclude_columns_as_mix_csv_lst; - --%test(Exclude columns fails on invalid XPath) + --%test(Exclude column with invalid filter will result in column being included ) procedure exclude_columns_xpath_invalid; --%test(Exclude columns by XPath is case sensitive) @@ -107,9 +119,6 @@ create or replace package test_expectations_cursor is --%test(Comma separated list of columns to include is case sensitive) procedure include_columns_as_csv; - --%test(Include columns fails on invalid XPath) - procedure include_columns_xpath_invalid; - --%test(Include columns by XPath is case sensitive) procedure include_columns_xpath; @@ -134,18 +143,24 @@ create or replace package test_expectations_cursor is --%test(Reports column diff on cursor with different column name) procedure column_diff_on_col_name_diff; - --%test(Reports column diff on cursor with different column positions) + --%test(Reports column diff on cursor with different column positions when column order is enforced) procedure column_diff_on_col_position; + --%test(Reports column diff on cursor with different column positions) + procedure column_diff_on_col_pos_unord; + --%test(Reports only mismatched columns on row data mismatch) procedure data_diff_on_col_data_mismatch; --%test(Reports only first 20 rows of diff and gives a full diff count) procedure data_diff_on_20_rows_only; - --%test(Reports data diff and column diff when both are different) + --%test(Reports data diff and column diff when both are different with enforced ordered columns) procedure column_and_data_diff; + --%test(Reports data diff and column diff when both are different when columns are not ordered) + procedure col_and_data_diff_not_ordered; + procedure prepare_table; procedure cleanup_table; @@ -193,21 +208,24 @@ create or replace package test_expectations_cursor is --%test(Reports column match on cursor with column name implicit ) procedure col_mtch_on_col_name_implicit; - --%test( Fail on passing implicit column name as include filter ) - procedure include_col_name_implicit; - - --%test( Fail on passing implicit column name as exclude filter ) - procedure exclude_col_name_implicit; - --%test( Compare cursors using unordered method success) procedure cursor_unorderd_compr_success; + --%test( Compare cursors using unordered method success and unordered columns position) + procedure cursor_unord_compr_success_uc; + --%test( Compare cursors using unordered method failure) procedure cursor_unordered_compare_fail; --%test( Compare cursors join by single key ) procedure cursor_joinby_compare; + --%test( Compare cursors join by single key with unordered columns position using shortname) + procedure cursor_joinby_compare_uc; + + --%test(Compare cursors by single key with unordered columns position) + procedure cursor_joinby_col_not_ord; + --%test( Compare cursors join by composite key) procedure cursor_joinby_compare_twocols; @@ -231,6 +249,9 @@ create or replace package test_expectations_cursor is --%test( Compare cursors join by single key more than 1000 rows) procedure cursor_joinby_compare_1000; + + --%test( Compare cursors unorder more than 1000 rows) + procedure cursor_unorder_compare_1000; --%test( Compare two column cursors join by and fail to match ) procedure cursor_joinby_compare_fail; @@ -319,5 +340,56 @@ create or replace package test_expectations_cursor is --%test( Unordered fix for issues with duplicate no : #764 ) procedure unordered_fix_764; + --%test( Success when cursor contains data from another cursor) + procedure cursor_to_contain; + + --%test( Fail cursor contains data from another cursor using second keyword) + procedure cursor_to_contain_fail; + + --%test( Success cursor to contain cursor with unordered columns) + procedure cursor_to_contain_uc; + + --%test( Does not fail when comparing cursor to contain cursor with unordered rows option) + procedure cursor_to_contain_unordered; + + --%test( Cursor contains data from another cursor with joinby) + procedure cursor_contain_joinby; + + --%test( Fail cursor contains data from another cursor with joinby) + procedure cursor_contain_joinby_fail; + + --%test(Cursor contains data with list of columns to include) + procedure to_contain_incl_cols_as_list; + + --%test(Cursor contains data with of columns to include and join by value) + procedure to_cont_join_incl_cols_as_lst; + + --%test(Cursor contains data with of columns to exclude and join by value) + procedure contain_join_excl_cols_as_lst; + + --%test(Cursor contains data with of columns to exclude) + procedure contain_excl_cols_as_list; + + --%test( Cursor not to contains data from another cursor) + procedure cursor_not_to_contain; + + --%test( Cursor fail not to contains data from another cursor) + procedure cursor_not_to_contain_fail; + + --%test( Cursor not contains data from another cursor with joinby clause) + procedure cursor_not_to_contain_joinby; + + --%test(Cursor not contains data with of columns to include and join by value) + procedure not_cont_join_incl_cols_as_lst; + + --%test(Cursor not contains data with of columns to exclude and join by value) + procedure not_cont_join_excl_cols_as_lst; + + --%test(Cursor to contain duplicates) + procedure to_contain_duplicates; + + --%test(Cursor to contain duplicates fail) + procedure to_contain_duplicates_fail; + end; / diff --git a/test/core/expectations/unary/test_expect_to_be_empty.pkb b/test/core/expectations/unary/test_expect_to_be_empty.pkb index ef4c34c12..0db499906 100644 --- a/test/core/expectations/unary/test_expect_to_be_empty.pkb +++ b/test/core/expectations/unary/test_expect_to_be_empty.pkb @@ -166,7 +166,11 @@ was expected to be empty%%]'; --Assert ut.expect(expectations.failed_expectations_data()).not_to_be_empty(); end; - + + /** + * https://docs.oracle.com/en/database/oracle/oracle-database/18/adobj/declaring-initializing-objects-in-plsql.html#GUID-23135172-82E2-4C3E-800D-E584B43B578E + * User-defined types, just like collections, are atomically null, until you initialize the object by calling the constructor for its object type. That is, the object itself is null, not just its attributes. + */ procedure fail_not_be_empty_object is l_actual anydata; begin diff --git a/test/core/expectations/unary/test_expect_to_be_empty.pks b/test/core/expectations/unary/test_expect_to_be_empty.pks index f48db600f..92a7e0bef 100644 --- a/test/core/expectations/unary/test_expect_to_be_empty.pks +++ b/test/core/expectations/unary/test_expect_to_be_empty.pks @@ -66,7 +66,7 @@ create or replace package test_expect_to_be_empty is --%test(Gives failure for an empty collection) procedure fail_not_be_empty_null_coll; - --%test(Gives failure for an object) + --%test(Gives failure for an empty object) procedure fail_not_be_empty_object; --%test(Gives failure for a null object) diff --git a/test/core/expectations/unary/test_expect_to_have_count.pkb b/test/core/expectations/unary/test_expect_to_have_count.pkb index 663f60962..a95a6778a 100644 --- a/test/core/expectations/unary/test_expect_to_have_count.pkb +++ b/test/core/expectations/unary/test_expect_to_have_count.pkb @@ -158,22 +158,32 @@ create or replace package body test_expect_to_have_count is end; procedure fail_have_count_number is + l_expected_message varchar2(32767); + l_actual_message varchar2(32767); begin -- Act ut3.ut.expect( 1 ).to_( ut3.have_count(0) ); --Assert - ut.expect(expectations.failed_expectations_data()).not_to_be_empty(); + l_expected_message := q'[%The matcher 'have count' cannot be used with data type (number).%]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); end; procedure fail_not_have_count_object is l_actual anydata; + l_expected_message varchar2(32767); + l_actual_message varchar2(32767); begin --Arrange l_actual := anydata.convertObject(ut3.ut_data_value_number(1)); -- Act ut3.ut.expect(l_actual).not_to_have_count(0); --Assert - ut.expect(expectations.failed_expectations_data()).not_to_be_empty(); + l_expected_message := q'[%The matcher 'have count' cannot be used with data type (ut3.ut_data_value_number).%]'; + l_actual_message := ut3.ut_expectation_processor.get_failed_expectations()(1).message; + --Assert + ut.expect(l_actual_message).to_be_like(l_expected_message); end; procedure fail_not_have_count_null_obj is diff --git a/test/core/min_grant_user/test_min_grant_user.pkb b/test/core/min_grant_user/test_min_grant_user.pkb index 13893357c..aafa88fd0 100644 --- a/test/core/min_grant_user/test_min_grant_user.pkb +++ b/test/core/min_grant_user/test_min_grant_user.pkb @@ -6,7 +6,7 @@ create or replace package body test_min_grant_user is execute immediate 'begin ut3$user#.test_cursor_grants.run(); end;'; l_results := core.get_dbms_output_as_clob(); --Assert - ut.expect( l_results ).to_be_like( '%execute join by test [.% sec]' || + ut.expect( l_results ).to_be_like( '%execute join by test [% sec]' || '%1 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s)%' ); end; diff --git a/test/helpers/test_tab_varchar2.tps b/test/helpers/test_tab_varchar2.tps new file mode 100644 index 000000000..17086292a --- /dev/null +++ b/test/helpers/test_tab_varchar2.tps @@ -0,0 +1,12 @@ +declare + l_exists integer; +begin + select count(1) into l_exists from user_types where type_name = 'T_TAB_VARCHAR'; + if l_exists > 0 then + execute immediate 'drop type t_tab_varchar force'; + end if; +end; +/ + +create or replace type t_tab_varchar is table of varchar2(1) +/ \ No newline at end of file diff --git a/test/helpers/test_tab_varray.tps b/test/helpers/test_tab_varray.tps new file mode 100644 index 000000000..3c684afca --- /dev/null +++ b/test/helpers/test_tab_varray.tps @@ -0,0 +1,13 @@ +declare + l_exists integer; +begin + select count(1) into l_exists from user_types where type_name = 'T_VARRAY'; + if l_exists > 0 then + execute immediate 'drop type t_varray force'; + end if; +end; +/ + + +create or replace type t_varray is varray(1) of number +/ \ No newline at end of file diff --git a/test/install_and_run_tests.sh b/test/install_and_run_tests.sh index 4e00a01c9..385cb46a4 100755 --- a/test/install_and_run_tests.sh +++ b/test/install_and_run_tests.sh @@ -10,6 +10,7 @@ time "$SQLCLI" ${UT3_TESTER}/${UT3_TESTER_PASSWORD}@//${CONNECTION_STR} @install cd .. + time utPLSQL-cli/bin/utplsql run ${UT3_TESTER}/${UT3_TESTER_PASSWORD}@${CONNECTION_STR} \ -source_path=source -owner=ut3 \ -test_path=test -c \ diff --git a/test/install_tests.sql b/test/install_tests.sql index ae2a4f1a0..5a2f7d664 100644 --- a/test/install_tests.sql +++ b/test/install_tests.sql @@ -15,6 +15,8 @@ alter session set plsql_optimize_level=0; @@helpers/test_dummy_object_list.tps @@helpers/test_event_object.tps @@helpers/test_event_list.tps +@@helpers/test_tab_varchar2.tps +@@helpers/test_tab_varray.tps @@helpers/ut3user#.test_cursor_grants.pks @@helpers/ut3user#.test_cursor_grants.pkb