dataops-testgen 2.2.0__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.
- dataops_testgen-2.2.0.dist-info/LICENSE +203 -0
- dataops_testgen-2.2.0.dist-info/METADATA +287 -0
- dataops_testgen-2.2.0.dist-info/NOTICE +5 -0
- dataops_testgen-2.2.0.dist-info/RECORD +270 -0
- dataops_testgen-2.2.0.dist-info/WHEEL +5 -0
- dataops_testgen-2.2.0.dist-info/entry_points.txt +2 -0
- dataops_testgen-2.2.0.dist-info/top_level.txt +1 -0
- testgen/__init__.py +0 -0
- testgen/__main__.py +770 -0
- testgen/commands/__init__.py +0 -0
- testgen/commands/queries/__init__.py +0 -0
- testgen/commands/queries/execute_cat_tests_query.py +95 -0
- testgen/commands/queries/execute_tests_query.py +160 -0
- testgen/commands/queries/generate_tests_query.py +94 -0
- testgen/commands/queries/profiling_query.py +366 -0
- testgen/commands/queries/test_parameter_validation_query.py +88 -0
- testgen/commands/run_execute_cat_tests.py +162 -0
- testgen/commands/run_execute_tests.py +168 -0
- testgen/commands/run_generate_tests.py +107 -0
- testgen/commands/run_get_entities.py +122 -0
- testgen/commands/run_launch_db_config.py +84 -0
- testgen/commands/run_observability_exporter.py +330 -0
- testgen/commands/run_profiling_bridge.py +495 -0
- testgen/commands/run_quick_start.py +168 -0
- testgen/commands/run_setup_profiling_tools.py +96 -0
- testgen/commands/run_test_definition.py +146 -0
- testgen/commands/run_test_parameter_validation.py +135 -0
- testgen/commands/run_upgrade_db_config.py +156 -0
- testgen/common/__init__.py +8 -0
- testgen/common/clean_sql.py +53 -0
- testgen/common/credentials.py +25 -0
- testgen/common/database/__init__.py +0 -0
- testgen/common/database/database_service.py +629 -0
- testgen/common/database/flavor/__init__.py +0 -0
- testgen/common/database/flavor/flavor_service.py +75 -0
- testgen/common/database/flavor/mssql_flavor_service.py +34 -0
- testgen/common/database/flavor/postgresql_flavor_service.py +5 -0
- testgen/common/database/flavor/redshift_flavor_service.py +22 -0
- testgen/common/database/flavor/snowflake_flavor_service.py +69 -0
- testgen/common/database/flavor/trino_flavor_service.py +21 -0
- testgen/common/date_service.py +68 -0
- testgen/common/display_service.py +85 -0
- testgen/common/docker_service.py +76 -0
- testgen/common/encrypt.py +55 -0
- testgen/common/get_pipeline_parms.py +57 -0
- testgen/common/logs.py +79 -0
- testgen/common/process_service.py +62 -0
- testgen/common/read_file.py +69 -0
- testgen/settings.py +440 -0
- testgen/template/dbsetup/010_create_base_schema.sql +2 -0
- testgen/template/dbsetup/020_create_standard_functions_sprocs.sql +179 -0
- testgen/template/dbsetup/030_initialize_new_schema_structure.sql +735 -0
- testgen/template/dbsetup/040_populate_new_schema_project.sql +59 -0
- testgen/template/dbsetup/050_populate_new_schema_metadata.sql +1517 -0
- testgen/template/dbsetup/060_create_standard_views.sql +248 -0
- testgen/template/dbsetup/070_create_default_users.sql +17 -0
- testgen/template/dbsetup/075_grant_role_rights.sql +43 -0
- testgen/template/dbsetup/080_set_current_revision.sql +5 -0
- testgen/template/dbupgrade/0100_incremental_upgrade.sql +5 -0
- testgen/template/dbupgrade/0101_incremental_upgrade.sql +15 -0
- testgen/template/dbupgrade/0102_incremental_upgrade.sql +4 -0
- testgen/template/dbupgrade/0103_incremental_upgrade.sql +22 -0
- testgen/template/dbupgrade/0104_incremental_upgrade.sql +44 -0
- testgen/template/dbupgrade/0105_incremental_upgrade.sql +1 -0
- testgen/template/dbupgrade/0106_incremental_upgrade.sql +5 -0
- testgen/template/dbupgrade/0107_incremental_upgrade.sql +3 -0
- testgen/template/dbupgrade_helpers/get_tg_revision.sql +2 -0
- testgen/template/exec_cat_tests/ex_cat_build_agg_table_tests.sql +116 -0
- testgen/template/exec_cat_tests/ex_cat_get_distinct_tables.sql +11 -0
- testgen/template/exec_cat_tests/ex_cat_results_parse.sql +69 -0
- testgen/template/exec_cat_tests/ex_cat_retrieve_agg_test_parms.sql +6 -0
- testgen/template/exec_cat_tests/ex_cat_test_query.sql +8 -0
- testgen/template/execution/ex_finalize_test_run_results.sql +37 -0
- testgen/template/execution/ex_get_tests_non_cat.sql +47 -0
- testgen/template/execution/ex_update_test_record_in_testrun_table.sql +27 -0
- testgen/template/execution/ex_write_test_record_to_testrun_table.sql +6 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_no_drops_generic.sql +48 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_num_incr_generic.sql +34 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_percent_above_generic.sql +49 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_percent_within_generic.sql +49 -0
- testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_same_generic.sql +49 -0
- testgen/template/flavors/generic/exec_query_tests/ex_custom_query_generic.sql +39 -0
- testgen/template/flavors/generic/exec_query_tests/ex_data_match_2way_generic.sql +58 -0
- testgen/template/flavors/generic/exec_query_tests/ex_data_match_generic.sql +44 -0
- testgen/template/flavors/generic/exec_query_tests/ex_prior_match_generic.sql +37 -0
- testgen/template/flavors/generic/exec_query_tests/ex_relative_entropy_generic.sql +53 -0
- testgen/template/flavors/generic/exec_query_tests/ex_window_match_no_drops_generic.sql +46 -0
- testgen/template/flavors/generic/exec_query_tests/ex_window_match_same_generic.sql +59 -0
- testgen/template/flavors/generic/profiling/contingency_counts.sql +3 -0
- testgen/template/flavors/generic/validate_tests/ex_get_project_column_list_generic.sql +3 -0
- testgen/template/flavors/mssql/exec_query_tests/ex_relative_entropy_mssql.sql +53 -0
- testgen/template/flavors/mssql/profiling/project_ddf_query_mssql.sql +35 -0
- testgen/template/flavors/mssql/profiling/project_profiling_query_mssql.yaml +246 -0
- testgen/template/flavors/mssql/profiling/project_secondary_profiling_query_mssql.sql +36 -0
- testgen/template/flavors/mssql/setup_profiling_tools/00_drop_existing_functions_mssql.sql +8 -0
- testgen/template/flavors/mssql/setup_profiling_tools/01_create_functions_mssql.sql +12 -0
- testgen/template/flavors/mssql/setup_profiling_tools/02_create_functions_mssql.sql +54 -0
- testgen/template/flavors/mssql/setup_profiling_tools/create_qc_schema_mssql.sql +4 -0
- testgen/template/flavors/mssql/setup_profiling_tools/grant_execute_privileges_mssql.sql +1 -0
- testgen/template/flavors/postgresql/exec_query_tests/ex_window_match_no_drops_postgresql.sql +46 -0
- testgen/template/flavors/postgresql/exec_query_tests/ex_window_match_same_postgresql.sql +59 -0
- testgen/template/flavors/postgresql/profiling/project_ddf_query_postgresql.sql +42 -0
- testgen/template/flavors/postgresql/profiling/project_profiling_query_postgresql.yaml +225 -0
- testgen/template/flavors/postgresql/profiling/project_secondary_profiling_query_postgresql.sql +28 -0
- testgen/template/flavors/postgresql/setup_profiling_tools/create_functions_postgresql.sql +157 -0
- testgen/template/flavors/postgresql/setup_profiling_tools/create_qc_schema_postgresql.sql +1 -0
- testgen/template/flavors/postgresql/setup_profiling_tools/grant_execute_privileges_postgresql.sql +2 -0
- testgen/template/flavors/redshift/profiling/project_ddf_query_redshift.sql +38 -0
- testgen/template/flavors/redshift/profiling/project_profiling_query_redshift.yaml +221 -0
- testgen/template/flavors/redshift/profiling/project_secondary_profiling_query_redshift.sql +29 -0
- testgen/template/flavors/redshift/setup_profiling_tools/create_functions_redshift.sql +115 -0
- testgen/template/flavors/redshift/setup_profiling_tools/create_qc_schema_redshift.sql +1 -0
- testgen/template/flavors/redshift/setup_profiling_tools/grant_execute_privileges_redshift.sql +2 -0
- testgen/template/flavors/snowflake/profiling/project_ddf_query_snowflake.sql +38 -0
- testgen/template/flavors/snowflake/profiling/project_profiling_query_snowflake.yaml +220 -0
- testgen/template/flavors/snowflake/profiling/project_secondary_profiling_query_snowflake.sql +29 -0
- testgen/template/flavors/snowflake/setup_profiling_tools/create_functions_snowflake.sql +69 -0
- testgen/template/flavors/snowflake/setup_profiling_tools/create_qc_schema_snowflake.sql +1 -0
- testgen/template/flavors/snowflake/setup_profiling_tools/grant_execute_privileges_snowflake.sql +6 -0
- testgen/template/flavors/trino/profiling/project_profiling_query_trino.yaml +219 -0
- testgen/template/flavors/trino/setup_profiling_tools/create_functions_trino.sql +92 -0
- testgen/template/flavors/trino/setup_profiling_tools/create_qc_schema_trino.sql +1 -0
- testgen/template/gen_funny_cat_tests/gen_test_constant.sql +104 -0
- testgen/template/gen_funny_cat_tests/gen_test_distinct_value_ct.sql +98 -0
- testgen/template/gen_funny_cat_tests/gen_test_row_ct.sql +57 -0
- testgen/template/gen_funny_cat_tests/gen_test_row_ct_pct.sql +59 -0
- testgen/template/generation/gen_delete_old_tests.sql +5 -0
- testgen/template/generation/gen_insert_test_suite.sql +5 -0
- testgen/template/generation/gen_retrieve_or_insert_test_suite.sql +58 -0
- testgen/template/generation/gen_standard_test_type_list.sql +13 -0
- testgen/template/generation/gen_standard_tests.sql +48 -0
- testgen/template/get_entities/get_connection.sql +21 -0
- testgen/template/get_entities/get_connections_list.sql +9 -0
- testgen/template/get_entities/get_latest.sql +4 -0
- testgen/template/get_entities/get_profile.sql +12 -0
- testgen/template/get_entities/get_profile_info.sql +17 -0
- testgen/template/get_entities/get_profile_list.sql +17 -0
- testgen/template/get_entities/get_profile_screen.sql +275 -0
- testgen/template/get_entities/get_project_list.sql +6 -0
- testgen/template/get_entities/get_table_group_list.sql +10 -0
- testgen/template/get_entities/get_test_generation_list.sql +18 -0
- testgen/template/get_entities/get_test_info.sql +41 -0
- testgen/template/get_entities/get_test_results_for_run_cli.sql +16 -0
- testgen/template/get_entities/get_test_run_list.sql +24 -0
- testgen/template/get_entities/get_test_suite.sql +13 -0
- testgen/template/get_entities/get_test_suite_list.sql +18 -0
- testgen/template/get_entities/list_test_types.sql +4 -0
- testgen/template/observability/get_event_data.sql +23 -0
- testgen/template/observability/get_test_results.sql +41 -0
- testgen/template/observability/update_test_results_exported_to_observability.sql +12 -0
- testgen/template/parms/parms_profiling.sql +34 -0
- testgen/template/parms/parms_test_execution.sql +13 -0
- testgen/template/parms/parms_test_gen.sql +23 -0
- testgen/template/profiling/contingency_columns.sql +7 -0
- testgen/template/profiling/datatype_suggestions.sql +56 -0
- testgen/template/profiling/functional_datatype.sql +523 -0
- testgen/template/profiling/functional_tabletype_stage.sql +48 -0
- testgen/template/profiling/functional_tabletype_update.sql +8 -0
- testgen/template/profiling/pii_flag.sql +133 -0
- testgen/template/profiling/profile_anomalies_screen_column.sql +22 -0
- testgen/template/profiling/profile_anomalies_screen_multi_column.sql +58 -0
- testgen/template/profiling/profile_anomalies_screen_table.sql +22 -0
- testgen/template/profiling/profile_anomalies_screen_table_dates.sql +30 -0
- testgen/template/profiling/profile_anomalies_screen_variants.sql +40 -0
- testgen/template/profiling/profile_anomaly_types_get.sql +3 -0
- testgen/template/profiling/project_get_table_sample_count.sql +22 -0
- testgen/template/profiling/project_profile_run_record_insert.sql +8 -0
- testgen/template/profiling/project_profile_run_record_update.sql +5 -0
- testgen/template/profiling/project_profile_run_record_update_status.sql +5 -0
- testgen/template/profiling/project_update_profile_results_to_estimates.sql +32 -0
- testgen/template/profiling/refresh_anomalies.sql +33 -0
- testgen/template/profiling/refresh_data_chars_from_profiling.sql +156 -0
- testgen/template/profiling/secondary_profiling_columns.sql +12 -0
- testgen/template/profiling/secondary_profiling_delete.sql +4 -0
- testgen/template/profiling/secondary_profiling_update.sql +18 -0
- testgen/template/quick_start/populate_target_data.sql +1077 -0
- testgen/template/quick_start/recreate_target_data_schema.sql +167 -0
- testgen/template/quick_start/update_target_data.sql +100 -0
- testgen/template/updates/create_tmp_test_definition.sql +19 -0
- testgen/template/updates/get_test_def_parms.sql +38 -0
- testgen/template/updates/populate_stg_test_definitions.sql +184 -0
- testgen/template/validate_tests/ex_disable_tests_test_definitions.sql +5 -0
- testgen/template/validate_tests/ex_flag_tests_test_definitions.sql +64 -0
- testgen/template/validate_tests/ex_get_project_column_list_generic.sql +3 -0
- testgen/template/validate_tests/ex_get_test_column_list_tg.sql +65 -0
- testgen/template/validate_tests/ex_write_test_val_errors.sql +22 -0
- testgen/ui/__init__.py +0 -0
- testgen/ui/app.py +98 -0
- testgen/ui/assets/dk_logo.svg +46 -0
- testgen/ui/assets/question_mark.png +0 -0
- testgen/ui/assets/scripts.js +68 -0
- testgen/ui/assets/style.css +140 -0
- testgen/ui/bootstrap.py +109 -0
- testgen/ui/components/__init__.py +0 -0
- testgen/ui/components/frontend/css/KFOlCnqEu92Fr1MmEU9fBBc4.woff2 +0 -0
- testgen/ui/components/frontend/css/KFOlCnqEu92Fr1MmEU9fChc4EsA.woff2 +0 -0
- testgen/ui/components/frontend/css/KFOmCnqEu92Fr1Mu4mxK.woff2 +0 -0
- testgen/ui/components/frontend/css/KFOmCnqEu92Fr1Mu7GxKOzY.woff2 +0 -0
- testgen/ui/components/frontend/css/material-symbols-rounded.css +24 -0
- testgen/ui/components/frontend/css/material-symbols-rounded.woff2 +0 -0
- testgen/ui/components/frontend/css/roboto-font-faces.css +35 -0
- testgen/ui/components/frontend/css/shared.css +36 -0
- testgen/ui/components/frontend/img/dk_logo.svg +46 -0
- testgen/ui/components/frontend/index.html +17 -0
- testgen/ui/components/frontend/js/components/breadcrumbs.js +86 -0
- testgen/ui/components/frontend/js/components/button.js +66 -0
- testgen/ui/components/frontend/js/components/location.js +62 -0
- testgen/ui/components/frontend/js/components/select.js +75 -0
- testgen/ui/components/frontend/js/components/sidebar.js +358 -0
- testgen/ui/components/frontend/js/main.js +99 -0
- testgen/ui/components/frontend/js/streamlit.js +19 -0
- testgen/ui/components/frontend/js/van.min.js +1 -0
- testgen/ui/components/utils/__init__.py +0 -0
- testgen/ui/components/utils/callbacks.py +51 -0
- testgen/ui/components/utils/component.py +13 -0
- testgen/ui/components/widgets/__init__.py +6 -0
- testgen/ui/components/widgets/breadcrumbs.py +32 -0
- testgen/ui/components/widgets/location.py +65 -0
- testgen/ui/components/widgets/modal.py +97 -0
- testgen/ui/components/widgets/sidebar.py +69 -0
- testgen/ui/navigation/__init__.py +0 -0
- testgen/ui/navigation/menu.py +42 -0
- testgen/ui/navigation/page.py +20 -0
- testgen/ui/navigation/router.py +63 -0
- testgen/ui/queries/__init__.py +0 -0
- testgen/ui/queries/authentication_queries.py +47 -0
- testgen/ui/queries/connection_queries.py +121 -0
- testgen/ui/queries/profiling_queries.py +148 -0
- testgen/ui/queries/project_queries.py +9 -0
- testgen/ui/queries/table_group_queries.py +186 -0
- testgen/ui/queries/test_definition_queries.py +270 -0
- testgen/ui/queries/test_run_queries.py +32 -0
- testgen/ui/queries/test_suite_queries.py +145 -0
- testgen/ui/scripts/__init__.py +0 -0
- testgen/ui/scripts/patch_streamlit.py +111 -0
- testgen/ui/services/__init__.py +0 -0
- testgen/ui/services/authentication_service.py +119 -0
- testgen/ui/services/connection_service.py +220 -0
- testgen/ui/services/database_service.py +282 -0
- testgen/ui/services/form_service.py +1008 -0
- testgen/ui/services/javascript_service.py +44 -0
- testgen/ui/services/query_service.py +316 -0
- testgen/ui/services/string_service.py +12 -0
- testgen/ui/services/table_group_service.py +130 -0
- testgen/ui/services/test_definition_service.py +117 -0
- testgen/ui/services/test_run_service.py +13 -0
- testgen/ui/services/test_suite_service.py +76 -0
- testgen/ui/services/toolbar_service.py +77 -0
- testgen/ui/session.py +46 -0
- testgen/ui/views/__init__.py +0 -0
- testgen/ui/views/app_log_modal.py +92 -0
- testgen/ui/views/connections.py +72 -0
- testgen/ui/views/connections_base.py +367 -0
- testgen/ui/views/login.py +40 -0
- testgen/ui/views/not_found.py +16 -0
- testgen/ui/views/overview.py +34 -0
- testgen/ui/views/profiling_anomalies.py +501 -0
- testgen/ui/views/profiling_details.py +335 -0
- testgen/ui/views/profiling_modal.py +40 -0
- testgen/ui/views/profiling_results.py +206 -0
- testgen/ui/views/profiling_summary.py +177 -0
- testgen/ui/views/project_settings.py +74 -0
- testgen/ui/views/table_groups.py +530 -0
- testgen/ui/views/test_definitions.py +1020 -0
- testgen/ui/views/test_results.py +908 -0
- testgen/ui/views/test_runs.py +195 -0
- testgen/ui/views/test_suites.py +545 -0
- testgen/utils/__init__.py +0 -0
- testgen/utils/plugins.py +17 -0
- testgen/utils/singleton.py +14 -0
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
import concurrent.futures
|
|
2
|
+
import csv
|
|
3
|
+
import importlib
|
|
4
|
+
import logging
|
|
5
|
+
import queue as qu
|
|
6
|
+
import threading
|
|
7
|
+
from contextlib import suppress
|
|
8
|
+
from io import StringIO
|
|
9
|
+
from urllib.parse import quote_plus
|
|
10
|
+
|
|
11
|
+
from sqlalchemy import create_engine, text
|
|
12
|
+
from sqlalchemy.exc import ProgrammingError, SQLAlchemyError
|
|
13
|
+
|
|
14
|
+
from testgen import settings
|
|
15
|
+
from testgen.common.credentials import (
|
|
16
|
+
get_tg_db,
|
|
17
|
+
get_tg_host,
|
|
18
|
+
get_tg_password,
|
|
19
|
+
get_tg_port,
|
|
20
|
+
get_tg_schema,
|
|
21
|
+
get_tg_username,
|
|
22
|
+
)
|
|
23
|
+
from testgen.common.encrypt import DecryptText
|
|
24
|
+
from testgen.common.read_file import get_template_files
|
|
25
|
+
|
|
26
|
+
LOG = logging.getLogger("testgen")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CConnectParms:
|
|
30
|
+
connectname = ""
|
|
31
|
+
projectcode = ""
|
|
32
|
+
connectid = ""
|
|
33
|
+
hostname = ""
|
|
34
|
+
port = ""
|
|
35
|
+
dbname = ""
|
|
36
|
+
schemaname = ""
|
|
37
|
+
username = ""
|
|
38
|
+
sql_flavor = ""
|
|
39
|
+
url = ""
|
|
40
|
+
connect_by_url = ""
|
|
41
|
+
connect_by_key = ""
|
|
42
|
+
private_key = ""
|
|
43
|
+
private_key_passphrase = ""
|
|
44
|
+
password = None
|
|
45
|
+
|
|
46
|
+
def __init__(self, connectname):
|
|
47
|
+
self.connectname = connectname
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Initialize variables global to this script
|
|
51
|
+
clsConnectParms = CConnectParms("NONE")
|
|
52
|
+
dctDBEngines = {}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def QuoteCSVItems(str_csv_row, char_quote='"'):
|
|
56
|
+
if str_csv_row:
|
|
57
|
+
lst_values = str_csv_row.split(",")
|
|
58
|
+
# Process each value individually, quoting it if not already quoted
|
|
59
|
+
str_quoted_values = ",".join(
|
|
60
|
+
[
|
|
61
|
+
(
|
|
62
|
+
f"{char_quote}{value}{char_quote}"
|
|
63
|
+
if not (value.startswith(char_quote) and value.endswith(char_quote))
|
|
64
|
+
else value
|
|
65
|
+
)
|
|
66
|
+
for value in lst_values
|
|
67
|
+
]
|
|
68
|
+
)
|
|
69
|
+
return str_quoted_values
|
|
70
|
+
return str_csv_row
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def empty_cache():
|
|
74
|
+
global dctDBEngines
|
|
75
|
+
dctDBEngines = {}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def AssignConnectParms(
|
|
79
|
+
projectcode,
|
|
80
|
+
connectid,
|
|
81
|
+
host,
|
|
82
|
+
port,
|
|
83
|
+
dbname,
|
|
84
|
+
schema,
|
|
85
|
+
user,
|
|
86
|
+
flavor,
|
|
87
|
+
url,
|
|
88
|
+
connect_by_url,
|
|
89
|
+
connect_by_key,
|
|
90
|
+
private_key,
|
|
91
|
+
private_key_passphrase,
|
|
92
|
+
connectname="PROJECT",
|
|
93
|
+
password=None,
|
|
94
|
+
):
|
|
95
|
+
global clsConnectParms
|
|
96
|
+
|
|
97
|
+
clsConnectParms.connectname = connectname
|
|
98
|
+
clsConnectParms.projectcode = projectcode
|
|
99
|
+
clsConnectParms.connectid = connectid
|
|
100
|
+
clsConnectParms.hostname = host
|
|
101
|
+
clsConnectParms.port = port
|
|
102
|
+
clsConnectParms.dbname = dbname
|
|
103
|
+
clsConnectParms.schemaname = schema
|
|
104
|
+
clsConnectParms.username = user
|
|
105
|
+
clsConnectParms.sql_flavor = flavor
|
|
106
|
+
clsConnectParms.password = password
|
|
107
|
+
clsConnectParms.url = url
|
|
108
|
+
clsConnectParms.connect_by_url = connect_by_url
|
|
109
|
+
clsConnectParms.connect_by_key = connect_by_key
|
|
110
|
+
clsConnectParms.private_key = private_key
|
|
111
|
+
clsConnectParms.private_key_passphrase = private_key_passphrase
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _RetrieveProjectPW(strProjectCode, strConnID):
|
|
115
|
+
strSQL = """ SELECT project_pw_encrypted
|
|
116
|
+
FROM connections cc
|
|
117
|
+
WHERE cc.project_code = '{PROJECT_CODE}' AND cc.connection_id = {CONNECTION_ID}; """
|
|
118
|
+
|
|
119
|
+
# Replace Parameters
|
|
120
|
+
strSQL = strSQL.replace("{PROJECT_CODE}", strProjectCode)
|
|
121
|
+
strSQL = strSQL.replace("{CONNECTION_ID}", str(strConnID))
|
|
122
|
+
# Execute Query
|
|
123
|
+
strPW = RetrieveSingleResultValue("DKTG", strSQL)
|
|
124
|
+
# Convert Postgres bytea to Python byte array
|
|
125
|
+
strPW = bytes(strPW) if strPW else None
|
|
126
|
+
|
|
127
|
+
# Perform Decryption
|
|
128
|
+
strPW = DecryptText(strPW)
|
|
129
|
+
return strPW
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _GetDBPassword(strCredentialSet):
|
|
133
|
+
global clsConnectParms
|
|
134
|
+
|
|
135
|
+
if strCredentialSet == "PROJECT":
|
|
136
|
+
if not clsConnectParms.password:
|
|
137
|
+
strPW = _RetrieveProjectPW(clsConnectParms.projectcode, clsConnectParms.connectid)
|
|
138
|
+
else:
|
|
139
|
+
strPW = clsConnectParms.password
|
|
140
|
+
elif strCredentialSet == "DKTG":
|
|
141
|
+
strPW = get_tg_password()
|
|
142
|
+
else:
|
|
143
|
+
raise ValueError('Credential Set "' + strCredentialSet + '" is unknown.')
|
|
144
|
+
|
|
145
|
+
if strPW == "":
|
|
146
|
+
raise ValueError('Password for Credential Set "' + strCredentialSet + '" is unknown.')
|
|
147
|
+
else:
|
|
148
|
+
return strPW
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_db_type(sql_flavor):
|
|
152
|
+
# This is for connection purposes. sqlalchemy 1.4.46 uses postgresql to connect to redshift database
|
|
153
|
+
if sql_flavor == "redshift":
|
|
154
|
+
return "postgresql"
|
|
155
|
+
else:
|
|
156
|
+
return sql_flavor
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _GetDBCredentials(strCredentialSet):
|
|
160
|
+
global clsConnectParms
|
|
161
|
+
|
|
162
|
+
if strCredentialSet == "PROJECT":
|
|
163
|
+
# Check for unassigned parms
|
|
164
|
+
if clsConnectParms.connectname == "NONE":
|
|
165
|
+
raise ValueError("Project Connection Parameters were not set.")
|
|
166
|
+
|
|
167
|
+
strConnectflavor = get_db_type(clsConnectParms.sql_flavor)
|
|
168
|
+
|
|
169
|
+
# Get project credentials from clsConnectParms
|
|
170
|
+
dctCredentials = {
|
|
171
|
+
"name": strCredentialSet,
|
|
172
|
+
"host": clsConnectParms.hostname,
|
|
173
|
+
"port": clsConnectParms.port,
|
|
174
|
+
"dbname": clsConnectParms.dbname,
|
|
175
|
+
"dbschema": clsConnectParms.schemaname,
|
|
176
|
+
"user": clsConnectParms.username,
|
|
177
|
+
"flavor": strConnectflavor,
|
|
178
|
+
"dbtype": clsConnectParms.sql_flavor,
|
|
179
|
+
"url": clsConnectParms.url,
|
|
180
|
+
"connect_by_url": clsConnectParms.connect_by_url,
|
|
181
|
+
"connect_by_key": clsConnectParms.connect_by_key,
|
|
182
|
+
"private_key": clsConnectParms.private_key,
|
|
183
|
+
"private_key_passphrase": clsConnectParms.private_key_passphrase,
|
|
184
|
+
}
|
|
185
|
+
elif strCredentialSet == "DKTG":
|
|
186
|
+
# Get credentials from functions in my_dk_credentials.py
|
|
187
|
+
dctCredentials = {
|
|
188
|
+
"name": strCredentialSet,
|
|
189
|
+
"host": get_tg_host(),
|
|
190
|
+
"port": get_tg_port(),
|
|
191
|
+
"dbname": get_tg_db(),
|
|
192
|
+
"dbschema": get_tg_schema(),
|
|
193
|
+
"user": get_tg_username(),
|
|
194
|
+
"flavor": "postgresql",
|
|
195
|
+
"dbtype": "postgresql",
|
|
196
|
+
}
|
|
197
|
+
else:
|
|
198
|
+
raise ValueError("Credentials for " + strCredentialSet + " are not defined.")
|
|
199
|
+
|
|
200
|
+
return dctCredentials
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def get_flavor_service(flavor):
|
|
204
|
+
module_path = f"testgen.common.database.flavor.{flavor}_flavor_service"
|
|
205
|
+
class_name = f"{flavor.capitalize()}FlavorService"
|
|
206
|
+
module = importlib.import_module(module_path)
|
|
207
|
+
flavor_class = getattr(module, class_name)
|
|
208
|
+
return flavor_class()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _InitDBConnection(strCredentialSet, strRaw="N", strAdmin="N", user_override=None, pwd_override=None):
|
|
212
|
+
# Get DB Credentials
|
|
213
|
+
dctCredentials = _GetDBCredentials(strCredentialSet)
|
|
214
|
+
|
|
215
|
+
if strCredentialSet == "DKTG":
|
|
216
|
+
con = _InitDBConnection_appdb(dctCredentials, strCredentialSet, strRaw, strAdmin, user_override, pwd_override)
|
|
217
|
+
else:
|
|
218
|
+
flavor_service = get_flavor_service(dctCredentials["dbtype"])
|
|
219
|
+
flavor_service.init(dctCredentials)
|
|
220
|
+
con = _InitDBConnection_target_db(flavor_service, strCredentialSet, strRaw, user_override, pwd_override)
|
|
221
|
+
return con
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _InitDBConnection_appdb(
|
|
225
|
+
dctCredentials, strCredentialSet, strRaw="N", strAdmin="N", user_override=None, pwd_override=None
|
|
226
|
+
):
|
|
227
|
+
# Get DB Credentials
|
|
228
|
+
dctCredentials = _GetDBCredentials(strCredentialSet)
|
|
229
|
+
|
|
230
|
+
# Set DB Credential Overrides for Admin connections
|
|
231
|
+
# strAdmin = "N": Log into DB/schema for normal stuff
|
|
232
|
+
# strAdmin = "D": Log into postgres/public to create DB via override user/password
|
|
233
|
+
# strAdmin = "S": Log into DB/public to create schema and run scripts via override user/password
|
|
234
|
+
if strAdmin in {"D", "S"}:
|
|
235
|
+
dctCredentials["user"] = user_override
|
|
236
|
+
dctCredentials["dbschema"] = "public"
|
|
237
|
+
if strAdmin == "D":
|
|
238
|
+
dctCredentials["dbname"] = "postgres"
|
|
239
|
+
|
|
240
|
+
# Get DBEngine using credentials
|
|
241
|
+
if strCredentialSet in dctDBEngines and strAdmin == "N":
|
|
242
|
+
# Retrieve existing engine from store
|
|
243
|
+
dbEngine = dctDBEngines[strCredentialSet]
|
|
244
|
+
else:
|
|
245
|
+
# Handle Admin overrides or circumstantial password override
|
|
246
|
+
if strAdmin in {"D", "S"} or pwd_override is not None:
|
|
247
|
+
strPW = pwd_override
|
|
248
|
+
else:
|
|
249
|
+
strPW = _GetDBPassword(strCredentialSet)
|
|
250
|
+
|
|
251
|
+
# Open a new engine with appropriate connection parms
|
|
252
|
+
# STANDARD FORMAT: strConnect = 'flavor://username:password@host:port/database'
|
|
253
|
+
strConnect = "{}://{}:{}@{}:{}/{}".format(
|
|
254
|
+
dctCredentials["flavor"],
|
|
255
|
+
dctCredentials["user"],
|
|
256
|
+
quote_plus(strPW),
|
|
257
|
+
dctCredentials["host"],
|
|
258
|
+
dctCredentials["port"],
|
|
259
|
+
dctCredentials["dbname"],
|
|
260
|
+
)
|
|
261
|
+
try:
|
|
262
|
+
# Timeout in seconds: 1 hour = 60 * 60 second = 3600
|
|
263
|
+
dbEngine = create_engine(strConnect, connect_args={"connect_timeout": 3600})
|
|
264
|
+
dctDBEngines[strCredentialSet] = dbEngine
|
|
265
|
+
|
|
266
|
+
except SQLAlchemyError as e:
|
|
267
|
+
raise ValueError(
|
|
268
|
+
f"Failed to create engine (Admin={strAdmin}) \
|
|
269
|
+
for database {dctCredentials['dbname']}"
|
|
270
|
+
) from e
|
|
271
|
+
|
|
272
|
+
# Second, create a connection from our engine
|
|
273
|
+
try:
|
|
274
|
+
if strRaw == "N":
|
|
275
|
+
con = dbEngine.connect()
|
|
276
|
+
if strAdmin == "N":
|
|
277
|
+
strSchemaSQL = f"SET SEARCH_PATH = {dctCredentials['dbschema']};"
|
|
278
|
+
con.execute(text(strSchemaSQL))
|
|
279
|
+
else:
|
|
280
|
+
con = dbEngine.raw_connection()
|
|
281
|
+
strSchemaSQL = "SET SEARCH_PATH = " + dctCredentials["dbschema"]
|
|
282
|
+
with con.cursor() as cur:
|
|
283
|
+
cur.execute(strSchemaSQL)
|
|
284
|
+
con.commit()
|
|
285
|
+
except SQLAlchemyError as e:
|
|
286
|
+
raise ValueError("Failed to connect to database " + dctCredentials["dbname"]) from e
|
|
287
|
+
|
|
288
|
+
return con
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _InitDBConnection_target_db(flavor_service, strCredentialSet, strRaw="N", user_override=None, pwd_override=None):
|
|
292
|
+
# Get DBEngine using credentials
|
|
293
|
+
if strCredentialSet in dctDBEngines:
|
|
294
|
+
# Retrieve existing engine from store
|
|
295
|
+
dbEngine = dctDBEngines[strCredentialSet]
|
|
296
|
+
else:
|
|
297
|
+
# Handle user override
|
|
298
|
+
if user_override is not None:
|
|
299
|
+
flavor_service.override_user(user_override)
|
|
300
|
+
# Handle password override
|
|
301
|
+
if pwd_override is not None:
|
|
302
|
+
strPW = pwd_override
|
|
303
|
+
elif not flavor_service.is_connect_by_key():
|
|
304
|
+
strPW = _GetDBPassword(strCredentialSet)
|
|
305
|
+
else:
|
|
306
|
+
strPW = None
|
|
307
|
+
|
|
308
|
+
# Open a new engine with appropriate connection parms
|
|
309
|
+
is_password_overwritten = pwd_override is not None
|
|
310
|
+
strConnect = flavor_service.get_connection_string(strPW, is_password_overwritten)
|
|
311
|
+
|
|
312
|
+
connect_args = {"connect_timeout": 3600}
|
|
313
|
+
connect_args.update(flavor_service.get_connect_args(is_password_overwritten))
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
# Timeout in seconds: 1 hour = 60 * 60 second = 3600
|
|
317
|
+
dbEngine = create_engine(strConnect, connect_args=connect_args)
|
|
318
|
+
dctDBEngines[strCredentialSet] = dbEngine
|
|
319
|
+
|
|
320
|
+
except SQLAlchemyError as e:
|
|
321
|
+
raise ValueError(f"Failed to create engine for database {flavor_service.get_db_name}") from e
|
|
322
|
+
|
|
323
|
+
# Second, create a connection from our engine
|
|
324
|
+
queries = flavor_service.get_pre_connection_queries()
|
|
325
|
+
if strRaw == "N":
|
|
326
|
+
connection = dbEngine.connect()
|
|
327
|
+
for query in queries:
|
|
328
|
+
try:
|
|
329
|
+
connection.execute(text(query))
|
|
330
|
+
except Exception:
|
|
331
|
+
LOG.warning(
|
|
332
|
+
f"failed executing pre connection query: `{query}`",
|
|
333
|
+
exc_info=settings.IS_DEBUG,
|
|
334
|
+
stack_info=settings.IS_DEBUG,
|
|
335
|
+
)
|
|
336
|
+
else:
|
|
337
|
+
connection = dbEngine.raw_connection()
|
|
338
|
+
with connection.cursor() as cur:
|
|
339
|
+
for query in queries:
|
|
340
|
+
try:
|
|
341
|
+
cur.execute(query)
|
|
342
|
+
except Exception:
|
|
343
|
+
LOG.warning(
|
|
344
|
+
f"failed executing pre connection query: `{query}`",
|
|
345
|
+
exc_info=settings.IS_DEBUG,
|
|
346
|
+
stack_info=settings.IS_DEBUG,
|
|
347
|
+
)
|
|
348
|
+
connection.commit()
|
|
349
|
+
|
|
350
|
+
return connection
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def CreateDatabaseIfNotExists(strDBName: str, params_mapping: dict, delete_db: bool, drop_users_and_roles: bool = True):
|
|
354
|
+
LOG.info("CurrentDB Operation: CreateDatabase. Creds: DKTG Admin")
|
|
355
|
+
|
|
356
|
+
con = _InitDBConnection(
|
|
357
|
+
"DKTG",
|
|
358
|
+
strAdmin="D",
|
|
359
|
+
user_override=params_mapping["TESTGEN_ADMIN_USER"],
|
|
360
|
+
pwd_override=params_mapping["TESTGEN_ADMIN_PASSWORD"],
|
|
361
|
+
)
|
|
362
|
+
con.execute("commit")
|
|
363
|
+
|
|
364
|
+
# Catch and ignore error if database already exists
|
|
365
|
+
with con:
|
|
366
|
+
if delete_db:
|
|
367
|
+
con.execute(
|
|
368
|
+
f"SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '{strDBName}'"
|
|
369
|
+
)
|
|
370
|
+
con.execute("commit")
|
|
371
|
+
con.execute(f"DROP DATABASE IF EXISTS {strDBName}")
|
|
372
|
+
con.execute("commit")
|
|
373
|
+
if drop_users_and_roles:
|
|
374
|
+
con.execute(replace_params("DROP USER IF EXISTS {TESTGEN_USER}", params_mapping))
|
|
375
|
+
con.execute(replace_params("DROP USER IF EXISTS {TESTGEN_REPORT_USER}", params_mapping))
|
|
376
|
+
con.execute("DROP ROLE IF EXISTS testgen_execute_role")
|
|
377
|
+
con.execute("DROP ROLE IF EXISTS testgen_report_role")
|
|
378
|
+
con.execute("commit")
|
|
379
|
+
with suppress(ProgrammingError):
|
|
380
|
+
con.execute("create database " + strDBName)
|
|
381
|
+
con.close()
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def RunActionQueryList(strCredentialSet, lstQueries, strAdminNDS="N", user_override=None, pwd_override=None):
|
|
385
|
+
LOG.info("CurrentDB Operation: RunActionQueryList. Creds: %s", strCredentialSet)
|
|
386
|
+
|
|
387
|
+
with _InitDBConnection(
|
|
388
|
+
strCredentialSet, strAdmin=strAdminNDS, user_override=user_override, pwd_override=pwd_override
|
|
389
|
+
) as con:
|
|
390
|
+
i = 0
|
|
391
|
+
n = len(lstQueries)
|
|
392
|
+
if n == 0:
|
|
393
|
+
LOG.info("No queries to process")
|
|
394
|
+
for q in lstQueries:
|
|
395
|
+
i += 1
|
|
396
|
+
LOG.debug(f"LastQuery = {q}")
|
|
397
|
+
LOG.info(f"(Processing {i} of {n})")
|
|
398
|
+
tx = con.begin()
|
|
399
|
+
exQ = con.execute(text(q))
|
|
400
|
+
if exQ.rowcount == -1:
|
|
401
|
+
strMsg = "Action query processed no records."
|
|
402
|
+
else:
|
|
403
|
+
strMsg = str(exQ.rowcount) + " records processed."
|
|
404
|
+
tx.commit()
|
|
405
|
+
LOG.info(strMsg)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def RunRetrievalQueryList(strCredentialSet, lstQueries):
|
|
409
|
+
LOG.info("CurrentDB Operation: RunRetrievalQueryList. Creds: %s", strCredentialSet)
|
|
410
|
+
|
|
411
|
+
with _InitDBConnection(strCredentialSet) as con:
|
|
412
|
+
colNames = None
|
|
413
|
+
lstResults = []
|
|
414
|
+
i = 0
|
|
415
|
+
n = len(lstQueries)
|
|
416
|
+
if n == 0:
|
|
417
|
+
LOG.info("No queries to process")
|
|
418
|
+
for q in lstQueries:
|
|
419
|
+
i += 1
|
|
420
|
+
LOG.debug("LastQuery = %s", q)
|
|
421
|
+
LOG.info("(Processing %s of %s)", i, n)
|
|
422
|
+
|
|
423
|
+
exQ = con.execute(text(q))
|
|
424
|
+
lstOneResult = exQ.fetchall()
|
|
425
|
+
if not colNames:
|
|
426
|
+
colNames = exQ.keys()
|
|
427
|
+
strRows = str(exQ.rowcount)
|
|
428
|
+
lstResults.extend(lstOneResult)
|
|
429
|
+
|
|
430
|
+
LOG.info("%s records retrieved.", strRows)
|
|
431
|
+
|
|
432
|
+
return lstResults, colNames
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
class _CThreadedFetch:
|
|
436
|
+
def __init__(self, strCredentialSet, count_lock):
|
|
437
|
+
self.strCredentialSet = strCredentialSet
|
|
438
|
+
self.count_lock = count_lock
|
|
439
|
+
self.count = 0
|
|
440
|
+
|
|
441
|
+
def __call__(self, strQuery):
|
|
442
|
+
colNames = None
|
|
443
|
+
lstResult = None
|
|
444
|
+
booError = False
|
|
445
|
+
|
|
446
|
+
with self.count_lock:
|
|
447
|
+
self.count += 1
|
|
448
|
+
i = self.count
|
|
449
|
+
|
|
450
|
+
try:
|
|
451
|
+
with _InitDBConnection(self.strCredentialSet) as con:
|
|
452
|
+
try:
|
|
453
|
+
exQ = con.execute(text(strQuery))
|
|
454
|
+
lstResult = exQ.fetchall()
|
|
455
|
+
if not colNames:
|
|
456
|
+
colNames = exQ.keys()
|
|
457
|
+
LOG.info("(Processed Threaded Query %s on thread %s)", i, threading.current_thread().name)
|
|
458
|
+
except Exception:
|
|
459
|
+
LOG.exception(f"Failed Query. LastQuery: {strQuery}")
|
|
460
|
+
booError = True
|
|
461
|
+
except Exception as e:
|
|
462
|
+
LOG.info("LastQuery: %s", strQuery)
|
|
463
|
+
raise ValueError(f"Failed to execute threaded query: {e}") from e
|
|
464
|
+
else:
|
|
465
|
+
return lstResult, colNames, booError
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def RunThreadedRetrievalQueryList(strCredentialSet, lstQueries, intMaxThreads, spinner):
|
|
469
|
+
LOG.info("CurrentDB Operation: RunThreadedRetrievalQueryList. Creds: %s", strCredentialSet)
|
|
470
|
+
|
|
471
|
+
lstResults = []
|
|
472
|
+
colNames = []
|
|
473
|
+
intErrors = 0
|
|
474
|
+
|
|
475
|
+
if intMaxThreads is None:
|
|
476
|
+
intMaxThreads = 4
|
|
477
|
+
elif intMaxThreads < 1 or intMaxThreads > 10:
|
|
478
|
+
intMaxThreads = 4
|
|
479
|
+
|
|
480
|
+
qq = qu.Queue()
|
|
481
|
+
|
|
482
|
+
for query in lstQueries:
|
|
483
|
+
qq.put(query)
|
|
484
|
+
|
|
485
|
+
# Initialize count and lock
|
|
486
|
+
count_lock = threading.Lock()
|
|
487
|
+
|
|
488
|
+
clsThreadedFetch = _CThreadedFetch(strCredentialSet, count_lock)
|
|
489
|
+
|
|
490
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=intMaxThreads) as executor:
|
|
491
|
+
try:
|
|
492
|
+
futures = []
|
|
493
|
+
while not qq.empty():
|
|
494
|
+
query = qq.get()
|
|
495
|
+
futures.append(executor.submit(clsThreadedFetch, query))
|
|
496
|
+
|
|
497
|
+
for future in futures:
|
|
498
|
+
lstOneResult, colName, booError = future.result()
|
|
499
|
+
if spinner:
|
|
500
|
+
spinner.next()
|
|
501
|
+
intErrors += 1 if booError else 0
|
|
502
|
+
if lstOneResult:
|
|
503
|
+
lstResults.append(lstOneResult)
|
|
504
|
+
colNames = colName
|
|
505
|
+
|
|
506
|
+
except Exception:
|
|
507
|
+
LOG.exception("Failed to execute threaded queries")
|
|
508
|
+
|
|
509
|
+
lstResults = [element for sublist in lstResults for element in sublist]
|
|
510
|
+
|
|
511
|
+
return lstResults, colNames, intErrors
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def RetrieveDBResultsToList(strCredentialSet, strRunSQL):
|
|
515
|
+
LOG.info("CurrentDB Operation: RetrieveDBResultsToList. Creds: %s", strCredentialSet)
|
|
516
|
+
|
|
517
|
+
with _InitDBConnection(strCredentialSet) as con:
|
|
518
|
+
exQ = con.execute(text(strRunSQL))
|
|
519
|
+
lstResults = exQ.fetchall()
|
|
520
|
+
colNames = exQ.keys()
|
|
521
|
+
|
|
522
|
+
LOG.debug("Last Query='%s'", strRunSQL)
|
|
523
|
+
LOG.debug("%s records retrieved.", exQ.rowcount)
|
|
524
|
+
|
|
525
|
+
return lstResults, colNames
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def RetrieveDBResultsToDictList(strCredentialSet, strRunSQL):
|
|
529
|
+
LOG.info("CurrentDB Operation: RetrieveDBResultsToDictList. Creds: %s", strCredentialSet)
|
|
530
|
+
LOG.info("(Processing Query)")
|
|
531
|
+
|
|
532
|
+
with _InitDBConnection(strCredentialSet) as con:
|
|
533
|
+
LOG.debug("Last Query='%s'", strRunSQL)
|
|
534
|
+
exQ = con.execute(text(strRunSQL))
|
|
535
|
+
|
|
536
|
+
# Creates list of dictionaries so records are addressible by column name
|
|
537
|
+
lstResults = [row._mapping for row in exQ]
|
|
538
|
+
LOG.debug("%s records retrieved.", exQ.rowcount)
|
|
539
|
+
|
|
540
|
+
return lstResults
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def ExecuteDBQuery(strCredentialSet, strRunSQL):
|
|
544
|
+
LOG.info("CurrentDB Operation: ExecuteDBQuery. Creds: %s", strCredentialSet)
|
|
545
|
+
LOG.info("(Processing Query)")
|
|
546
|
+
|
|
547
|
+
with _InitDBConnection(strCredentialSet) as con:
|
|
548
|
+
LOG.debug("Last Query='%s'", strRunSQL)
|
|
549
|
+
con.execute(text(strRunSQL))
|
|
550
|
+
con.execute("commit")
|
|
551
|
+
LOG.debug("Query ran.")
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def RetrieveSingleResultValue(strCredentialSet, strRunSQL):
|
|
555
|
+
LOG.info("CurrentDB Operation: RetrieveSingleResultValue. Creds: %s", strCredentialSet)
|
|
556
|
+
|
|
557
|
+
with _InitDBConnection(strCredentialSet) as con:
|
|
558
|
+
LOG.debug("Last Query='%s'", strRunSQL)
|
|
559
|
+
lstResult = con.execute(text(strRunSQL)).fetchone()
|
|
560
|
+
if lstResult:
|
|
561
|
+
LOG.debug("Single result retrieved.")
|
|
562
|
+
valReturn = lstResult[0]
|
|
563
|
+
return valReturn
|
|
564
|
+
else:
|
|
565
|
+
LOG.debug("Single result NOT retrieved.")
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def WriteListToDB(strCredentialSet, lstData, lstColumns, strDBTable):
|
|
569
|
+
LOG.info("CurrentDB Operation: WriteListToDB. Creds: %s", strCredentialSet)
|
|
570
|
+
LOG.debug("(Processing ingestion query: %s records)", lstData)
|
|
571
|
+
# List should have same column names as destination table, though not all columns in table are required
|
|
572
|
+
|
|
573
|
+
# Use COPY for DKTG database, otherwise executemany()
|
|
574
|
+
|
|
575
|
+
con = _InitDBConnection(strCredentialSet, "Y")
|
|
576
|
+
cur = con.cursor()
|
|
577
|
+
if strCredentialSet == "DKTG":
|
|
578
|
+
# Write List to CSV in memory
|
|
579
|
+
sio = StringIO()
|
|
580
|
+
writer = csv.writer(sio, quoting=csv.QUOTE_MINIMAL)
|
|
581
|
+
writer.writerows(lstData)
|
|
582
|
+
sio.seek(0)
|
|
583
|
+
|
|
584
|
+
# Get list of column names for COPY statement
|
|
585
|
+
strColumnNames = ", ".join(lstColumns)
|
|
586
|
+
strCopySQL = f"COPY {strDBTable} ({strColumnNames}) FROM STDIN WITH (FORMAT CSV)"
|
|
587
|
+
LOG.debug("Last Query='%s'", strCopySQL)
|
|
588
|
+
|
|
589
|
+
cur.copy_expert(strCopySQL, sio)
|
|
590
|
+
con.commit()
|
|
591
|
+
|
|
592
|
+
else:
|
|
593
|
+
# Get list of column names and column names formatted as parms
|
|
594
|
+
strColumnNames = ", ".join(lstColumns)
|
|
595
|
+
lstColumnParms = [":" + column_name for column_name in lstColumns]
|
|
596
|
+
strColumnParms = ", ".join(lstColumnParms)
|
|
597
|
+
|
|
598
|
+
# Prep data as list of dictionaries
|
|
599
|
+
lstRowDicts = [dict(row) for row in lstData]
|
|
600
|
+
|
|
601
|
+
strInsertSQL = "INSERT INTO " + strDBTable + "(" + strColumnNames + ")" + " VALUES (" + strColumnParms + ")"
|
|
602
|
+
LOG.debug("Last Query='%s'", strInsertSQL)
|
|
603
|
+
|
|
604
|
+
exQ = con.execute(text(strInsertSQL), lstRowDicts)
|
|
605
|
+
con.commit()
|
|
606
|
+
LOG.debug("%s records saved", exQ.rowcount)
|
|
607
|
+
con.close()
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def replace_params(query: str, params_mapping: dict) -> str:
|
|
611
|
+
for key, value in params_mapping.items():
|
|
612
|
+
query = query.replace(f"{{{key}}}", str(value))
|
|
613
|
+
return query
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def get_queries_for_command(sub_directory: str, params_mapping: dict, mask: str = r"^.*sql$", path: str | None = None) -> list[str]:
|
|
617
|
+
files = sorted(get_template_files(mask=mask, sub_directory=sub_directory, path=path), key=lambda key: str(key))
|
|
618
|
+
|
|
619
|
+
queries = []
|
|
620
|
+
for file in files:
|
|
621
|
+
query = file.read_text("utf-8")
|
|
622
|
+
template = replace_params(query, params_mapping)
|
|
623
|
+
|
|
624
|
+
queries.append(template)
|
|
625
|
+
|
|
626
|
+
if len(queries) == 0:
|
|
627
|
+
LOG.warning(f"No sql files were found for the mask {mask} in subdirectory {sub_directory}")
|
|
628
|
+
|
|
629
|
+
return queries
|
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
|
|
3
|
+
from testgen import settings
|
|
4
|
+
from testgen.common.encrypt import DecryptText
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FlavorService:
|
|
8
|
+
|
|
9
|
+
url = None
|
|
10
|
+
connect_by_url = None
|
|
11
|
+
username = None
|
|
12
|
+
host = None
|
|
13
|
+
port = None
|
|
14
|
+
dbname = None
|
|
15
|
+
flavor = None
|
|
16
|
+
dbschema = None
|
|
17
|
+
connect_by_key = None
|
|
18
|
+
private_key = None
|
|
19
|
+
private_key_passphrase = None
|
|
20
|
+
catalog = None
|
|
21
|
+
|
|
22
|
+
def init(self, connection_params: dict):
|
|
23
|
+
self.url = connection_params.get("url", None)
|
|
24
|
+
self.connect_by_url = connection_params.get("connect_by_url", False)
|
|
25
|
+
self.username = connection_params.get("user")
|
|
26
|
+
self.host = connection_params.get("host")
|
|
27
|
+
self.port = connection_params.get("port")
|
|
28
|
+
self.dbname = connection_params.get("dbname")
|
|
29
|
+
self.flavor = connection_params.get("flavor")
|
|
30
|
+
self.dbschema = connection_params.get("dbschema", None)
|
|
31
|
+
self.connect_by_key = connection_params.get("connect_by_key", False)
|
|
32
|
+
self.catalog = connection_params.get("catalog", None)
|
|
33
|
+
|
|
34
|
+
private_key = connection_params.get("private_key", None)
|
|
35
|
+
if isinstance(private_key, memoryview):
|
|
36
|
+
private_key = DecryptText(private_key)
|
|
37
|
+
self.private_key = private_key
|
|
38
|
+
|
|
39
|
+
private_key_passphrase = connection_params.get("private_key_passphrase", None)
|
|
40
|
+
if isinstance(private_key_passphrase, memoryview):
|
|
41
|
+
private_key_passphrase = DecryptText(private_key_passphrase)
|
|
42
|
+
self.private_key_passphrase = private_key_passphrase
|
|
43
|
+
|
|
44
|
+
def override_user(self, user_override: str):
|
|
45
|
+
self.username = user_override
|
|
46
|
+
|
|
47
|
+
def get_db_name(self) -> str:
|
|
48
|
+
return self.dbname
|
|
49
|
+
|
|
50
|
+
def is_connect_by_key(self) -> str:
|
|
51
|
+
return self.connect_by_key
|
|
52
|
+
|
|
53
|
+
def get_connect_args(self, is_password_overwritten: bool = False): # NOQA ARG002
|
|
54
|
+
if settings.SKIP_DATABASE_CERTIFICATE_VERIFICATION:
|
|
55
|
+
return {"TrustServerCertificate": "yes"}
|
|
56
|
+
return {}
|
|
57
|
+
|
|
58
|
+
def get_concat_operator(self):
|
|
59
|
+
return "||"
|
|
60
|
+
|
|
61
|
+
def get_connection_string(self, strPW, is_password_overwritten: bool = False):
|
|
62
|
+
if self.connect_by_url:
|
|
63
|
+
header = self.get_connection_string_head(strPW)
|
|
64
|
+
url = header + self.url
|
|
65
|
+
return url
|
|
66
|
+
else:
|
|
67
|
+
return self.get_connection_string_from_fields(strPW, is_password_overwritten)
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def get_connection_string_from_fields(self, strPW, is_password_overwritten: bool = False):
|
|
71
|
+
raise NotImplementedError("Subclasses must implement this method")
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def get_connection_string_head(self, strPW):
|
|
75
|
+
raise NotImplementedError("Subclasses must implement this method")
|