google-pso-data-validator 8.2.0__tar.gz → 8.3.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.
- {google_pso_data_validator-8.2.0/google_pso_data_validator.egg-info → google_pso_data_validator-8.3.0}/PKG-INFO +8 -9
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/README.md +3 -4
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/__main__.py +6 -4
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/cli_tools.py +7 -12
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/config_manager.py +53 -3
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/data_validation.py +16 -9
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/find_tables.py +18 -8
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/partition_builder.py +27 -20
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/result_handlers/base_backend.py +2 -2
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/result_handlers/bigquery.py +49 -4
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/result_handlers/factory.py +34 -29
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/result_handlers/postgres.py +3 -3
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/state_manager.py +1 -4
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/util.py +18 -19
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0/google_pso_data_validator.egg-info}/PKG-INFO +8 -9
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/google_pso_data_validator.egg-info/requires.txt +2 -2
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/setup.py +5 -5
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_mssql/__init__.py +9 -4
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/LICENSE +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/app.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/client_info.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/clients.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/combiner.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/consts.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/exceptions.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/gcs_helper.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/jellyfish_distance.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/metadata.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/query_builder/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/query_builder/partition_row_builder.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/query_builder/query_builder.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/query_builder/random_row_builder.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/raw_query.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/result_handlers/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/result_handlers/text.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/schema_validation.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/secret_manager.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/validation_builder.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/google_pso_data_validator.egg-info/SOURCES.txt +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/google_pso_data_validator.egg-info/dependency_links.txt +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/google_pso_data_validator.egg-info/entry_points.txt +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/google_pso_data_validator.egg-info/top_level.txt +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/setup.cfg +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_addon/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_addon/api.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_addon/operations.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_bigquery/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_bigquery/api.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_bigquery/registry.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_cloud_spanner/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_cloud_spanner/api.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_cloud_spanner/client.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_cloud_spanner/compiler.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_cloud_spanner/datatypes.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_cloud_spanner/registry.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_cloud_spanner/tests/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_cloud_spanner/tests/conftest.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_cloud_spanner/to_pandas.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_db2/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_db2/api.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_db2/compiler.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_db2/datatypes.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_db2/registry.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_impala/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_impala/api.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_mssql/api.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_mssql/datatypes.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_mssql/registry.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_mysql/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_mysql/base_sql_compiler/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_mysql/base_sql_compiler/select_builder.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_mysql/compiler.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_oracle/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_oracle/api.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_oracle/compiler.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_oracle/datatypes.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_oracle/registry.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_postgres/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_postgres/client.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_postgres/datatypes.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_postgres/registry.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_redshift/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_redshift/api.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_redshift/compiler.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_snowflake/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_snowflake/api.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_snowflake/datatypes.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_teradata/__init__.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_teradata/api.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_teradata/compiler.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_teradata/datatypes.py +0 -0
- {google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/third_party/ibis/ibis_teradata/registry.py +0 -0
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: google-pso-data-validator
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.3.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
|
|
7
7
|
Classifier: Development Status :: 4 - Beta
|
|
8
8
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
9
9
|
Classifier: Programming Language :: Python
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.9
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
|
-
Requires-Python: >=3.
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: Flask>=2.2
|
|
18
|
-
Requires-Dist: fsspec
|
|
18
|
+
Requires-Dist: fsspec
|
|
19
19
|
Requires-Dist: google-api-python-client>=2.144.0
|
|
20
20
|
Requires-Dist: google-cloud-bigquery>=3.25.0
|
|
21
21
|
Requires-Dist: google-cloud-bigquery-storage>=2.26.0
|
|
@@ -25,7 +25,7 @@ Requires-Dist: google-cloud-storage>=2.18.2
|
|
|
25
25
|
Requires-Dist: ibis-framework==5.1.0
|
|
26
26
|
Requires-Dist: impyla>=0.19.0
|
|
27
27
|
Requires-Dist: jellyfish>=1.1.0
|
|
28
|
-
Requires-Dist: pandas
|
|
28
|
+
Requires-Dist: pandas
|
|
29
29
|
Requires-Dist: parsy>=2.1
|
|
30
30
|
Requires-Dist: psycopg2-binary>=2.9.9
|
|
31
31
|
Requires-Dist: pyarrow==14.0.1
|
|
@@ -340,7 +340,7 @@ data-validation
|
|
|
340
340
|
Directory Path to store YAML Config Files
|
|
341
341
|
GCS: Provide a full gs:// path of the target directory. Eg: `gs://<BUCKET>/partitions_dir`
|
|
342
342
|
Local: Provide a relative path of the target directory. Eg: `partitions_dir`
|
|
343
|
-
|
|
343
|
+
For custom queries, the yaml files are stored in CONFIG_DIR and, for tables the yamls are stored in a directory named <source_schema>.<table> within CONFIG_DIR.
|
|
344
344
|
--partition-num INT, -pn INT
|
|
345
345
|
Number of partitions into which the table should be split, e.g. 1000 or 10000
|
|
346
346
|
In case this value exceeds the row count of the source/target table, it will be decreased to max(source_row_count, target_row_count)
|
|
@@ -653,9 +653,8 @@ The `--config-dir` flag will specify the directory with the YAML files to be exe
|
|
|
653
653
|
|
|
654
654
|
### Validation Reports
|
|
655
655
|
|
|
656
|
-
The result handlers tell DVT where to store the results of each validation. The
|
|
657
|
-
|
|
658
|
-
or print to stdout (default). View the schema of the results table [here](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/terraform/results_schema.json).
|
|
656
|
+
The result handlers tell DVT where to store the results of each validation. The tool can write the results of a validation run to Google BigQuery, PostgreSQL or print to stdout (default). See [result handler setup](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/installation.md#result-handler-setup) for pre-requisites. View the schema of the BigQuery results table [here](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/terraform/results_schema.json).
|
|
657
|
+
|
|
659
658
|
|
|
660
659
|
To output to BigQuery or PostgreSQL, simply include the `-rh` flag during a validation run including
|
|
661
660
|
the schema and table name for the results.
|
|
@@ -281,7 +281,7 @@ data-validation
|
|
|
281
281
|
Directory Path to store YAML Config Files
|
|
282
282
|
GCS: Provide a full gs:// path of the target directory. Eg: `gs://<BUCKET>/partitions_dir`
|
|
283
283
|
Local: Provide a relative path of the target directory. Eg: `partitions_dir`
|
|
284
|
-
|
|
284
|
+
For custom queries, the yaml files are stored in CONFIG_DIR and, for tables the yamls are stored in a directory named <source_schema>.<table> within CONFIG_DIR.
|
|
285
285
|
--partition-num INT, -pn INT
|
|
286
286
|
Number of partitions into which the table should be split, e.g. 1000 or 10000
|
|
287
287
|
In case this value exceeds the row count of the source/target table, it will be decreased to max(source_row_count, target_row_count)
|
|
@@ -594,9 +594,8 @@ The `--config-dir` flag will specify the directory with the YAML files to be exe
|
|
|
594
594
|
|
|
595
595
|
### Validation Reports
|
|
596
596
|
|
|
597
|
-
The result handlers tell DVT where to store the results of each validation. The
|
|
598
|
-
|
|
599
|
-
or print to stdout (default). View the schema of the results table [here](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/terraform/results_schema.json).
|
|
597
|
+
The result handlers tell DVT where to store the results of each validation. The tool can write the results of a validation run to Google BigQuery, PostgreSQL or print to stdout (default). See [result handler setup](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/installation.md#result-handler-setup) for pre-requisites. View the schema of the BigQuery results table [here](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/terraform/results_schema.json).
|
|
598
|
+
|
|
600
599
|
|
|
601
600
|
To output to BigQuery or PostgreSQL, simply include the `-rh` flag during a validation run including
|
|
602
601
|
the schema and table name for the results.
|
{google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/__main__.py
RENAMED
|
@@ -524,8 +524,8 @@ def run_validation(config_manager: ConfigManager, dry_run=False, verbose=False):
|
|
|
524
524
|
validation_builder=None,
|
|
525
525
|
result_handler=None,
|
|
526
526
|
verbose=verbose,
|
|
527
|
-
|
|
528
|
-
|
|
527
|
+
cached_source_client=source_client,
|
|
528
|
+
cached_target_client=target_client,
|
|
529
529
|
) as validator:
|
|
530
530
|
|
|
531
531
|
if dry_run:
|
|
@@ -533,10 +533,12 @@ def run_validation(config_manager: ConfigManager, dry_run=False, verbose=False):
|
|
|
533
533
|
json.dumps(
|
|
534
534
|
{
|
|
535
535
|
"source_query": util.ibis_table_to_sql(
|
|
536
|
-
validator.validation_builder.get_source_query()
|
|
536
|
+
validator.validation_builder.get_source_query(),
|
|
537
|
+
source_client,
|
|
537
538
|
),
|
|
538
539
|
"target_query": util.ibis_table_to_sql(
|
|
539
|
-
validator.validation_builder.get_target_query()
|
|
540
|
+
validator.validation_builder.get_target_query(),
|
|
541
|
+
target_client,
|
|
540
542
|
),
|
|
541
543
|
},
|
|
542
544
|
indent=4,
|
{google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/cli_tools.py
RENAMED
|
@@ -1308,21 +1308,16 @@ def _get_result_handler(rc_value: str, sa_file=None) -> dict:
|
|
|
1308
1308
|
if config[0] in connections:
|
|
1309
1309
|
# We received connection_name.results_table.
|
|
1310
1310
|
conn_from_file = get_connection(config[0])
|
|
1311
|
-
if conn_from_file[consts.SOURCE_TYPE]
|
|
1311
|
+
if conn_from_file[consts.SOURCE_TYPE] in [
|
|
1312
|
+
consts.SOURCE_TYPE_BIGQUERY,
|
|
1313
|
+
consts.SOURCE_TYPE_POSTGRES,
|
|
1314
|
+
]:
|
|
1312
1315
|
result_handler = {
|
|
1313
1316
|
consts.RH_TYPE: conn_from_file[consts.SOURCE_TYPE],
|
|
1314
|
-
consts.PROJECT_ID: conn_from_file["project_id"],
|
|
1315
1317
|
consts.TABLE_ID: config[1],
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
),
|
|
1320
|
-
}
|
|
1321
|
-
elif conn_from_file[consts.SOURCE_TYPE] == consts.SOURCE_TYPE_POSTGRES:
|
|
1322
|
-
result_handler = {
|
|
1323
|
-
consts.RH_TYPE: conn_from_file[consts.SOURCE_TYPE],
|
|
1324
|
-
consts.TABLE_ID: config[1],
|
|
1325
|
-
consts.RH_CONN: conn_from_file,
|
|
1318
|
+
# Only store the connection name in the result handler to avoid accidentally
|
|
1319
|
+
# storing credentials in config files.
|
|
1320
|
+
consts.RH_CONN: config[0],
|
|
1326
1321
|
}
|
|
1327
1322
|
# TODO Add filesytem handler too.
|
|
1328
1323
|
else:
|
|
@@ -671,20 +671,38 @@ class ConfigManager(object):
|
|
|
671
671
|
|
|
672
672
|
This is because SQL Server text does not support the len() function to get the length in characters.
|
|
673
673
|
Instead we must fall back to using ByteLength."""
|
|
674
|
+
return self._is_sql_server_type(
|
|
675
|
+
source_column_name, target_column_name, ["text", "ntext"]
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
def _is_sql_server_image(
|
|
679
|
+
self, source_column_name: str, target_column_name: str
|
|
680
|
+
) -> bool:
|
|
681
|
+
"""Returns True when either source or target column is of SQL Server image data type.
|
|
682
|
+
|
|
683
|
+
This is because SQL Server image is deprecated and not supported by a number of SQL functions.
|
|
684
|
+
"""
|
|
685
|
+
return self._is_sql_server_type(
|
|
686
|
+
source_column_name, target_column_name, ["image"]
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
def _is_sql_server_type(
|
|
690
|
+
self, source_column_name: str, target_column_name: str, type_list: List[str]
|
|
691
|
+
) -> bool:
|
|
692
|
+
"""Returns True when either source or target column is of a SQL Server type listed in type_list."""
|
|
674
693
|
|
|
675
694
|
raw_source_types = self.get_source_raw_data_types()
|
|
676
695
|
raw_target_types = self.get_target_raw_data_types()
|
|
677
|
-
text_types = ("text", "ntext")
|
|
678
696
|
return bool(
|
|
679
697
|
(
|
|
680
698
|
self.source_client.name == "mssql"
|
|
681
699
|
and raw_source_types
|
|
682
|
-
and raw_source_types.get(source_column_name, [None])[0] in
|
|
700
|
+
and raw_source_types.get(source_column_name, [None])[0] in type_list
|
|
683
701
|
)
|
|
684
702
|
or (
|
|
685
703
|
self.target_client.name == "mssql"
|
|
686
704
|
and raw_target_types
|
|
687
|
-
and raw_target_types.get(target_column_name, [None])[0] in
|
|
705
|
+
and raw_target_types.get(target_column_name, [None])[0] in type_list
|
|
688
706
|
)
|
|
689
707
|
)
|
|
690
708
|
|
|
@@ -1075,6 +1093,11 @@ class ConfigManager(object):
|
|
|
1075
1093
|
f"Skipping {agg_type} on {column} due to data type: {column_type}"
|
|
1076
1094
|
)
|
|
1077
1095
|
continue
|
|
1096
|
+
elif self._is_sql_server_image(column, column):
|
|
1097
|
+
logging.info(
|
|
1098
|
+
f"Skipping {agg_type} on {column} due to SQL Server image data type"
|
|
1099
|
+
)
|
|
1100
|
+
continue
|
|
1078
1101
|
|
|
1079
1102
|
if require_pre_agg_calc_field(
|
|
1080
1103
|
column_type, target_column_type, agg_type, cast_to_bigint
|
|
@@ -1252,8 +1275,25 @@ class ConfigManager(object):
|
|
|
1252
1275
|
raise ValueError(
|
|
1253
1276
|
"Exclude columns flag cannot be present with column list '*'"
|
|
1254
1277
|
)
|
|
1278
|
+
|
|
1255
1279
|
return casefold_columns
|
|
1256
1280
|
|
|
1281
|
+
def _filter_columns_by_data_type(
|
|
1282
|
+
self, source_columns: dict, target_columns: dict
|
|
1283
|
+
) -> tuple:
|
|
1284
|
+
"""Filter out columns with a data type that is incompatible with DVT."""
|
|
1285
|
+
result_source_columns = source_columns.copy()
|
|
1286
|
+
result_target_columns = target_columns.copy()
|
|
1287
|
+
for source_column, target_column in zip(source_columns, target_columns):
|
|
1288
|
+
if self._is_sql_server_image(source_column, target_column):
|
|
1289
|
+
logging.info(
|
|
1290
|
+
f"Skipping column {source_column} due to SQL Server image data type"
|
|
1291
|
+
)
|
|
1292
|
+
result_source_columns.pop(source_column)
|
|
1293
|
+
result_target_columns.pop(target_column)
|
|
1294
|
+
|
|
1295
|
+
return result_source_columns, result_target_columns
|
|
1296
|
+
|
|
1257
1297
|
def build_dependent_aliases(self, calc_type: str, col_list=None) -> List[Dict]:
|
|
1258
1298
|
"""This is a utility function for determining the required depth of all fields"""
|
|
1259
1299
|
source_table = self.get_source_ibis_calculated_table()
|
|
@@ -1269,6 +1309,13 @@ class ConfigManager(object):
|
|
|
1269
1309
|
casefold_target_columns, col_list
|
|
1270
1310
|
)
|
|
1271
1311
|
|
|
1312
|
+
(
|
|
1313
|
+
casefold_source_columns,
|
|
1314
|
+
casefold_target_columns,
|
|
1315
|
+
) = self._filter_columns_by_data_type(
|
|
1316
|
+
casefold_source_columns, casefold_target_columns
|
|
1317
|
+
)
|
|
1318
|
+
|
|
1272
1319
|
column_aliases = {}
|
|
1273
1320
|
col_names = []
|
|
1274
1321
|
for i, calc in enumerate(self._get_order_of_operations(calc_type)):
|
|
@@ -1325,6 +1372,9 @@ class ConfigManager(object):
|
|
|
1325
1372
|
casefold_source_columns = self._filter_columns_by_column_list(
|
|
1326
1373
|
casefold_source_columns, col_list, exclude_cols=exclude_cols
|
|
1327
1374
|
)
|
|
1375
|
+
casefold_source_columns, _ = self._filter_columns_by_data_type(
|
|
1376
|
+
casefold_source_columns, casefold_source_columns
|
|
1377
|
+
)
|
|
1328
1378
|
return casefold_source_columns
|
|
1329
1379
|
|
|
1330
1380
|
def auto_list_primary_keys(self) -> list:
|
|
@@ -16,9 +16,11 @@ import json
|
|
|
16
16
|
import logging
|
|
17
17
|
import warnings
|
|
18
18
|
from concurrent.futures import ThreadPoolExecutor
|
|
19
|
-
import ibis.backends.pandas
|
|
20
|
-
import pandas
|
|
21
19
|
import uuid
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
21
|
+
|
|
22
|
+
import ibis
|
|
23
|
+
import pandas
|
|
22
24
|
|
|
23
25
|
from data_validation import combiner, consts, metadata, util
|
|
24
26
|
from data_validation.config_manager import ConfigManager
|
|
@@ -26,6 +28,9 @@ from data_validation.query_builder.random_row_builder import RandomRowBuilder
|
|
|
26
28
|
from data_validation.schema_validation import SchemaValidation
|
|
27
29
|
from data_validation.validation_builder import ValidationBuilder
|
|
28
30
|
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from ibis.backends.base import BaseBackend
|
|
33
|
+
|
|
29
34
|
""" The DataValidation class is where the code becomes source/target aware
|
|
30
35
|
|
|
31
36
|
The class builds specific source and target clients and is likely where someone would go to
|
|
@@ -43,8 +48,8 @@ class DataValidation(object):
|
|
|
43
48
|
schema_validator=None,
|
|
44
49
|
result_handler=None,
|
|
45
50
|
verbose=False,
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
cached_source_client: "BaseBackend" = None,
|
|
52
|
+
cached_target_client: "BaseBackend" = None,
|
|
48
53
|
):
|
|
49
54
|
"""Initialize a DataValidation client
|
|
50
55
|
|
|
@@ -54,19 +59,21 @@ class DataValidation(object):
|
|
|
54
59
|
schema_validator (SchemaValidation): Optional instance of a SchemaValidation.
|
|
55
60
|
result_handler (ResultHandler): Optional instance of as ResultHandler client.
|
|
56
61
|
verbose (bool): If verbose, the Data Validation client will print the queries run.
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
cached_source_client: Optional client to avoid unnecessary connections,
|
|
63
|
+
cached_target_client: Optional client to avoid unnecessary connections,
|
|
59
64
|
"""
|
|
60
65
|
self.verbose = verbose
|
|
61
|
-
self._fresh_connections = not bool(
|
|
66
|
+
self._fresh_connections = not bool(
|
|
67
|
+
cached_source_client and cached_target_client
|
|
68
|
+
)
|
|
62
69
|
|
|
63
70
|
# Data Client Management
|
|
64
71
|
self.config = config
|
|
65
72
|
|
|
66
73
|
self.config_manager = ConfigManager(
|
|
67
74
|
config,
|
|
68
|
-
source_client=
|
|
69
|
-
target_client=
|
|
75
|
+
source_client=cached_source_client,
|
|
76
|
+
target_client=cached_target_client,
|
|
70
77
|
verbose=self.verbose,
|
|
71
78
|
)
|
|
72
79
|
|
{google_pso_data_validator-8.2.0 → google_pso_data_validator-8.3.0}/data_validation/find_tables.py
RENAMED
|
@@ -60,15 +60,9 @@ def _compare_match_tables(source_table_map, target_table_map, score_cutoff=0.8)
|
|
|
60
60
|
return table_configs
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
def
|
|
64
|
-
|
|
65
|
-
):
|
|
66
|
-
"""Return dict with searchable keys for table matching."""
|
|
63
|
+
def _get_table_map_from_obj_list(table_objs: list) -> dict:
|
|
64
|
+
"""Convert list of schema, table tuples into dict with searchable keys for table matching."""
|
|
67
65
|
table_map = {}
|
|
68
|
-
table_objs = clients.get_all_tables(
|
|
69
|
-
client, allowed_schemas=allowed_schemas, tables_only=(not include_views)
|
|
70
|
-
)
|
|
71
|
-
|
|
72
66
|
for table_obj in table_objs:
|
|
73
67
|
table_key = ".".join([t for t in table_obj if t])
|
|
74
68
|
table_map[table_key] = {
|
|
@@ -76,9 +70,25 @@ def _get_table_map(
|
|
|
76
70
|
consts.CONFIG_TABLE_NAME: table_obj[1],
|
|
77
71
|
}
|
|
78
72
|
|
|
73
|
+
# Post process table_map and lower case table_keys, if possible.
|
|
74
|
+
for table_key in [_ for _ in table_map if _ != _.casefold()]:
|
|
75
|
+
if table_key.casefold() not in table_map:
|
|
76
|
+
# Only lower case the key if there isn't one already.
|
|
77
|
+
table_map[table_key.casefold()] = table_map.pop(table_key)
|
|
78
|
+
|
|
79
79
|
return table_map
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def _get_table_map(
|
|
83
|
+
client: "ibis.backends.base.BaseBackend", allowed_schemas=None, include_views=False
|
|
84
|
+
) -> dict:
|
|
85
|
+
"""Return dict with searchable keys for table matching."""
|
|
86
|
+
table_objs = clients.get_all_tables(
|
|
87
|
+
client, allowed_schemas=allowed_schemas, tables_only=(not include_views)
|
|
88
|
+
)
|
|
89
|
+
return _get_table_map_from_obj_list(table_objs)
|
|
90
|
+
|
|
91
|
+
|
|
82
92
|
def get_mapped_table_configs(
|
|
83
93
|
source_client: "ibis.backends.base.BaseBackend",
|
|
84
94
|
target_client: "ibis.backends.base.BaseBackend",
|
|
@@ -18,8 +18,6 @@ import pandas
|
|
|
18
18
|
import logging
|
|
19
19
|
import re
|
|
20
20
|
import datetime
|
|
21
|
-
import hashlib
|
|
22
|
-
import base64
|
|
23
21
|
from typing import List, Dict, TYPE_CHECKING
|
|
24
22
|
|
|
25
23
|
from data_validation import cli_tools, consts, util
|
|
@@ -30,7 +28,8 @@ from data_validation.validation_builder import list_to_sublists
|
|
|
30
28
|
|
|
31
29
|
if TYPE_CHECKING:
|
|
32
30
|
from argparse import Namespace
|
|
33
|
-
|
|
31
|
+
from ibis.backends.base import BaseBackend
|
|
32
|
+
from ibis.expr.types.relations import Table as IbisTable
|
|
34
33
|
|
|
35
34
|
|
|
36
35
|
class PartitionBuilder:
|
|
@@ -40,6 +39,12 @@ class PartitionBuilder:
|
|
|
40
39
|
self.args = args
|
|
41
40
|
self.config_dir = self._get_arg_config_dir()
|
|
42
41
|
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _definitely_no_time_part(value: datetime.datetime) -> bool:
|
|
44
|
+
"""Oracle date has a time portion - this function ensures we don't truncate the time when we really want it."""
|
|
45
|
+
# The function name is a bit of a misnomer, it's checking if there is a time part.
|
|
46
|
+
return value.hour + value.minute + value.second + value.microsecond == 0
|
|
47
|
+
|
|
43
48
|
def _get_arg_config_dir(self) -> str:
|
|
44
49
|
"""Return String yaml config folder path."""
|
|
45
50
|
if not self.args.config_dir:
|
|
@@ -101,7 +106,7 @@ class PartitionBuilder:
|
|
|
101
106
|
self._store_partitions(yaml_configs_list)
|
|
102
107
|
|
|
103
108
|
@staticmethod
|
|
104
|
-
def _extract_where(table_expr: "
|
|
109
|
+
def _extract_where(table_expr: "IbisTable", client: "BaseBackend") -> str:
|
|
105
110
|
"""Given a ibis table expression with a filter (i.e. WHERE) clause, this function extracts the
|
|
106
111
|
where clause in plain text.
|
|
107
112
|
|
|
@@ -111,8 +116,9 @@ class PartitionBuilder:
|
|
|
111
116
|
# This extraction of the where clause is a bit of a hack. To extract it correctly, the SQL table
|
|
112
117
|
# expression should be correctly parsed and the where clause extracted. Perhaps use something like Sqlglot.
|
|
113
118
|
sql_where_expr = re.split(
|
|
114
|
-
r"\sWHERE\s", util.ibis_table_to_sql(table_expr), flags=re.I
|
|
119
|
+
r"\sWHERE\s", util.ibis_table_to_sql(table_expr, client), flags=re.I
|
|
115
120
|
)[-1]
|
|
121
|
+
|
|
116
122
|
sql_string_re = re.compile(r"'(?:''|\\'|[^'])*'")
|
|
117
123
|
sql_not_string_re = re.compile(r"[^']+")
|
|
118
124
|
sql_where_less_ws = ""
|
|
@@ -253,7 +259,6 @@ class PartitionBuilder:
|
|
|
253
259
|
# Up until this point, we have built the table expression, have not executed the query yet.
|
|
254
260
|
# The query is now executed to find the first element of each partition
|
|
255
261
|
first_elements = first_keys_table.execute().to_numpy()
|
|
256
|
-
|
|
257
262
|
# The objective is to generate the SQL expression string that is saved in the yaml file as a
|
|
258
263
|
# filters property. This SQL expression is used as a filter during validation to ensure
|
|
259
264
|
# that the yaml file is only validating the specific partition. This string is backend specific as
|
|
@@ -280,13 +285,14 @@ class PartitionBuilder:
|
|
|
280
285
|
values[0].date()
|
|
281
286
|
if key_column.type().is_date()
|
|
282
287
|
and isinstance(values[0], datetime.datetime)
|
|
288
|
+
and self._definitely_no_time_part(values[0])
|
|
283
289
|
else values[0]
|
|
284
290
|
)
|
|
285
291
|
if len(keys) == 1:
|
|
286
|
-
return key_column < value
|
|
292
|
+
return key_column < ibis.literal(value)
|
|
287
293
|
else:
|
|
288
|
-
return (key_column < value) | (
|
|
289
|
-
(key_column == value)
|
|
294
|
+
return (key_column < ibis.literal(value)) | (
|
|
295
|
+
(key_column == ibis.literal(value))
|
|
290
296
|
& less_than_value(table, keys[1:], values[1:])
|
|
291
297
|
)
|
|
292
298
|
|
|
@@ -297,14 +303,16 @@ class PartitionBuilder:
|
|
|
297
303
|
values[0].date()
|
|
298
304
|
if key_column.type().is_date()
|
|
299
305
|
and isinstance(values[0], datetime.datetime)
|
|
306
|
+
and self._definitely_no_time_part(values[0])
|
|
300
307
|
else values[0]
|
|
301
308
|
)
|
|
302
309
|
|
|
303
310
|
if len(keys) == 1:
|
|
304
|
-
return key_column >= value
|
|
311
|
+
return key_column >= ibis.literal(value)
|
|
305
312
|
else:
|
|
306
|
-
return (key_column > value) | (
|
|
307
|
-
(key_column == value)
|
|
313
|
+
return (key_column > ibis.literal(value)) | (
|
|
314
|
+
(key_column == ibis.literal(value))
|
|
315
|
+
& geq_value(table, keys[1:], values[1:])
|
|
308
316
|
)
|
|
309
317
|
|
|
310
318
|
filter_source_clause = less_than_value(
|
|
@@ -320,11 +328,13 @@ class PartitionBuilder:
|
|
|
320
328
|
source_where_list.append(
|
|
321
329
|
self._extract_where(
|
|
322
330
|
source_table.filter(filter_source_clause),
|
|
331
|
+
config_manager.source_client,
|
|
323
332
|
)
|
|
324
333
|
)
|
|
325
334
|
target_where_list.append(
|
|
326
335
|
self._extract_where(
|
|
327
336
|
target_table.filter(filter_target_clause),
|
|
337
|
+
config_manager.target_client,
|
|
328
338
|
)
|
|
329
339
|
)
|
|
330
340
|
|
|
@@ -350,11 +360,13 @@ class PartitionBuilder:
|
|
|
350
360
|
source_where_list.append(
|
|
351
361
|
self._extract_where(
|
|
352
362
|
source_table.filter(filter_source_clause),
|
|
363
|
+
config_manager.source_client,
|
|
353
364
|
)
|
|
354
365
|
)
|
|
355
366
|
target_where_list.append(
|
|
356
367
|
self._extract_where(
|
|
357
368
|
target_table.filter(filter_target_clause),
|
|
369
|
+
config_manager.target_client,
|
|
358
370
|
)
|
|
359
371
|
)
|
|
360
372
|
filter_source_clause = geq_value(
|
|
@@ -370,11 +382,13 @@ class PartitionBuilder:
|
|
|
370
382
|
source_where_list.append(
|
|
371
383
|
self._extract_where(
|
|
372
384
|
source_table.filter(filter_source_clause),
|
|
385
|
+
config_manager.source_client,
|
|
373
386
|
)
|
|
374
387
|
)
|
|
375
388
|
target_where_list.append(
|
|
376
389
|
self._extract_where(
|
|
377
390
|
target_table.filter(filter_target_clause),
|
|
391
|
+
config_manager.target_client,
|
|
378
392
|
)
|
|
379
393
|
)
|
|
380
394
|
master_filter_list.append([source_where_list, target_where_list])
|
|
@@ -409,14 +423,7 @@ class PartitionBuilder:
|
|
|
409
423
|
if config_manager.source_table:
|
|
410
424
|
dir_name = config_manager.full_source_table
|
|
411
425
|
else:
|
|
412
|
-
dir_name =
|
|
413
|
-
"custom."
|
|
414
|
-
+ base64.b64encode(
|
|
415
|
-
hashlib.sha256(
|
|
416
|
-
config_manager.source_query.encode("utf-8")
|
|
417
|
-
).digest()
|
|
418
|
-
).decode("ascii")[:5]
|
|
419
|
-
)
|
|
426
|
+
dir_name = ""
|
|
420
427
|
filter_list = partition_filters[ind]
|
|
421
428
|
|
|
422
429
|
if src_config_dict.get(dir_name) is None:
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import logging
|
|
16
|
-
from typing import TYPE_CHECKING
|
|
16
|
+
from typing import Optional, TYPE_CHECKING
|
|
17
17
|
|
|
18
18
|
import ibis
|
|
19
19
|
|
|
@@ -58,7 +58,7 @@ class BaseBackendResultHandler:
|
|
|
58
58
|
"""Write results of data validation to a backend."""
|
|
59
59
|
|
|
60
60
|
_table_id: str = None
|
|
61
|
-
_status_list: list = None
|
|
61
|
+
_status_list: Optional[list] = None
|
|
62
62
|
_text_format: str = None
|
|
63
63
|
|
|
64
64
|
def _filter_by_status_list(self, result_df: "DataFrame") -> "DataFrame":
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"""Output validation report to BigQuery tables"""
|
|
16
16
|
|
|
17
17
|
import logging
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
import google.oauth2.service_account
|
|
21
|
+
|
|
18
22
|
|
|
19
23
|
from data_validation import clients, consts, exceptions, util
|
|
20
24
|
from data_validation.result_handlers.base_backend import BaseBackendResultHandler
|
|
@@ -24,6 +28,14 @@ BQRH_WRITE_MESSAGE = "Results written to BigQuery"
|
|
|
24
28
|
BQRH_NO_WRITE_MESSAGE = "No results to write to BigQuery"
|
|
25
29
|
|
|
26
30
|
|
|
31
|
+
def credentials_from_key_path(sa_key_path):
|
|
32
|
+
if not sa_key_path:
|
|
33
|
+
return None
|
|
34
|
+
return google.oauth2.service_account.Credentials.from_service_account_file(
|
|
35
|
+
sa_key_path
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
27
39
|
class BigQueryResultHandler(BaseBackendResultHandler):
|
|
28
40
|
"""Write results of data validation to BigQuery.
|
|
29
41
|
|
|
@@ -38,7 +50,7 @@ class BigQueryResultHandler(BaseBackendResultHandler):
|
|
|
38
50
|
def __init__(
|
|
39
51
|
self,
|
|
40
52
|
bigquery_client,
|
|
41
|
-
status_list: list = None,
|
|
53
|
+
status_list: Optional[list] = None,
|
|
42
54
|
table_id: str = "pso_data_validator.results",
|
|
43
55
|
text_format: str = consts.FORMAT_TYPE_TABLE,
|
|
44
56
|
):
|
|
@@ -50,10 +62,10 @@ class BigQueryResultHandler(BaseBackendResultHandler):
|
|
|
50
62
|
@staticmethod
|
|
51
63
|
def get_handler_for_project(
|
|
52
64
|
project_id,
|
|
53
|
-
status_list=None,
|
|
65
|
+
status_list: Optional[list] = None,
|
|
54
66
|
table_id: str = "pso_data_validator.results",
|
|
55
|
-
|
|
56
|
-
api_endpoint: str = None,
|
|
67
|
+
sa_key_path: Optional[str] = None,
|
|
68
|
+
api_endpoint: Optional[str] = None,
|
|
57
69
|
text_format: str = consts.FORMAT_TYPE_TABLE,
|
|
58
70
|
):
|
|
59
71
|
"""Return BigQueryResultHandler instance for given project.
|
|
@@ -70,6 +82,39 @@ class BigQueryResultHandler(BaseBackendResultHandler):
|
|
|
70
82
|
This allows the user to influence the text results written via logger.debug.
|
|
71
83
|
See: https://github.com/GoogleCloudPlatform/professional-services-data-validator/issues/871
|
|
72
84
|
"""
|
|
85
|
+
|
|
86
|
+
credentials = credentials_from_key_path(sa_key_path)
|
|
87
|
+
client = clients.get_google_bigquery_client(
|
|
88
|
+
project_id, credentials=credentials, api_endpoint=api_endpoint
|
|
89
|
+
)
|
|
90
|
+
return BigQueryResultHandler(
|
|
91
|
+
client,
|
|
92
|
+
status_list=status_list,
|
|
93
|
+
table_id=table_id,
|
|
94
|
+
text_format=text_format,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def get_handler_for_connection(
|
|
99
|
+
connection_config: dict,
|
|
100
|
+
status_list: Optional[list] = None,
|
|
101
|
+
table_id: str = "pso_data_validator.results",
|
|
102
|
+
text_format: str = consts.FORMAT_TYPE_TABLE,
|
|
103
|
+
):
|
|
104
|
+
"""Return BigQueryResultHandler instance for given connection config.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
table_id (str): Table ID used for validation results.
|
|
108
|
+
status_list (list): provided status to filter the results with
|
|
109
|
+
text_format (str, optional):
|
|
110
|
+
This allows the user to influence the text results written via logger.debug.
|
|
111
|
+
See: https://github.com/GoogleCloudPlatform/professional-services-data-validator/issues/871
|
|
112
|
+
"""
|
|
113
|
+
project_id = connection_config[consts.PROJECT_ID]
|
|
114
|
+
credentials = credentials_from_key_path(
|
|
115
|
+
connection_config.get(consts.GOOGLE_SERVICE_ACCOUNT_KEY_PATH)
|
|
116
|
+
)
|
|
117
|
+
api_endpoint = connection_config.get(consts.API_ENDPOINT)
|
|
73
118
|
client = clients.get_google_bigquery_client(
|
|
74
119
|
project_id, credentials=credentials, api_endpoint=api_endpoint
|
|
75
120
|
)
|