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.
Files changed (270) hide show
  1. dataops_testgen-2.2.0.dist-info/LICENSE +203 -0
  2. dataops_testgen-2.2.0.dist-info/METADATA +287 -0
  3. dataops_testgen-2.2.0.dist-info/NOTICE +5 -0
  4. dataops_testgen-2.2.0.dist-info/RECORD +270 -0
  5. dataops_testgen-2.2.0.dist-info/WHEEL +5 -0
  6. dataops_testgen-2.2.0.dist-info/entry_points.txt +2 -0
  7. dataops_testgen-2.2.0.dist-info/top_level.txt +1 -0
  8. testgen/__init__.py +0 -0
  9. testgen/__main__.py +770 -0
  10. testgen/commands/__init__.py +0 -0
  11. testgen/commands/queries/__init__.py +0 -0
  12. testgen/commands/queries/execute_cat_tests_query.py +95 -0
  13. testgen/commands/queries/execute_tests_query.py +160 -0
  14. testgen/commands/queries/generate_tests_query.py +94 -0
  15. testgen/commands/queries/profiling_query.py +366 -0
  16. testgen/commands/queries/test_parameter_validation_query.py +88 -0
  17. testgen/commands/run_execute_cat_tests.py +162 -0
  18. testgen/commands/run_execute_tests.py +168 -0
  19. testgen/commands/run_generate_tests.py +107 -0
  20. testgen/commands/run_get_entities.py +122 -0
  21. testgen/commands/run_launch_db_config.py +84 -0
  22. testgen/commands/run_observability_exporter.py +330 -0
  23. testgen/commands/run_profiling_bridge.py +495 -0
  24. testgen/commands/run_quick_start.py +168 -0
  25. testgen/commands/run_setup_profiling_tools.py +96 -0
  26. testgen/commands/run_test_definition.py +146 -0
  27. testgen/commands/run_test_parameter_validation.py +135 -0
  28. testgen/commands/run_upgrade_db_config.py +156 -0
  29. testgen/common/__init__.py +8 -0
  30. testgen/common/clean_sql.py +53 -0
  31. testgen/common/credentials.py +25 -0
  32. testgen/common/database/__init__.py +0 -0
  33. testgen/common/database/database_service.py +629 -0
  34. testgen/common/database/flavor/__init__.py +0 -0
  35. testgen/common/database/flavor/flavor_service.py +75 -0
  36. testgen/common/database/flavor/mssql_flavor_service.py +34 -0
  37. testgen/common/database/flavor/postgresql_flavor_service.py +5 -0
  38. testgen/common/database/flavor/redshift_flavor_service.py +22 -0
  39. testgen/common/database/flavor/snowflake_flavor_service.py +69 -0
  40. testgen/common/database/flavor/trino_flavor_service.py +21 -0
  41. testgen/common/date_service.py +68 -0
  42. testgen/common/display_service.py +85 -0
  43. testgen/common/docker_service.py +76 -0
  44. testgen/common/encrypt.py +55 -0
  45. testgen/common/get_pipeline_parms.py +57 -0
  46. testgen/common/logs.py +79 -0
  47. testgen/common/process_service.py +62 -0
  48. testgen/common/read_file.py +69 -0
  49. testgen/settings.py +440 -0
  50. testgen/template/dbsetup/010_create_base_schema.sql +2 -0
  51. testgen/template/dbsetup/020_create_standard_functions_sprocs.sql +179 -0
  52. testgen/template/dbsetup/030_initialize_new_schema_structure.sql +735 -0
  53. testgen/template/dbsetup/040_populate_new_schema_project.sql +59 -0
  54. testgen/template/dbsetup/050_populate_new_schema_metadata.sql +1517 -0
  55. testgen/template/dbsetup/060_create_standard_views.sql +248 -0
  56. testgen/template/dbsetup/070_create_default_users.sql +17 -0
  57. testgen/template/dbsetup/075_grant_role_rights.sql +43 -0
  58. testgen/template/dbsetup/080_set_current_revision.sql +5 -0
  59. testgen/template/dbupgrade/0100_incremental_upgrade.sql +5 -0
  60. testgen/template/dbupgrade/0101_incremental_upgrade.sql +15 -0
  61. testgen/template/dbupgrade/0102_incremental_upgrade.sql +4 -0
  62. testgen/template/dbupgrade/0103_incremental_upgrade.sql +22 -0
  63. testgen/template/dbupgrade/0104_incremental_upgrade.sql +44 -0
  64. testgen/template/dbupgrade/0105_incremental_upgrade.sql +1 -0
  65. testgen/template/dbupgrade/0106_incremental_upgrade.sql +5 -0
  66. testgen/template/dbupgrade/0107_incremental_upgrade.sql +3 -0
  67. testgen/template/dbupgrade_helpers/get_tg_revision.sql +2 -0
  68. testgen/template/exec_cat_tests/ex_cat_build_agg_table_tests.sql +116 -0
  69. testgen/template/exec_cat_tests/ex_cat_get_distinct_tables.sql +11 -0
  70. testgen/template/exec_cat_tests/ex_cat_results_parse.sql +69 -0
  71. testgen/template/exec_cat_tests/ex_cat_retrieve_agg_test_parms.sql +6 -0
  72. testgen/template/exec_cat_tests/ex_cat_test_query.sql +8 -0
  73. testgen/template/execution/ex_finalize_test_run_results.sql +37 -0
  74. testgen/template/execution/ex_get_tests_non_cat.sql +47 -0
  75. testgen/template/execution/ex_update_test_record_in_testrun_table.sql +27 -0
  76. testgen/template/execution/ex_write_test_record_to_testrun_table.sql +6 -0
  77. testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_no_drops_generic.sql +48 -0
  78. testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_num_incr_generic.sql +34 -0
  79. testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_percent_above_generic.sql +49 -0
  80. testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_percent_within_generic.sql +49 -0
  81. testgen/template/flavors/generic/exec_query_tests/ex_aggregate_match_same_generic.sql +49 -0
  82. testgen/template/flavors/generic/exec_query_tests/ex_custom_query_generic.sql +39 -0
  83. testgen/template/flavors/generic/exec_query_tests/ex_data_match_2way_generic.sql +58 -0
  84. testgen/template/flavors/generic/exec_query_tests/ex_data_match_generic.sql +44 -0
  85. testgen/template/flavors/generic/exec_query_tests/ex_prior_match_generic.sql +37 -0
  86. testgen/template/flavors/generic/exec_query_tests/ex_relative_entropy_generic.sql +53 -0
  87. testgen/template/flavors/generic/exec_query_tests/ex_window_match_no_drops_generic.sql +46 -0
  88. testgen/template/flavors/generic/exec_query_tests/ex_window_match_same_generic.sql +59 -0
  89. testgen/template/flavors/generic/profiling/contingency_counts.sql +3 -0
  90. testgen/template/flavors/generic/validate_tests/ex_get_project_column_list_generic.sql +3 -0
  91. testgen/template/flavors/mssql/exec_query_tests/ex_relative_entropy_mssql.sql +53 -0
  92. testgen/template/flavors/mssql/profiling/project_ddf_query_mssql.sql +35 -0
  93. testgen/template/flavors/mssql/profiling/project_profiling_query_mssql.yaml +246 -0
  94. testgen/template/flavors/mssql/profiling/project_secondary_profiling_query_mssql.sql +36 -0
  95. testgen/template/flavors/mssql/setup_profiling_tools/00_drop_existing_functions_mssql.sql +8 -0
  96. testgen/template/flavors/mssql/setup_profiling_tools/01_create_functions_mssql.sql +12 -0
  97. testgen/template/flavors/mssql/setup_profiling_tools/02_create_functions_mssql.sql +54 -0
  98. testgen/template/flavors/mssql/setup_profiling_tools/create_qc_schema_mssql.sql +4 -0
  99. testgen/template/flavors/mssql/setup_profiling_tools/grant_execute_privileges_mssql.sql +1 -0
  100. testgen/template/flavors/postgresql/exec_query_tests/ex_window_match_no_drops_postgresql.sql +46 -0
  101. testgen/template/flavors/postgresql/exec_query_tests/ex_window_match_same_postgresql.sql +59 -0
  102. testgen/template/flavors/postgresql/profiling/project_ddf_query_postgresql.sql +42 -0
  103. testgen/template/flavors/postgresql/profiling/project_profiling_query_postgresql.yaml +225 -0
  104. testgen/template/flavors/postgresql/profiling/project_secondary_profiling_query_postgresql.sql +28 -0
  105. testgen/template/flavors/postgresql/setup_profiling_tools/create_functions_postgresql.sql +157 -0
  106. testgen/template/flavors/postgresql/setup_profiling_tools/create_qc_schema_postgresql.sql +1 -0
  107. testgen/template/flavors/postgresql/setup_profiling_tools/grant_execute_privileges_postgresql.sql +2 -0
  108. testgen/template/flavors/redshift/profiling/project_ddf_query_redshift.sql +38 -0
  109. testgen/template/flavors/redshift/profiling/project_profiling_query_redshift.yaml +221 -0
  110. testgen/template/flavors/redshift/profiling/project_secondary_profiling_query_redshift.sql +29 -0
  111. testgen/template/flavors/redshift/setup_profiling_tools/create_functions_redshift.sql +115 -0
  112. testgen/template/flavors/redshift/setup_profiling_tools/create_qc_schema_redshift.sql +1 -0
  113. testgen/template/flavors/redshift/setup_profiling_tools/grant_execute_privileges_redshift.sql +2 -0
  114. testgen/template/flavors/snowflake/profiling/project_ddf_query_snowflake.sql +38 -0
  115. testgen/template/flavors/snowflake/profiling/project_profiling_query_snowflake.yaml +220 -0
  116. testgen/template/flavors/snowflake/profiling/project_secondary_profiling_query_snowflake.sql +29 -0
  117. testgen/template/flavors/snowflake/setup_profiling_tools/create_functions_snowflake.sql +69 -0
  118. testgen/template/flavors/snowflake/setup_profiling_tools/create_qc_schema_snowflake.sql +1 -0
  119. testgen/template/flavors/snowflake/setup_profiling_tools/grant_execute_privileges_snowflake.sql +6 -0
  120. testgen/template/flavors/trino/profiling/project_profiling_query_trino.yaml +219 -0
  121. testgen/template/flavors/trino/setup_profiling_tools/create_functions_trino.sql +92 -0
  122. testgen/template/flavors/trino/setup_profiling_tools/create_qc_schema_trino.sql +1 -0
  123. testgen/template/gen_funny_cat_tests/gen_test_constant.sql +104 -0
  124. testgen/template/gen_funny_cat_tests/gen_test_distinct_value_ct.sql +98 -0
  125. testgen/template/gen_funny_cat_tests/gen_test_row_ct.sql +57 -0
  126. testgen/template/gen_funny_cat_tests/gen_test_row_ct_pct.sql +59 -0
  127. testgen/template/generation/gen_delete_old_tests.sql +5 -0
  128. testgen/template/generation/gen_insert_test_suite.sql +5 -0
  129. testgen/template/generation/gen_retrieve_or_insert_test_suite.sql +58 -0
  130. testgen/template/generation/gen_standard_test_type_list.sql +13 -0
  131. testgen/template/generation/gen_standard_tests.sql +48 -0
  132. testgen/template/get_entities/get_connection.sql +21 -0
  133. testgen/template/get_entities/get_connections_list.sql +9 -0
  134. testgen/template/get_entities/get_latest.sql +4 -0
  135. testgen/template/get_entities/get_profile.sql +12 -0
  136. testgen/template/get_entities/get_profile_info.sql +17 -0
  137. testgen/template/get_entities/get_profile_list.sql +17 -0
  138. testgen/template/get_entities/get_profile_screen.sql +275 -0
  139. testgen/template/get_entities/get_project_list.sql +6 -0
  140. testgen/template/get_entities/get_table_group_list.sql +10 -0
  141. testgen/template/get_entities/get_test_generation_list.sql +18 -0
  142. testgen/template/get_entities/get_test_info.sql +41 -0
  143. testgen/template/get_entities/get_test_results_for_run_cli.sql +16 -0
  144. testgen/template/get_entities/get_test_run_list.sql +24 -0
  145. testgen/template/get_entities/get_test_suite.sql +13 -0
  146. testgen/template/get_entities/get_test_suite_list.sql +18 -0
  147. testgen/template/get_entities/list_test_types.sql +4 -0
  148. testgen/template/observability/get_event_data.sql +23 -0
  149. testgen/template/observability/get_test_results.sql +41 -0
  150. testgen/template/observability/update_test_results_exported_to_observability.sql +12 -0
  151. testgen/template/parms/parms_profiling.sql +34 -0
  152. testgen/template/parms/parms_test_execution.sql +13 -0
  153. testgen/template/parms/parms_test_gen.sql +23 -0
  154. testgen/template/profiling/contingency_columns.sql +7 -0
  155. testgen/template/profiling/datatype_suggestions.sql +56 -0
  156. testgen/template/profiling/functional_datatype.sql +523 -0
  157. testgen/template/profiling/functional_tabletype_stage.sql +48 -0
  158. testgen/template/profiling/functional_tabletype_update.sql +8 -0
  159. testgen/template/profiling/pii_flag.sql +133 -0
  160. testgen/template/profiling/profile_anomalies_screen_column.sql +22 -0
  161. testgen/template/profiling/profile_anomalies_screen_multi_column.sql +58 -0
  162. testgen/template/profiling/profile_anomalies_screen_table.sql +22 -0
  163. testgen/template/profiling/profile_anomalies_screen_table_dates.sql +30 -0
  164. testgen/template/profiling/profile_anomalies_screen_variants.sql +40 -0
  165. testgen/template/profiling/profile_anomaly_types_get.sql +3 -0
  166. testgen/template/profiling/project_get_table_sample_count.sql +22 -0
  167. testgen/template/profiling/project_profile_run_record_insert.sql +8 -0
  168. testgen/template/profiling/project_profile_run_record_update.sql +5 -0
  169. testgen/template/profiling/project_profile_run_record_update_status.sql +5 -0
  170. testgen/template/profiling/project_update_profile_results_to_estimates.sql +32 -0
  171. testgen/template/profiling/refresh_anomalies.sql +33 -0
  172. testgen/template/profiling/refresh_data_chars_from_profiling.sql +156 -0
  173. testgen/template/profiling/secondary_profiling_columns.sql +12 -0
  174. testgen/template/profiling/secondary_profiling_delete.sql +4 -0
  175. testgen/template/profiling/secondary_profiling_update.sql +18 -0
  176. testgen/template/quick_start/populate_target_data.sql +1077 -0
  177. testgen/template/quick_start/recreate_target_data_schema.sql +167 -0
  178. testgen/template/quick_start/update_target_data.sql +100 -0
  179. testgen/template/updates/create_tmp_test_definition.sql +19 -0
  180. testgen/template/updates/get_test_def_parms.sql +38 -0
  181. testgen/template/updates/populate_stg_test_definitions.sql +184 -0
  182. testgen/template/validate_tests/ex_disable_tests_test_definitions.sql +5 -0
  183. testgen/template/validate_tests/ex_flag_tests_test_definitions.sql +64 -0
  184. testgen/template/validate_tests/ex_get_project_column_list_generic.sql +3 -0
  185. testgen/template/validate_tests/ex_get_test_column_list_tg.sql +65 -0
  186. testgen/template/validate_tests/ex_write_test_val_errors.sql +22 -0
  187. testgen/ui/__init__.py +0 -0
  188. testgen/ui/app.py +98 -0
  189. testgen/ui/assets/dk_logo.svg +46 -0
  190. testgen/ui/assets/question_mark.png +0 -0
  191. testgen/ui/assets/scripts.js +68 -0
  192. testgen/ui/assets/style.css +140 -0
  193. testgen/ui/bootstrap.py +109 -0
  194. testgen/ui/components/__init__.py +0 -0
  195. testgen/ui/components/frontend/css/KFOlCnqEu92Fr1MmEU9fBBc4.woff2 +0 -0
  196. testgen/ui/components/frontend/css/KFOlCnqEu92Fr1MmEU9fChc4EsA.woff2 +0 -0
  197. testgen/ui/components/frontend/css/KFOmCnqEu92Fr1Mu4mxK.woff2 +0 -0
  198. testgen/ui/components/frontend/css/KFOmCnqEu92Fr1Mu7GxKOzY.woff2 +0 -0
  199. testgen/ui/components/frontend/css/material-symbols-rounded.css +24 -0
  200. testgen/ui/components/frontend/css/material-symbols-rounded.woff2 +0 -0
  201. testgen/ui/components/frontend/css/roboto-font-faces.css +35 -0
  202. testgen/ui/components/frontend/css/shared.css +36 -0
  203. testgen/ui/components/frontend/img/dk_logo.svg +46 -0
  204. testgen/ui/components/frontend/index.html +17 -0
  205. testgen/ui/components/frontend/js/components/breadcrumbs.js +86 -0
  206. testgen/ui/components/frontend/js/components/button.js +66 -0
  207. testgen/ui/components/frontend/js/components/location.js +62 -0
  208. testgen/ui/components/frontend/js/components/select.js +75 -0
  209. testgen/ui/components/frontend/js/components/sidebar.js +358 -0
  210. testgen/ui/components/frontend/js/main.js +99 -0
  211. testgen/ui/components/frontend/js/streamlit.js +19 -0
  212. testgen/ui/components/frontend/js/van.min.js +1 -0
  213. testgen/ui/components/utils/__init__.py +0 -0
  214. testgen/ui/components/utils/callbacks.py +51 -0
  215. testgen/ui/components/utils/component.py +13 -0
  216. testgen/ui/components/widgets/__init__.py +6 -0
  217. testgen/ui/components/widgets/breadcrumbs.py +32 -0
  218. testgen/ui/components/widgets/location.py +65 -0
  219. testgen/ui/components/widgets/modal.py +97 -0
  220. testgen/ui/components/widgets/sidebar.py +69 -0
  221. testgen/ui/navigation/__init__.py +0 -0
  222. testgen/ui/navigation/menu.py +42 -0
  223. testgen/ui/navigation/page.py +20 -0
  224. testgen/ui/navigation/router.py +63 -0
  225. testgen/ui/queries/__init__.py +0 -0
  226. testgen/ui/queries/authentication_queries.py +47 -0
  227. testgen/ui/queries/connection_queries.py +121 -0
  228. testgen/ui/queries/profiling_queries.py +148 -0
  229. testgen/ui/queries/project_queries.py +9 -0
  230. testgen/ui/queries/table_group_queries.py +186 -0
  231. testgen/ui/queries/test_definition_queries.py +270 -0
  232. testgen/ui/queries/test_run_queries.py +32 -0
  233. testgen/ui/queries/test_suite_queries.py +145 -0
  234. testgen/ui/scripts/__init__.py +0 -0
  235. testgen/ui/scripts/patch_streamlit.py +111 -0
  236. testgen/ui/services/__init__.py +0 -0
  237. testgen/ui/services/authentication_service.py +119 -0
  238. testgen/ui/services/connection_service.py +220 -0
  239. testgen/ui/services/database_service.py +282 -0
  240. testgen/ui/services/form_service.py +1008 -0
  241. testgen/ui/services/javascript_service.py +44 -0
  242. testgen/ui/services/query_service.py +316 -0
  243. testgen/ui/services/string_service.py +12 -0
  244. testgen/ui/services/table_group_service.py +130 -0
  245. testgen/ui/services/test_definition_service.py +117 -0
  246. testgen/ui/services/test_run_service.py +13 -0
  247. testgen/ui/services/test_suite_service.py +76 -0
  248. testgen/ui/services/toolbar_service.py +77 -0
  249. testgen/ui/session.py +46 -0
  250. testgen/ui/views/__init__.py +0 -0
  251. testgen/ui/views/app_log_modal.py +92 -0
  252. testgen/ui/views/connections.py +72 -0
  253. testgen/ui/views/connections_base.py +367 -0
  254. testgen/ui/views/login.py +40 -0
  255. testgen/ui/views/not_found.py +16 -0
  256. testgen/ui/views/overview.py +34 -0
  257. testgen/ui/views/profiling_anomalies.py +501 -0
  258. testgen/ui/views/profiling_details.py +335 -0
  259. testgen/ui/views/profiling_modal.py +40 -0
  260. testgen/ui/views/profiling_results.py +206 -0
  261. testgen/ui/views/profiling_summary.py +177 -0
  262. testgen/ui/views/project_settings.py +74 -0
  263. testgen/ui/views/table_groups.py +530 -0
  264. testgen/ui/views/test_definitions.py +1020 -0
  265. testgen/ui/views/test_results.py +908 -0
  266. testgen/ui/views/test_runs.py +195 -0
  267. testgen/ui/views/test_suites.py +545 -0
  268. testgen/utils/__init__.py +0 -0
  269. testgen/utils/plugins.py +17 -0
  270. testgen/utils/singleton.py +14 -0
@@ -0,0 +1,69 @@
1
+ __all__ = ["get_template_files", "read_template_sql_file", "read_template_yaml_file"]
2
+
3
+ import logging
4
+ import re
5
+ from collections.abc import Generator
6
+ from functools import cache
7
+ from importlib.abc import Traversable
8
+ from importlib.resources import as_file, files
9
+
10
+ import yaml
11
+
12
+ LOG = logging.getLogger("testgen")
13
+
14
+
15
+ def _get_template_package_resource(
16
+ template_file_name: str | None = None,
17
+ sub_directory: str | None = None,
18
+ path: str | None = None,
19
+ ) -> Traversable:
20
+ if path is None:
21
+ path = "testgen.template"
22
+ if sub_directory:
23
+ path = f"{path}.{sub_directory.replace('/', '.')}"
24
+ if template_file_name:
25
+ return files(path).joinpath(template_file_name)
26
+ else:
27
+ return files(path)
28
+
29
+
30
+ @cache
31
+ def read_template_sql_file(template_file_name: str, sub_directory: str | None = None) -> str:
32
+ file = _get_template_package_resource(template_file_name, sub_directory)
33
+ LOG.debug("Reading SQL resource: %s", str(file))
34
+ try:
35
+ contents = file.read_text(encoding="utf-8").strip()
36
+ except FileNotFoundError as e:
37
+ raise ValueError(f"template.{sub_directory}.{template_file_name}: File not found") from e
38
+
39
+ if not contents.strip():
40
+ raise ValueError(f"template.{sub_directory}.{template_file_name}: file is empty")
41
+
42
+ return contents
43
+
44
+
45
+ def get_template_files(mask: str, sub_directory: str | None = None, path: str | None = None) -> Generator[Traversable, None, None]:
46
+ folder = _get_template_package_resource(template_file_name=None, sub_directory=sub_directory, path=path)
47
+ LOG.debug("Reading SQL folder resource: %s", str(folder))
48
+ for entry in folder.iterdir():
49
+ if entry.is_file() and re.search(mask, str(entry)):
50
+ yield entry
51
+
52
+
53
+ @cache
54
+ def read_template_yaml_file(template_file_name: str, sub_directory: str | None = None) -> dict:
55
+ if not template_file_name.endswith(("yaml", "yml")):
56
+ raise ValueError(f"{template_file_name}: does not have a yaml/yml suffix; is it yaml?")
57
+ resource_file = _get_template_package_resource(template_file_name=template_file_name, sub_directory=sub_directory)
58
+ LOG.debug("Reading Yaml resource: %s", str(resource_file))
59
+
60
+ try:
61
+ with as_file(resource_file) as f:
62
+ with f.open("r") as file:
63
+ template = yaml.safe_load(file)
64
+ except FileNotFoundError as e:
65
+ raise ValueError(f"{template_file_name}: File not found") from e
66
+ if not template:
67
+ raise ValueError(f"{template_file_name}: File is empty")
68
+
69
+ return template
testgen/settings.py ADDED
@@ -0,0 +1,440 @@
1
+ import os
2
+
3
+ IS_DEBUG_LOG_LEVEL: bool = os.getenv("TESTGEN_DEBUG_LOG_LEVEL", "no").lower() == "yes"
4
+ """
5
+ When set, logs will be at debug level.
6
+ defaults to: `no`
7
+ """
8
+
9
+ IS_DEBUG: bool = os.getenv("TESTGEN_DEBUG", "no").lower() == "yes"
10
+ """
11
+ When True invalidates the cache with the bootstrapped application
12
+ causing the changes to the routing and plugins to take effect on every
13
+ render.
14
+
15
+ from env variable: `TESTGEN_DEBUG`
16
+ defaults to: `True`
17
+ """
18
+
19
+ LOG_TO_FILE: bool = os.getenv("TESTGEN_LOG_TO_FILE", "yes").lower() == "yes"
20
+ """
21
+ When set, rotating file logs will be generated.
22
+ defaults to: `True`
23
+ """
24
+
25
+ LOG_FILE_PATH: str = os.getenv("TESTGEN_LOG_FILE_PATH", "/var/lib/testgen/log")
26
+ """
27
+ When set, rotating file logs will be generated under this path.
28
+
29
+ """
30
+
31
+ LOG_FILE_MAX_QTY: str = os.getenv("TESTGEN_LOG_FILE_MAX_QTY", "90")
32
+ """
33
+ Maximum log files to keep, defaults to 90 days (one file per day).
34
+ """
35
+
36
+ APP_ENCRYPTION_SALT: str = os.getenv("TG_DECRYPT_SALT")
37
+ """
38
+ Salt used to encrypt and decrypt user secrets. Only allows ascii
39
+ characters.
40
+
41
+ A minimun length of 16 characters is recommended.
42
+
43
+ from env variable: `TG_DECRYPT_SALT`
44
+ """
45
+
46
+ APP_ENCRYPTION_SECRET: str = os.getenv("TG_DECRYPT_PASSWORD")
47
+ """
48
+ Secret passcode used in combination with `APP_ENCRYPTION_SALT` to
49
+ encrypt and decrypt user secrets. Only allows ascii characters.
50
+
51
+ from env variable: `TG_DECRYPT_PASSWORD`
52
+ """
53
+
54
+ USERNAME: str = os.getenv("TESTGEN_USERNAME")
55
+ """
56
+ Username to log into the web application
57
+
58
+ from env variable: `TESTGEN_USERNAME`
59
+ """
60
+
61
+ PASSWORD: str = os.getenv("TESTGEN_PASSWORD")
62
+ """
63
+ Password to log into the web application
64
+
65
+ from env variable: `TESTGEN_PASSWORD`
66
+ """
67
+
68
+ DATABASE_USER: str = os.getenv("TG_METADATA_DB_USER", USERNAME)
69
+ """
70
+ User to connect to the testgen application postgres database.
71
+
72
+ from env variable: `TG_METADATA_DB_USER`
73
+ defaults to: `environ[USERNAME]`
74
+ """
75
+
76
+ DATABASE_PASSWORD: str = os.getenv("TG_METADATA_DB_PASSWORD", PASSWORD)
77
+ """
78
+ Password to connect to the testgen application postgres database.
79
+
80
+ from env variable: `TG_METADATA_DB_PASSWORD`
81
+ defaults to: `environ[PASSWORD]`
82
+ """
83
+
84
+ DATABASE_ADMIN_USER: str = os.getenv("DATABASE_ADMIN_USER", DATABASE_USER)
85
+ """
86
+ User with admin privileges in the testgen application postgres database
87
+ used to create roles, users, database and schema. Required if the user
88
+ in `TG_METADATA_DB_USER` does not have the required privileges.
89
+
90
+ from env variable: `DATABASE_ADMIN_USER`
91
+ defaults to: `environ[DATABASE_USER]`
92
+ """
93
+
94
+ DATABASE_ADMIN_PASSWORD: str = os.getenv("DATABASE_ADMIN_PASSWORD", DATABASE_PASSWORD)
95
+ """
96
+ Password for the admin user to connect to the testgen application
97
+ postgres database.
98
+
99
+ from env variable: `DATABASE_ADMIN_PASSWORD`
100
+ defaults to: `environ[DATABASE_PASSWORD]`
101
+ """
102
+
103
+ DATABASE_EXECUTE_USER: str = os.getenv("DATABASE_EXECUTE_USER", "testgen_execute")
104
+ """
105
+ User to be created into the testgen application postgres database. Will
106
+ be granted:
107
+
108
+ _ read/write to tables `test_results`, `test_suites` and `test_definitions`
109
+ _ read_only to all other tables
110
+
111
+ from env variable: `DATABASE_EXECUTE_USER`
112
+ defaults to: `testgen_execute`
113
+ """
114
+
115
+ DATABASE_REPORT_USER: str = os.getenv("DATABASE_REPORT_USER", "testgen_report")
116
+ """
117
+ User to be created into the testgen application postgres database. Will
118
+ be granted read_only access to all tables.
119
+
120
+ from env variable: `DATABASE_REPORT_USER`
121
+ defaults to: `testgen_report`
122
+ """
123
+
124
+ DATABASE_HOST: str = os.getenv("TG_METADATA_DB_HOST", "localhost")
125
+ """
126
+ Hostname where the testgen application postgres database is running in.
127
+
128
+ from env variable: `TG_METADATA_DB_HOST`
129
+ defaults to: `localhost`
130
+ """
131
+
132
+ DATABASE_PORT: str = os.getenv("TG_METADATA_DB_PORT", "5432")
133
+ """
134
+ Port at which the testgen application postgres database is exposed by
135
+ the host.
136
+
137
+ from env variable: `TG_METADATA_DB_PORT`
138
+ defaults to: `5432`
139
+ """
140
+
141
+ DATABASE_NAME: str = os.getenv("TG_METADATA_DB_NAME", "datakitchen")
142
+ """
143
+ Name of the database in postgres on which to store testgen metadata.
144
+
145
+ from env variable: `TG_METADATA_DB_NAME`
146
+ defaults to: `datakitchen`
147
+ """
148
+
149
+ DATABASE_SCHEMA: str = os.getenv("TG_METADATA_DB_SCHEMA", "testgen")
150
+ """
151
+ Name of the schema inside the postgres database on which to store
152
+ testgen metadata.
153
+
154
+ from env variable: `TG_METADATA_DB_SCHEMA`
155
+ defaults to: `testgen`
156
+ """
157
+
158
+ PROJECT_KEY: str = os.getenv("PROJECT_KEY", "DEFAULT")
159
+ """
160
+ Code used to uniquely identify the auto generated project.
161
+
162
+ from env variable: `PROJECT_KEY`
163
+ defaults to: `DEFAULT`
164
+ """
165
+
166
+ PROJECT_NAME: str = os.getenv("PROJECT_NAME", "Demo")
167
+ """
168
+ Name to assign to the auto generated project.
169
+
170
+ from env variable: `DEFAULT_PROJECT_NAME`
171
+ defaults to: `Demo`
172
+ """
173
+
174
+ PROJECT_SQL_FLAVOR: str = os.getenv("PROJECT_SQL_FLAVOR", "postgresql")
175
+ """
176
+ SQL flavor of the database the auto generated project will run tests
177
+ against.
178
+
179
+ Supported flavors are:
180
+ _ redshift
181
+ _ snowflake
182
+ _ mssql
183
+ _ postgresql
184
+
185
+ from env variable: `PROJECT_SQL_FLAVOR`
186
+ defaults to: `postgresql`
187
+ """
188
+
189
+ PROJECT_CONNECTION_NAME: str = os.getenv("PROJECT_CONNECTION_NAME", "default")
190
+ """
191
+ Name assigned to identify the connection to the project database.
192
+
193
+ from env variable: `PROJECT_CONNECTION_NAME`
194
+ defaults to: `default`
195
+ """
196
+
197
+ PROJECT_CONNECTION_MAX_THREADS: int = int(os.getenv("PROJECT_CONNECTION_MAX_THREADS", "4"))
198
+ """
199
+ Maximum number of concurrent queries executed when fetching data from
200
+ the project database.
201
+
202
+ from env variable: `PROJECT_CONNECTION_MAX_THREADS`
203
+ defaults to: `4`
204
+ """
205
+
206
+ PROJECT_CONNECTION_MAX_QUERY_CHAR: int = int(os.getenv("PROJECT_CONNECTION_MAX_QUERY_CHAR", "5000"))
207
+ """
208
+ Determine how many tests are grouped together in a single query.
209
+ Increase for better performance or decrease to better isolate test
210
+ failures. Accepted values are 500 to 14 000.
211
+
212
+ from env variable: `PROJECT_CONNECTION_MAX_QUERY_CHAR`
213
+ defaults to: `5000`
214
+ """
215
+
216
+ PROJECT_QC_SCHEMA: str = os.getenv("PROJECT_QC_SCHEMA", "qc")
217
+ """
218
+ Name of the schema to be created in the project database.
219
+
220
+ from env variable: `PROJECT_QC_SCHEMA`
221
+ defaults to: `qc`
222
+ """
223
+
224
+ PROJECT_DATABASE_NAME: str = os.getenv("PROJECT_DATABASE_NAME", "demo_db")
225
+ """
226
+ Name of the database the auto generated project will run test
227
+ against.
228
+
229
+ from env variable: `PROJECT_DATABASE_NAME`
230
+ defaults to: `demo_db`
231
+ """
232
+
233
+ PROJECT_DATABASE_SCHEMA: str = os.getenv("PROJECT_DATABASE_SCHEMA", "demo")
234
+ """
235
+ Name of the schema inside the project database the tests will be run
236
+ against.
237
+
238
+ from env variable: `PROJECT_DATABASE_SCHEMA`
239
+ defaults to: `demo`
240
+ """
241
+
242
+ PROJECT_DATABASE_USER: str = os.getenv("PROJECT_DATABASE_USER", DATABASE_USER)
243
+ """
244
+ User to be used by the auto generated project to connect to the database
245
+ under testing.
246
+
247
+ from env variable: `PROJECT_DATABASE_USER`
248
+ defaults to: `environ[DATABASE_USER]`
249
+ """
250
+
251
+ PROJECT_DATABASE_PASSWORD: str = os.getenv("PROJECT_DATABASE_PASSWORD", DATABASE_PASSWORD)
252
+ """
253
+ Password to be used by the auto generated project to connect to the
254
+ database under testing.
255
+
256
+ from env variable: `PROJECT_DATABASE_USER`
257
+ defaults to: `environ[DATABASE_PASSWORD]`
258
+ """
259
+
260
+ PROJECT_DATABASE_HOST: str = os.getenv("PROJECT_DATABASE_HOST", DATABASE_HOST)
261
+ """
262
+ Hostname where the database under testing is running in.
263
+
264
+ from env variable: `PROJECT_DATABASE_HOST`
265
+ defaults to: `environ[DATABASE_HOST]`
266
+ """
267
+
268
+ PROJECT_DATABASE_PORT: str = os.getenv("PROJECT_DATABASE_PORT", DATABASE_PORT)
269
+ """
270
+ Port at which the database under testing is exposed by the host.
271
+
272
+ from env variable: `PROJECT_DATABASE_PORT`
273
+ defaults to: `environ[DATABASE_PORT]`
274
+ """
275
+
276
+ SKIP_DATABASE_CERTIFICATE_VERIFICATION: bool = os.getenv("TG_TARGET_DB_TRUST_SERVER_CERTIFICATE", "no").lower() == "yes"
277
+ """
278
+ When True for supported SQL flavors, set up the SQLAlchemy connection to
279
+ trust the database server certificate.
280
+
281
+ from env variable: `TG_TARGET_DB_TRUST_SERVER_CERTIFICATE`
282
+ defaults to: `True`
283
+ """
284
+
285
+ DEFAULT_TABLE_GROUPS_NAME: str = os.getenv("DEFAULT_TABLE_GROUPS_NAME", "default")
286
+ """
287
+ Name assigned to the auto generated table group.
288
+
289
+ from env variable: `DEFAULT_TABLE_GROUPS_NAME`
290
+ defaults to: `default`
291
+ """
292
+
293
+ DEFAULT_TEST_SUITE_KEY: str = os.getenv("DEFAULT_TEST_SUITE_NAME", "default-suite-1")
294
+ """
295
+ Key to be assgined to the auto generated test suite.
296
+
297
+ from env variable: `DEFAULT_TEST_SUITE_NAME`
298
+ defaults to: `default-suite-1`
299
+ """
300
+
301
+ DEFAULT_TEST_SUITE_DESCRIPTION: str = os.getenv("DEFAULT_TEST_SUITE_DESCRIPTION", "default_suite_desc")
302
+ """
303
+ Description for the auto generated test suite.
304
+
305
+ from env variable: `DEFAULT_TEST_SUITE_DESCRIPTION`
306
+ defaults to: `default_suite_desc`
307
+ """
308
+
309
+ DEFAULT_PROFILING_TABLE_SET = os.getenv("DEFAULT_PROFILING_TABLE_SET", "")
310
+ """
311
+ Comma separated list of specific table names to include when running
312
+ profiling for the project database.
313
+
314
+ from env variable: `DEFAULT_PROFILING_TABLE_SET`
315
+ """
316
+
317
+ DEFAULT_PROFILING_INCLUDE_MASK = os.getenv("DEFAULT_PROFILING_INCLUDE_MASK", "%")
318
+ """
319
+ A SQL filter supported by the project database's `LIKE` operator for
320
+ table names to include.
321
+
322
+ from env variable: `DEFAULT_PROFILING_INCLUDE_MASK`
323
+ defaults to: `%`
324
+ """
325
+
326
+ DEFAULT_PROFILING_EXCLUDE_MASK = os.getenv("DEFAULT_PROFILING_EXCLUDE_MASK", "tmp%")
327
+ """
328
+ A SQL filter supported by the project database's `LIKE` operator for
329
+ table names to exclude.
330
+
331
+ from env variable: `DEFAULT_PROFILING_EXCLUDE_MASK`
332
+ defaults to: `tmp%`
333
+ """
334
+
335
+ DEFAULT_PROFILING_ID_COLUMN_MASK = os.getenv("DEFAULT_PROFILING_ID_COLUMN_MASK", "%id")
336
+ """
337
+ A SQL filter supported by the project database's `LIKE` operator
338
+ representing ID columns.
339
+
340
+ from env variable: `DEFAULT_PROFILING_ID_COLUMN_MASK`
341
+ defaults to: `%id`
342
+ """
343
+
344
+ DEFAULT_PROFILING_SK_COLUMN_MASK = os.getenv("DEFAULT_PROFILING_SK_COLUMN_MASK", "%sk")
345
+ """
346
+ A SQL filter supported by the project database's `LIKE` operator
347
+ representing surrogate key columns.
348
+
349
+ from env variable: `DEFAULT_PROFILING_SK_COLUMN_MASK`
350
+ defaults to: `%sk`
351
+ """
352
+
353
+ DEFAULT_PROFILING_USE_SAMPLING: str = os.getenv("DEFAULT_PROFILING_USE_SAMPLING", "N")
354
+ """
355
+ Toggle on to base profiling on a sample of records instead of the full
356
+ table. Accepts `Y` or `N`
357
+
358
+ from env variable: `DEFAULT_PROFILING_USE_SAMPLING`
359
+ defaults to: `N`
360
+ """
361
+
362
+ OBSERVABILITY_API_URL: str = os.getenv("OBSERVABILITY_API_URL", "")
363
+ """
364
+ API URL of your instance of Observability where to send events to for
365
+ the project.
366
+
367
+ You can configure this from the UI settings page.
368
+
369
+ from env variable: `OBSERVABILITY_API_URL`
370
+ """
371
+
372
+ OBSERVABILITY_API_KEY: str = os.getenv("OBSERVABILITY_API_KEY", "")
373
+ """
374
+ Authentication key with permissions to send events created in your
375
+ instance of Observability.
376
+
377
+ You can configure this from the UI settings page.
378
+
379
+ from env variable: `OBSERVABILITY_API_KEY`
380
+ """
381
+
382
+ OBSERVABILITY_VERIFY_SSL: bool = os.getenv("TG_EXPORT_TO_OBSERVABILITY_VERIFY_SSL", "yes").lower() in ["yes", "true"]
383
+ """
384
+ When False, exporting events to your instance of Observabilty will skip
385
+ SSL verification.
386
+
387
+ from env variable: `TG_EXPORT_TO_OBSERVABILITY_VERIFY_SSL`
388
+ defaults to: `True`
389
+ """
390
+
391
+ OBSERVABILITY_EXPORT_LIMIT: int = int(os.getenv("TG_OBSERVABILITY_EXPORT_MAX_QTY", "5000"))
392
+ """
393
+ When exporting to your instance of Observabilty, the maximum number of
394
+ events that will be sent to the events API on a single export.
395
+
396
+ from env variable: `TG_OBSERVABILITY_EXPORT_MAX_QTY`
397
+ defaults to: `5000`
398
+ """
399
+
400
+ OBSERVABILITY_DEFAULT_COMPONENT_TYPE: str = os.getenv("OBSERVABILITY_DEFAULT_COMPONENT_TYPE", "dataset")
401
+ """
402
+ When exporting to your instance of Observabilty, the type of event that
403
+ will be sent to the events API.
404
+
405
+ from env variable: `OBSERVABILITY_DEFAULT_COMPONENT_TYPE`
406
+ defaults to: `dataset`
407
+ """
408
+
409
+ OBSERVABILITY_DEFAULT_COMPONENT_KEY: str = os.getenv("OBSERVABILITY_DEFAULT_COMPONENT_KEY", "default")
410
+ """
411
+ When exporting to your instance of Observabilty, the key sent to the
412
+ events API to identify the components.
413
+
414
+ from env variable: `OBSERVABILITY_DEFAULT_COMPONENT_KEY`
415
+ defaults to: `default`
416
+ """
417
+
418
+ CHECK_FOR_LATEST_VERSION: bool = os.getenv("TG_DOCKER_RELEASE_CHECK_ENABLED", "yes").lower() == "yes"
419
+ """
420
+ When True, enables calling Docker Hub API to fetch the latest released
421
+ image tag. The fetched tag is displayed in the UI menu.
422
+
423
+ from env variable: `TG_DOCKER_RELEASE_CHECK_ENABLED`
424
+ defaults to: `True`
425
+ """
426
+
427
+ VERSION: str = os.getenv("TESTGEN_VERSION", "unknown")
428
+ """
429
+ Current deployed version. The value is displayed in the UI menu.
430
+
431
+ from env variable: `TESTGEN_VERSION`
432
+ defaults to: `unknown`
433
+ """
434
+
435
+ SSL_CERT_FILE: str = os.getenv("SSL_CERT_FILE", "")
436
+ SSL_KEY_FILE: str = os.getenv("SSL_KEY_FILE", "")
437
+ """
438
+ File paths for SSL certificate and private key to support HTTPS.
439
+ Both files must be provided.
440
+ """
@@ -0,0 +1,2 @@
1
+ -- Create base tg schema
2
+ create schema if not exists {SCHEMA_NAME};
@@ -0,0 +1,179 @@
1
+ CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.datediff(difftype character varying, firstdate timestamp without time zone, seconddate timestamp without time zone) returns bigint
2
+ language plpgsql
3
+ as
4
+ $$
5
+ BEGIN
6
+ RETURN
7
+ CASE
8
+ WHEN UPPER(difftype) IN ('DAY', 'DD')
9
+ THEN DATE_PART('day', seconddate - firstdate)
10
+ WHEN UPPER(difftype) IN ('WEEK','WK')
11
+ THEN TRUNC(DATE_PART('day', seconddate - firstdate)/7)
12
+ WHEN UPPER(difftype) IN ('MON', 'MM')
13
+ THEN 12 * (DATE_PART('year', seconddate) - DATE_PART('year', firstdate))
14
+ + (DATE_PART('month', seconddate) - DATE_PART('month', firstdate))
15
+ WHEN UPPER(difftype) IN ('QUARTER', 'QTR')
16
+ THEN 4 * (DATE_PART('year', seconddate) - DATE_PART('year', firstdate))
17
+ + (DATE_PART('qtr', seconddate) - DATE_PART('month', firstdate))
18
+ WHEN UPPER(difftype) IN ('YEAR', 'YY')
19
+ THEN DATE_PART('year', seconddate) - DATE_PART('year', firstdate)
20
+ END;
21
+ END;
22
+ $$;
23
+
24
+
25
+ CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.fn_charcount(instring character varying, searchstring character varying) returns bigint
26
+ language plpgsql
27
+ as
28
+ $$
29
+ BEGIN
30
+ RETURN (CHAR_LENGTH(instring) - CHAR_LENGTH(REPLACE(instring, searchstring, ''))) / CHAR_LENGTH(searchstring);
31
+ END;
32
+ $$;
33
+
34
+
35
+ CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.fn_parsefreq(top_freq_values VARCHAR(1000), rowno INTEGER, colno INTEGER) returns VARCHAR(1000)
36
+ LANGUAGE SQL
37
+ STABLE
38
+ as
39
+ $$
40
+ WITH first AS
41
+ (
42
+ SELECT SPLIT_PART(top_freq_values, CHR(10), rowno) AS first_row
43
+ )
44
+ SELECT
45
+ CASE
46
+ WHEN colno = 1 THEN CAST(TRIM(LEADING '|' FROM SUBSTRING(first_row, POSITION('|' IN first_row), LENGTH(first_row) - POSITION('|' IN REVERSE(first_row)))) AS VARCHAR)
47
+ WHEN colno = 2 THEN CAST(TRIM(SUBSTRING(first_row, LENGTH(first_row) - POSITION('|' IN REVERSE(first_row)) + 2)) AS VARCHAR)
48
+ ELSE NULL
49
+ END
50
+ FROM first
51
+ $$;
52
+
53
+
54
+ CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.fn_PrepColumnName(value_to_check TEXT)
55
+ RETURNS TEXT AS
56
+ $$
57
+ DECLARE
58
+ keyword_arr TEXT[] := ARRAY ['ALL', 'ALTER', 'ANALYSE', 'ANALYZE', 'AND', 'ANY', 'ARRAY', 'AS', 'ASC', 'ASYMMETRIC',
59
+ 'AUTHORIZATION', 'BINARY', 'BOTH', 'CASE', 'CAST', 'CHECK', 'COLLATE', 'COLLATION',
60
+ 'COLUMN', 'CONCURRENTLY', 'CONSTRAINT', 'CREATE', 'CROSS', 'CURRENT_CATALOG',
61
+ 'CURRENT_DATE', 'CURRENT_ROLE', 'CURRENT_SCHEMA', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
62
+ 'CURRENT_USER', 'CREDENTIALS',
63
+ 'DEFAULT', 'DEFERRABLE', 'DESC', 'DISTINCT', 'DO', 'DROP',
64
+ 'ELSE', 'END', 'EXCEPT', 'FALSE', 'FETCH', 'FOR', 'FOREIGN', 'FREEZE', 'FROM', 'FULL',
65
+ 'GRANT', 'GROUP', 'HAVING', 'ILIKE', 'IN', 'INITIALLY', 'INNER', 'INTERSECT', 'INTO',
66
+ 'IS', 'ISNULL', 'JOIN', 'LATERAL', 'LEADING', 'LEFT', 'LIKE', 'LIMIT', 'LOCALTIME',
67
+ 'LOCALTIMESTAMP', 'NATURAL', 'NOT', 'NOTNULL', 'NULL', 'OFFSET', 'ON', 'ONLY', 'OR',
68
+ 'ORDER', 'OUTER', 'OVERLAPS', 'PLACING', 'PRIMARY', 'REFERENCES', 'RETURNING', 'RIGHT',
69
+ 'SELECT', 'SESSION_USER', 'SIMILAR', 'SOME', 'SYMMETRIC', 'TABLE', 'TABLESAMPLE',
70
+ 'THEN', 'TIMESTAMP', 'TIMEZONE', 'TO', 'TRAILING', 'TRUE', 'UNION', 'UNIQUE', 'USER', 'USING',
71
+ 'VARIADIC', 'VERBOSE', 'WHEN', 'WHERE', 'WINDOW', 'WITH']; -- Add more keywords here
72
+ BEGIN
73
+ -- Check if the value matches any of the keywords (case-insensitive)
74
+ IF value_to_check ILIKE ANY (keyword_arr) THEN
75
+ RETURN '"' || value_to_check || '"';
76
+ -- Check if the value contains a space or a comma or it starts with a number
77
+ ELSIF value_to_check !~ '^[a-zA-Z_][a-zA-Z0-9_]*$' THEN
78
+ RETURN '"' || value_to_check || '"';
79
+ ELSE
80
+ RETURN value_to_check;
81
+ END IF;
82
+ END;
83
+ $$ LANGUAGE plpgsql;
84
+
85
+
86
+ CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.fn_pct(numerator NUMERIC, denominator NUMERIC, decs INTEGER DEFAULT 0) returns NUMERIC
87
+ language plpgsql
88
+ as
89
+ $$
90
+ BEGIN
91
+ RETURN ROUND((100.0 * numerator/denominator), decs);
92
+ END;
93
+ $$;
94
+
95
+ CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.fn_quote_literal_escape(var_value varchar, sql_flavor varchar) RETURNS varchar
96
+ LANGUAGE plpgsql
97
+ AS
98
+ $$
99
+ DECLARE
100
+ escaped_value varchar;
101
+ lower_case_sql_flavor varchar;
102
+ BEGIN
103
+ lower_case_sql_flavor := LOWER(sql_flavor);
104
+
105
+ IF lower_case_sql_flavor = 'postgres' OR lower_case_sql_flavor = 'postgresql' THEN
106
+ escaped_value := QUOTE_LITERAL(var_value);
107
+ ELSIF lower_case_sql_flavor = 'redshift' OR lower_case_sql_flavor = 'snowflake' THEN
108
+ escaped_value := TRIM(LEADING 'E' FROM QUOTE_LITERAL(var_value));
109
+ ELSIF lower_case_sql_flavor = 'mssql' THEN
110
+ escaped_value := '''' || REPLACE(var_value, '''', '''''') || '''';
111
+ ELSE
112
+ RAISE EXCEPTION 'Invalid sql_flavor name: %', sql_flavor;
113
+ END IF;
114
+
115
+ RETURN escaped_value;
116
+ END;
117
+ $$;
118
+
119
+ CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.fn_format_csv_no_quotes(str_csv TEXT) returns TEXT
120
+ LANGUAGE SQL
121
+ IMMUTABLE
122
+ as
123
+ $$
124
+ SELECT
125
+ REGEXP_REPLACE(
126
+ REGEXP_REPLACE(str_csv::VARCHAR, '''', '', 'g'), -- Remove single quotes
127
+ '\s*,\s*', -- Match comma, with or without surrounding spaces
128
+ ', ', -- Replace with comma followed by a space
129
+ 'g' -- Global replace
130
+ ) AS formatted_value
131
+ $$;
132
+
133
+ CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.fn_format_csv_quotes(str_csv TEXT) returns TEXT
134
+ LANGUAGE SQL
135
+ IMMUTABLE
136
+ as
137
+ $$
138
+ SELECT
139
+ '''' || REGEXP_REPLACE(str_csv::VARCHAR, '\s*,\s*', ''', ''', 'g') || ''''
140
+ AS formatted_value
141
+ $$;
142
+
143
+ CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.fn_count_intersecting_items(list1 VARCHAR, list2 VARCHAR, separator VARCHAR)
144
+ RETURNS BIGINT AS $$
145
+ SELECT COUNT(*)
146
+ FROM (
147
+ SELECT unnest(string_to_array(list1, separator)) AS element
148
+ INTERSECT
149
+ SELECT unnest(string_to_array(list2, separator))
150
+ ) AS intersection
151
+ $$ LANGUAGE sql;
152
+
153
+ CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.fn_extract_intersecting_items(list1 VARCHAR, list2 VARCHAR, separator VARCHAR)
154
+ RETURNS VARCHAR AS $$
155
+ SELECT STRING_AGG(DISTINCT element, separator) as shared_vals
156
+ FROM (
157
+ SELECT unnest(string_to_array(list1, separator)) AS element
158
+ INTERSECT
159
+ SELECT unnest(string_to_array(list2, separator))
160
+ ) AS intersection
161
+ $$ LANGUAGE sql;
162
+
163
+ CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.fn_extract_distinct_items(list VARCHAR, separator VARCHAR)
164
+ RETURNS VARCHAR AS $$
165
+ SELECT STRING_AGG(DISTINCT element, separator) as distinct_items
166
+ FROM (
167
+ SELECT unnest(string_to_array(list, separator)) AS element
168
+ ) AS all_items
169
+ $$ LANGUAGE sql;
170
+
171
+
172
+ CREATE OR REPLACE FUNCTION {SCHEMA_NAME}.fn_extract_top_values(input_string TEXT)
173
+ RETURNS TEXT AS $$
174
+ SELECT string_agg(trim(split_part(value, '|', 2)), '|') AS values_only
175
+ FROM (
176
+ SELECT unnest(regexp_split_to_array(input_string, E'\n')) AS value
177
+ ) AS t
178
+ WHERE trim(value) <> ''
179
+ $$ LANGUAGE sql;