google-pso-data-validator 8.3.0__tar.gz → 8.5.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 (98) hide show
  1. {google_pso_data_validator-8.3.0/google_pso_data_validator.egg-info → google_pso_data_validator-8.5.0}/PKG-INFO +9 -3
  2. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/README.md +6 -1
  3. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/__main__.py +1 -0
  4. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/cli_tools.py +42 -25
  5. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/clients.py +43 -14
  6. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/config_manager.py +79 -4
  7. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/consts.py +7 -0
  8. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/exceptions.py +4 -0
  9. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/partition_builder.py +10 -1
  10. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/query_builder/query_builder.py +4 -1
  11. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/query_builder/random_row_builder.py +2 -1
  12. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0/google_pso_data_validator.egg-info}/PKG-INFO +9 -3
  13. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/google_pso_data_validator.egg-info/SOURCES.txt +5 -0
  14. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/google_pso_data_validator.egg-info/requires.txt +2 -1
  15. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/setup.py +3 -2
  16. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_addon/api.py +34 -1
  17. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_addon/operations.py +15 -16
  18. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_bigquery/__init__.py +20 -10
  19. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_db2/compiler.py +1 -0
  20. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_db2/datatypes.py +7 -1
  21. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_db2/registry.py +24 -42
  22. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_mssql/registry.py +1 -1
  23. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_oracle/datatypes.py +1 -1
  24. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_redshift/__init__.py +1 -1
  25. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_snowflake/api.py +16 -7
  26. google_pso_data_validator-8.5.0/third_party/ibis/ibis_sybase/__init__.py +185 -0
  27. google_pso_data_validator-8.5.0/third_party/ibis/ibis_sybase/api.py +42 -0
  28. google_pso_data_validator-8.5.0/third_party/ibis/ibis_sybase/compiler.py +101 -0
  29. google_pso_data_validator-8.5.0/third_party/ibis/ibis_sybase/datatypes.py +95 -0
  30. google_pso_data_validator-8.5.0/third_party/ibis/ibis_sybase/registry.py +209 -0
  31. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/LICENSE +0 -0
  32. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/__init__.py +0 -0
  33. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/app.py +0 -0
  34. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/client_info.py +0 -0
  35. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/combiner.py +0 -0
  36. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/data_validation.py +0 -0
  37. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/find_tables.py +0 -0
  38. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/gcs_helper.py +0 -0
  39. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/jellyfish_distance.py +0 -0
  40. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/metadata.py +0 -0
  41. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/query_builder/__init__.py +0 -0
  42. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/query_builder/partition_row_builder.py +0 -0
  43. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/raw_query.py +0 -0
  44. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/result_handlers/__init__.py +0 -0
  45. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/result_handlers/base_backend.py +0 -0
  46. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/result_handlers/bigquery.py +0 -0
  47. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/result_handlers/factory.py +0 -0
  48. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/result_handlers/postgres.py +0 -0
  49. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/result_handlers/text.py +0 -0
  50. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/schema_validation.py +0 -0
  51. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/secret_manager.py +0 -0
  52. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/state_manager.py +0 -0
  53. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/util.py +0 -0
  54. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/data_validation/validation_builder.py +0 -0
  55. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/google_pso_data_validator.egg-info/dependency_links.txt +0 -0
  56. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/google_pso_data_validator.egg-info/entry_points.txt +0 -0
  57. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/google_pso_data_validator.egg-info/top_level.txt +0 -0
  58. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/setup.cfg +0 -0
  59. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_addon/__init__.py +0 -0
  60. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_bigquery/api.py +0 -0
  61. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_bigquery/registry.py +0 -0
  62. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_cloud_spanner/__init__.py +0 -0
  63. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_cloud_spanner/api.py +0 -0
  64. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_cloud_spanner/client.py +0 -0
  65. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_cloud_spanner/compiler.py +0 -0
  66. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_cloud_spanner/datatypes.py +0 -0
  67. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_cloud_spanner/registry.py +0 -0
  68. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_cloud_spanner/tests/__init__.py +0 -0
  69. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_cloud_spanner/tests/conftest.py +0 -0
  70. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_cloud_spanner/to_pandas.py +0 -0
  71. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_db2/__init__.py +0 -0
  72. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_db2/api.py +0 -0
  73. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_impala/__init__.py +0 -0
  74. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_impala/api.py +0 -0
  75. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_mssql/__init__.py +0 -0
  76. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_mssql/api.py +0 -0
  77. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_mssql/datatypes.py +0 -0
  78. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_mysql/__init__.py +0 -0
  79. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_mysql/base_sql_compiler/__init__.py +0 -0
  80. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_mysql/base_sql_compiler/select_builder.py +0 -0
  81. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_mysql/compiler.py +0 -0
  82. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_oracle/__init__.py +0 -0
  83. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_oracle/api.py +0 -0
  84. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_oracle/compiler.py +0 -0
  85. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_oracle/registry.py +0 -0
  86. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_postgres/__init__.py +0 -0
  87. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_postgres/client.py +0 -0
  88. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_postgres/datatypes.py +0 -0
  89. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_postgres/registry.py +0 -0
  90. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_redshift/api.py +0 -0
  91. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_redshift/compiler.py +0 -0
  92. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_snowflake/__init__.py +0 -0
  93. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_snowflake/datatypes.py +0 -0
  94. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_teradata/__init__.py +0 -0
  95. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_teradata/api.py +0 -0
  96. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_teradata/compiler.py +0 -0
  97. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.0}/third_party/ibis/ibis_teradata/datatypes.py +0 -0
  98. {google_pso_data_validator-8.3.0 → google_pso_data_validator-8.5.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.3.0
3
+ Version: 8.5.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
@@ -28,7 +28,7 @@ Requires-Dist: jellyfish>=1.1.0
28
28
  Requires-Dist: pandas
29
29
  Requires-Dist: parsy>=2.1
30
30
  Requires-Dist: psycopg2-binary>=2.9.9
31
- Requires-Dist: pyarrow==14.0.1
31
+ Requires-Dist: pyarrow
32
32
  Requires-Dist: pydata-google-auth>=1.8.2
33
33
  Requires-Dist: PyMySQL>=1.1.1
34
34
  Requires-Dist: PyYAML>=6.0.2
@@ -45,6 +45,7 @@ Requires-Dist: freezegun; extra == "develop"
45
45
  Requires-Dist: pyfakefs; extra == "develop"
46
46
  Requires-Dist: pytest; extra == "develop"
47
47
  Requires-Dist: pytest-cov; extra == "develop"
48
+ Requires-Dist: pytest-rerunfailures; extra == "develop"
48
49
  Requires-Dist: pytest-timeout; extra == "develop"
49
50
  Dynamic: author
50
51
  Dynamic: author-email
@@ -92,8 +93,9 @@ DVT supports the following connection types:
92
93
  * [Postgres](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#postgres)
93
94
  * [Redshift](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#redshift)
94
95
  * [Spanner](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#google-spanner)
95
- * [Teradata](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#teradata)
96
96
  * [Snowflake](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#snowflake)
97
+ * [Sybase ASE](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#sybase)
98
+ * [Teradata](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#teradata)
97
99
 
98
100
  The [Connections](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md) page provides details about how to create
99
101
  and list connections for the validation tool.
@@ -552,6 +554,10 @@ data-validation
552
554
  Comma-separated key value pair labels for the run.
553
555
  [--format or -fmt FORMAT]
554
556
  Format for stdout output. Supported formats are (text, csv, json, table). Defaults to table.
557
+ [--use-random-row or -rr]
558
+ Finds a set of random rows of the first primary key supplied.
559
+ [--random-row-batch-size or -rbs]
560
+ Row batch size used for random row filters (default 10,000).
555
561
  [--filter-status or -fs STATUSES_LIST]
556
562
  Comma separated list of statuses to filter the validation results. Supported statuses are (success, fail). If no list is provided, all statuses are returned.
557
563
  [--case-insensitive-match, -cim]
@@ -33,8 +33,9 @@ DVT supports the following connection types:
33
33
  * [Postgres](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#postgres)
34
34
  * [Redshift](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#redshift)
35
35
  * [Spanner](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#google-spanner)
36
- * [Teradata](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#teradata)
37
36
  * [Snowflake](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#snowflake)
37
+ * [Sybase ASE](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#sybase)
38
+ * [Teradata](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#teradata)
38
39
 
39
40
  The [Connections](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md) page provides details about how to create
40
41
  and list connections for the validation tool.
@@ -493,6 +494,10 @@ data-validation
493
494
  Comma-separated key value pair labels for the run.
494
495
  [--format or -fmt FORMAT]
495
496
  Format for stdout output. Supported formats are (text, csv, json, table). Defaults to table.
497
+ [--use-random-row or -rr]
498
+ Finds a set of random rows of the first primary key supplied.
499
+ [--random-row-batch-size or -rbs]
500
+ Row batch size used for random row filters (default 10,000).
496
501
  [--filter-status or -fs STATUSES_LIST]
497
502
  Comma separated list of statuses to filter the validation results. Supported statuses are (success, fail). If no list is provided, all statuses are returned.
498
503
  [--case-insensitive-match, -cim]
@@ -596,6 +596,7 @@ def partition_and_store_config_files(args: "Namespace") -> None:
596
596
  setattr(args, "custom_query_type", "row")
597
597
  config_managers = build_config_managers_from_args(args, consts.CUSTOM_QUERY)
598
598
  partition_builder = PartitionBuilder(config_managers, args)
599
+ partition_builder.check_partition_configs()
599
600
  partition_builder.partition_configs()
600
601
 
601
602
 
@@ -73,6 +73,10 @@ if TYPE_CHECKING:
73
73
  CONNECTION_SOURCE_FIELDS = {
74
74
  consts.SOURCE_TYPE_BIGQUERY: [
75
75
  ["project_id", "GCP Project to use for BigQuery"],
76
+ [
77
+ "client_project_id",
78
+ "(Optional) BigQuery job/billing project (can differ from data project)",
79
+ ],
76
80
  ["google_service_account_key_path", "(Optional) GCP SA Key Path"],
77
81
  [
78
82
  "api_endpoint",
@@ -84,7 +88,7 @@ CONNECTION_SOURCE_FIELDS = {
84
88
  ],
85
89
  ],
86
90
  consts.SOURCE_TYPE_TERADATA: [
87
- ["host", "Desired Teradata host"],
91
+ ["host", "Teradata host"],
88
92
  ["port", "Teradata port to connect on"],
89
93
  ["user_name", "User used to connect"],
90
94
  ["password", "Password for supplied user"],
@@ -93,7 +97,7 @@ CONNECTION_SOURCE_FIELDS = {
93
97
  ["json_params", "(Optional) Additional teradatasql JSON string parameters"],
94
98
  ],
95
99
  consts.SOURCE_TYPE_ORACLE: [
96
- ["host", "Desired Oracle host"],
100
+ ["host", "Oracle host"],
97
101
  ["port", "Oracle port to connect on"],
98
102
  ["user", "User used to connect"],
99
103
  ["thick_mode", "Flag indicating thick_mode"],
@@ -103,7 +107,7 @@ CONNECTION_SOURCE_FIELDS = {
103
107
  ["connect_args", "(Optional) Additional connection argument mapping"],
104
108
  ],
105
109
  consts.SOURCE_TYPE_MSSQL: [
106
- ["host", "Desired SQL Server host (default localhost)"],
110
+ ["host", "SQL Server host (default localhost)"],
107
111
  ["port", "SQL Server port to connect on (default 1433)"],
108
112
  ["user", "User used to connect"],
109
113
  ["password", "Password for supplied user"],
@@ -112,7 +116,7 @@ CONNECTION_SOURCE_FIELDS = {
112
116
  ["url", "SQL Server SQLAlchemy connection URL"],
113
117
  ],
114
118
  consts.SOURCE_TYPE_MYSQL: [
115
- ["host", "Desired MySQL host (default localhost)"],
119
+ ["host", "MySQL host (default localhost)"],
116
120
  ["port", "MySQL port to connect on (default 3306)"],
117
121
  ["user", "User used to connect"],
118
122
  ["password", "Password for supplied user"],
@@ -123,17 +127,19 @@ CONNECTION_SOURCE_FIELDS = {
123
127
  ["password", "Password for authentication of user"],
124
128
  ["account", "Snowflake account to connect to"],
125
129
  ["database", "Database in snowflake to connect to"],
130
+ ["warehouse", "Warehouse in snowflake to connect to"],
131
+ ["role", "Role in snowflake to connect as"],
126
132
  ["connect_args", "(Optional) Additional connection argument mapping"],
127
133
  ],
128
134
  consts.SOURCE_TYPE_POSTGRES: [
129
- ["host", "Desired PostgreSQL host."],
135
+ ["host", "PostgreSQL host."],
130
136
  ["port", "PostgreSQL port to connect on (e.g. 5432)"],
131
137
  ["user", "Username to connect to"],
132
138
  ["password", "Password for authentication of user"],
133
139
  ["database", "Database in PostgreSQL to connect to (default postgres)"],
134
140
  ],
135
141
  consts.SOURCE_TYPE_REDSHIFT: [
136
- ["host", "Desired Redshift host."],
142
+ ["host", "Redshift host."],
137
143
  ["port", "Redshift port to connect on (e.g. 5439)"],
138
144
  ["user", "Username to connect to"],
139
145
  ["password", "Password for authentication of user"],
@@ -149,16 +155,26 @@ CONNECTION_SOURCE_FIELDS = {
149
155
  '(Optional) GCP Spanner API endpoint (e.g. "https://spanner-mypsc.p.googleapis.com")',
150
156
  ],
151
157
  ],
158
+ consts.SOURCE_TYPE_SYBASE: [
159
+ ["host", "Sybase host (default localhost)"],
160
+ ["port", "Sybase port to connect on (default 5000)"],
161
+ ["user", "User used to connect"],
162
+ ["password", "Password for supplied user"],
163
+ ["database", "Database to connect to (default master)"],
164
+ ["odbc_driver", "ODBC driver name"],
165
+ ["query", "Connection query parameters"],
166
+ ["url", "Sybase SQLAlchemy connection URL"],
167
+ ],
152
168
  consts.SOURCE_TYPE_FILESYSTEM: [
153
169
  ["table_name", "Table name to use as reference for file data"],
154
170
  ["file_path", "The local, s3, or GCS file path to the data"],
155
171
  ["file_type", "The file type of the file. 'csv', 'orc', 'parquet' or 'json'"],
156
172
  ],
157
173
  consts.SOURCE_TYPE_IMPALA: [
158
- ["host", "Desired Impala host"],
159
- ["port", "Desired Impala port (10000 if not provided)"],
160
- ["database", "Desired Impala database (default if not provided)"],
161
- ["auth_mechanism", "Desired Impala auth mechanism (PLAIN if not provided)"],
174
+ ["host", "Impala host"],
175
+ ["port", "Impala port (10000 if not provided)"],
176
+ ["database", "Impala database (default if not provided)"],
177
+ ["auth_mechanism", "Impala auth mechanism (PLAIN if not provided)"],
162
178
  [
163
179
  "kerberos_service_name",
164
180
  "Desired Kerberos service name ('impala' if not provided)",
@@ -183,8 +199,8 @@ CONNECTION_SOURCE_FIELDS = {
183
199
  ["http_path", "URL path of HTTP proxy"],
184
200
  ],
185
201
  consts.SOURCE_TYPE_DB2: [
186
- ["host", "Desired DB2 host"],
187
- ["port", "Desired DB2 port (50000 if not provided)"],
202
+ ["host", "DB2 host"],
203
+ ["port", "DB2 port (50000 if not provided)"],
188
204
  ["user", "Username to connect to"],
189
205
  ["password", "Password for authentication of user"],
190
206
  ["database", "Database in DB2 to connect to"],
@@ -646,8 +662,8 @@ def _configure_row_parser(
646
662
  defaults, so most users do not need to use this option unless they encounter errors."""
647
663
  ),
648
664
  )
649
- # Generate-table-partitions and custom-query does not support random row
650
- if not (is_generate_partitions or is_custom_query):
665
+ # Generate-table-partitions does not support random row
666
+ if not is_generate_partitions:
651
667
  optional_arguments.add_argument(
652
668
  "--use-random-row",
653
669
  "-rr",
@@ -659,17 +675,18 @@ def _configure_row_parser(
659
675
  "-rbs",
660
676
  help="Row batch size used for random row filters (default 10,000).",
661
677
  )
662
- # Generate table partitions follows a new argument spec where either the table names or queries can be provided, but not both.
663
- # that is specified in configure_partition_parser. If we use the same spec for row and column validation, the custom query commands
664
- # may get subsumed by validate and validate commands by specifying tables name or queries. Until this -tbls will be
665
- # a required argument for validate row, validate column and validate schema.
666
- required_arguments.add_argument(
667
- "--tables-list",
668
- "-tbls",
669
- default=None,
670
- required=True,
671
- help="Comma separated tables list in the form 'schema.table=target_schema.target_table'",
672
- )
678
+ if not is_custom_query:
679
+ # Generate table partitions follows a new argument spec where either the table names or queries can be provided, but not both.
680
+ # that is specified in configure_partition_parser. If we use the same spec for row and column validation, the custom query commands
681
+ # may get subsumed by validate and validate commands by specifying tables name or queries. Until this -tbls will be
682
+ # a required argument for validate row, validate column and validate schema.
683
+ required_arguments.add_argument(
684
+ "--tables-list",
685
+ "-tbls",
686
+ default=None,
687
+ required=True,
688
+ help="Comma separated tables list in the form 'schema.table=target_schema.target_table'",
689
+ )
673
690
 
674
691
  # Group for mutually exclusive required arguments. Either must be supplied
675
692
  mutually_exclusive_arguments = required_arguments.add_mutually_exclusive_group(
@@ -16,7 +16,7 @@
16
16
  from contextlib import contextmanager
17
17
  import copy
18
18
  import logging
19
- from typing import TYPE_CHECKING
19
+ from typing import TYPE_CHECKING, Optional
20
20
  import warnings
21
21
 
22
22
  import google.oauth2.service_account
@@ -56,6 +56,7 @@ IBIS_ALCHEMY_BACKENDS = [
56
56
  "mssql",
57
57
  "redshift",
58
58
  "snowflake",
59
+ "sybase",
59
60
  ]
60
61
 
61
62
 
@@ -93,19 +94,32 @@ try:
93
94
  except ImportError:
94
95
  db2_connect = _raise_missing_client_error("pip install ibm_db_sa")
95
96
 
97
+ # Sybase requires sqlalchemy_sybase package.
98
+ try:
99
+ from third_party.ibis.ibis_sybase.api import sybase_connect
100
+ except ImportError:
101
+ sybase_connect = _raise_missing_client_error("pip install sqlalchemy_sybase")
102
+
96
103
 
97
104
  def get_google_bigquery_client(
98
- project_id: str, credentials=None, api_endpoint: str = None
105
+ project_id: str,
106
+ credentials=None,
107
+ api_endpoint: Optional[str] = None,
108
+ quota_project_id: Optional[str] = None,
99
109
  ):
100
110
  info = client_info.get_http_client_info()
101
111
  job_config = bigquery.QueryJobConfig(
102
112
  connection_properties=[bigquery.ConnectionProperty("time_zone", "UTC")]
103
113
  )
114
+ effective_project = quota_project_id or project_id
104
115
  options = None
105
- if api_endpoint:
106
- options = client_options.ClientOptions(api_endpoint=api_endpoint)
116
+ if api_endpoint or quota_project_id:
117
+ options = client_options.ClientOptions(
118
+ api_endpoint=api_endpoint,
119
+ quota_project_id=quota_project_id if quota_project_id else None,
120
+ )
107
121
  return bigquery.Client(
108
- project=project_id,
122
+ project=effective_project,
109
123
  client_info=info,
110
124
  credentials=credentials,
111
125
  default_query_job_config=job_config,
@@ -113,10 +127,17 @@ def get_google_bigquery_client(
113
127
  )
114
128
 
115
129
 
116
- def _get_google_bqstorage_client(credentials=None, api_endpoint: str = None):
130
+ def _get_google_bqstorage_client(
131
+ credentials=None,
132
+ api_endpoint: Optional[str] = None,
133
+ quota_project_id: Optional[str] = None,
134
+ ):
117
135
  options = None
118
- if api_endpoint:
119
- options = client_options.ClientOptions(api_endpoint=api_endpoint)
136
+ if api_endpoint or quota_project_id:
137
+ options = client_options.ClientOptions(
138
+ api_endpoint=api_endpoint,
139
+ quota_project_id=quota_project_id if quota_project_id else None,
140
+ )
120
141
  from google.cloud import bigquery_storage_v1 as bigquery_storage
121
142
 
122
143
  return bigquery_storage.BigQueryReadClient(
@@ -129,20 +150,26 @@ def get_bigquery_client(
129
150
  project_id: str,
130
151
  dataset_id: str = "",
131
152
  credentials=None,
132
- api_endpoint: str = None,
133
- storage_api_endpoint: str = None,
153
+ api_endpoint: Optional[str] = None,
154
+ storage_api_endpoint: Optional[str] = None,
155
+ client_project_id: Optional[str] = None,
134
156
  ):
135
157
  google_client = get_google_bigquery_client(
136
- project_id, credentials=credentials, api_endpoint=api_endpoint
158
+ project_id,
159
+ credentials=credentials,
160
+ api_endpoint=api_endpoint,
161
+ quota_project_id=client_project_id,
137
162
  )
138
163
  bqstorage_client = None
139
164
  if storage_api_endpoint:
140
165
  bqstorage_client = _get_google_bqstorage_client(
141
- credentials=credentials, api_endpoint=storage_api_endpoint
166
+ credentials=credentials,
167
+ api_endpoint=storage_api_endpoint,
168
+ quota_project_id=client_project_id,
142
169
  )
143
170
 
144
171
  return bigquery_connect(
145
- project_id=project_id,
172
+ project_id=project_id or client_project_id,
146
173
  dataset_id=dataset_id,
147
174
  credentials=credentials,
148
175
  bigquery_client=google_client,
@@ -202,6 +229,7 @@ def get_ibis_table(client, schema_name, table_name, database_name=None):
202
229
  "db2",
203
230
  "mssql",
204
231
  "redshift",
232
+ "sybase",
205
233
  ]:
206
234
  return client.table(table_name, database=database_name, schema=schema_name)
207
235
  elif client.name == "pandas":
@@ -261,7 +289,7 @@ def list_tables(client, schema_name, tables_only=True):
261
289
  if tables_only and client.name != "pandas"
262
290
  else client.list_tables
263
291
  )
264
- if client.name in ["db2", "redshift", "snowflake", "pandas"]:
292
+ if client.name in ["redshift", "snowflake", "pandas"]:
265
293
  return fn()
266
294
  return fn(database=schema_name)
267
295
 
@@ -400,5 +428,6 @@ CLIENT_LOOKUP = {
400
428
  consts.SOURCE_TYPE_MSSQL: mssql_connect,
401
429
  consts.SOURCE_TYPE_SNOWFLAKE: snowflake_connect,
402
430
  consts.SOURCE_TYPE_SPANNER: spanner_connect,
431
+ consts.SOURCE_TYPE_SYBASE: sybase_connect,
403
432
  consts.SOURCE_TYPE_DB2: db2_connect,
404
433
  }
@@ -23,7 +23,10 @@ from data_validation import clients, consts, gcs_helper, state_manager
23
23
  from data_validation.result_handlers.factory import build_result_handler
24
24
  from data_validation.validation_builder import ValidationBuilder
25
25
 
26
+ from third_party.ibis.ibis_addon.api import db2_type_string_length
27
+
26
28
  if TYPE_CHECKING:
29
+ from ibis.backends.base import BaseBackend
27
30
  import ibis.expr.types.Table
28
31
 
29
32
 
@@ -71,7 +74,7 @@ class ConfigManager(object):
71
74
  """Return config object."""
72
75
  return self._config
73
76
 
74
- def get_source_connection(self):
77
+ def get_source_connection(self) -> dict:
75
78
  """Return source connection object."""
76
79
  if not self._source_conn:
77
80
  if self._config.get(consts.CONFIG_SOURCE_CONN):
@@ -82,7 +85,7 @@ class ConfigManager(object):
82
85
 
83
86
  return self._source_conn
84
87
 
85
- def get_target_connection(self):
88
+ def get_target_connection(self) -> dict:
86
89
  """Return target connection object."""
87
90
  if not self._target_conn:
88
91
  if self._config.get(consts.CONFIG_TARGET_CONN):
@@ -1160,6 +1163,8 @@ class ConfigManager(object):
1160
1163
  calculated_config.update(custom_params)
1161
1164
  elif calc_type == consts.CONFIG_CAST and custom_params:
1162
1165
  calculated_config[consts.CONFIG_DEFAULT_CAST] = custom_params
1166
+ elif calc_type == consts.CALC_FIELD_IFNULL and custom_params:
1167
+ calculated_config.update(custom_params)
1163
1168
 
1164
1169
  return calculated_config
1165
1170
 
@@ -1294,7 +1299,45 @@ class ConfigManager(object):
1294
1299
 
1295
1300
  return result_source_columns, result_target_columns
1296
1301
 
1297
- def build_dependent_aliases(self, calc_type: str, col_list=None) -> List[Dict]:
1302
+ def _string_column_ifnull_limits(
1303
+ self,
1304
+ client: "BaseBackend",
1305
+ casefold_columns: dict,
1306
+ ibis_table: "ibis.expr.types.Table",
1307
+ raw_types: dict,
1308
+ string_column_ifnull_limits: dict,
1309
+ ):
1310
+ """Defines IfNull replacement token length limits (due to Db2 limitations).
1311
+
1312
+ This is because in Db2 a coalesce of a column cannot exceed the length of the initial argument resulting in:
1313
+ String data right truncation. SQLSTATE=22001
1314
+
1315
+ We need to ensure both source and target systems use the same replacement token therefore these limits are used for both.
1316
+
1317
+ Mutates string_column_ifnull_limits to contain a dict of column names with replacement token length limits:
1318
+ {"col1": 10, "col2": 12, "col_unlimited": None}
1319
+ """
1320
+ if not casefold_columns or client.name != "db2":
1321
+ return {}
1322
+ table_schema = {k: v for k, v in ibis_table.schema().items()}
1323
+ for casefold_column, column in casefold_columns.items():
1324
+ ifnull_limit = db2_type_string_length(
1325
+ table_schema[casefold_column], raw_types.get(column, [])
1326
+ )
1327
+ if (
1328
+ not string_column_ifnull_limits.get(casefold_column, None)
1329
+ # If the current limit is shorter than an existing one then overwrite the value.
1330
+ or (
1331
+ ifnull_limit
1332
+ and ifnull_limit < string_column_ifnull_limits[casefold_column]
1333
+ )
1334
+ ):
1335
+ string_column_ifnull_limits[casefold_column] = ifnull_limit
1336
+ return string_column_ifnull_limits
1337
+
1338
+ def build_dependent_aliases(
1339
+ self, calc_type: str, col_list: Optional[list] = None
1340
+ ) -> List[Dict]:
1298
1341
  """This is a utility function for determining the required depth of all fields"""
1299
1342
  source_table = self.get_source_ibis_calculated_table()
1300
1343
  target_table = self.get_target_ibis_calculated_table()
@@ -1318,6 +1361,23 @@ class ConfigManager(object):
1318
1361
 
1319
1362
  column_aliases = {}
1320
1363
  col_names = []
1364
+ # Get any ifnull token limits from either the source or target system.
1365
+ # Most engines have no requirement for limits and will return an empty dict.
1366
+ string_column_ifnull_limits = {}
1367
+ self._string_column_ifnull_limits(
1368
+ self.source_client,
1369
+ casefold_source_columns,
1370
+ source_table,
1371
+ self.get_source_raw_data_types(),
1372
+ string_column_ifnull_limits,
1373
+ )
1374
+ self._string_column_ifnull_limits(
1375
+ self.target_client,
1376
+ casefold_target_columns,
1377
+ target_table,
1378
+ self.get_target_raw_data_types(),
1379
+ string_column_ifnull_limits,
1380
+ )
1321
1381
  for i, calc in enumerate(self._get_order_of_operations(calc_type)):
1322
1382
  if i == 0:
1323
1383
  previous_level = [x for x in casefold_source_columns.keys()]
@@ -1331,7 +1391,7 @@ class ConfigManager(object):
1331
1391
  col["calc_type"] = calc
1332
1392
  col["depth"] = i
1333
1393
  name = col["name"]
1334
- # need to capture all aliases at the previous level. probably name concat__all
1394
+ # Need to capture all aliases at the previous level. probably name concat__all
1335
1395
  column_aliases[name] = i
1336
1396
  col_names.append(col)
1337
1397
  else:
@@ -1343,6 +1403,16 @@ class ConfigManager(object):
1343
1403
  col["name"] = self._prefix_calc_col_name(column, calc, j)
1344
1404
  col["calc_type"] = calc
1345
1405
  col["depth"] = i
1406
+ if (
1407
+ calc == consts.CALC_FIELD_IFNULL
1408
+ and string_column_ifnull_limits.get(column)
1409
+ ):
1410
+ # Trim the replacement token to the max length allowed for the column.
1411
+ col["calc_params"] = {
1412
+ consts.CALC_FIELD_IFNULL_DEFAULT: consts.CALC_FIELD_IFNULL_DEFAULT_STRING[
1413
+ 0 : string_column_ifnull_limits[column]
1414
+ ]
1415
+ }
1346
1416
 
1347
1417
  if i == 0:
1348
1418
  # If depth 0, get raw column name with correct casing
@@ -1363,6 +1433,11 @@ class ConfigManager(object):
1363
1433
  name = col["name"]
1364
1434
  column_aliases[name] = i
1365
1435
  col_names.append(col)
1436
+ if string_column_ifnull_limits.get(column):
1437
+ # Keep track of column limits as we move through aliased expressions.
1438
+ string_column_ifnull_limits[name] = string_column_ifnull_limits[
1439
+ column
1440
+ ]
1366
1441
  return col_names
1367
1442
 
1368
1443
  def build_comp_fields(self, col_list: list, exclude_cols: bool) -> dict:
@@ -139,6 +139,7 @@ SOURCE_TYPE_POSTGRES = "Postgres"
139
139
  SOURCE_TYPE_REDSHIFT = "Redshift"
140
140
  SOURCE_TYPE_SNOWFLAKE = "Snowflake"
141
141
  SOURCE_TYPE_SPANNER = "Spanner"
142
+ SOURCE_TYPE_SYBASE = "Sybase"
142
143
  SOURCE_TYPE_TERADATA = "Teradata"
143
144
 
144
145
  # BigQuery Result Handler Configs
@@ -149,6 +150,7 @@ TABLE_ID = "table_id"
149
150
  GOOGLE_SERVICE_ACCOUNT_KEY_PATH = "google_service_account_key_path"
150
151
  API_ENDPOINT = "api_endpoint"
151
152
  STORAGE_API_ENDPOINT = "storage_api_endpoint"
153
+ CLIENT_PROJECT_ID = "client_project_id"
152
154
 
153
155
  # Result Handler Output Table Fields
154
156
  VALIDATION_TYPE = "validation_type"
@@ -258,7 +260,12 @@ CALC_FIELD_BYTE_LENGTH = "byte_length"
258
260
  CALC_FIELD_EPOCH_SECONDS = "epoch_seconds"
259
261
  CALC_FIELD_HASH = "hash"
260
262
  CALC_FIELD_IFNULL = "ifnull"
263
+ CALC_FIELD_IFNULL_DEFAULT = "default_null_string"
264
+ CALC_FIELD_IFNULL_DEFAULT_STRING = "DEFAULT_REPLACEMENT_STRING"
261
265
  CALC_FIELD_LENGTH = "length"
262
266
  CALC_FIELD_PADDED_CHAR_LENGTH = "padded_char_length"
263
267
  CALC_FIELD_RSTRIP = "rstrip"
264
268
  CALC_FIELD_UPPER = "upper"
269
+
270
+ # Backends that do not support window functions.
271
+ NO_WINDOW_FUNCTION_SUPPORT = [SOURCE_TYPE_SYBASE]
@@ -27,3 +27,7 @@ class ResultHandlerException(Exception):
27
27
 
28
28
  class SchemaValidationException(ValidationException):
29
29
  pass
30
+
31
+
32
+ class PartitionBuilderException(ValidationException):
33
+ pass
@@ -20,7 +20,7 @@ import re
20
20
  import datetime
21
21
  from typing import List, Dict, TYPE_CHECKING
22
22
 
23
- from data_validation import cli_tools, consts, util
23
+ from data_validation import cli_tools, consts, exceptions, util
24
24
  from data_validation.config_manager import ConfigManager
25
25
  from data_validation.query_builder.partition_row_builder import PartitionRowBuilder
26
26
  from data_validation.validation_builder import ValidationBuilder
@@ -105,6 +105,15 @@ class PartitionBuilder:
105
105
  yaml_configs_list = self._add_partition_filters(partition_filters)
106
106
  self._store_partitions(yaml_configs_list)
107
107
 
108
+ def check_partition_configs(self) -> None:
109
+ for config_manager in self.config_managers:
110
+ source_type = config_manager.get_source_connection()[consts.SOURCE_TYPE]
111
+ if source_type in consts.NO_WINDOW_FUNCTION_SUPPORT:
112
+ raise exceptions.PartitionBuilderException(
113
+ f"Source client {source_type} does not support window functions, "
114
+ "please use this connection as a target connection for generate-table-partitions."
115
+ )
116
+
108
117
  @staticmethod
109
118
  def _extract_where(table_expr: "IbisTable", client: "BaseBackend") -> str:
110
119
  """Given a ibis table expression with a filter (i.e. WHERE) clause, this function extracts the
@@ -341,7 +341,10 @@ class CalculatedField(object):
341
341
  @staticmethod
342
342
  def ifnull(config, fields):
343
343
  default_null_string = ibis.literal(
344
- config.get("default_null_string", "DEFAULT_REPLACEMENT_STRING")
344
+ config.get(
345
+ consts.CALC_FIELD_IFNULL_DEFAULT,
346
+ consts.CALC_FIELD_IFNULL_DEFAULT_STRING,
347
+ )
345
348
  )
346
349
  fields = [fields[0], default_null_string]
347
350
  return CalculatedField(
@@ -30,9 +30,10 @@ RANDOM_SORT_SUPPORTS = [
30
30
  "mssql",
31
31
  "db2",
32
32
  "mysql",
33
- "spanner",
34
33
  "redshift",
35
34
  "snowflake",
35
+ "spanner",
36
+ "sybase",
36
37
  ]
37
38
 
38
39
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-pso-data-validator
3
- Version: 8.3.0
3
+ Version: 8.5.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
@@ -28,7 +28,7 @@ Requires-Dist: jellyfish>=1.1.0
28
28
  Requires-Dist: pandas
29
29
  Requires-Dist: parsy>=2.1
30
30
  Requires-Dist: psycopg2-binary>=2.9.9
31
- Requires-Dist: pyarrow==14.0.1
31
+ Requires-Dist: pyarrow
32
32
  Requires-Dist: pydata-google-auth>=1.8.2
33
33
  Requires-Dist: PyMySQL>=1.1.1
34
34
  Requires-Dist: PyYAML>=6.0.2
@@ -45,6 +45,7 @@ Requires-Dist: freezegun; extra == "develop"
45
45
  Requires-Dist: pyfakefs; extra == "develop"
46
46
  Requires-Dist: pytest; extra == "develop"
47
47
  Requires-Dist: pytest-cov; extra == "develop"
48
+ Requires-Dist: pytest-rerunfailures; extra == "develop"
48
49
  Requires-Dist: pytest-timeout; extra == "develop"
49
50
  Dynamic: author
50
51
  Dynamic: author-email
@@ -92,8 +93,9 @@ DVT supports the following connection types:
92
93
  * [Postgres](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#postgres)
93
94
  * [Redshift](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#redshift)
94
95
  * [Spanner](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#google-spanner)
95
- * [Teradata](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#teradata)
96
96
  * [Snowflake](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#snowflake)
97
+ * [Sybase ASE](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#sybase)
98
+ * [Teradata](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md#teradata)
97
99
 
98
100
  The [Connections](https://github.com/GoogleCloudPlatform/professional-services-data-validator/blob/develop/docs/connections.md) page provides details about how to create
99
101
  and list connections for the validation tool.
@@ -552,6 +554,10 @@ data-validation
552
554
  Comma-separated key value pair labels for the run.
553
555
  [--format or -fmt FORMAT]
554
556
  Format for stdout output. Supported formats are (text, csv, json, table). Defaults to table.
557
+ [--use-random-row or -rr]
558
+ Finds a set of random rows of the first primary key supplied.
559
+ [--random-row-batch-size or -rbs]
560
+ Row batch size used for random row filters (default 10,000).
555
561
  [--filter-status or -fs STATUSES_LIST]
556
562
  Comma separated list of statuses to filter the validation results. Supported statuses are (success, fail). If no list is provided, all statuses are returned.
557
563
  [--case-insensitive-match, -cim]
@@ -85,6 +85,11 @@ third_party/ibis/ibis_redshift/compiler.py
85
85
  third_party/ibis/ibis_snowflake/__init__.py
86
86
  third_party/ibis/ibis_snowflake/api.py
87
87
  third_party/ibis/ibis_snowflake/datatypes.py
88
+ third_party/ibis/ibis_sybase/__init__.py
89
+ third_party/ibis/ibis_sybase/api.py
90
+ third_party/ibis/ibis_sybase/compiler.py
91
+ third_party/ibis/ibis_sybase/datatypes.py
92
+ third_party/ibis/ibis_sybase/registry.py
88
93
  third_party/ibis/ibis_teradata/__init__.py
89
94
  third_party/ibis/ibis_teradata/api.py
90
95
  third_party/ibis/ibis_teradata/compiler.py