google-pso-data-validator 8.5.0__tar.gz → 8.5.2__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.5.0/google_pso_data_validator.egg-info → google_pso_data_validator-8.5.2}/PKG-INFO +1 -1
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/combiner.py +7 -5
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/config_manager.py +66 -11
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2/google_pso_data_validator.egg-info}/PKG-INFO +1 -1
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/setup.py +1 -1
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_addon/api.py +10 -1
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_addon/operations.py +1 -1
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_db2/__init__.py +61 -14
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_db2/datatypes.py +7 -2
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_db2/registry.py +38 -7
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_redshift/__init__.py +9 -7
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_sybase/registry.py +1 -1
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/LICENSE +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/README.md +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/__main__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/app.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/cli_tools.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/client_info.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/clients.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/consts.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/data_validation.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/exceptions.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/find_tables.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/gcs_helper.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/jellyfish_distance.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/metadata.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/partition_builder.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/query_builder/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/query_builder/partition_row_builder.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/query_builder/query_builder.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/query_builder/random_row_builder.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/raw_query.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/result_handlers/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/result_handlers/base_backend.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/result_handlers/bigquery.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/result_handlers/factory.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/result_handlers/postgres.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/result_handlers/text.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/schema_validation.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/secret_manager.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/state_manager.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/util.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/validation_builder.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/google_pso_data_validator.egg-info/SOURCES.txt +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/google_pso_data_validator.egg-info/dependency_links.txt +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/google_pso_data_validator.egg-info/entry_points.txt +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/google_pso_data_validator.egg-info/requires.txt +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/google_pso_data_validator.egg-info/top_level.txt +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/setup.cfg +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_addon/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_bigquery/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_bigquery/api.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_bigquery/registry.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_cloud_spanner/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_cloud_spanner/api.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_cloud_spanner/client.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_cloud_spanner/compiler.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_cloud_spanner/datatypes.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_cloud_spanner/registry.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_cloud_spanner/tests/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_cloud_spanner/tests/conftest.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_cloud_spanner/to_pandas.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_db2/api.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_db2/compiler.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_impala/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_impala/api.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_mssql/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_mssql/api.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_mssql/datatypes.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_mssql/registry.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_mysql/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_mysql/base_sql_compiler/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_mysql/base_sql_compiler/select_builder.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_mysql/compiler.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_oracle/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_oracle/api.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_oracle/compiler.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_oracle/datatypes.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_oracle/registry.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_postgres/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_postgres/client.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_postgres/datatypes.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_postgres/registry.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_redshift/api.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_redshift/compiler.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_snowflake/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_snowflake/api.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_snowflake/datatypes.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_sybase/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_sybase/api.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_sybase/compiler.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_sybase/datatypes.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_teradata/__init__.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_teradata/api.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_teradata/compiler.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_teradata/datatypes.py +0 -0
- {google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_teradata/registry.py +0 -0
{google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/combiner.py
RENAMED
|
@@ -113,11 +113,11 @@ def generate_report(
|
|
|
113
113
|
# Get the first validation metadata object to fill source and/or target empty table names.
|
|
114
114
|
first = run_metadata.validations[next(iter(run_metadata.validations))]
|
|
115
115
|
if first.validation_type != consts.CUSTOM_QUERY:
|
|
116
|
-
result_df
|
|
117
|
-
first.get_table_name(consts.RESULT_TYPE_SOURCE)
|
|
116
|
+
result_df["source_table_name"] = result_df["source_table_name"].fillna(
|
|
117
|
+
first.get_table_name(consts.RESULT_TYPE_SOURCE)
|
|
118
118
|
)
|
|
119
|
-
result_df
|
|
120
|
-
first.get_table_name(consts.RESULT_TYPE_TARGET)
|
|
119
|
+
result_df["target_table_name"] = result_df["target_table_name"].fillna(
|
|
120
|
+
first.get_table_name(consts.RESULT_TYPE_TARGET)
|
|
121
121
|
)
|
|
122
122
|
|
|
123
123
|
_get_summary(run_metadata, result_df, source_df, target_df)
|
|
@@ -194,7 +194,9 @@ def _generate_report_slice(
|
|
|
194
194
|
logging.debug(documented.compile())
|
|
195
195
|
|
|
196
196
|
result_df = client.execute(documented)
|
|
197
|
-
result_df
|
|
197
|
+
result_df["validation_status"] = result_df["validation_status"].fillna(
|
|
198
|
+
consts.VALIDATION_STATUS_FAIL
|
|
199
|
+
)
|
|
198
200
|
return result_df
|
|
199
201
|
|
|
200
202
|
|
|
@@ -667,6 +667,27 @@ class ConfigManager(object):
|
|
|
667
667
|
isinstance(source_type, dt.UUID) or isinstance(target_type, dt.UUID)
|
|
668
668
|
)
|
|
669
669
|
|
|
670
|
+
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."""
|
|
672
|
+
return self._is_raw_data_type(
|
|
673
|
+
"db2", source_column_name, target_column_name, ["XML"]
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
def _is_oracle_lob(self, source_column_name: str, target_column_name: str) -> bool:
|
|
677
|
+
"""Returns True when either source or target column is Oracle LOB data type.
|
|
678
|
+
|
|
679
|
+
Unexpectedly the raw types for for LOB types are:
|
|
680
|
+
BLOB: LONG_RAW
|
|
681
|
+
CLOB: LONG
|
|
682
|
+
NCLOB: LONG_NVARCHAR
|
|
683
|
+
"""
|
|
684
|
+
return self._is_raw_data_type(
|
|
685
|
+
"oracle",
|
|
686
|
+
source_column_name,
|
|
687
|
+
target_column_name,
|
|
688
|
+
["LONG", "LONG_NVARCHAR", "LONG_RAW"],
|
|
689
|
+
)
|
|
690
|
+
|
|
670
691
|
def _is_sql_server_text(
|
|
671
692
|
self, source_column_name: str, target_column_name: str
|
|
672
693
|
) -> bool:
|
|
@@ -693,19 +714,36 @@ class ConfigManager(object):
|
|
|
693
714
|
self, source_column_name: str, target_column_name: str, type_list: List[str]
|
|
694
715
|
) -> bool:
|
|
695
716
|
"""Returns True when either source or target column is of a SQL Server type listed in type_list."""
|
|
717
|
+
return self._is_raw_data_type(
|
|
718
|
+
"mssql",
|
|
719
|
+
source_column_name,
|
|
720
|
+
target_column_name,
|
|
721
|
+
type_list,
|
|
722
|
+
)
|
|
696
723
|
|
|
724
|
+
def _is_raw_data_type(
|
|
725
|
+
self,
|
|
726
|
+
client_name: str,
|
|
727
|
+
source_column_name: str,
|
|
728
|
+
target_column_name: str,
|
|
729
|
+
type_list: List[str],
|
|
730
|
+
) -> bool:
|
|
731
|
+
"""Returns True when either source or target column is of a client & type."""
|
|
697
732
|
raw_source_types = self.get_source_raw_data_types()
|
|
698
733
|
raw_target_types = self.get_target_raw_data_types()
|
|
734
|
+
# Raw data type map uses casefold column name as the key.
|
|
699
735
|
return bool(
|
|
700
736
|
(
|
|
701
|
-
self.source_client.name ==
|
|
737
|
+
self.source_client.name == client_name
|
|
702
738
|
and raw_source_types
|
|
703
|
-
and raw_source_types.get(source_column_name, [None])[0]
|
|
739
|
+
and raw_source_types.get(source_column_name.casefold(), [None])[0]
|
|
740
|
+
in type_list
|
|
704
741
|
)
|
|
705
742
|
or (
|
|
706
|
-
self.target_client.name ==
|
|
743
|
+
self.target_client.name == client_name
|
|
707
744
|
and raw_target_types
|
|
708
|
-
and raw_target_types.get(target_column_name, [None])[0]
|
|
745
|
+
and raw_target_types.get(target_column_name.casefold(), [None])[0]
|
|
746
|
+
in type_list
|
|
709
747
|
)
|
|
710
748
|
)
|
|
711
749
|
|
|
@@ -853,6 +891,7 @@ class ConfigManager(object):
|
|
|
853
891
|
]
|
|
854
892
|
depth = 1
|
|
855
893
|
calc_func = consts.CALC_FIELD_LENGTH
|
|
894
|
+
|
|
856
895
|
elif column_type in ["string", "!string"]:
|
|
857
896
|
if self._is_sql_server_text(source_column, target_column):
|
|
858
897
|
calc_func = consts.CALC_FIELD_BYTE_LENGTH
|
|
@@ -992,6 +1031,8 @@ class ConfigManager(object):
|
|
|
992
1031
|
"""Return list of aggregate objects of given agg_type."""
|
|
993
1032
|
|
|
994
1033
|
def require_pre_agg_calc_field(
|
|
1034
|
+
source_column: str,
|
|
1035
|
+
target_column: str,
|
|
995
1036
|
column_type: str,
|
|
996
1037
|
target_column_type: str,
|
|
997
1038
|
agg_type: str,
|
|
@@ -1001,8 +1042,15 @@ class ConfigManager(object):
|
|
|
1001
1042
|
_ in ["string", "!string", "json", "!json"]
|
|
1002
1043
|
for _ in [column_type, target_column_type]
|
|
1003
1044
|
):
|
|
1004
|
-
# These data types are aggregated using their lengths.
|
|
1005
|
-
|
|
1045
|
+
# These data types are aggregated using their lengths, except for count().
|
|
1046
|
+
if agg_type == "count":
|
|
1047
|
+
# Oracle LOBs & SQL Server TEXT need a length before the count().
|
|
1048
|
+
# TODO As does Sybase TEXT, see issue-1675.
|
|
1049
|
+
return self._is_oracle_lob(
|
|
1050
|
+
source_column, target_column
|
|
1051
|
+
) or self._is_sql_server_text(source_column, target_column)
|
|
1052
|
+
else:
|
|
1053
|
+
return True
|
|
1006
1054
|
elif self._is_uuid(column_type, target_column_type):
|
|
1007
1055
|
return True
|
|
1008
1056
|
elif column_type in ["binary", "!binary"]:
|
|
@@ -1010,10 +1058,7 @@ class ConfigManager(object):
|
|
|
1010
1058
|
# Oracle BLOB is invalid for use with SQL COUNT function.
|
|
1011
1059
|
# The expression below returns True if client is Oracle which
|
|
1012
1060
|
# has the effect of triggering use of byte_length transformation.
|
|
1013
|
-
return
|
|
1014
|
-
self.source_client.name == "oracle"
|
|
1015
|
-
or self.target_client.name == "oracle"
|
|
1016
|
-
)
|
|
1061
|
+
return self._is_oracle_lob(source_column, target_column)
|
|
1017
1062
|
else:
|
|
1018
1063
|
# Convert to length for any min/max/sum on binary columns.
|
|
1019
1064
|
return True
|
|
@@ -1101,9 +1146,19 @@ class ConfigManager(object):
|
|
|
1101
1146
|
f"Skipping {agg_type} on {column} due to SQL Server image data type"
|
|
1102
1147
|
)
|
|
1103
1148
|
continue
|
|
1149
|
+
elif agg_type != "count" and self._is_db2_xml(column, column):
|
|
1150
|
+
logging.info(
|
|
1151
|
+
f"Skipping {agg_type} on {column} due to Db2 XML data type"
|
|
1152
|
+
)
|
|
1153
|
+
continue
|
|
1104
1154
|
|
|
1105
1155
|
if require_pre_agg_calc_field(
|
|
1106
|
-
|
|
1156
|
+
casefold_source_columns[column],
|
|
1157
|
+
casefold_target_columns[column],
|
|
1158
|
+
column_type,
|
|
1159
|
+
target_column_type,
|
|
1160
|
+
agg_type,
|
|
1161
|
+
cast_to_bigint,
|
|
1107
1162
|
):
|
|
1108
1163
|
aggregate_config = self.append_pre_agg_calc_field(
|
|
1109
1164
|
casefold_source_columns[column],
|
|
@@ -173,6 +173,14 @@ def db2_type_string_length(
|
|
|
173
173
|
# Timestamp when converted to string will be at least 19 characters (YYYY-MM-DD HH:MI:SS).
|
|
174
174
|
return 19
|
|
175
175
|
elif ibis_column.is_integer():
|
|
176
|
+
return ibis_integer_string_length(ibis_column)
|
|
177
|
+
else:
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def ibis_integer_string_length(ibis_column: dt.DataType) -> Optional[int]:
|
|
182
|
+
"""Return the maximum string length of an integer column, including minus sign."""
|
|
183
|
+
if ibis_column.is_integer():
|
|
176
184
|
if isinstance(ibis_column, dt.Int64):
|
|
177
185
|
return 20
|
|
178
186
|
elif isinstance(ibis_column, dt.Int32):
|
|
@@ -181,7 +189,8 @@ def db2_type_string_length(
|
|
|
181
189
|
return 6
|
|
182
190
|
elif isinstance(ibis_column, dt.Int8):
|
|
183
191
|
return 4
|
|
184
|
-
|
|
192
|
+
# Safe catch all length.
|
|
193
|
+
return 38
|
|
185
194
|
else:
|
|
186
195
|
return None
|
|
187
196
|
|
|
@@ -439,7 +439,7 @@ def string_to_epoch(ts: str) -> int:
|
|
|
439
439
|
|
|
440
440
|
@execute_node.register(ops.ExtractEpochSeconds, (datetime.datetime, pd.Series))
|
|
441
441
|
def execute_epoch_seconds_new(op, data, **kwargs):
|
|
442
|
-
convert =
|
|
442
|
+
convert = data.astype
|
|
443
443
|
try:
|
|
444
444
|
series = convert(np.int64)
|
|
445
445
|
# We need int64 below because NaT overflows int32.
|
|
@@ -11,11 +11,13 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
import re
|
|
16
|
+
from typing import Iterable, Optional, Tuple
|
|
17
|
+
|
|
18
|
+
import sqlalchemy as sa
|
|
16
19
|
|
|
17
20
|
import ibis.expr.datatypes as dt
|
|
18
|
-
from typing import Iterable, Tuple
|
|
19
21
|
from ibis.backends.base.sql.alchemy import BaseAlchemyBackend
|
|
20
22
|
from third_party.ibis.ibis_db2.compiler import Db2Compiler
|
|
21
23
|
from third_party.ibis.ibis_db2.datatypes import _get_type
|
|
@@ -28,11 +30,11 @@ class Backend(BaseAlchemyBackend):
|
|
|
28
30
|
def do_connect(
|
|
29
31
|
self,
|
|
30
32
|
host: str = "localhost",
|
|
31
|
-
user: str = None,
|
|
32
|
-
password: str = None,
|
|
33
|
+
user: Optional[str] = None,
|
|
34
|
+
password: Optional[str] = None,
|
|
33
35
|
port: int = 50000,
|
|
34
|
-
database: str = None,
|
|
35
|
-
url: str = None,
|
|
36
|
+
database: Optional[str] = None,
|
|
37
|
+
url: Optional[str] = None,
|
|
36
38
|
driver: str = "ibm_db_sa",
|
|
37
39
|
) -> None:
|
|
38
40
|
if url is None:
|
|
@@ -102,24 +104,69 @@ class Backend(BaseAlchemyBackend):
|
|
|
102
104
|
)
|
|
103
105
|
return [_[0] for _ in result.cursor.fetchall()]
|
|
104
106
|
|
|
105
|
-
def
|
|
107
|
+
def raw_column_metadata(
|
|
106
108
|
self, database: str = None, table: str = None, query: str = None
|
|
107
109
|
) -> list:
|
|
108
110
|
"""Define this method to allow DVT to test if backend specific transformations may be needed for comparison.
|
|
109
|
-
Partner method to _metadata that retains raw data type information instead of converting
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
THIS METHOD IS NOT IMPLEMENTED YET.
|
|
111
|
+
Partner method to _metadata that retains raw data type information instead of converting to Ibis types.
|
|
112
|
+
This works in the same way as _metadata by running a query over the DVT source, either schema.table or a
|
|
113
|
+
custom query, and fetching the first row. From the cursor we can detect data types of the row's columns.
|
|
114
114
|
|
|
115
|
+
NOTE: This only works for table look-ups. For custom queries the raw data types are not available to us
|
|
116
|
+
due to the IBM Db2 driver hiding the real data types.
|
|
115
117
|
|
|
116
118
|
Returns:
|
|
117
119
|
list: A list of tuples containing the standard 7 DB API fields:
|
|
118
120
|
https://peps.python.org/pep-0249/#description
|
|
119
121
|
"""
|
|
120
|
-
|
|
122
|
+
assert (database and table) or query, "We should never receive all args=None"
|
|
123
|
+
if database and table:
|
|
124
|
+
# For table-based validation, query the system catalog to get the true data type.
|
|
125
|
+
get_column_metadata_sql = """
|
|
126
|
+
SELECT COLNAME, TYPENAME, LENGTH, SCALE, NULLS
|
|
127
|
+
FROM SYSCAT.COLUMNS
|
|
128
|
+
WHERE TABSCHEMA = ? AND TABNAME = ?
|
|
129
|
+
ORDER BY COLNO
|
|
130
|
+
"""
|
|
131
|
+
with self.begin() as con:
|
|
132
|
+
result = con.exec_driver_sql(
|
|
133
|
+
get_column_metadata_sql,
|
|
134
|
+
parameters=(database.upper(), table.upper()),
|
|
135
|
+
)
|
|
136
|
+
# Yield a 7-tuple mimicking cursor.description, with the true typename.
|
|
137
|
+
for (
|
|
138
|
+
colname,
|
|
139
|
+
typename,
|
|
140
|
+
col_length,
|
|
141
|
+
col_scale,
|
|
142
|
+
nullable,
|
|
143
|
+
) in result.cursor.fetchall():
|
|
144
|
+
yield (
|
|
145
|
+
colname,
|
|
146
|
+
typename,
|
|
147
|
+
col_length,
|
|
148
|
+
col_length,
|
|
149
|
+
col_length,
|
|
150
|
+
col_scale,
|
|
151
|
+
nullable,
|
|
152
|
+
)
|
|
153
|
+
elif query:
|
|
154
|
+
# For custom queries, the system catalog cannot be used. Fall back to
|
|
155
|
+
# cursor.description, which may not distinguish padded char types.
|
|
156
|
+
source = f"({query})"
|
|
157
|
+
with self.begin() as con:
|
|
158
|
+
result = con.exec_driver_sql(f"SELECT * FROM {source} t0 LIMIT 0")
|
|
159
|
+
cursor = result.cursor
|
|
160
|
+
yield from (column for column in cursor.description)
|
|
121
161
|
|
|
122
162
|
def is_char_type_padded(self, char_type: Tuple) -> bool:
|
|
123
163
|
"""Define this method if the backend supports character/string types that are padded and returns
|
|
124
164
|
padded values, which DVT may want to trim"""
|
|
125
|
-
|
|
165
|
+
type_code = char_type[0]
|
|
166
|
+
if isinstance(type_code, str):
|
|
167
|
+
return type_code.upper() == "CHARACTER"
|
|
168
|
+
else:
|
|
169
|
+
# From cursor.description for custom queries, this is a DBAPITypeObject.
|
|
170
|
+
# It's not possible to distinguish padded char types in this case,
|
|
171
|
+
# so we default to False to be safe and avoid trimming incorrectly.
|
|
172
|
+
return False
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
import ibm_db_dbi
|
|
16
16
|
|
|
17
17
|
import ibis.expr.datatypes as dt
|
|
18
|
-
from ibis.backends.base.sql.alchemy.datatypes import ibis_type_to_sqla
|
|
19
18
|
from ibm_db_sa.ibm_db import DB2Dialect_ibm_db
|
|
20
19
|
import sqlalchemy as sa
|
|
21
20
|
import sqlalchemy.types as sat
|
|
22
21
|
|
|
22
|
+
from ibis.backends.base.sql.alchemy import to_sqla_type
|
|
23
23
|
|
|
24
24
|
# Types from https://github.com/ibmdb/python-ibmdb/blob/master/IBM_DB/ibm_db/ibm_db_dbi.py
|
|
25
25
|
_type_mapping = {
|
|
@@ -37,7 +37,12 @@ _type_mapping = {
|
|
|
37
37
|
ibm_db_dbi.ROWID: dt.String,
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
|
|
41
|
+
@to_sqla_type.register(DB2Dialect_ibm_db, dt.String)
|
|
42
|
+
def _string(_, itype):
|
|
43
|
+
"""Include a limit for casts to String due to line size limits supported by Db2 concat()."""
|
|
44
|
+
return sa.sql.sqltypes.String(length=3000)
|
|
45
|
+
|
|
41
46
|
|
|
42
47
|
DB2Dialect_ibm_db.ischema_names["DECFLOAT"] = sat.DOUBLE
|
|
43
48
|
|
|
@@ -33,6 +33,9 @@ from ibis.backends.base.sql.alchemy import (
|
|
|
33
33
|
)
|
|
34
34
|
from ibis.backends.base.sql.alchemy.registry import variance_reduction
|
|
35
35
|
|
|
36
|
+
from third_party.ibis.ibis_addon.api import ibis_integer_string_length
|
|
37
|
+
|
|
38
|
+
|
|
36
39
|
operation_registry = sqlalchemy_operation_registry.copy()
|
|
37
40
|
operation_registry.update(sqlalchemy_window_functions_registry)
|
|
38
41
|
|
|
@@ -121,9 +124,41 @@ def _cast(t, op):
|
|
|
121
124
|
|
|
122
125
|
sa_arg = t.translate(arg)
|
|
123
126
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
if arg_dtype.is_integer():
|
|
128
|
+
if typ.is_timestamp():
|
|
129
|
+
# specialize going from an integer type to a timestamp
|
|
130
|
+
return t.integer_to_timestamp(sa_arg, tz=typ.timezone)
|
|
131
|
+
elif typ.is_string():
|
|
132
|
+
string_length = ibis_integer_string_length(arg_dtype)
|
|
133
|
+
if string_length:
|
|
134
|
+
# Restring target string length to appropriate length for integer size.
|
|
135
|
+
return sa.cast(sa_arg, sa.String(string_length))
|
|
136
|
+
elif arg_dtype.is_decimal() and typ.is_string():
|
|
137
|
+
if arg_dtype.scale is not None and arg_dtype.scale > 0:
|
|
138
|
+
# Db2 always pads fractional part of the number out to length of scale.
|
|
139
|
+
# We need to remove those insignificant digits.
|
|
140
|
+
precision = arg_dtype.precision or 31
|
|
141
|
+
fmt = (
|
|
142
|
+
("9" * (precision - arg_dtype.scale - 1))
|
|
143
|
+
+ "0."
|
|
144
|
+
+ ("9" * arg_dtype.scale)
|
|
145
|
+
)
|
|
146
|
+
return sa.func.ltrim(
|
|
147
|
+
sa.func.regexp_replace(
|
|
148
|
+
sa.func.to_char(sa_arg, fmt),
|
|
149
|
+
sa.literal_column("'\\.?0+$'"),
|
|
150
|
+
sa.literal_column("''"),
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
# Max expected precision 38 plus 2 for minus sign and decimal place.
|
|
154
|
+
return sa.cast(sa_arg, sa.String(40))
|
|
155
|
+
elif arg_dtype.is_floating() and typ.is_string():
|
|
156
|
+
# Max expected precision 38 plus 2 for minus sign and decimal place.
|
|
157
|
+
return sa.cast(sa_arg, sa.String(40))
|
|
158
|
+
|
|
159
|
+
if arg_dtype.is_time() and typ.is_string():
|
|
160
|
+
# Force colons as time separator with CHAR(column,JIS) expression.
|
|
161
|
+
return sa.func.char(sa_arg, sa.literal_column("JIS"))
|
|
127
162
|
|
|
128
163
|
if arg_dtype.is_binary() and typ.is_string():
|
|
129
164
|
# Binary to string cast is a "to hex" conversion for DVT.
|
|
@@ -297,10 +332,6 @@ def _reduction(func_name):
|
|
|
297
332
|
return reduction_compiler
|
|
298
333
|
|
|
299
334
|
|
|
300
|
-
def _count_start(sa_func):
|
|
301
|
-
return sa_func
|
|
302
|
-
|
|
303
|
-
|
|
304
335
|
def _reduction_count(sa_func):
|
|
305
336
|
def formatter(t, op):
|
|
306
337
|
*args, where = op.args
|
|
@@ -82,14 +82,16 @@ class Backend(BaseAlchemyBackend):
|
|
|
82
82
|
return self._filter_with_like(databases, like)
|
|
83
83
|
|
|
84
84
|
def _metadata(self, query: str) -> Iterable[Tuple[str, dt.DataType]]:
|
|
85
|
-
raw_name = util.guid()
|
|
85
|
+
raw_name = util.guid().lower()
|
|
86
86
|
name = self._quote(raw_name)
|
|
87
|
-
type_info_sql = """
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
type_info_sql = """
|
|
88
|
+
SELECT
|
|
89
|
+
"column_name",
|
|
90
|
+
"data_type"
|
|
91
|
+
FROM SVV_ALL_COLUMNS
|
|
92
|
+
WHERE table_name = :raw_name
|
|
93
|
+
ORDER BY ordinal_position
|
|
94
|
+
"""
|
|
93
95
|
if self.inspector.has_table(query):
|
|
94
96
|
query = f"TABLE {query}"
|
|
95
97
|
with self.begin() as con:
|
|
@@ -104,7 +104,7 @@ def sa_format_hashbytes(translator, op):
|
|
|
104
104
|
Sybase ASE does not support a SHA256 algorithm:
|
|
105
105
|
https://userapps.support.sap.com/sap/support/knowledge/en/3108409
|
|
106
106
|
|
|
107
|
-
It might be possible to add a
|
|
107
|
+
It might be possible to add a custom UDF for this, see issue-1538 for details.
|
|
108
108
|
"""
|
|
109
109
|
arg = translator.translate(op.arg)
|
|
110
110
|
cast_arg = sa.func.convert(sa.sql.literal_column("VARCHAR(16384)"), arg)
|
|
File without changes
|
|
File without changes
|
{google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/__init__.py
RENAMED
|
File without changes
|
{google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
{google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/cli_tools.py
RENAMED
|
File without changes
|
{google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/client_info.py
RENAMED
|
File without changes
|
{google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/clients.py
RENAMED
|
File without changes
|
{google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/consts.py
RENAMED
|
File without changes
|
|
File without changes
|
{google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/exceptions.py
RENAMED
|
File without changes
|
{google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/find_tables.py
RENAMED
|
File without changes
|
{google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/gcs_helper.py
RENAMED
|
File without changes
|
|
File without changes
|
{google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/metadata.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/raw_query.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/data_validation/state_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{google_pso_data_validator-8.5.0 → google_pso_data_validator-8.5.2}/third_party/ibis/ibis_db2/api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|