google-pso-data-validator 8.5.2__tar.gz → 8.6.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 (99) hide show
  1. {google_pso_data_validator-8.5.2/google_pso_data_validator.egg-info → google_pso_data_validator-8.6.0}/PKG-INFO +12 -2
  2. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/README.md +10 -0
  3. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/__init__.py +0 -1
  4. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/cli_tools.py +3 -1
  5. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/client_info.py +0 -1
  6. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/clients.py +4 -4
  7. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/config_manager.py +16 -0
  8. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/find_tables.py +34 -10
  9. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/gcs_helper.py +0 -1
  10. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/query_builder/random_row_builder.py +0 -1
  11. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/result_handlers/base_backend.py +0 -1
  12. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/result_handlers/bigquery.py +0 -1
  13. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/result_handlers/postgres.py +0 -1
  14. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/schema_validation.py +4 -2
  15. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/util.py +17 -0
  16. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0/google_pso_data_validator.egg-info}/PKG-INFO +12 -2
  17. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/google_pso_data_validator.egg-info/requires.txt +1 -1
  18. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/setup.py +2 -2
  19. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_addon/api.py +44 -0
  20. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_addon/operations.py +19 -18
  21. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/compiler.py +0 -1
  22. google_pso_data_validator-8.6.0/third_party/ibis/ibis_db2/__init__.py +260 -0
  23. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_db2/datatypes.py +17 -2
  24. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_db2/registry.py +11 -4
  25. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_impala/api.py +0 -1
  26. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mssql/__init__.py +0 -1
  27. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_oracle/__init__.py +0 -1
  28. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_oracle/datatypes.py +3 -3
  29. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_postgres/registry.py +6 -1
  30. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_redshift/__init__.py +16 -9
  31. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_sybase/__init__.py +0 -1
  32. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_sybase/datatypes.py +0 -1
  33. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_teradata/__init__.py +3 -3
  34. google_pso_data_validator-8.5.2/third_party/ibis/ibis_db2/__init__.py +0 -172
  35. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/LICENSE +0 -0
  36. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/__main__.py +0 -0
  37. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/app.py +0 -0
  38. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/combiner.py +0 -0
  39. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/consts.py +0 -0
  40. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/data_validation.py +0 -0
  41. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/exceptions.py +0 -0
  42. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/jellyfish_distance.py +0 -0
  43. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/metadata.py +0 -0
  44. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/partition_builder.py +0 -0
  45. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/query_builder/__init__.py +0 -0
  46. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/query_builder/partition_row_builder.py +0 -0
  47. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/query_builder/query_builder.py +0 -0
  48. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/raw_query.py +0 -0
  49. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/result_handlers/__init__.py +0 -0
  50. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/result_handlers/factory.py +0 -0
  51. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/result_handlers/text.py +1 -1
  52. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/secret_manager.py +0 -0
  53. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/state_manager.py +0 -0
  54. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/data_validation/validation_builder.py +0 -0
  55. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/google_pso_data_validator.egg-info/SOURCES.txt +0 -0
  56. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/google_pso_data_validator.egg-info/dependency_links.txt +0 -0
  57. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/google_pso_data_validator.egg-info/entry_points.txt +0 -0
  58. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/google_pso_data_validator.egg-info/top_level.txt +0 -0
  59. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/setup.cfg +0 -0
  60. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_addon/__init__.py +0 -0
  61. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_bigquery/__init__.py +0 -0
  62. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_bigquery/api.py +0 -0
  63. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_bigquery/registry.py +0 -0
  64. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/__init__.py +0 -0
  65. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/api.py +0 -0
  66. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/client.py +0 -0
  67. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/datatypes.py +0 -0
  68. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/registry.py +0 -0
  69. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/tests/__init__.py +0 -0
  70. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/tests/conftest.py +0 -0
  71. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_cloud_spanner/to_pandas.py +0 -0
  72. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_db2/api.py +0 -0
  73. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_db2/compiler.py +0 -0
  74. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_impala/__init__.py +0 -0
  75. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mssql/api.py +0 -0
  76. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mssql/datatypes.py +0 -0
  77. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mssql/registry.py +0 -0
  78. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mysql/__init__.py +0 -0
  79. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mysql/base_sql_compiler/__init__.py +0 -0
  80. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mysql/base_sql_compiler/select_builder.py +0 -0
  81. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_mysql/compiler.py +0 -0
  82. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_oracle/api.py +0 -0
  83. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_oracle/compiler.py +0 -0
  84. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_oracle/registry.py +0 -0
  85. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_postgres/__init__.py +0 -0
  86. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_postgres/client.py +0 -0
  87. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_postgres/datatypes.py +0 -0
  88. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_redshift/api.py +0 -0
  89. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_redshift/compiler.py +0 -0
  90. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_snowflake/__init__.py +0 -0
  91. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_snowflake/api.py +0 -0
  92. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_snowflake/datatypes.py +0 -0
  93. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_sybase/api.py +0 -0
  94. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_sybase/compiler.py +0 -0
  95. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_sybase/registry.py +0 -0
  96. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_teradata/api.py +0 -0
  97. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_teradata/compiler.py +0 -0
  98. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.0}/third_party/ibis/ibis_teradata/datatypes.py +0 -0
  99. {google_pso_data_validator-8.5.2 → google_pso_data_validator-8.6.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.6.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"
@@ -719,17 +719,27 @@ target. The `find-tables` command:
719
719
  - Pulls all tables in the source (applying a supplied `allowed-schemas` filter)
720
720
  - Pulls all tables from the target
721
721
  - Uses Jaro Similarity algorithm to match tables
722
+ - Optionally translates source schema names using mappings supplied in `allowed-schemas` before matching
722
723
  - Finally, it prints a JSON list of tables which can be a reference for the
723
724
  validation run config.
724
725
 
725
726
  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
727
 
728
+
727
729
  ```shell
728
730
  data-validation find-tables --source-conn source --target-conn target \
729
731
  --allowed-schemas pso_data_validator \
730
732
  --score-cutoff 1
731
733
  ```
732
734
 
735
+ 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.
736
+
737
+ ```shell
738
+ data-validation find-tables --source-conn source --target-conn target \
739
+ --allowed-schemas prod=dwh \
740
+ --score-cutoff 1
741
+ ```
742
+
733
743
  ### Using Beta CLI Features
734
744
 
735
745
  There may be occasions we want to release a new CLI feature under a Beta flag.
@@ -659,17 +659,27 @@ target. The `find-tables` command:
659
659
  - Pulls all tables in the source (applying a supplied `allowed-schemas` filter)
660
660
  - Pulls all tables from the target
661
661
  - Uses Jaro Similarity algorithm to match tables
662
+ - Optionally translates source schema names using mappings supplied in `allowed-schemas` before matching
662
663
  - Finally, it prints a JSON list of tables which can be a reference for the
663
664
  validation run config.
664
665
 
665
666
  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
667
 
668
+
667
669
  ```shell
668
670
  data-validation find-tables --source-conn source --target-conn target \
669
671
  --allowed-schemas pso_data_validator \
670
672
  --score-cutoff 1
671
673
  ```
672
674
 
675
+ 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.
676
+
677
+ ```shell
678
+ data-validation find-tables --source-conn source --target-conn target \
679
+ --allowed-schemas prod=dwh \
680
+ --score-cutoff 1
681
+ ```
682
+
673
683
  ### Using Beta CLI Features
674
684
 
675
685
  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__"]
@@ -404,7 +404,9 @@ def _configure_find_tables(subparsers):
404
404
  "--target-conn", "-tc", help="Target connection name."
405
405
  )
406
406
  find_tables_parser.add_argument(
407
- "--allowed-schemas", "-as", help="List of source schemas to match."
407
+ "--allowed-schemas",
408
+ "-as",
409
+ help="List of source schemas to match. Can include mappings like 'source_schema=target_schema'.",
408
410
  )
409
411
  find_tables_parser.add_argument(
410
412
  "--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
 
@@ -342,10 +342,10 @@ def get_data_client(connection_config):
342
342
  consts.GOOGLE_SERVICE_ACCOUNT_KEY_PATH
343
343
  )
344
344
  if key_path:
345
- decrypted_connection_config[
346
- "credentials"
347
- ] = google.oauth2.service_account.Credentials.from_service_account_file(
348
- key_path
345
+ decrypted_connection_config["credentials"] = (
346
+ google.oauth2.service_account.Credentials.from_service_account_file(
347
+ key_path
348
+ )
349
349
  )
350
350
 
351
351
  if source_type not in CLIENT_LOOKUP:
@@ -667,6 +667,14 @@ 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
679
  """Returns True when either source or target column is Oracle LOB data type."""
672
680
  return self._is_raw_data_type(
@@ -1351,6 +1359,14 @@ class ConfigManager(object):
1351
1359
  )
1352
1360
  result_source_columns.pop(source_column)
1353
1361
  result_target_columns.pop(target_column)
1362
+ if self._is_db2_blob(source_column, target_column):
1363
+ # Db2 BLOB is incompatible with Db2s own HEX function.
1364
+ # Users would need to use a custom query to a custom UDF to row validate these.
1365
+ logging.info(
1366
+ f"Skipping column {source_column} due to Db2 BLOB data type"
1367
+ )
1368
+ result_source_columns.pop(source_column)
1369
+ result_target_columns.pop(target_column)
1354
1370
 
1355
1371
  return result_source_columns, result_target_columns
1356
1372
 
@@ -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,7 +19,6 @@ 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
23
  RANDOM_SORT_SUPPORTS = [
25
24
  "bigquery",
@@ -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 lower case 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()}"
@@ -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.6.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"
@@ -719,17 +719,27 @@ target. The `find-tables` command:
719
719
  - Pulls all tables in the source (applying a supplied `allowed-schemas` filter)
720
720
  - Pulls all tables from the target
721
721
  - Uses Jaro Similarity algorithm to match tables
722
+ - Optionally translates source schema names using mappings supplied in `allowed-schemas` before matching
722
723
  - Finally, it prints a JSON list of tables which can be a reference for the
723
724
  validation run config.
724
725
 
725
726
  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
727
 
728
+
727
729
  ```shell
728
730
  data-validation find-tables --source-conn source --target-conn target \
729
731
  --allowed-schemas pso_data_validator \
730
732
  --score-cutoff 1
731
733
  ```
732
734
 
735
+ 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.
736
+
737
+ ```shell
738
+ data-validation find-tables --source-conn source --target-conn target \
739
+ --allowed-schemas prod=dwh \
740
+ --score-cutoff 1
741
+ ```
742
+
733
743
  ### Using Beta CLI Features
734
744
 
735
745
  There may be occasions we want to release a new CLI feature under a Beta flag.
@@ -23,7 +23,7 @@ tabulate>=0.9.0
23
23
  1.10.11
24
24
 
25
25
  [develop]
26
- black==22.3.0
26
+ black==26.1.0
27
27
  flake8
28
28
  freezegun
29
29
  pyfakefs
@@ -18,7 +18,7 @@ import setuptools
18
18
 
19
19
  name = "google-pso-data-validator"
20
20
  description = "A package to enable easy data validation"
21
- version = "8.5.2"
21
+ version = "8.6.0"
22
22
  release_status = "Development Status :: 4 - Beta"
23
23
 
24
24
  with open("README.md", "r") as fh:
@@ -51,7 +51,7 @@ extras_require = {
51
51
  "apache-airflow": "1.10.11",
52
52
  "pyspark": "3.0.0",
53
53
  "develop": [
54
- "black==22.3.0",
54
+ "black==26.1.0",
55
55
  "flake8",
56
56
  "freezegun",
57
57
  "pyfakefs",
@@ -13,6 +13,8 @@
13
13
  # limitations under the License.
14
14
 
15
15
  from typing import Iterable, Optional
16
+ import functools
17
+ import inspect
16
18
 
17
19
  from ibis.backends.base.sql.alchemy.datatypes import to_sqla_type
18
20
  import ibis.expr.datatypes as dt
@@ -174,6 +176,9 @@ def db2_type_string_length(
174
176
  return 19
175
177
  elif ibis_column.is_integer():
176
178
  return ibis_integer_string_length(ibis_column)
179
+ elif ibis_column.is_binary() and raw_data_type:
180
+ # Position 1 in raw_types is the data length.
181
+ return raw_data_type[1]
177
182
  else:
178
183
  return None
179
184
 
@@ -196,3 +201,42 @@ def ibis_integer_string_length(ibis_column: dt.DataType) -> Optional[int]:
196
201
 
197
202
 
198
203
  Value.force_cast = force_cast
204
+
205
+
206
+ def cache_generator_results(func):
207
+ """Decorator to cache generator results at the instance level."""
208
+ # Pre-compute the function signature to avoid doing this on every call
209
+ sig = inspect.signature(func)
210
+
211
+ @functools.wraps(func)
212
+ def wrapper(self, *args, **kwargs):
213
+ # Create an instance-level cache if it doesn't already exist.
214
+ # This prevents instances from sharing cache state and avoids memory leaks.
215
+ if not hasattr(self, "_generator_cache"):
216
+ self._generator_cache = {}
217
+
218
+ # Bind the provided args and kwargs to the function's signature.
219
+ # This creates a unified representation of the arguments, avoiding cache misses
220
+ # when identical values are passed differently (e.g., positional vs. keyword).
221
+ bound_args = sig.bind(self, *args, **kwargs)
222
+ bound_args.apply_defaults()
223
+
224
+ # Remove 'self' from the arguments since the cache is already scoped to the instance
225
+ arguments = bound_args.arguments.copy()
226
+ arguments.pop("self", None)
227
+
228
+ # Ensure only supported types are used to prevent frozenset failures on unhashable types
229
+ for v in arguments.values():
230
+ assert isinstance(
231
+ v, (str, int, type(None))
232
+ ), f"Unsupported argument type in cache_generator_results: {type(v)}"
233
+
234
+ # Generate a unique cache key based on the function name and the canonical arguments
235
+ key = (func.__name__, frozenset(arguments.items()))
236
+ if key not in self._generator_cache:
237
+ # Fully evaluate the generator to a list to store it in the cache
238
+ self._generator_cache[key] = list(func(self, *args, **kwargs))
239
+
240
+ yield from self._generator_cache[key]
241
+
242
+ return wrapper
@@ -22,6 +22,7 @@ extended its own registry. Eventually this can potentially be pushed to
22
22
  Ibis as an override, though it would not apply for Pandas and other
23
23
  non-textual languages.
24
24
  """
25
+
25
26
  import datetime
26
27
  import dateutil
27
28
  import numpy as np
@@ -508,19 +509,19 @@ if OracleExprTranslator:
508
509
  ops.StringLength
509
510
  ]
510
511
 
511
- PostgreSQLExprTranslator._registry[
512
- ops.HashBytes
513
- ] = postgres_registry.sa_format_hashbytes
512
+ PostgreSQLExprTranslator._registry[ops.HashBytes] = (
513
+ postgres_registry.sa_format_hashbytes
514
+ )
514
515
  PostgreSQLExprTranslator._registry[RawSQL] = sa_format_raw_sql
515
516
  PostgreSQLExprTranslator._registry[ToChar] = sa_format_to_char
516
517
  PostgreSQLExprTranslator._registry[ops.Cast] = postgres_registry.sa_cast_postgres
517
518
  PostgreSQLExprTranslator._registry[BinaryLength] = sa_format_binary_length
518
- PostgreSQLExprTranslator._registry[
519
- ops.ExtractEpochSeconds
520
- ] = postgres_registry.sa_epoch_seconds
521
- PostgreSQLExprTranslator._registry[
522
- PaddedCharLength
523
- ] = postgres_registry.sa_format_postgres_padded_char_length
519
+ PostgreSQLExprTranslator._registry[ops.ExtractEpochSeconds] = (
520
+ postgres_registry.sa_epoch_seconds
521
+ )
522
+ PostgreSQLExprTranslator._registry[PaddedCharLength] = (
523
+ postgres_registry.sa_format_postgres_padded_char_length
524
+ )
524
525
 
525
526
 
526
527
  MsSqlExprTranslator._registry[ops.HashBytes] = mssql_registry.sa_format_hashbytes
@@ -568,9 +569,9 @@ if TeradataExprTranslator:
568
569
  TeradataExprTranslator._registry[RawSQL] = format_raw_sql
569
570
  TeradataExprTranslator._registry[ops.HashBytes] = format_hashbytes_teradata
570
571
  TeradataExprTranslator._registry[BinaryLength] = sa_format_binary_length
571
- TeradataExprTranslator._registry[
572
- PaddedCharLength
573
- ] = TeradataExprTranslator._registry[ops.StringLength]
572
+ TeradataExprTranslator._registry[PaddedCharLength] = (
573
+ TeradataExprTranslator._registry[ops.StringLength]
574
+ )
574
575
 
575
576
  if SnowflakeExprTranslator:
576
577
  SnowflakeExprTranslator._registry[ops.Cast] = sa_cast_snowflake
@@ -583,10 +584,10 @@ if SnowflakeExprTranslator:
583
584
  SnowflakeExprTranslator._registry[ops.RStrip] = _sa_whitespace_rstrip
584
585
 
585
586
  if SybaseExprTranslator:
586
- SybaseExprTranslator._registry[
587
- BinaryLength
588
- ] = mssql_registry.sa_format_binary_length
587
+ SybaseExprTranslator._registry[BinaryLength] = (
588
+ mssql_registry.sa_format_binary_length
589
+ )
589
590
  SybaseExprTranslator._registry[RawSQL] = sa_format_raw_sql
590
- SybaseExprTranslator._registry[
591
- PaddedCharLength
592
- ] = mssql_registry.sa_format_string_length
591
+ SybaseExprTranslator._registry[PaddedCharLength] = (
592
+ mssql_registry.sa_format_string_length
593
+ )
@@ -19,7 +19,6 @@ from ibis.backends.bigquery import compiler as bigquery_compiler
19
19
  from ibis.backends.bigquery import rewrites
20
20
  from third_party.ibis.ibis_cloud_spanner import registry
21
21
 
22
-
23
22
  _NAME_REGEX = re.compile(r"[_A-Za-z][A-Za-z_0-9]*")
24
23
 
25
24