google-pso-data-validator 8.5.2__tar.gz → 8.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. {google_pso_data_validator-8.5.2/google_pso_data_validator.egg-info → google_pso_data_validator-8.7.0}/PKG-INFO +15 -4
  2. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/README.md +13 -2
  3. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/__init__.py +0 -1
  4. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/cli_tools.py +26 -9
  5. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/client_info.py +0 -1
  6. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/clients.py +23 -11
  7. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/config_manager.py +41 -2
  8. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/consts.py +2 -0
  9. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/find_tables.py +34 -10
  10. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/gcs_helper.py +0 -1
  11. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/query_builder/random_row_builder.py +15 -3
  12. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/result_handlers/base_backend.py +0 -1
  13. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/result_handlers/bigquery.py +0 -1
  14. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/result_handlers/postgres.py +0 -1
  15. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/schema_validation.py +4 -2
  16. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/util.py +17 -0
  17. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0/google_pso_data_validator.egg-info}/PKG-INFO +15 -4
  18. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/google_pso_data_validator.egg-info/SOURCES.txt +5 -0
  19. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/google_pso_data_validator.egg-info/requires.txt +1 -1
  20. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/setup.py +2 -2
  21. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_addon/api.py +44 -0
  22. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_addon/operations.py +34 -71
  23. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_cloud_spanner/compiler.py +0 -1
  24. google_pso_data_validator-8.7.0/third_party/ibis/ibis_db2/__init__.py +271 -0
  25. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_db2/api.py +11 -7
  26. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_db2/datatypes.py +22 -6
  27. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_db2/registry.py +44 -18
  28. google_pso_data_validator-8.7.0/third_party/ibis/ibis_db2_zos/__init__.py +84 -0
  29. google_pso_data_validator-8.7.0/third_party/ibis/ibis_db2_zos/api.py +45 -0
  30. google_pso_data_validator-8.7.0/third_party/ibis/ibis_db2_zos/compiler.py +27 -0
  31. google_pso_data_validator-8.7.0/third_party/ibis/ibis_db2_zos/registry.py +105 -0
  32. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_impala/api.py +3 -1
  33. google_pso_data_validator-8.7.0/third_party/ibis/ibis_impala/registry.py +70 -0
  34. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_mssql/__init__.py +0 -1
  35. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_oracle/__init__.py +0 -1
  36. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_oracle/datatypes.py +3 -3
  37. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_postgres/registry.py +6 -1
  38. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_redshift/__init__.py +16 -9
  39. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_sybase/__init__.py +0 -1
  40. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_sybase/datatypes.py +0 -1
  41. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_teradata/__init__.py +3 -3
  42. google_pso_data_validator-8.5.2/third_party/ibis/ibis_db2/__init__.py +0 -172
  43. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/LICENSE +0 -0
  44. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/__main__.py +0 -0
  45. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/app.py +0 -0
  46. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/combiner.py +0 -0
  47. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/data_validation.py +0 -0
  48. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/exceptions.py +0 -0
  49. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/jellyfish_distance.py +0 -0
  50. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/metadata.py +0 -0
  51. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/partition_builder.py +0 -0
  52. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/query_builder/__init__.py +0 -0
  53. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/query_builder/partition_row_builder.py +0 -0
  54. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/query_builder/query_builder.py +0 -0
  55. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/raw_query.py +0 -0
  56. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/result_handlers/__init__.py +0 -0
  57. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/result_handlers/factory.py +0 -0
  58. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/result_handlers/text.py +1 -1
  59. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/secret_manager.py +0 -0
  60. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/state_manager.py +0 -0
  61. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/data_validation/validation_builder.py +0 -0
  62. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/google_pso_data_validator.egg-info/dependency_links.txt +0 -0
  63. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/google_pso_data_validator.egg-info/entry_points.txt +0 -0
  64. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/google_pso_data_validator.egg-info/top_level.txt +0 -0
  65. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/setup.cfg +0 -0
  66. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_addon/__init__.py +0 -0
  67. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_bigquery/__init__.py +0 -0
  68. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_bigquery/api.py +0 -0
  69. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_bigquery/registry.py +0 -0
  70. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_cloud_spanner/__init__.py +0 -0
  71. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_cloud_spanner/api.py +0 -0
  72. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_cloud_spanner/client.py +0 -0
  73. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_cloud_spanner/datatypes.py +0 -0
  74. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_cloud_spanner/registry.py +0 -0
  75. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_cloud_spanner/tests/__init__.py +0 -0
  76. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_cloud_spanner/tests/conftest.py +0 -0
  77. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_cloud_spanner/to_pandas.py +0 -0
  78. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_db2/compiler.py +0 -0
  79. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_impala/__init__.py +0 -0
  80. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_mssql/api.py +0 -0
  81. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_mssql/datatypes.py +0 -0
  82. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_mssql/registry.py +0 -0
  83. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_mysql/__init__.py +0 -0
  84. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_mysql/base_sql_compiler/__init__.py +0 -0
  85. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_mysql/base_sql_compiler/select_builder.py +0 -0
  86. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_mysql/compiler.py +0 -0
  87. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_oracle/api.py +0 -0
  88. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_oracle/compiler.py +0 -0
  89. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_oracle/registry.py +0 -0
  90. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_postgres/__init__.py +0 -0
  91. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_postgres/client.py +0 -0
  92. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_postgres/datatypes.py +0 -0
  93. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_redshift/api.py +0 -0
  94. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_redshift/compiler.py +0 -0
  95. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_snowflake/__init__.py +0 -0
  96. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_snowflake/api.py +0 -0
  97. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_snowflake/datatypes.py +0 -0
  98. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_sybase/api.py +0 -0
  99. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_sybase/compiler.py +0 -0
  100. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_sybase/registry.py +0 -0
  101. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_teradata/api.py +0 -0
  102. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_teradata/compiler.py +0 -0
  103. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_teradata/datatypes.py +0 -0
  104. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.7.0}/third_party/ibis/ibis_teradata/registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-pso-data-validator
3
- Version: 8.5.2
3
+ Version: 8.7.0
4
4
  Summary: A package to enable easy data validation
5
5
  Author: PSO DVT Engineering team
6
6
  Author-email: data-validator-eng@google.com
@@ -39,7 +39,7 @@ Requires-Dist: 1.10.11; extra == "apache-airflow"
39
39
  Provides-Extra: pyspark
40
40
  Requires-Dist: 3.0.0; extra == "pyspark"
41
41
  Provides-Extra: develop
42
- Requires-Dist: black==22.3.0; extra == "develop"
42
+ Requires-Dist: black==26.1.0; extra == "develop"
43
43
  Requires-Dist: flake8; extra == "develop"
44
44
  Requires-Dist: freezegun; extra == "develop"
45
45
  Requires-Dist: pyfakefs; extra == "develop"
@@ -83,7 +83,8 @@ DVT supports the following connection types:
83
83
 
84
84
  * [AlloyDB](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#alloydb)
85
85
  * [BigQuery](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#google-bigquery)
86
- * [DB2](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#db2)
86
+ * [Db2 LUW](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#db2_luw)
87
+ * [Db2 z/OS](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#db2_zos)
87
88
  * [FileSystem](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#filesystem)
88
89
  * [Hive](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#hive)
89
90
  * [Impala](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#impala)
@@ -130,7 +131,7 @@ The CLI is the main interface to use this tool and it has several different
130
131
  commands which can be used to create and run validations. DVT is designed to run in
131
132
  an environment connected to GCP services, specifically, BigQuery, GCS and Secret manager.
132
133
  If DVT is being run on-premises or in an environment with restricted access to GCP services, see
133
- [running DVT at on-prem](#running-dvt-at-on-prem). Below are the command syntax and options for running validations.
134
+ [running DVT at on-prem](#running-dvt-at-on-prem). Below are the command syntax and options for running validations.
134
135
 
135
136
  Alternatives to running DVT in the CLI include deploying DVT to Cloud Run, Cloud Functions, or Airflow
136
137
  ([Examples Here](https://github.com/GoogleCloudPlatform/professional-services-data-validator/tree/develop/samples)). See the [Validation Logic](https://github.com/GoogleCloudPlatform/professional-services-data-validator#validation-logic) section
@@ -719,17 +720,27 @@ target. The `find-tables` command:
719
720
  - Pulls all tables in the source (applying a supplied `allowed-schemas` filter)
720
721
  - Pulls all tables from the target
721
722
  - Uses Jaro Similarity algorithm to match tables
723
+ - Optionally translates source schema names using mappings supplied in `allowed-schemas` before matching
722
724
  - Finally, it prints a JSON list of tables which can be a reference for the
723
725
  validation run config.
724
726
 
725
727
  Note that our default value for the `score-cutoff` parameter is 1 and it seeks for identical matches. If no matches occur, reduce this value as deemed necessary. By using smaller numbers such as 0.7, 0.65 etc you can get more matches. For reference, we make use of [this jaro_similarity method](https://jamesturk.github.io/jellyfish/functions/#jaro-similarity) for the string comparison.
726
728
 
729
+
727
730
  ```shell
728
731
  data-validation find-tables --source-conn source --target-conn target \
729
732
  --allowed-schemas pso_data_validator \
730
733
  --score-cutoff 1
731
734
  ```
732
735
 
736
+ If your source and target environments have different schema names (e.g., `prod_raw` vs `dwh_raw`), you can provide a mapping directly in the `--allowed-schemas` flag using the `source_schema=target_schema` syntax.
737
+
738
+ ```shell
739
+ data-validation find-tables --source-conn source --target-conn target \
740
+ --allowed-schemas prod=dwh \
741
+ --score-cutoff 1
742
+ ```
743
+
733
744
  ### Using Beta CLI Features
734
745
 
735
746
  There may be occasions we want to release a new CLI feature under a Beta flag.
@@ -23,7 +23,8 @@ DVT supports the following connection types:
23
23
 
24
24
  * [AlloyDB](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#alloydb)
25
25
  * [BigQuery](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#google-bigquery)
26
- * [DB2](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#db2)
26
+ * [Db2 LUW](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#db2_luw)
27
+ * [Db2 z/OS](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#db2_zos)
27
28
  * [FileSystem](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#filesystem)
28
29
  * [Hive](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#hive)
29
30
  * [Impala](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#impala)
@@ -70,7 +71,7 @@ The CLI is the main interface to use this tool and it has several different
70
71
  commands which can be used to create and run validations. DVT is designed to run in
71
72
  an environment connected to GCP services, specifically, BigQuery, GCS and Secret manager.
72
73
  If DVT is being run on-premises or in an environment with restricted access to GCP services, see
73
- [running DVT at on-prem](#running-dvt-at-on-prem). Below are the command syntax and options for running validations.
74
+ [running DVT at on-prem](#running-dvt-at-on-prem). Below are the command syntax and options for running validations.
74
75
 
75
76
  Alternatives to running DVT in the CLI include deploying DVT to Cloud Run, Cloud Functions, or Airflow
76
77
  ([Examples Here](https://github.com/GoogleCloudPlatform/professional-services-data-validator/tree/develop/samples)). See the [Validation Logic](https://github.com/GoogleCloudPlatform/professional-services-data-validator#validation-logic) section
@@ -659,17 +660,27 @@ target. The `find-tables` command:
659
660
  - Pulls all tables in the source (applying a supplied `allowed-schemas` filter)
660
661
  - Pulls all tables from the target
661
662
  - Uses Jaro Similarity algorithm to match tables
663
+ - Optionally translates source schema names using mappings supplied in `allowed-schemas` before matching
662
664
  - Finally, it prints a JSON list of tables which can be a reference for the
663
665
  validation run config.
664
666
 
665
667
  Note that our default value for the `score-cutoff` parameter is 1 and it seeks for identical matches. If no matches occur, reduce this value as deemed necessary. By using smaller numbers such as 0.7, 0.65 etc you can get more matches. For reference, we make use of [this jaro_similarity method](https://jamesturk.github.io/jellyfish/functions/#jaro-similarity) for the string comparison.
666
668
 
669
+
667
670
  ```shell
668
671
  data-validation find-tables --source-conn source --target-conn target \
669
672
  --allowed-schemas pso_data_validator \
670
673
  --score-cutoff 1
671
674
  ```
672
675
 
676
+ If your source and target environments have different schema names (e.g., `prod_raw` vs `dwh_raw`), you can provide a mapping directly in the `--allowed-schemas` flag using the `source_schema=target_schema` syntax.
677
+
678
+ ```shell
679
+ data-validation find-tables --source-conn source --target-conn target \
680
+ --allowed-schemas prod=dwh \
681
+ --score-cutoff 1
682
+ ```
683
+
673
684
  ### Using Beta CLI Features
674
685
 
675
686
  There may be occasions we want to release a new CLI feature under a Beta flag.
@@ -14,6 +14,5 @@
14
14
 
15
15
  from importlib.metadata import version
16
16
 
17
-
18
17
  __version__ = version("google-pso-data-validator")
19
18
  __all__ = ["__version__"]
@@ -75,9 +75,13 @@ CONNECTION_SOURCE_FIELDS = {
75
75
  ["project_id", "GCP Project to use for BigQuery"],
76
76
  [
77
77
  "client_project_id",
78
- "(Optional) BigQuery job/billing project (can differ from data project)",
78
+ "(Deprecated) BigQuery job/billing project (can differ from data project)",
79
79
  ],
80
- ["google_service_account_key_path", "(Optional) GCP SA Key Path"],
80
+ [
81
+ "billing_project_id",
82
+ "(Optional) BigQuery billing project to override default billing project",
83
+ ],
84
+ ["google_service_account_key_path", "(Deprecated) GCP SA Key Path"],
81
85
  [
82
86
  "api_endpoint",
83
87
  '(Optional) GCP BigQuery API endpoint (e.g. "https://bigquery-mypsc.p.googleapis.com")',
@@ -149,7 +153,7 @@ CONNECTION_SOURCE_FIELDS = {
149
153
  ["project_id", "GCP Project to use for Spanner"],
150
154
  ["instance_id", "ID of Spanner instance to connect to"],
151
155
  ["database_id", "ID of Spanner database (schema) to connect to"],
152
- ["google_service_account_key_path", "(Optional) GCP SA Key Path"],
156
+ ["google_service_account_key_path", "(Deprecated) GCP SA Key Path"],
153
157
  [
154
158
  "api_endpoint",
155
159
  '(Optional) GCP Spanner API endpoint (e.g. "https://spanner-mypsc.p.googleapis.com")',
@@ -199,13 +203,24 @@ CONNECTION_SOURCE_FIELDS = {
199
203
  ["http_path", "URL path of HTTP proxy"],
200
204
  ],
201
205
  consts.SOURCE_TYPE_DB2: [
202
- ["host", "DB2 host"],
203
- ["port", "DB2 port (50000 if not provided)"],
206
+ ["host", "Db2 host"],
207
+ ["port", "Db2 port (50000 if not provided)"],
204
208
  ["user", "Username to connect to"],
205
209
  ["password", "Password for authentication of user"],
206
- ["database", "Database in DB2 to connect to"],
207
- ["url", "URL link in DB2 to connect to"],
208
- ["driver", "Driver link in DB2 to connect to (default ibm_db_sa)"],
210
+ ["database", "Database in Db2 to connect to"],
211
+ ["url", "URL link in Db2 to connect to"],
212
+ ["driver", "Driver link in Db2 to connect to (default ibm_db_sa)"],
213
+ ["connect_args", "(Optional) Additional connection argument mapping"],
214
+ ],
215
+ consts.SOURCE_TYPE_DB2_ZOS: [
216
+ ["host", "Db2 host"],
217
+ ["port", "Db2 port (50000 if not provided)"],
218
+ ["user", "Username to connect to"],
219
+ ["password", "Password for authentication of user"],
220
+ ["database", "Database in Db2 to connect to"],
221
+ ["url", "URL link in Db2 to connect to"],
222
+ ["driver", "Driver link in Db2 to connect to (default ibm_db_sa)"],
223
+ ["connect_args", "(Optional) Additional connection argument mapping"],
209
224
  ],
210
225
  }
211
226
 
@@ -404,7 +419,9 @@ def _configure_find_tables(subparsers):
404
419
  "--target-conn", "-tc", help="Target connection name."
405
420
  )
406
421
  find_tables_parser.add_argument(
407
- "--allowed-schemas", "-as", help="List of source schemas to match."
422
+ "--allowed-schemas",
423
+ "-as",
424
+ help="List of source schemas to match. Can include mappings like 'source_schema=target_schema'.",
408
425
  )
409
426
  find_tables_parser.add_argument(
410
427
  "--include-views",
@@ -16,7 +16,6 @@ from google.api_core import client_info as http_client_info
16
16
 
17
17
  import data_validation
18
18
 
19
-
20
19
  APPLICATION_NAME = "google-pso-tool/data-validator"
21
20
  USER_AGENT = "{}/{}".format(APPLICATION_NAME, data_validation.__version__)
22
21
 
@@ -53,6 +53,7 @@ IBIS_ALCHEMY_BACKENDS = [
53
53
  "oracle",
54
54
  "postgres",
55
55
  "db2",
56
+ "db2_zos",
56
57
  "mssql",
57
58
  "redshift",
58
59
  "snowflake",
@@ -91,8 +92,10 @@ except ImportError:
91
92
  # DB2 requires ibm_db_sa
92
93
  try:
93
94
  from third_party.ibis.ibis_db2.api import db2_connect
95
+ from third_party.ibis.ibis_db2_zos.api import db2_zos_connect
94
96
  except ImportError:
95
97
  db2_connect = _raise_missing_client_error("pip install ibm_db_sa")
98
+ db2_zos_connect = _raise_missing_client_error("pip install ibm_db_sa")
96
99
 
97
100
  # Sybase requires sqlalchemy_sybase package.
98
101
  try:
@@ -111,15 +114,13 @@ def get_google_bigquery_client(
111
114
  job_config = bigquery.QueryJobConfig(
112
115
  connection_properties=[bigquery.ConnectionProperty("time_zone", "UTC")]
113
116
  )
114
- effective_project = quota_project_id or project_id
115
117
  options = None
116
118
  if api_endpoint or quota_project_id:
117
119
  options = client_options.ClientOptions(
118
120
  api_endpoint=api_endpoint,
119
- quota_project_id=quota_project_id if quota_project_id else None,
120
121
  )
121
122
  return bigquery.Client(
122
- project=effective_project,
123
+ project=quota_project_id,
123
124
  client_info=info,
124
125
  credentials=credentials,
125
126
  default_query_job_config=job_config,
@@ -136,7 +137,6 @@ def _get_google_bqstorage_client(
136
137
  if api_endpoint or quota_project_id:
137
138
  options = client_options.ClientOptions(
138
139
  api_endpoint=api_endpoint,
139
- quota_project_id=quota_project_id if quota_project_id else None,
140
140
  )
141
141
  from google.cloud import bigquery_storage_v1 as bigquery_storage
142
142
 
@@ -152,20 +152,26 @@ def get_bigquery_client(
152
152
  credentials=None,
153
153
  api_endpoint: Optional[str] = None,
154
154
  storage_api_endpoint: Optional[str] = None,
155
- client_project_id: Optional[str] = None,
155
+ client_project_id: Optional[str] = None, # to be deprecated in the future
156
+ billing_project_id: Optional[str] = None,
156
157
  ):
158
+ if client_project_id:
159
+ logging.warning(
160
+ "client_project_id is deprecated and will be removed in the future, use --billing-project-id instead"
161
+ )
162
+ billing_project_id = client_project_id
157
163
  google_client = get_google_bigquery_client(
158
164
  project_id,
159
165
  credentials=credentials,
160
166
  api_endpoint=api_endpoint,
161
- quota_project_id=client_project_id,
167
+ quota_project_id=billing_project_id,
162
168
  )
163
169
  bqstorage_client = None
164
170
  if storage_api_endpoint:
165
171
  bqstorage_client = _get_google_bqstorage_client(
166
172
  credentials=credentials,
167
173
  api_endpoint=storage_api_endpoint,
168
- quota_project_id=client_project_id,
174
+ quota_project_id=billing_project_id,
169
175
  )
170
176
 
171
177
  return bigquery_connect(
@@ -227,6 +233,7 @@ def get_ibis_table(client, schema_name, table_name, database_name=None):
227
233
  "oracle",
228
234
  "postgres",
229
235
  "db2",
236
+ "db2_zos",
230
237
  "mssql",
231
238
  "redshift",
232
239
  "sybase",
@@ -342,10 +349,14 @@ def get_data_client(connection_config):
342
349
  consts.GOOGLE_SERVICE_ACCOUNT_KEY_PATH
343
350
  )
344
351
  if key_path:
345
- decrypted_connection_config[
346
- "credentials"
347
- ] = google.oauth2.service_account.Credentials.from_service_account_file(
348
- key_path
352
+ logging.warning(
353
+ """"Use of Service Account Keys is strongly discouraged. This option is deprecated and may be removed.
354
+ Run DVT with the service account identity by using application-default-credentials with --impersonate-service-account"""
355
+ )
356
+ decrypted_connection_config["credentials"] = (
357
+ google.oauth2.service_account.Credentials.from_service_account_file(
358
+ key_path
359
+ )
349
360
  )
350
361
 
351
362
  if source_type not in CLIENT_LOOKUP:
@@ -430,4 +441,5 @@ CLIENT_LOOKUP = {
430
441
  consts.SOURCE_TYPE_SPANNER: spanner_connect,
431
442
  consts.SOURCE_TYPE_SYBASE: sybase_connect,
432
443
  consts.SOURCE_TYPE_DB2: db2_connect,
444
+ consts.SOURCE_TYPE_DB2_ZOS: db2_zos_connect,
433
445
  }
@@ -667,12 +667,34 @@ class ConfigManager(object):
667
667
  isinstance(source_type, dt.UUID) or isinstance(target_type, dt.UUID)
668
668
  )
669
669
 
670
+ def _is_db2_blob(self, source_column_name: str, target_column_name: str) -> bool:
671
+ """Returns True when either source or target column is Db2 (LUW and z/OS) BLOB data type."""
672
+ return self._is_raw_data_type(
673
+ "db2", source_column_name, target_column_name, ["BLOB"]
674
+ ) or self._is_raw_data_type(
675
+ "db2_zos", source_column_name, target_column_name, ["BLOB"]
676
+ )
677
+
670
678
  def _is_db2_xml(self, source_column_name: str, target_column_name: str) -> bool:
671
- """Returns True when either source or target column is Oracle LOB data type."""
679
+ """Returns True when either source or target column is Db2 XML data type."""
672
680
  return self._is_raw_data_type(
673
681
  "db2", source_column_name, target_column_name, ["XML"]
674
682
  )
675
683
 
684
+ def _is_db2_zos_blob(
685
+ self, source_column_name: str, target_column_name: str
686
+ ) -> bool:
687
+ """Returns True when either source or target column is Db2 z/OS BLOB data type."""
688
+ return self._is_raw_data_type(
689
+ "db2_zos", source_column_name, target_column_name, ["BLOB"]
690
+ )
691
+
692
+ def _is_db2_zos_xml(self, source_column_name: str, target_column_name: str) -> bool:
693
+ """Returns True when either source or target column is Db2 z/OS XML data type."""
694
+ return self._is_raw_data_type(
695
+ "db2_zos", source_column_name, target_column_name, ["XML"]
696
+ )
697
+
676
698
  def _is_oracle_lob(self, source_column_name: str, target_column_name: str) -> bool:
677
699
  """Returns True when either source or target column is Oracle LOB data type.
678
700
 
@@ -1058,7 +1080,11 @@ class ConfigManager(object):
1058
1080
  # Oracle BLOB is invalid for use with SQL COUNT function.
1059
1081
  # The expression below returns True if client is Oracle which
1060
1082
  # has the effect of triggering use of byte_length transformation.
1061
- return self._is_oracle_lob(source_column, target_column)
1083
+ # Same for Db2 z/OS.
1084
+ return bool(
1085
+ self._is_oracle_lob(source_column, target_column)
1086
+ or self._is_db2_zos_blob(source_column, target_column)
1087
+ )
1062
1088
  else:
1063
1089
  # Convert to length for any min/max/sum on binary columns.
1064
1090
  return True
@@ -1146,6 +1172,11 @@ class ConfigManager(object):
1146
1172
  f"Skipping {agg_type} on {column} due to SQL Server image data type"
1147
1173
  )
1148
1174
  continue
1175
+ elif self._is_db2_zos_xml(column, column):
1176
+ logging.info(
1177
+ f"Skipping {agg_type} on {column} due to Db2 z/OS XML data type"
1178
+ )
1179
+ continue
1149
1180
  elif agg_type != "count" and self._is_db2_xml(column, column):
1150
1181
  logging.info(
1151
1182
  f"Skipping {agg_type} on {column} due to Db2 XML data type"
@@ -1351,6 +1382,14 @@ class ConfigManager(object):
1351
1382
  )
1352
1383
  result_source_columns.pop(source_column)
1353
1384
  result_target_columns.pop(target_column)
1385
+ if self._is_db2_blob(source_column, target_column):
1386
+ # Db2 BLOB is incompatible with Db2s own HEX function.
1387
+ # Users would need to use a custom query to a custom UDF to row validate these.
1388
+ logging.info(
1389
+ f"Skipping column {source_column} due to Db2 BLOB data type"
1390
+ )
1391
+ result_source_columns.pop(source_column)
1392
+ result_target_columns.pop(target_column)
1354
1393
 
1355
1394
  return result_source_columns, result_target_columns
1356
1395
 
@@ -130,6 +130,7 @@ YAML_VALIDATIONS = "validations"
130
130
  # Connection key constants.
131
131
  SOURCE_TYPE_BIGQUERY = "BigQuery"
132
132
  SOURCE_TYPE_DB2 = "DB2"
133
+ SOURCE_TYPE_DB2_ZOS = "DB2_ZOS"
133
134
  SOURCE_TYPE_FILESYSTEM = "FileSystem"
134
135
  SOURCE_TYPE_IMPALA = "Impala"
135
136
  SOURCE_TYPE_MSSQL = "MSSQL"
@@ -151,6 +152,7 @@ GOOGLE_SERVICE_ACCOUNT_KEY_PATH = "google_service_account_key_path"
151
152
  API_ENDPOINT = "api_endpoint"
152
153
  STORAGE_API_ENDPOINT = "storage_api_endpoint"
153
154
  CLIENT_PROJECT_ID = "client_project_id"
155
+ BILLING_PROJECT_ID = "billing_project_id"
154
156
 
155
157
  # Result Handler Output Table Fields
156
158
  VALIDATION_TYPE = "validation_type"
@@ -28,26 +28,37 @@ if TYPE_CHECKING:
28
28
  import ibis
29
29
 
30
30
 
31
- def _compare_match_tables(source_table_map, target_table_map, score_cutoff=0.8) -> list:
31
+ def _compare_match_tables(
32
+ source_table_map: dict,
33
+ target_table_map: dict,
34
+ score_cutoff: float = 0.8,
35
+ schema_map: dict = None,
36
+ ) -> list:
32
37
  """Return dict config object from matching tables."""
33
38
  # TODO(dhercher): evaluate if improved comparison and score cutoffs should be used.
34
39
  table_configs = []
40
+ schema_map = schema_map or {}
35
41
 
36
42
  target_keys = target_table_map.keys()
37
43
  for source_key in source_table_map:
44
+ source_schema = source_table_map[source_key][consts.CONFIG_SCHEMA_NAME]
45
+ source_table = source_table_map[source_key][consts.CONFIG_TABLE_NAME]
46
+
47
+ # Apply schema mapping if it exists, when there is a mapping
48
+ # lookup_key is {mapped_schema}.{source_name} which should then
49
+ # find a match in target_keys.
50
+ lookup_schema = schema_map.get(source_schema, source_schema)
51
+ lookup_key = f"{lookup_schema}.{source_table}".casefold()
52
+
38
53
  target_key = jellyfish_distance.extract_closest_match(
39
- source_key, target_keys, score_cutoff=score_cutoff
54
+ lookup_key, target_keys, score_cutoff=score_cutoff
40
55
  )
41
56
  if target_key is None:
42
57
  continue
43
58
 
44
59
  table_config = {
45
- consts.CONFIG_SCHEMA_NAME: source_table_map[source_key][
46
- consts.CONFIG_SCHEMA_NAME
47
- ],
48
- consts.CONFIG_TABLE_NAME: source_table_map[source_key][
49
- consts.CONFIG_TABLE_NAME
50
- ],
60
+ consts.CONFIG_SCHEMA_NAME: source_schema,
61
+ consts.CONFIG_TABLE_NAME: source_table,
51
62
  consts.CONFIG_TARGET_SCHEMA_NAME: target_table_map[target_key][
52
63
  consts.CONFIG_SCHEMA_NAME
53
64
  ],
@@ -95,6 +106,7 @@ def get_mapped_table_configs(
95
106
  allowed_schemas: list = None,
96
107
  include_views: bool = False,
97
108
  score_cutoff: int = 1,
109
+ schema_map: dict = None,
98
110
  ) -> list:
99
111
  """Get table list from each client and match them together into a single list of dicts."""
100
112
  source_table_map = _get_table_map(
@@ -102,7 +114,10 @@ def get_mapped_table_configs(
102
114
  )
103
115
  target_table_map = _get_table_map(target_client, include_views=include_views)
104
116
  return _compare_match_tables(
105
- source_table_map, target_table_map, score_cutoff=score_cutoff
117
+ source_table_map,
118
+ target_table_map,
119
+ score_cutoff=score_cutoff,
120
+ schema_map=schema_map,
106
121
  )
107
122
 
108
123
 
@@ -114,13 +129,22 @@ def find_tables_using_string_matching(args) -> str:
114
129
  source_client = clients.get_data_client(mgr.get_connection_config(args.source_conn))
115
130
  target_client = clients.get_data_client(mgr.get_connection_config(args.target_conn))
116
131
 
117
- allowed_schemas = cli_tools.get_arg_list(args.allowed_schemas)
132
+ allowed_schemas_raw = cli_tools.get_arg_list(args.allowed_schemas) or []
133
+ allowed_schemas = []
134
+ schema_map = {}
135
+ for schema in allowed_schemas_raw:
136
+ if "=" in schema:
137
+ schema, target_schema = schema.split("=", 1)
138
+ schema_map[schema] = target_schema
139
+ allowed_schemas.append(schema)
140
+
118
141
  table_configs = get_mapped_table_configs(
119
142
  source_client,
120
143
  target_client,
121
144
  allowed_schemas=allowed_schemas,
122
145
  include_views=args.include_views,
123
146
  score_cutoff=score_cutoff,
147
+ schema_map=schema_map,
124
148
  )
125
149
  return json.dumps(table_configs)
126
150
 
@@ -19,7 +19,6 @@ from typing import List
19
19
  from google.cloud import storage
20
20
  from data_validation import client_info
21
21
 
22
-
23
22
  WRITE_SUCCESS_STRING = "Success! Config output written to"
24
23
  DELETE_SUCCESS_STRING = "Successfully deleted"
25
24
 
@@ -19,9 +19,8 @@ import ibis
19
19
  from data_validation import clients
20
20
  from data_validation.query_builder.query_builder import QueryBuilder
21
21
 
22
-
23
22
  # Adding new data sources should be done by adding the Backend name here
24
- RANDOM_SORT_SUPPORTS = [
23
+ SUPPORTS_SORT_BY_RANDOM = [
25
24
  "bigquery",
26
25
  "teradata",
27
26
  "impala",
@@ -36,6 +35,10 @@ RANDOM_SORT_SUPPORTS = [
36
35
  "sybase",
37
36
  ]
38
37
 
38
+ SUPPORTS_SORT_BY_ALIASED_RANDOM = [
39
+ "db2_zos",
40
+ ]
41
+
39
42
 
40
43
  class RandomRowBuilder(object):
41
44
  def __init__(self, primary_keys: List[str], batch_size: int):
@@ -88,7 +91,16 @@ class RandomRowBuilder(object):
88
91
  self, data_client: ibis.backends.base.BaseBackend, table: ibis.Expr
89
92
  ) -> ibis.Expr:
90
93
  """Return a randomly sorted query if it is supported for the client."""
91
- if data_client.name in RANDOM_SORT_SUPPORTS:
94
+ if data_client.name in SUPPORTS_SORT_BY_ALIASED_RANDOM:
95
+ random_sort_col = "random_sort"
96
+ return (
97
+ table[self.primary_keys]
98
+ .mutate(**{random_sort_col: ibis.random()})
99
+ .order_by(random_sort_col)
100
+ .limit(self.batch_size)[self.primary_keys]
101
+ )
102
+
103
+ if data_client.name in SUPPORTS_SORT_BY_RANDOM:
92
104
  # Teradata 'SAMPLE' and Spanner 'TABLESAMPLE' is random by nature
93
105
  # and does not require a sort by
94
106
  if data_client.name == "teradata" or data_client.name == "spanner":
@@ -20,7 +20,6 @@ import ibis
20
20
  from data_validation import consts, util
21
21
  from data_validation.result_handlers import text as text_handler
22
22
 
23
-
24
23
  if TYPE_CHECKING:
25
24
  from pandas import DataFrame
26
25
 
@@ -23,7 +23,6 @@ import google.oauth2.service_account
23
23
  from data_validation import clients, consts, exceptions, util
24
24
  from data_validation.result_handlers.base_backend import BaseBackendResultHandler
25
25
 
26
-
27
26
  BQRH_WRITE_MESSAGE = "Results written to BigQuery"
28
27
  BQRH_NO_WRITE_MESSAGE = "No results to write to BigQuery"
29
28
 
@@ -28,7 +28,6 @@ from data_validation.result_handlers.base_backend import (
28
28
  RH_NO_WRITE_MESSAGE,
29
29
  )
30
30
 
31
-
32
31
  if TYPE_CHECKING:
33
32
  from pandas import DataFrame
34
33
  from ibis.backends.base import BaseBackend
@@ -20,7 +20,6 @@ import re
20
20
 
21
21
  from data_validation import metadata, consts, clients, exceptions
22
22
 
23
-
24
23
  # Check for decimal data type with precision and/or scale. Permits hyphen in p/s for value ranges.
25
24
  DECIMAL_PRECISION_SCALE_PATTERN = re.compile(
26
25
  r"([!]?decimal)\(([0-9\-]+)(?:,[ ]*([0-9\-]+))?\)", re.I
@@ -187,7 +186,10 @@ def schema_validation_matching(
187
186
  )
188
187
  else:
189
188
  # Target data type mismatch
190
- (higher_precision, lower_precision,) = parse_n_validate_datatypes(
189
+ (
190
+ higher_precision,
191
+ lower_precision,
192
+ ) = parse_n_validate_datatypes(
191
193
  string_val(source_field_type), string_val(target_field_type)
192
194
  )
193
195
  if higher_precision:
@@ -16,6 +16,7 @@ import json
16
16
  import logging
17
17
  import re
18
18
  import time
19
+ import uuid
19
20
 
20
21
  from data_validation import clients, exceptions
21
22
 
@@ -87,3 +88,19 @@ def ibis_table_to_sql(ibis_table: "IbisTable", alchemy_client: "BaseBackend") ->
87
88
  else:
88
89
  sql_string = str(ibis_table.compile())
89
90
  return sql_string
91
+
92
+
93
+ def dvt_temp_object_name(prefix: str = "dvt_temp") -> str:
94
+ """Generate a random name for when DVT needs to create a temporary object.
95
+
96
+ Args:
97
+ prefix: The prefix to use for the temporary object name.
98
+
99
+ Returns:
100
+ A random name for when DVT needs to create a temporary object.
101
+ """
102
+ if not isinstance(prefix, str) or not re.match(r"^[a-zA-Z0-9_]+$", prefix):
103
+ raise exceptions.ValidationException(
104
+ f"Invalid prefix: '{prefix}'. Only alphanumeric and underscore characters are allowed."
105
+ )
106
+ return f"{prefix}_{uuid.uuid4().hex[:8].lower()}"