snowflake-data-validation 1.0.1__py3-none-any.whl
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.
- snowflake/snowflake_data_validation/__init__.py +112 -0
- snowflake/snowflake_data_validation/__main__.py +35 -0
- snowflake/snowflake_data_validation/__version__.py +16 -0
- snowflake/snowflake_data_validation/comparison_orchestrator.py +250 -0
- snowflake/snowflake_data_validation/configuration/__init__.py +16 -0
- snowflake/snowflake_data_validation/configuration/configuration_loader.py +66 -0
- snowflake/snowflake_data_validation/configuration/model/configuration_model.py +122 -0
- snowflake/snowflake_data_validation/configuration/model/connection_types.py +46 -0
- snowflake/snowflake_data_validation/configuration/model/connections/__init__.py +47 -0
- snowflake/snowflake_data_validation/configuration/model/logging_configuration.py +63 -0
- snowflake/snowflake_data_validation/configuration/model/table_configuration.py +210 -0
- snowflake/snowflake_data_validation/configuration/model/validation_configuration.py +59 -0
- snowflake/snowflake_data_validation/configuration/singleton.py +16 -0
- snowflake/snowflake_data_validation/connector/__init__.py +32 -0
- snowflake/snowflake_data_validation/connector/connector_base.py +183 -0
- snowflake/snowflake_data_validation/connector/connector_factory_base.py +161 -0
- snowflake/snowflake_data_validation/executer/__init__.py +45 -0
- snowflake/snowflake_data_validation/executer/async_generation_executor.py +167 -0
- snowflake/snowflake_data_validation/executer/async_validation_executor.py +409 -0
- snowflake/snowflake_data_validation/executer/base_validation_executor.py +451 -0
- snowflake/snowflake_data_validation/executer/executor_factory.py +248 -0
- snowflake/snowflake_data_validation/executer/extractor_types.py +24 -0
- snowflake/snowflake_data_validation/executer/sync_validation_executor.py +713 -0
- snowflake/snowflake_data_validation/extractor/__init__.py +28 -0
- snowflake/snowflake_data_validation/extractor/metadata_extractor_base.py +374 -0
- snowflake/snowflake_data_validation/extractor/sql_queries_template_generator.py +570 -0
- snowflake/snowflake_data_validation/main_cli.py +92 -0
- snowflake/snowflake_data_validation/orchestration/parallel_execution_engine.py +337 -0
- snowflake/snowflake_data_validation/orchestration/table_metadata_processor.py +400 -0
- snowflake/snowflake_data_validation/orchestration/validation_progress_reporter.py +116 -0
- snowflake/snowflake_data_validation/query/__init__.py +22 -0
- snowflake/snowflake_data_validation/query/query_generator_base.py +346 -0
- snowflake/snowflake_data_validation/redshift/__init__.py +36 -0
- snowflake/snowflake_data_validation/redshift/connector/__init__.py +17 -0
- snowflake/snowflake_data_validation/redshift/connector/connector_factory_redshift.py +91 -0
- snowflake/snowflake_data_validation/redshift/connector/connector_redshift.py +237 -0
- snowflake/snowflake_data_validation/redshift/extractor/metadata_extractor_redshift.py +257 -0
- snowflake/snowflake_data_validation/redshift/extractor/redshift_cte_generator.py +163 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_chunk_row_concatenated_insert_template.sql.j2 +26 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_chunk_row_concatenated_table_template.sql.j2 +6 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_chunk_row_md5_insert_template.sql.j2 +8 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_chunk_row_md5_table_template.sql.j2 +6 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_chunks_md5_table_template.sql.j2 +4 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_column_metrics_templates.yaml +696 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_columns_cte_template.sql.j2 +8 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_datatypes_normalization_templates.yaml +46 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_extract_chunks_md5_table_template.sql.j2 +1 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_extract_md5_rows_chunk.sql.j2 +10 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_get_columns_metadata.sql.j2 +85 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_insert_chunk_row_md5_template.sql.j2 +5 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_row_count_query.sql.j2 +1 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_table_metadata_query.sql.j2 +29 -0
- snowflake/snowflake_data_validation/redshift/extractor/templates/redshift_to_snowflake_datatypes_mapping_template.yaml +44 -0
- snowflake/snowflake_data_validation/redshift/model/__init__.py +21 -0
- snowflake/snowflake_data_validation/redshift/model/redshift_credentials_connection.py +69 -0
- snowflake/snowflake_data_validation/redshift/query/__init__.py +22 -0
- snowflake/snowflake_data_validation/redshift/query/query_generator_redshift.py +315 -0
- snowflake/snowflake_data_validation/redshift/redshift_arguments_manager.py +129 -0
- snowflake/snowflake_data_validation/redshift/redshift_cli.py +618 -0
- snowflake/snowflake_data_validation/redshift/script_writer/__init__.py +22 -0
- snowflake/snowflake_data_validation/redshift/script_writer/script_writer_redshift.py +142 -0
- snowflake/snowflake_data_validation/script_writer/__init__.py +22 -0
- snowflake/snowflake_data_validation/script_writer/script_writer_base.py +152 -0
- snowflake/snowflake_data_validation/snowflake/__init__.py +38 -0
- snowflake/snowflake_data_validation/snowflake/connector/connector_factory_snowflake.py +159 -0
- snowflake/snowflake_data_validation/snowflake/connector/connector_snowflake.py +327 -0
- snowflake/snowflake_data_validation/snowflake/extractor/metadata_extractor_snowflake.py +352 -0
- snowflake/snowflake_data_validation/snowflake/extractor/snowflake_cte_generator.py +155 -0
- snowflake/snowflake_data_validation/snowflake/extractor/templates/snowflake_chunk_row_concatenated_template.sql.j2 +33 -0
- snowflake/snowflake_data_validation/snowflake/extractor/templates/snowflake_chunk_row_md5_template.sql.j2 +11 -0
- snowflake/snowflake_data_validation/snowflake/extractor/templates/snowflake_chunks_md5_table_template.sql.j2 +4 -0
- snowflake/snowflake_data_validation/snowflake/extractor/templates/snowflake_column_metrics_templates.yaml +151 -0
- snowflake/snowflake_data_validation/snowflake/extractor/templates/snowflake_columns_cte_template.sql.j2 +8 -0
- snowflake/snowflake_data_validation/snowflake/extractor/templates/snowflake_datatypes_normalization_templates.yaml +9 -0
- snowflake/snowflake_data_validation/snowflake/extractor/templates/snowflake_extract_chunks_md5_table_template.sql.j2 +5 -0
- snowflake/snowflake_data_validation/snowflake/extractor/templates/snowflake_extract_md5_rows_chunk.sql.j2 +10 -0
- snowflake/snowflake_data_validation/snowflake/extractor/templates/snowflake_get_case_sensitive_columns.sql.j2 +8 -0
- snowflake/snowflake_data_validation/snowflake/extractor/templates/snowflake_get_columns_metadata.sql.j2 +76 -0
- snowflake/snowflake_data_validation/snowflake/extractor/templates/snowflake_insert_chunk_row_md5_template.sql.j2 +1 -0
- snowflake/snowflake_data_validation/snowflake/extractor/templates/snowflake_row_count_query.sql.j2 +1 -0
- snowflake/snowflake_data_validation/snowflake/extractor/templates/snowflake_table_metadata_query.sql.j2 +30 -0
- snowflake/snowflake_data_validation/snowflake/model/__init__.py +25 -0
- snowflake/snowflake_data_validation/snowflake/model/snowflake_credentials_connection.py +62 -0
- snowflake/snowflake_data_validation/snowflake/model/snowflake_default_connection.py +31 -0
- snowflake/snowflake_data_validation/snowflake/model/snowflake_named_connection.py +36 -0
- snowflake/snowflake_data_validation/snowflake/query/__init__.py +22 -0
- snowflake/snowflake_data_validation/snowflake/query/query_generator_snowflake.py +223 -0
- snowflake/snowflake_data_validation/snowflake/script_writer/__init__.py +22 -0
- snowflake/snowflake_data_validation/snowflake/script_writer/script_writer_snowflake.py +96 -0
- snowflake/snowflake_data_validation/snowflake/snowflake_arguments_manager.py +204 -0
- snowflake/snowflake_data_validation/snowflake/snowflake_cli.py +392 -0
- snowflake/snowflake_data_validation/sqlserver/__init__.py +46 -0
- snowflake/snowflake_data_validation/sqlserver/connector/__init__.py +17 -0
- snowflake/snowflake_data_validation/sqlserver/connector/connector_factory_sql_server.py +92 -0
- snowflake/snowflake_data_validation/sqlserver/connector/connector_sql_server.py +312 -0
- snowflake/snowflake_data_validation/sqlserver/extractor/__init__.py +16 -0
- snowflake/snowflake_data_validation/sqlserver/extractor/metadata_extractor_sqlserver.py +257 -0
- snowflake/snowflake_data_validation/sqlserver/extractor/sqlserver_cte_generator.py +161 -0
- snowflake/snowflake_data_validation/sqlserver/extractor/templates/sqlserver_chunks_md5_table_template.sql.j2 +4 -0
- snowflake/snowflake_data_validation/sqlserver/extractor/templates/sqlserver_column_metrics_templates.yaml +537 -0
- snowflake/snowflake_data_validation/sqlserver/extractor/templates/sqlserver_columns_cte_template.sql.j2 +8 -0
- snowflake/snowflake_data_validation/sqlserver/extractor/templates/sqlserver_compute_md5_sql.j2 +55 -0
- snowflake/snowflake_data_validation/sqlserver/extractor/templates/sqlserver_datatypes_normalization_templates.yaml +26 -0
- snowflake/snowflake_data_validation/sqlserver/extractor/templates/sqlserver_extract_chunks_md5_table_template.sql.j2 +1 -0
- snowflake/snowflake_data_validation/sqlserver/extractor/templates/sqlserver_extract_md5_rows_chunk.sql.j2 +10 -0
- snowflake/snowflake_data_validation/sqlserver/extractor/templates/sqlserver_get_columns_metadata.sql.j2 +70 -0
- snowflake/snowflake_data_validation/sqlserver/extractor/templates/sqlserver_row_count_query.sql.j2 +1 -0
- snowflake/snowflake_data_validation/sqlserver/extractor/templates/sqlserver_table_metadata_query.sql.j2 +23 -0
- snowflake/snowflake_data_validation/sqlserver/extractor/templates/sqlserver_to_snowflake_datatypes_mapping_template.yaml +32 -0
- snowflake/snowflake_data_validation/sqlserver/model/__init__.py +21 -0
- snowflake/snowflake_data_validation/sqlserver/model/sqlserver_credentials_connection.py +71 -0
- snowflake/snowflake_data_validation/sqlserver/query/__init__.py +22 -0
- snowflake/snowflake_data_validation/sqlserver/query/query_generator_sqlserver.py +197 -0
- snowflake/snowflake_data_validation/sqlserver/script_writer/__init__.py +22 -0
- snowflake/snowflake_data_validation/sqlserver/script_writer/script_writer_sqlserver.py +177 -0
- snowflake/snowflake_data_validation/sqlserver/sqlserver_arguments_manager.py +147 -0
- snowflake/snowflake_data_validation/sqlserver/sqlserver_cli.py +701 -0
- snowflake/snowflake_data_validation/table_partitioning_strategy.md +96 -0
- snowflake/snowflake_data_validation/teradata/__init__.py +14 -0
- snowflake/snowflake_data_validation/teradata/connector/__init__.py +14 -0
- snowflake/snowflake_data_validation/teradata/connector/connector_factory_teradata.py +79 -0
- snowflake/snowflake_data_validation/teradata/connector/connector_teradata.py +264 -0
- snowflake/snowflake_data_validation/teradata/extractor/__init__.py +19 -0
- snowflake/snowflake_data_validation/teradata/extractor/metadata_extractor_teradata.py +264 -0
- snowflake/snowflake_data_validation/teradata/extractor/templates/teradata_chunks_md5_table_template.sql.j2 +4 -0
- snowflake/snowflake_data_validation/teradata/extractor/templates/teradata_column_metrics_templates.yaml +497 -0
- snowflake/snowflake_data_validation/teradata/extractor/templates/teradata_columns_cte_template.sql.j2 +8 -0
- snowflake/snowflake_data_validation/teradata/extractor/templates/teradata_compute_md5_sql.j2 +62 -0
- snowflake/snowflake_data_validation/teradata/extractor/templates/teradata_create_row_concatenated.sql.j2 +7 -0
- snowflake/snowflake_data_validation/teradata/extractor/templates/teradata_create_row_md5.sql.j2 +7 -0
- snowflake/snowflake_data_validation/teradata/extractor/templates/teradata_datatypes_normalization_templates.yaml +15 -0
- snowflake/snowflake_data_validation/teradata/extractor/templates/teradata_extract_chunks_md5_table_template.sql.j2 +1 -0
- snowflake/snowflake_data_validation/teradata/extractor/templates/teradata_extract_md5_rows_chunk.sql.j2 +10 -0
- snowflake/snowflake_data_validation/teradata/extractor/templates/teradata_get_columns_metadata.sql.j2 +70 -0
- snowflake/snowflake_data_validation/teradata/extractor/templates/teradata_row_count_query.sql.j2 +1 -0
- snowflake/snowflake_data_validation/teradata/extractor/templates/teradata_table_metadata_query.sql.j2 +92 -0
- snowflake/snowflake_data_validation/teradata/extractor/templates/teradata_to_snowflake_datatypes_mapping_template.yaml +48 -0
- snowflake/snowflake_data_validation/teradata/extractor/teradata_cte_generator.py +190 -0
- snowflake/snowflake_data_validation/teradata/model/__init__.py +21 -0
- snowflake/snowflake_data_validation/teradata/model/teradata_credentials_connection.py +53 -0
- snowflake/snowflake_data_validation/teradata/query/__init__.py +19 -0
- snowflake/snowflake_data_validation/teradata/query/query_generator_teradata.py +231 -0
- snowflake/snowflake_data_validation/teradata/script_writer/__init__.py +19 -0
- snowflake/snowflake_data_validation/teradata/script_writer/script_writer_teradata.py +50 -0
- snowflake/snowflake_data_validation/teradata/teradata_arguments_manager.py +143 -0
- snowflake/snowflake_data_validation/teradata/teradata_cli.py +745 -0
- snowflake/snowflake_data_validation/utils/__init__.py +16 -0
- snowflake/snowflake_data_validation/utils/arguments_manager_base.py +482 -0
- snowflake/snowflake_data_validation/utils/arguments_manager_factory.py +130 -0
- snowflake/snowflake_data_validation/utils/base_output_handler.py +66 -0
- snowflake/snowflake_data_validation/utils/configuration_file_editor.py +86 -0
- snowflake/snowflake_data_validation/utils/configuration_file_generator.py +165 -0
- snowflake/snowflake_data_validation/utils/connection_pool.py +349 -0
- snowflake/snowflake_data_validation/utils/connector_factory.py +71 -0
- snowflake/snowflake_data_validation/utils/console_output_handler.py +95 -0
- snowflake/snowflake_data_validation/utils/constants.py +350 -0
- snowflake/snowflake_data_validation/utils/context.py +126 -0
- snowflake/snowflake_data_validation/utils/cpu_optimizer.py +156 -0
- snowflake/snowflake_data_validation/utils/helper.py +30 -0
- snowflake/snowflake_data_validation/utils/helpers/helper_database.py +54 -0
- snowflake/snowflake_data_validation/utils/helpers/helper_dataframe.py +83 -0
- snowflake/snowflake_data_validation/utils/helpers/helper_io.py +85 -0
- snowflake/snowflake_data_validation/utils/helpers/helper_misc.py +95 -0
- snowflake/snowflake_data_validation/utils/helpers/helper_templates.py +294 -0
- snowflake/snowflake_data_validation/utils/logging_config.py +197 -0
- snowflake/snowflake_data_validation/utils/logging_utils.py +68 -0
- snowflake/snowflake_data_validation/utils/model/chunk.py +16 -0
- snowflake/snowflake_data_validation/utils/model/column_metadata.py +45 -0
- snowflake/snowflake_data_validation/utils/model/table_column_metadata.py +94 -0
- snowflake/snowflake_data_validation/utils/model/table_context.py +351 -0
- snowflake/snowflake_data_validation/utils/model/templates_loader_manager.py +123 -0
- snowflake/snowflake_data_validation/utils/progress_reporter.py +61 -0
- snowflake/snowflake_data_validation/utils/run_context.py +59 -0
- snowflake/snowflake_data_validation/utils/table_partitioning_strategy.py +148 -0
- snowflake/snowflake_data_validation/utils/telemetry.py +863 -0
- snowflake/snowflake_data_validation/utils/templates/configuration_file_templates.py +95 -0
- snowflake/snowflake_data_validation/utils/thread_safe_singleton.py +49 -0
- snowflake/snowflake_data_validation/utils/validation_utils.py +142 -0
- snowflake/snowflake_data_validation/validation/__init__.py +16 -0
- snowflake/snowflake_data_validation/validation/data_validator_base.py +452 -0
- snowflake/snowflake_data_validation/validation/metrics_data_validator.py +251 -0
- snowflake/snowflake_data_validation/validation/row_data_validator.py +479 -0
- snowflake/snowflake_data_validation/validation/schema_data_validator.py +197 -0
- snowflake/snowflake_data_validation/validation/validation_report_buffer.py +196 -0
- snowflake_data_validation-1.0.1.dist-info/METADATA +228 -0
- snowflake_data_validation-1.0.1.dist-info/RECORD +189 -0
- snowflake_data_validation-1.0.1.dist-info/WHEEL +4 -0
- snowflake_data_validation-1.0.1.dist-info/entry_points.txt +3 -0
- snowflake_data_validation-1.0.1.dist-info/licenses/LICENSE +177 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Copyright 2025 Snowflake Inc.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
|
|
13
|
+
"""Connection models for different database dialects."""
|
|
14
|
+
|
|
15
|
+
# Import SQL Server connection models
|
|
16
|
+
from snowflake.snowflake_data_validation.sqlserver.model.sqlserver_credentials_connection import (
|
|
17
|
+
SqlServerCredentialsConnection,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from snowflake.snowflake_data_validation.redshift.model.redshift_credentials_connection import (
|
|
21
|
+
RedshiftCredentialsConnection,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Import Teradata connection models
|
|
25
|
+
from snowflake.snowflake_data_validation.teradata.model.teradata_credentials_connection import (
|
|
26
|
+
TeradataCredentialsConnection,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Import Snowflake connection models
|
|
30
|
+
from snowflake.snowflake_data_validation.snowflake.model.snowflake_default_connection import (
|
|
31
|
+
SnowflakeDefaultConnection,
|
|
32
|
+
)
|
|
33
|
+
from snowflake.snowflake_data_validation.snowflake.model.snowflake_named_connection import (
|
|
34
|
+
SnowflakeNamedConnection,
|
|
35
|
+
)
|
|
36
|
+
from snowflake.snowflake_data_validation.snowflake.model.snowflake_credentials_connection import (
|
|
37
|
+
SnowflakeCredentialsConnection,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"SqlServerCredentialsConnection",
|
|
42
|
+
"TeradataCredentialsConnection",
|
|
43
|
+
"SnowflakeDefaultConnection",
|
|
44
|
+
"SnowflakeNamedConnection",
|
|
45
|
+
"SnowflakeCredentialsConnection",
|
|
46
|
+
"RedshiftCredentialsConnection",
|
|
47
|
+
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Copyright 2025 Snowflake Inc.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
|
|
13
|
+
"""Logging configuration model for Snowflake Data Validation."""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel, field_validator
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LoggingConfiguration(BaseModel):
|
|
21
|
+
"""Logging configuration model.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
25
|
+
console_level: Console logging level (defaults to level if not specified)
|
|
26
|
+
file_level: File logging level (defaults to level if not specified)
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
level: str = "INFO"
|
|
31
|
+
console_level: str | None = None
|
|
32
|
+
file_level: str | None = None
|
|
33
|
+
|
|
34
|
+
@field_validator("level", "console_level", "file_level")
|
|
35
|
+
@classmethod
|
|
36
|
+
def validate_log_level(cls, value: str | None) -> str | None:
|
|
37
|
+
"""Validate logging level values."""
|
|
38
|
+
if value is None:
|
|
39
|
+
return value
|
|
40
|
+
|
|
41
|
+
valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
42
|
+
if value.upper() not in valid_levels:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"Invalid logging level: {value}. "
|
|
45
|
+
f"Valid levels are: {', '.join(valid_levels)}"
|
|
46
|
+
)
|
|
47
|
+
return value.upper()
|
|
48
|
+
|
|
49
|
+
def get_console_level(self) -> str:
|
|
50
|
+
"""Get the console logging level."""
|
|
51
|
+
return self.console_level or self.level
|
|
52
|
+
|
|
53
|
+
def get_file_level(self) -> str:
|
|
54
|
+
"""Get the file logging level."""
|
|
55
|
+
return self.file_level or self.level
|
|
56
|
+
|
|
57
|
+
def get_console_level_int(self) -> int:
|
|
58
|
+
"""Get the console logging level as integer."""
|
|
59
|
+
return getattr(logging, self.get_console_level())
|
|
60
|
+
|
|
61
|
+
def get_file_level_int(self) -> int:
|
|
62
|
+
"""Get the file logging level as integer."""
|
|
63
|
+
return getattr(logging, self.get_file_level())
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, model_validator
|
|
6
|
+
from typing_extensions import Self
|
|
7
|
+
|
|
8
|
+
from snowflake.snowflake_data_validation.configuration.model.validation_configuration import (
|
|
9
|
+
ValidationConfiguration,
|
|
10
|
+
)
|
|
11
|
+
from snowflake.snowflake_data_validation.utils.constants import NEWLINE
|
|
12
|
+
from snowflake.snowflake_data_validation.utils.helper import Helper
|
|
13
|
+
from snowflake.snowflake_data_validation.utils.helpers.helper_database import (
|
|
14
|
+
HelperDatabase,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
LOGGER = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
MANDATORY_PROPERTIES: set[str] = {
|
|
21
|
+
"target_name",
|
|
22
|
+
"use_column_selection_as_exclude_list",
|
|
23
|
+
"column_selection_list",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
VISIBLE_PROPERTIES: set[str] = {
|
|
27
|
+
"apply_metric_column_modifier",
|
|
28
|
+
"chunk_number",
|
|
29
|
+
"column_mappings",
|
|
30
|
+
"exclude_metrics",
|
|
31
|
+
"index_column_list",
|
|
32
|
+
"is_case_sensitive",
|
|
33
|
+
"max_failed_rows_number",
|
|
34
|
+
"target_database",
|
|
35
|
+
"target_index_column_list",
|
|
36
|
+
"target_schema",
|
|
37
|
+
"target_where_clause",
|
|
38
|
+
"where_clause",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TableConfiguration(BaseModel):
|
|
43
|
+
"""Table configuration model.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
pydantic.BaseModel (pydantic.BaseModel): pydantic BaseModel
|
|
47
|
+
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
_id_counter: ClassVar[int] = 0
|
|
51
|
+
id: int | None = None
|
|
52
|
+
|
|
53
|
+
fully_qualified_name: str
|
|
54
|
+
use_column_selection_as_exclude_list: bool
|
|
55
|
+
column_selection_list: list[str]
|
|
56
|
+
validation_configuration: ValidationConfiguration | None = None
|
|
57
|
+
|
|
58
|
+
source_database: str | None = None
|
|
59
|
+
source_schema: str | None = None
|
|
60
|
+
source_table: str | None = None
|
|
61
|
+
target_name: str | None = None
|
|
62
|
+
|
|
63
|
+
target_fully_qualified_name: str = ""
|
|
64
|
+
where_clause: str = ""
|
|
65
|
+
target_where_clause: str = ""
|
|
66
|
+
has_where_clause: bool = False
|
|
67
|
+
has_target_where_clause: bool = False
|
|
68
|
+
target_database: str | None = None
|
|
69
|
+
target_schema: str | None = None
|
|
70
|
+
index_column_list: list[str] = []
|
|
71
|
+
target_index_column_list: list[str] = []
|
|
72
|
+
is_case_sensitive: bool = False
|
|
73
|
+
chunk_number: int | None = None
|
|
74
|
+
column_mappings: dict[str, str] = {}
|
|
75
|
+
max_failed_rows_number: int | None = None
|
|
76
|
+
exclude_metrics: bool | None = None
|
|
77
|
+
apply_metric_column_modifier: bool | None = None
|
|
78
|
+
|
|
79
|
+
@model_validator(mode="after")
|
|
80
|
+
def load(self) -> Self:
|
|
81
|
+
self._assign_id()
|
|
82
|
+
self._load_source_decomposed_fully_qualified_name()
|
|
83
|
+
self._load_target_fully_qualified_name()
|
|
84
|
+
self._set_has_where_clause()
|
|
85
|
+
self._set_has_target_where_clause()
|
|
86
|
+
self._normalize_where_clause()
|
|
87
|
+
self._set_target_index_column_list()
|
|
88
|
+
self._check_chunk_number()
|
|
89
|
+
self._check_target_where_clause()
|
|
90
|
+
self._set_column_mappings()
|
|
91
|
+
self._check_max_failed_rows_number()
|
|
92
|
+
return self
|
|
93
|
+
|
|
94
|
+
def _assign_id(self) -> None:
|
|
95
|
+
if self.id is None:
|
|
96
|
+
TableConfiguration._id_counter += 1
|
|
97
|
+
self.id = TableConfiguration._id_counter
|
|
98
|
+
|
|
99
|
+
def _load_source_decomposed_fully_qualified_name(self) -> None:
|
|
100
|
+
self.fully_qualified_name = self.fully_qualified_name.replace('\\"', '"')
|
|
101
|
+
|
|
102
|
+
decomposed_tuple = Helper.get_decomposed_fully_qualified_name(
|
|
103
|
+
self.fully_qualified_name
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if len(decomposed_tuple) == 2:
|
|
107
|
+
self.source_database = None
|
|
108
|
+
self.source_schema = decomposed_tuple[0]
|
|
109
|
+
self.source_table = decomposed_tuple[1]
|
|
110
|
+
|
|
111
|
+
else:
|
|
112
|
+
self.source_database = decomposed_tuple[0]
|
|
113
|
+
self.source_schema = decomposed_tuple[1]
|
|
114
|
+
self.source_table = decomposed_tuple[2]
|
|
115
|
+
|
|
116
|
+
def _load_target_fully_qualified_name(self) -> None:
|
|
117
|
+
if self.target_database is None:
|
|
118
|
+
self.target_database = self.source_database
|
|
119
|
+
|
|
120
|
+
if self.target_schema is None:
|
|
121
|
+
self.target_schema = self.source_schema
|
|
122
|
+
|
|
123
|
+
if self.target_name is None:
|
|
124
|
+
self.target_name = self.source_table
|
|
125
|
+
else:
|
|
126
|
+
self.target_name = HelperDatabase.normalize_escape_quotes(self.target_name)
|
|
127
|
+
|
|
128
|
+
self.target_fully_qualified_name = (
|
|
129
|
+
f"{self.target_database}.{self.target_schema}.{self.target_name}"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def _set_has_where_clause(self) -> None:
|
|
133
|
+
self.has_where_clause = self.where_clause != ""
|
|
134
|
+
|
|
135
|
+
def _set_has_target_where_clause(self) -> None:
|
|
136
|
+
self.has_target_where_clause = self.target_where_clause != ""
|
|
137
|
+
|
|
138
|
+
def _check_target_where_clause(self) -> None:
|
|
139
|
+
if self.has_where_clause and not self.has_target_where_clause:
|
|
140
|
+
LOGGER.warning(
|
|
141
|
+
"Target where clause was not set for table %s. This may lead to unexpected results.",
|
|
142
|
+
self.target_fully_qualified_name,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def _set_target_index_column_list(self) -> None:
|
|
146
|
+
if self.target_index_column_list == []:
|
|
147
|
+
self.target_index_column_list = self.index_column_list.copy()
|
|
148
|
+
|
|
149
|
+
local_target_index_column_list = []
|
|
150
|
+
for target_column_name in self.target_index_column_list:
|
|
151
|
+
new_column_name = self.column_mappings.get(
|
|
152
|
+
target_column_name, target_column_name
|
|
153
|
+
)
|
|
154
|
+
local_target_index_column_list.append(new_column_name)
|
|
155
|
+
|
|
156
|
+
self.target_index_column_list = local_target_index_column_list
|
|
157
|
+
|
|
158
|
+
def _check_chunk_number(self) -> None:
|
|
159
|
+
if self.chunk_number is None:
|
|
160
|
+
return
|
|
161
|
+
elif self.chunk_number < 1:
|
|
162
|
+
raise ValueError("Chunk number must be greater than or equal to 1.")
|
|
163
|
+
|
|
164
|
+
def _set_column_mappings(self) -> None:
|
|
165
|
+
"""Normalize the keys and values of the column mappings dictionary to uppercase."""
|
|
166
|
+
local_column_mapping = {}
|
|
167
|
+
for key in self.column_mappings.keys():
|
|
168
|
+
if self.is_case_sensitive:
|
|
169
|
+
local_column_mapping[key] = self.column_mappings[key]
|
|
170
|
+
else:
|
|
171
|
+
local_column_mapping[key.upper()] = self.column_mappings[key].upper()
|
|
172
|
+
self.column_mappings = local_column_mapping
|
|
173
|
+
|
|
174
|
+
def _check_max_failed_rows_number(self) -> None:
|
|
175
|
+
# Accept None, because it will be overwritten by ConfigurationModel
|
|
176
|
+
# based on the value of ValidationConfiguration model.
|
|
177
|
+
if self.max_failed_rows_number is None:
|
|
178
|
+
return
|
|
179
|
+
elif self.max_failed_rows_number < 1:
|
|
180
|
+
raise ValueError(
|
|
181
|
+
f"Invalid value for max failed rows number in table f{self.fully_qualified_name}. "
|
|
182
|
+
"Value must be greater than or equal to 1."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def _normalize_where_clause(self) -> None:
|
|
186
|
+
if self.has_where_clause:
|
|
187
|
+
self.where_clause = HelperDatabase.remove_escape_quotes(self.where_clause)
|
|
188
|
+
|
|
189
|
+
if self.has_target_where_clause:
|
|
190
|
+
self.target_where_clause = HelperDatabase.remove_escape_quotes(
|
|
191
|
+
self.target_where_clause
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def __str__(self) -> str:
|
|
195
|
+
"""Return a formatted string representation of the table configuration."""
|
|
196
|
+
properties = []
|
|
197
|
+
|
|
198
|
+
# Add mandatory properties
|
|
199
|
+
for attr in MANDATORY_PROPERTIES:
|
|
200
|
+
properties.append(f" {attr}: {getattr(self, attr)}")
|
|
201
|
+
|
|
202
|
+
# Add visible properties with non-empty values
|
|
203
|
+
for attr in VISIBLE_PROPERTIES:
|
|
204
|
+
value = getattr(self, attr)
|
|
205
|
+
if value not in (None, [], {}, "", 0):
|
|
206
|
+
properties.append(f" {attr}: {value}")
|
|
207
|
+
|
|
208
|
+
properties.sort()
|
|
209
|
+
properties_str = NEWLINE.join(properties)
|
|
210
|
+
return f" - fully_qualified_name: {self.fully_qualified_name}{NEWLINE}{properties_str}"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Copyright 2025 Snowflake Inc.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, model_validator
|
|
19
|
+
|
|
20
|
+
from snowflake.snowflake_data_validation.utils.constants import (
|
|
21
|
+
MAX_FAILED_ROWS_NUMBER_DEFAULT_VALUE,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ValidationConfiguration(BaseModel):
|
|
26
|
+
"""Class representing the validation levels to be applied to a table."""
|
|
27
|
+
|
|
28
|
+
schema_validation: bool | None = False
|
|
29
|
+
metrics_validation: bool | None = False
|
|
30
|
+
row_validation: bool | None = False
|
|
31
|
+
# Custom templates path for validation scripts.
|
|
32
|
+
custom_templates_path: Path | None = None
|
|
33
|
+
max_failed_rows_number: int = MAX_FAILED_ROWS_NUMBER_DEFAULT_VALUE
|
|
34
|
+
exclude_metrics: bool = False
|
|
35
|
+
apply_metric_column_modifier: bool = False
|
|
36
|
+
|
|
37
|
+
@model_validator(mode="after")
|
|
38
|
+
def validate_configuration(self) -> "ValidationConfiguration":
|
|
39
|
+
"""Validate the configuration after initialization.
|
|
40
|
+
|
|
41
|
+
This method checks if at least one validation type is enabled.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ValueError: If no validation type is enabled.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
if not self.model_dump(exclude_none=True):
|
|
48
|
+
raise ValueError(
|
|
49
|
+
"At least one validation type must be enabled in case of adding the property."
|
|
50
|
+
)
|
|
51
|
+
self._check_max_failed_rows_number()
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
def _check_max_failed_rows_number(self) -> None:
|
|
55
|
+
if self.max_failed_rows_number < 1:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
"Invalid value for max failed rows number in validation configuration. "
|
|
58
|
+
"Value must be greater than or equal to 1."
|
|
59
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class Singleton(type):
|
|
2
|
+
|
|
3
|
+
"""Singleton metaclass.
|
|
4
|
+
|
|
5
|
+
This metaclass ensures that only one instance of a class exists.
|
|
6
|
+
Any class using this metaclass will return the same instance
|
|
7
|
+
every time it is instantiated, effectively implementing the
|
|
8
|
+
Singleton design pattern.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
_instances = {}
|
|
12
|
+
|
|
13
|
+
def __call__(cls, *args, **kwargs):
|
|
14
|
+
if cls not in cls._instances:
|
|
15
|
+
cls._instances[cls] = super().__call__(*args, **kwargs)
|
|
16
|
+
return cls._instances[cls]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Copyright 2025 Snowflake Inc.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Description: This file is used to expose the ConnectorBase class to the outside world.
|
|
18
|
+
# The ConnectorBase class is used to define the interface for the Snowflake connector.
|
|
19
|
+
# It is used to define the methods that the connector must implement.
|
|
20
|
+
# The methods defined in the ConnectorBase class are connect
|
|
21
|
+
# execute_query, and close.
|
|
22
|
+
# The connect method is used to establish a connection to the Snowflake database.
|
|
23
|
+
|
|
24
|
+
__all__ = ["ConnectorBase", "NullConnector", "ConnectorFactoryBase"]
|
|
25
|
+
|
|
26
|
+
from snowflake.snowflake_data_validation.connector.connector_base import (
|
|
27
|
+
ConnectorBase,
|
|
28
|
+
NullConnector,
|
|
29
|
+
)
|
|
30
|
+
from snowflake.snowflake_data_validation.connector.connector_factory_base import (
|
|
31
|
+
ConnectorFactoryBase,
|
|
32
|
+
)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Copyright 2025 Snowflake Inc.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
from abc import ABC, abstractmethod
|
|
21
|
+
|
|
22
|
+
from snowflake.snowflake_data_validation.utils.constants import (
|
|
23
|
+
DEFAULT_CONNECTION_MODE,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ConnectorBase(ABC):
|
|
28
|
+
"""Abstract base class for database connectors."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, output_path: str | None = None):
|
|
31
|
+
"""
|
|
32
|
+
Initialize the database connector base class.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
output_path (Optional[str], optional): Path for output files. Defaults to None.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
self.connection: object | None = None
|
|
39
|
+
self.output_path = output_path
|
|
40
|
+
self.logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def connect(
|
|
44
|
+
self, mode: str = DEFAULT_CONNECTION_MODE, connection_name: str = ""
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Establish a connection to the database."""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
def connect_with_retry(
|
|
50
|
+
self,
|
|
51
|
+
connect_func,
|
|
52
|
+
max_attempts: int = 3,
|
|
53
|
+
delay_seconds: float = 1.0,
|
|
54
|
+
delay_multiplier: float = 2.0,
|
|
55
|
+
*args,
|
|
56
|
+
**kwargs,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Establish a connection with retry logic.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
connect_func: The connection function to call
|
|
63
|
+
max_attempts: Maximum number of connection attempts
|
|
64
|
+
delay_seconds: Initial delay between retries in seconds
|
|
65
|
+
delay_multiplier: Multiplier for exponential backoff
|
|
66
|
+
*args: Arguments to pass to connect_func
|
|
67
|
+
**kwargs: Keyword arguments to pass to connect_func
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
ConnectionError: If all connection attempts fail
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
last_exception = None
|
|
74
|
+
current_delay = delay_seconds
|
|
75
|
+
|
|
76
|
+
for attempt in range(1, max_attempts + 1):
|
|
77
|
+
try:
|
|
78
|
+
self.logger.info(f"Connection attempt {attempt}/{max_attempts}")
|
|
79
|
+
connect_func(*args, **kwargs)
|
|
80
|
+
self.logger.info("Connection established successfully")
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
last_exception = e
|
|
85
|
+
self.logger.warning(
|
|
86
|
+
f"Connection attempt {attempt}/{max_attempts} failed: {str(e)}"
|
|
87
|
+
)
|
|
88
|
+
if attempt < max_attempts:
|
|
89
|
+
self.logger.info(f"Retrying in {current_delay:.1f} seconds...")
|
|
90
|
+
time.sleep(current_delay)
|
|
91
|
+
current_delay *= delay_multiplier
|
|
92
|
+
else:
|
|
93
|
+
self.logger.error(f"All {max_attempts} connection attempts failed")
|
|
94
|
+
raise ConnectionError(
|
|
95
|
+
f"Failed to establish connection after {max_attempts} attempts. "
|
|
96
|
+
f"Last error: {last_exception}"
|
|
97
|
+
) from last_exception
|
|
98
|
+
|
|
99
|
+
def _verify_connection(self) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Verify the connection by executing a simple test query.
|
|
102
|
+
|
|
103
|
+
Default implementation that can be overridden by child classes.
|
|
104
|
+
Child classes should implement their own verification logic.
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
ConnectionError: If connection verification fails
|
|
108
|
+
|
|
109
|
+
"""
|
|
110
|
+
if self.connection is None:
|
|
111
|
+
raise ConnectionError("Connection is None")
|
|
112
|
+
|
|
113
|
+
# Default implementation - child classes should override this
|
|
114
|
+
self.logger.debug("Using default connection verification (no-op)")
|
|
115
|
+
|
|
116
|
+
def _attempt_connection_and_verify(self, connect_func, *args, **kwargs) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Attempt connection and verify it.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
connect_func: The connection function to call
|
|
122
|
+
*args: Arguments to pass to connect_func
|
|
123
|
+
**kwargs: Keyword arguments to pass to connect_func
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
ConnectionError: If connection or verification fails
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
connect_func(*args, **kwargs)
|
|
131
|
+
self._verify_connection()
|
|
132
|
+
|
|
133
|
+
except Exception:
|
|
134
|
+
if self.connection is not None:
|
|
135
|
+
try:
|
|
136
|
+
close_method = getattr(self.connection, "close", None)
|
|
137
|
+
if close_method is not None:
|
|
138
|
+
close_method()
|
|
139
|
+
except Exception:
|
|
140
|
+
pass # Ignore errors during cleanup
|
|
141
|
+
self.connection = None
|
|
142
|
+
raise
|
|
143
|
+
|
|
144
|
+
@abstractmethod
|
|
145
|
+
def execute_query(self, query: str) -> list[tuple]:
|
|
146
|
+
"""Execute a query on the database."""
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
@abstractmethod
|
|
150
|
+
def execute_statement(self, statement: str) -> None:
|
|
151
|
+
"""Execute a statement on the database."""
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
@abstractmethod
|
|
155
|
+
def execute_query_no_return(self, query: str) -> None:
|
|
156
|
+
"""Execute a query on the database without returning results."""
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
@abstractmethod
|
|
160
|
+
def close(self) -> None:
|
|
161
|
+
"""Close the connection to the database."""
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class NullConnector(ConnectorBase):
|
|
166
|
+
"""A connector that does nothing. Used as a null object for async validation scenarios."""
|
|
167
|
+
|
|
168
|
+
def connect(
|
|
169
|
+
self, mode: str = DEFAULT_CONNECTION_MODE, connection_name: str = ""
|
|
170
|
+
) -> None:
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
def execute_query(self, query: str) -> list[tuple]:
|
|
174
|
+
return []
|
|
175
|
+
|
|
176
|
+
def execute_query_no_return(self, query: str) -> None:
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
def execute_statement(self, statement: str) -> None:
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
def close(self) -> None:
|
|
183
|
+
pass
|