databao-context-engine 0.1.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.
- databao_context_engine/__init__.py +35 -0
- databao_context_engine/build_sources/__init__.py +0 -0
- databao_context_engine/build_sources/internal/__init__.py +0 -0
- databao_context_engine/build_sources/internal/build_runner.py +111 -0
- databao_context_engine/build_sources/internal/build_service.py +77 -0
- databao_context_engine/build_sources/internal/build_wiring.py +52 -0
- databao_context_engine/build_sources/internal/export_results.py +43 -0
- databao_context_engine/build_sources/internal/plugin_execution.py +74 -0
- databao_context_engine/build_sources/public/__init__.py +0 -0
- databao_context_engine/build_sources/public/api.py +4 -0
- databao_context_engine/cli/__init__.py +0 -0
- databao_context_engine/cli/add_datasource_config.py +130 -0
- databao_context_engine/cli/commands.py +256 -0
- databao_context_engine/cli/datasources.py +64 -0
- databao_context_engine/cli/info.py +32 -0
- databao_context_engine/config/__init__.py +0 -0
- databao_context_engine/config/log_config.yaml +16 -0
- databao_context_engine/config/logging.py +43 -0
- databao_context_engine/databao_context_project_manager.py +92 -0
- databao_context_engine/databao_engine.py +85 -0
- databao_context_engine/datasource_config/__init__.py +0 -0
- databao_context_engine/datasource_config/add_config.py +50 -0
- databao_context_engine/datasource_config/check_config.py +131 -0
- databao_context_engine/datasource_config/datasource_context.py +60 -0
- databao_context_engine/event_journal/__init__.py +0 -0
- databao_context_engine/event_journal/writer.py +29 -0
- databao_context_engine/generate_configs_schemas.py +92 -0
- databao_context_engine/init_project.py +18 -0
- databao_context_engine/introspection/__init__.py +0 -0
- databao_context_engine/introspection/property_extract.py +202 -0
- databao_context_engine/llm/__init__.py +0 -0
- databao_context_engine/llm/config.py +20 -0
- databao_context_engine/llm/descriptions/__init__.py +0 -0
- databao_context_engine/llm/descriptions/ollama.py +21 -0
- databao_context_engine/llm/descriptions/provider.py +10 -0
- databao_context_engine/llm/embeddings/__init__.py +0 -0
- databao_context_engine/llm/embeddings/ollama.py +37 -0
- databao_context_engine/llm/embeddings/provider.py +13 -0
- databao_context_engine/llm/errors.py +16 -0
- databao_context_engine/llm/factory.py +61 -0
- databao_context_engine/llm/install.py +227 -0
- databao_context_engine/llm/runtime.py +73 -0
- databao_context_engine/llm/service.py +159 -0
- databao_context_engine/main.py +19 -0
- databao_context_engine/mcp/__init__.py +0 -0
- databao_context_engine/mcp/all_results_tool.py +5 -0
- databao_context_engine/mcp/mcp_runner.py +16 -0
- databao_context_engine/mcp/mcp_server.py +63 -0
- databao_context_engine/mcp/retrieve_tool.py +22 -0
- databao_context_engine/pluginlib/__init__.py +0 -0
- databao_context_engine/pluginlib/build_plugin.py +107 -0
- databao_context_engine/pluginlib/config.py +37 -0
- databao_context_engine/pluginlib/plugin_utils.py +68 -0
- databao_context_engine/plugins/__init__.py +0 -0
- databao_context_engine/plugins/athena_db_plugin.py +12 -0
- databao_context_engine/plugins/base_db_plugin.py +45 -0
- databao_context_engine/plugins/clickhouse_db_plugin.py +15 -0
- databao_context_engine/plugins/databases/__init__.py +0 -0
- databao_context_engine/plugins/databases/athena_introspector.py +101 -0
- databao_context_engine/plugins/databases/base_introspector.py +144 -0
- databao_context_engine/plugins/databases/clickhouse_introspector.py +162 -0
- databao_context_engine/plugins/databases/database_chunker.py +69 -0
- databao_context_engine/plugins/databases/databases_types.py +114 -0
- databao_context_engine/plugins/databases/duckdb_introspector.py +325 -0
- databao_context_engine/plugins/databases/introspection_model_builder.py +270 -0
- databao_context_engine/plugins/databases/introspection_scope.py +74 -0
- databao_context_engine/plugins/databases/introspection_scope_matcher.py +103 -0
- databao_context_engine/plugins/databases/mssql_introspector.py +433 -0
- databao_context_engine/plugins/databases/mysql_introspector.py +338 -0
- databao_context_engine/plugins/databases/postgresql_introspector.py +428 -0
- databao_context_engine/plugins/databases/snowflake_introspector.py +287 -0
- databao_context_engine/plugins/duckdb_db_plugin.py +12 -0
- databao_context_engine/plugins/mssql_db_plugin.py +12 -0
- databao_context_engine/plugins/mysql_db_plugin.py +12 -0
- databao_context_engine/plugins/parquet_plugin.py +32 -0
- databao_context_engine/plugins/plugin_loader.py +110 -0
- databao_context_engine/plugins/postgresql_db_plugin.py +12 -0
- databao_context_engine/plugins/resources/__init__.py +0 -0
- databao_context_engine/plugins/resources/parquet_chunker.py +23 -0
- databao_context_engine/plugins/resources/parquet_introspector.py +154 -0
- databao_context_engine/plugins/snowflake_db_plugin.py +12 -0
- databao_context_engine/plugins/unstructured_files_plugin.py +68 -0
- databao_context_engine/project/__init__.py +0 -0
- databao_context_engine/project/datasource_discovery.py +141 -0
- databao_context_engine/project/info.py +44 -0
- databao_context_engine/project/init_project.py +102 -0
- databao_context_engine/project/layout.py +127 -0
- databao_context_engine/project/project_config.py +32 -0
- databao_context_engine/project/resources/examples/src/databases/example_postgres.yaml +7 -0
- databao_context_engine/project/resources/examples/src/files/documentation.md +30 -0
- databao_context_engine/project/resources/examples/src/files/notes.txt +20 -0
- databao_context_engine/project/runs.py +39 -0
- databao_context_engine/project/types.py +134 -0
- databao_context_engine/retrieve_embeddings/__init__.py +0 -0
- databao_context_engine/retrieve_embeddings/internal/__init__.py +0 -0
- databao_context_engine/retrieve_embeddings/internal/export_results.py +12 -0
- databao_context_engine/retrieve_embeddings/internal/retrieve_runner.py +34 -0
- databao_context_engine/retrieve_embeddings/internal/retrieve_service.py +68 -0
- databao_context_engine/retrieve_embeddings/internal/retrieve_wiring.py +29 -0
- databao_context_engine/retrieve_embeddings/public/__init__.py +0 -0
- databao_context_engine/retrieve_embeddings/public/api.py +3 -0
- databao_context_engine/serialisation/__init__.py +0 -0
- databao_context_engine/serialisation/yaml.py +35 -0
- databao_context_engine/services/__init__.py +0 -0
- databao_context_engine/services/chunk_embedding_service.py +104 -0
- databao_context_engine/services/embedding_shard_resolver.py +64 -0
- databao_context_engine/services/factories.py +88 -0
- databao_context_engine/services/models.py +12 -0
- databao_context_engine/services/persistence_service.py +61 -0
- databao_context_engine/services/run_name_policy.py +8 -0
- databao_context_engine/services/table_name_policy.py +15 -0
- databao_context_engine/storage/__init__.py +0 -0
- databao_context_engine/storage/connection.py +32 -0
- databao_context_engine/storage/exceptions/__init__.py +0 -0
- databao_context_engine/storage/exceptions/exceptions.py +6 -0
- databao_context_engine/storage/migrate.py +127 -0
- databao_context_engine/storage/migrations/V01__init.sql +63 -0
- databao_context_engine/storage/models.py +51 -0
- databao_context_engine/storage/repositories/__init__.py +0 -0
- databao_context_engine/storage/repositories/chunk_repository.py +130 -0
- databao_context_engine/storage/repositories/datasource_run_repository.py +136 -0
- databao_context_engine/storage/repositories/embedding_model_registry_repository.py +87 -0
- databao_context_engine/storage/repositories/embedding_repository.py +113 -0
- databao_context_engine/storage/repositories/factories.py +35 -0
- databao_context_engine/storage/repositories/run_repository.py +157 -0
- databao_context_engine/storage/repositories/vector_search_repository.py +63 -0
- databao_context_engine/storage/transaction.py +14 -0
- databao_context_engine/system/__init__.py +0 -0
- databao_context_engine/system/properties.py +13 -0
- databao_context_engine/templating/__init__.py +0 -0
- databao_context_engine/templating/renderer.py +29 -0
- databao_context_engine-0.1.1.dist-info/METADATA +186 -0
- databao_context_engine-0.1.1.dist-info/RECORD +135 -0
- databao_context_engine-0.1.1.dist-info/WHEEL +4 -0
- databao_context_engine-0.1.1.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Mapping
|
|
4
|
+
|
|
5
|
+
from mssql_python import connect # type: ignore[import-untyped]
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from databao_context_engine.plugins.base_db_plugin import BaseDatabaseConfigFile
|
|
9
|
+
from databao_context_engine.plugins.databases.base_introspector import BaseIntrospector, SQLQuery
|
|
10
|
+
from databao_context_engine.plugins.databases.databases_types import DatabaseSchema, DatabaseTable
|
|
11
|
+
from databao_context_engine.plugins.databases.introspection_model_builder import IntrospectionModelBuilder
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MSSQLConfigFile(BaseDatabaseConfigFile):
|
|
15
|
+
type: str = Field(default="databases/mssql")
|
|
16
|
+
connection: dict[str, Any] = Field(
|
|
17
|
+
description="Connection parameters for the Microsoft Server SQL database. It can contain any of the keys supported by the Microsoft Server connection library"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MSSQLIntrospector(BaseIntrospector[MSSQLConfigFile]):
|
|
22
|
+
_IGNORED_SCHEMAS = {
|
|
23
|
+
"sys",
|
|
24
|
+
"information_schema",
|
|
25
|
+
"db_accessadmin",
|
|
26
|
+
"db_backupoperator",
|
|
27
|
+
"db_datareader",
|
|
28
|
+
"db_datawriter",
|
|
29
|
+
"db_ddladmin",
|
|
30
|
+
"db_denydatareader",
|
|
31
|
+
"db_denydatawriter",
|
|
32
|
+
"db_owner",
|
|
33
|
+
"db_securityadmin",
|
|
34
|
+
}
|
|
35
|
+
_IGNORED_CATALOGS = (
|
|
36
|
+
"master",
|
|
37
|
+
"model",
|
|
38
|
+
"msdb",
|
|
39
|
+
"tempdb",
|
|
40
|
+
)
|
|
41
|
+
supports_catalogs = True
|
|
42
|
+
|
|
43
|
+
def _connect(self, file_config: MSSQLConfigFile):
|
|
44
|
+
connection = file_config.connection
|
|
45
|
+
if not isinstance(connection, Mapping):
|
|
46
|
+
raise ValueError("Invalid YAML config: 'connection' must be a mapping of connection parameters")
|
|
47
|
+
|
|
48
|
+
connection_string = self._create_connection_string_for_config(connection)
|
|
49
|
+
return connect(connection_string)
|
|
50
|
+
|
|
51
|
+
def _connect_to_catalog(self, file_config: MSSQLConfigFile, catalog: str):
|
|
52
|
+
base_cfg = file_config.connection or {}
|
|
53
|
+
cfg_for_db: dict[str, Any] = dict(base_cfg)
|
|
54
|
+
cfg_for_db["database"] = catalog
|
|
55
|
+
|
|
56
|
+
connection_string = self._create_connection_string_for_config(cfg_for_db)
|
|
57
|
+
return connect(connection_string)
|
|
58
|
+
|
|
59
|
+
def _get_catalogs(self, connection, file_config: MSSQLConfigFile) -> list[str]:
|
|
60
|
+
database = file_config.connection.get("database")
|
|
61
|
+
if isinstance(database, str) and database:
|
|
62
|
+
return [database]
|
|
63
|
+
|
|
64
|
+
rows = self._fetchall_dicts(connection, "SELECT name FROM sys.databases", None)
|
|
65
|
+
all_catalogs = [row["name"] for row in rows]
|
|
66
|
+
return [catalog for catalog in all_catalogs if catalog not in self._IGNORED_CATALOGS]
|
|
67
|
+
|
|
68
|
+
def _sql_list_schemas(self, catalogs: list[str] | None) -> SQLQuery:
|
|
69
|
+
if not catalogs:
|
|
70
|
+
return SQLQuery("SELECT schema_name, catalog_name FROM information_schema.schemata", None)
|
|
71
|
+
|
|
72
|
+
parts = []
|
|
73
|
+
for catalog in catalogs:
|
|
74
|
+
parts.append(f"SELECT schema_name, catalog_name FROM {catalog}.information_schema.schemata")
|
|
75
|
+
return SQLQuery(" UNION ALL ".join(parts), None)
|
|
76
|
+
|
|
77
|
+
_USE_BATCH = True
|
|
78
|
+
|
|
79
|
+
def collect_catalog_model(self, connection, catalog: str, schemas: list[str]) -> list[DatabaseSchema] | None:
|
|
80
|
+
if not schemas:
|
|
81
|
+
return []
|
|
82
|
+
|
|
83
|
+
comps = self._component_queries()
|
|
84
|
+
|
|
85
|
+
values = ", ".join(f"({self._quote_literal(s)})" for s in schemas)
|
|
86
|
+
batch_prefix = "SET NOCOUNT ON; SET XACT_ABORT ON;"
|
|
87
|
+
schema_table = f"DECLARE @schemas TABLE (name sysname);\nINSERT INTO @schemas (name) VALUES {values};"
|
|
88
|
+
|
|
89
|
+
batch = (
|
|
90
|
+
batch_prefix
|
|
91
|
+
+ "\n"
|
|
92
|
+
+ schema_table
|
|
93
|
+
+ "\n"
|
|
94
|
+
+ ";\n".join(sql.strip().rstrip(";") for sql in comps.values())
|
|
95
|
+
+ ";"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
results: dict[str, list[dict]] = {name: [] for name in comps}
|
|
99
|
+
with connection.cursor() as cur:
|
|
100
|
+
cur.execute(batch)
|
|
101
|
+
for ix, name in enumerate(comps.keys(), start=1):
|
|
102
|
+
rows: list[dict] = []
|
|
103
|
+
if cur.description:
|
|
104
|
+
cols = [c[0].lower() for c in cur.description]
|
|
105
|
+
rows = [dict(zip(cols, r)) for r in cur.fetchall()]
|
|
106
|
+
results[name] = rows
|
|
107
|
+
|
|
108
|
+
if ix < len(comps):
|
|
109
|
+
ok = cur.nextset()
|
|
110
|
+
if not ok:
|
|
111
|
+
raise RuntimeError(f"Batch ended early after component #{ix} '{name}'")
|
|
112
|
+
|
|
113
|
+
return IntrospectionModelBuilder.build_schemas_from_components(
|
|
114
|
+
schemas=schemas,
|
|
115
|
+
rels=results.get("relations", []),
|
|
116
|
+
cols=results.get("columns", []),
|
|
117
|
+
pk_cols=results.get("pk", []),
|
|
118
|
+
uq_cols=results.get("uq", []),
|
|
119
|
+
checks=results.get("checks", []),
|
|
120
|
+
fk_cols=results.get("fks", []),
|
|
121
|
+
idx_cols=results.get("idx", []),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def collect_schema_model(self, connection, catalog: str, schema: str) -> list[DatabaseTable] | None:
|
|
125
|
+
comps = self._component_queries()
|
|
126
|
+
|
|
127
|
+
schema_lit = self._quote_literal(schema)
|
|
128
|
+
stmts = [sql.replace("{SCHEMA}", schema_lit) for sql in comps.values()]
|
|
129
|
+
batch_prefix = "SET NOCOUNT ON; SET XACT_ABORT ON;"
|
|
130
|
+
batch = batch_prefix + "\n" + ";\n".join(s.rstrip().rstrip(";") for s in stmts) + ";"
|
|
131
|
+
|
|
132
|
+
results: dict[str, list[dict]] = {name: [] for name in comps}
|
|
133
|
+
with connection.cursor() as cur:
|
|
134
|
+
cur.execute(batch)
|
|
135
|
+
for ix, name in enumerate(comps.keys(), start=1):
|
|
136
|
+
try:
|
|
137
|
+
rows: list[dict] = []
|
|
138
|
+
if cur.description:
|
|
139
|
+
cols = [c[0].lower() for c in cur.description]
|
|
140
|
+
rows = [dict(zip(cols, r)) for r in cur.fetchall()]
|
|
141
|
+
results[name] = rows
|
|
142
|
+
except Exception as e:
|
|
143
|
+
raise RuntimeError(f"Failed reading component #{ix} '{name}'") from e
|
|
144
|
+
if ix < len(comps):
|
|
145
|
+
ok = cur.nextset()
|
|
146
|
+
if not ok:
|
|
147
|
+
raise RuntimeError(f"Batch ended early after component #{ix} '{name}'")
|
|
148
|
+
|
|
149
|
+
return IntrospectionModelBuilder.build_tables_from_components(
|
|
150
|
+
rels=results.get("relations", []),
|
|
151
|
+
cols=results.get("columns", []),
|
|
152
|
+
pk_cols=results.get("pk", []),
|
|
153
|
+
uq_cols=results.get("uq", []),
|
|
154
|
+
checks=results.get("checks", []),
|
|
155
|
+
fk_cols=results.get("fks", []),
|
|
156
|
+
idx_cols=results.get("idx", []),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def _component_queries(self) -> dict[str, str]:
|
|
160
|
+
return {
|
|
161
|
+
"relations": self._sql_relations(),
|
|
162
|
+
"columns": self._sql_columns(),
|
|
163
|
+
"pk": self._sql_primary_keys(),
|
|
164
|
+
"uq": self._sql_uniques(),
|
|
165
|
+
"checks": self._sql_checks(),
|
|
166
|
+
"fks": self._sql_foreign_keys(),
|
|
167
|
+
"idx": self._sql_indexes(),
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
def _sql_relations(self) -> str:
|
|
171
|
+
return r"""
|
|
172
|
+
SELECT
|
|
173
|
+
s.name AS schema_name,
|
|
174
|
+
t.name AS table_name,
|
|
175
|
+
'table' AS kind,
|
|
176
|
+
CAST(ep.value AS nvarchar(4000)) AS description
|
|
177
|
+
FROM
|
|
178
|
+
sys.tables t
|
|
179
|
+
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
|
180
|
+
LEFT JOIN sys.extended_properties ep
|
|
181
|
+
ON ep.major_id = t.object_id AND ep.minor_id = 0 AND ep.class = 1 AND ep.name = 'MS_Description'
|
|
182
|
+
WHERE
|
|
183
|
+
s.name IN (SELECT name FROM @schemas)
|
|
184
|
+
UNION ALL
|
|
185
|
+
SELECT
|
|
186
|
+
s.name AS schema_name,
|
|
187
|
+
v.name AS table_name,
|
|
188
|
+
'view' AS kind,
|
|
189
|
+
CAST(ep.value AS nvarchar(4000)) AS description
|
|
190
|
+
FROM
|
|
191
|
+
sys.views v
|
|
192
|
+
JOIN sys.schemas s ON s.schema_id = v.schema_id
|
|
193
|
+
LEFT JOIN sys.extended_properties ep
|
|
194
|
+
ON ep.major_id = v.object_id AND ep.minor_id = 0 AND ep.class = 1 AND ep.name = 'MS_Description'
|
|
195
|
+
WHERE
|
|
196
|
+
s.name IN (SELECT name FROM @schemas)
|
|
197
|
+
UNION ALL
|
|
198
|
+
SELECT
|
|
199
|
+
s.name AS schema_name,
|
|
200
|
+
et.name AS table_name,
|
|
201
|
+
'external_table' AS kind,
|
|
202
|
+
CAST(ep.value AS nvarchar(4000)) AS description
|
|
203
|
+
FROM
|
|
204
|
+
sys.external_tables et
|
|
205
|
+
JOIN sys.schemas s ON s.schema_id = et.schema_id
|
|
206
|
+
LEFT JOIN sys.extended_properties ep
|
|
207
|
+
ON ep.major_id = et.object_id AND ep.minor_id = 0 AND ep.class = 1 AND ep.name = 'MS_Description'
|
|
208
|
+
WHERE
|
|
209
|
+
s.name IN (SELECT name FROM @schemas)
|
|
210
|
+
ORDER BY
|
|
211
|
+
table_name;
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
# TODO: simplify case when for datatype
|
|
215
|
+
def _sql_columns(self) -> str:
|
|
216
|
+
return r"""
|
|
217
|
+
SELECT
|
|
218
|
+
s.name AS schema_name,
|
|
219
|
+
o.name AS table_name,
|
|
220
|
+
c.name AS column_name,
|
|
221
|
+
c.column_id AS ordinal_position,
|
|
222
|
+
CASE
|
|
223
|
+
WHEN t.name IN ('varchar','char','varbinary','binary') THEN t.name + '(' + CASE WHEN c.max_length = -1 THEN 'MAX' ELSE CAST(c.max_length AS varchar(10)) END + ')'
|
|
224
|
+
WHEN t.name IN ('nvarchar','nchar') THEN t.name + '(' + CASE WHEN c.max_length = -1 THEN 'MAX' ELSE CAST(c.max_length / 2 AS varchar(10)) END + ')'
|
|
225
|
+
WHEN t.name IN ('decimal','numeric') THEN t.name + '(' + CAST(c.precision AS varchar(10)) + ',' + CAST(c.scale AS varchar(10)) + ')'
|
|
226
|
+
ELSE t.name
|
|
227
|
+
END AS data_type,
|
|
228
|
+
CAST(c.is_nullable AS bit) AS is_nullable,
|
|
229
|
+
CASE
|
|
230
|
+
WHEN cc.object_id IS NOT NULL THEN CAST(cc.definition AS nvarchar(4000))
|
|
231
|
+
ELSE CAST(dc.definition AS nvarchar(4000))
|
|
232
|
+
END AS default_expression,
|
|
233
|
+
CASE
|
|
234
|
+
WHEN c.is_identity = 1 THEN 'identity'
|
|
235
|
+
WHEN c.is_computed = 1 THEN 'computed'
|
|
236
|
+
END AS generated,
|
|
237
|
+
CAST(ep.value AS nvarchar(4000)) AS description
|
|
238
|
+
FROM
|
|
239
|
+
sys.columns c
|
|
240
|
+
JOIN sys.objects o ON o.object_id = c.object_id AND o.type IN ('U','V')
|
|
241
|
+
JOIN sys.schemas s ON s.schema_id = o.schema_id
|
|
242
|
+
JOIN sys.types t ON t.user_type_id = c.user_type_id
|
|
243
|
+
LEFT JOIN sys.computed_columns cc ON cc.object_id = c.object_id AND cc.column_id = c.column_id
|
|
244
|
+
LEFT JOIN sys.default_constraints dc ON dc.object_id = c.default_object_id
|
|
245
|
+
LEFT JOIN sys.extended_properties ep ON ep.class = 1 AND ep.major_id = c.object_id AND ep.minor_id = c.column_id AND ep.name = 'MS_Description'
|
|
246
|
+
WHERE
|
|
247
|
+
s.name IN (SELECT name FROM @schemas)
|
|
248
|
+
ORDER BY
|
|
249
|
+
o.name,
|
|
250
|
+
c.column_id;
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
def _sql_primary_keys(self) -> str:
|
|
254
|
+
return r"""
|
|
255
|
+
SELECT
|
|
256
|
+
s.name AS schema_name,
|
|
257
|
+
t.name AS table_name,
|
|
258
|
+
kc.name AS constraint_name,
|
|
259
|
+
c.name AS column_name,
|
|
260
|
+
ic.key_ordinal AS position
|
|
261
|
+
FROM
|
|
262
|
+
sys.key_constraints kc
|
|
263
|
+
JOIN sys.tables t ON t.object_id = kc.parent_object_id
|
|
264
|
+
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
|
265
|
+
JOIN sys.index_columns ic ON ic.object_id = kc.parent_object_id AND ic.index_id = kc.unique_index_id AND ic.is_included_column = 0
|
|
266
|
+
JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
|
|
267
|
+
WHERE
|
|
268
|
+
s.name IN (SELECT name FROM @schemas)
|
|
269
|
+
AND kc.type = 'PK'
|
|
270
|
+
ORDER BY
|
|
271
|
+
t.name,
|
|
272
|
+
kc.name,
|
|
273
|
+
ic.key_ordinal;
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
def _sql_uniques(self) -> str:
|
|
277
|
+
return r"""
|
|
278
|
+
SELECT
|
|
279
|
+
s.name AS schema_name,
|
|
280
|
+
t.name AS table_name,
|
|
281
|
+
kc.name AS constraint_name,
|
|
282
|
+
c.name AS column_name,
|
|
283
|
+
ic.key_ordinal AS position
|
|
284
|
+
FROM
|
|
285
|
+
sys.key_constraints kc
|
|
286
|
+
JOIN sys.tables t ON t.object_id = kc.parent_object_id
|
|
287
|
+
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
|
288
|
+
JOIN sys.index_columns ic ON ic.object_id = kc.parent_object_id AND ic.index_id = kc.unique_index_id AND ic.is_included_column = 0
|
|
289
|
+
JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
|
|
290
|
+
WHERE
|
|
291
|
+
s.name IN (SELECT name FROM @schemas)
|
|
292
|
+
AND kc.type = 'UQ'
|
|
293
|
+
ORDER BY
|
|
294
|
+
t.name,
|
|
295
|
+
kc.name,
|
|
296
|
+
ic.key_ordinal;
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
def _sql_checks(self) -> str:
|
|
300
|
+
return r"""
|
|
301
|
+
SELECT
|
|
302
|
+
s.name AS schema_name,
|
|
303
|
+
t.name AS table_name,
|
|
304
|
+
cc.name AS constraint_name,
|
|
305
|
+
CAST(cc.definition AS nvarchar(4000)) AS expression,
|
|
306
|
+
CAST(CASE
|
|
307
|
+
WHEN cc.is_not_trusted = 0 THEN 1
|
|
308
|
+
ELSE 0
|
|
309
|
+
END AS bit) AS validated
|
|
310
|
+
FROM
|
|
311
|
+
sys.check_constraints cc
|
|
312
|
+
JOIN sys.tables t ON t.object_id = cc.parent_object_id
|
|
313
|
+
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
|
314
|
+
WHERE
|
|
315
|
+
s.name IN (SELECT name FROM @schemas)
|
|
316
|
+
ORDER BY
|
|
317
|
+
t.name,
|
|
318
|
+
cc.name;
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
def _sql_foreign_keys(self) -> str:
|
|
322
|
+
return r"""
|
|
323
|
+
SELECT
|
|
324
|
+
s.name AS schema_name,
|
|
325
|
+
t.name AS table_name,
|
|
326
|
+
fk.name AS constraint_name,
|
|
327
|
+
fkc.constraint_column_id AS position,
|
|
328
|
+
pc.name AS from_column,
|
|
329
|
+
rs.name AS ref_schema,
|
|
330
|
+
rt.name AS ref_table,
|
|
331
|
+
rc.name AS to_column,
|
|
332
|
+
CAST(CASE WHEN fk.is_disabled = 0 THEN 1 ELSE 0 END AS bit) AS enforced,
|
|
333
|
+
CAST(CASE WHEN fk.is_not_trusted = 0 THEN 1 ELSE 0 END AS bit) AS validated,
|
|
334
|
+
LOWER(fk.update_referential_action_desc) AS on_update,
|
|
335
|
+
LOWER(fk.delete_referential_action_desc) AS on_delete
|
|
336
|
+
FROM
|
|
337
|
+
sys.foreign_keys fk
|
|
338
|
+
JOIN sys.tables t ON t.object_id = fk.parent_object_id
|
|
339
|
+
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
|
340
|
+
JOIN sys.tables rt ON rt.object_id = fk.referenced_object_id
|
|
341
|
+
JOIN sys.schemas rs ON rs.schema_id = rt.schema_id
|
|
342
|
+
JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id
|
|
343
|
+
JOIN sys.columns pc ON pc.object_id = fkc.parent_object_id AND pc.column_id = fkc.parent_column_id
|
|
344
|
+
JOIN sys.columns rc ON rc.object_id = fkc.referenced_object_id AND rc.column_id = fkc.referenced_column_id
|
|
345
|
+
WHERE
|
|
346
|
+
s.name IN (SELECT name FROM @schemas)
|
|
347
|
+
ORDER BY
|
|
348
|
+
t.name,
|
|
349
|
+
fk.name,
|
|
350
|
+
fkc.constraint_column_id;
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
# TODO: case when is confusing
|
|
354
|
+
def _sql_indexes(self) -> str:
|
|
355
|
+
return r"""
|
|
356
|
+
SELECT
|
|
357
|
+
s.name AS schema_name,
|
|
358
|
+
t.name AS table_name,
|
|
359
|
+
i.name AS index_name,
|
|
360
|
+
ic.key_ordinal AS position,
|
|
361
|
+
CAST(
|
|
362
|
+
CASE
|
|
363
|
+
WHEN ic.is_descending_key = 1 THEN c.name + ' DESC'
|
|
364
|
+
ELSE c.name
|
|
365
|
+
END AS nvarchar(4000)
|
|
366
|
+
) AS expr,
|
|
367
|
+
CAST(i.is_unique AS bit) AS is_unique,
|
|
368
|
+
LOWER(i.type_desc) AS method,
|
|
369
|
+
CAST(i.filter_definition AS nvarchar(4000)) AS predicate
|
|
370
|
+
FROM
|
|
371
|
+
sys.indexes i
|
|
372
|
+
JOIN sys.tables t ON t.object_id = i.object_id
|
|
373
|
+
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
|
374
|
+
JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
|
|
375
|
+
JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
|
|
376
|
+
WHERE
|
|
377
|
+
s.name IN (SELECT name FROM @schemas)
|
|
378
|
+
AND i.is_primary_key = 0
|
|
379
|
+
AND i.is_unique_constraint = 0
|
|
380
|
+
AND ic.is_included_column = 0
|
|
381
|
+
AND ic.key_ordinal > 0
|
|
382
|
+
ORDER BY
|
|
383
|
+
t.name,
|
|
384
|
+
i.name,
|
|
385
|
+
ic.key_ordinal;
|
|
386
|
+
"""
|
|
387
|
+
|
|
388
|
+
def _create_connection_string_for_config(self, file_config: Mapping[str, Any]) -> str:
|
|
389
|
+
def _escape_odbc_value(value: str) -> str:
|
|
390
|
+
return "{" + value.replace("}", "}}").replace("{", "{{") + "}"
|
|
391
|
+
|
|
392
|
+
host = file_config.get("host")
|
|
393
|
+
if not host:
|
|
394
|
+
raise ValueError("A host must be provided to connect to the MSSQL database.")
|
|
395
|
+
|
|
396
|
+
port = file_config.get("port", 1433)
|
|
397
|
+
instance = file_config.get("instanceName")
|
|
398
|
+
if instance:
|
|
399
|
+
server_part = f"{host}\\{instance}"
|
|
400
|
+
else:
|
|
401
|
+
server_part = f"{host},{port}"
|
|
402
|
+
|
|
403
|
+
database = file_config.get("database")
|
|
404
|
+
user = file_config.get("user")
|
|
405
|
+
password = file_config.get("password")
|
|
406
|
+
|
|
407
|
+
connection_parts = {
|
|
408
|
+
"server": _escape_odbc_value(server_part),
|
|
409
|
+
"database": _escape_odbc_value(str(database)) if database is not None else None,
|
|
410
|
+
"uid": _escape_odbc_value(str(user)) if user is not None else None,
|
|
411
|
+
"pwd": _escape_odbc_value(str(password)) if password is not None else None,
|
|
412
|
+
"encrypt": file_config.get("encrypt"),
|
|
413
|
+
"trust_server_certificate": "yes" if file_config.get("trust_server_certificate") else None,
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
connection_string = ";".join(f"{k}={v}" for k, v in connection_parts.items() if v is not None)
|
|
417
|
+
return connection_string
|
|
418
|
+
|
|
419
|
+
def _fetchall_dicts(self, connection, sql: str, params) -> list[dict]:
|
|
420
|
+
with connection.cursor() as cursor:
|
|
421
|
+
cursor.execute(sql, params or ())
|
|
422
|
+
if not cursor.description:
|
|
423
|
+
return []
|
|
424
|
+
|
|
425
|
+
columns = [col[0].lower() for col in cursor.description]
|
|
426
|
+
return [dict(zip(columns, row)) for row in cursor.fetchall()]
|
|
427
|
+
|
|
428
|
+
def _quote_literal(self, value: str) -> str:
|
|
429
|
+
return "'" + str(value).replace("'", "''") + "'"
|
|
430
|
+
|
|
431
|
+
def _sql_sample_rows(self, catalog: str, schema: str, table: str, limit: int) -> SQLQuery:
|
|
432
|
+
sql = f'SELECT TOP ({limit}) * FROM "{catalog}"."{schema}"."{table}"'
|
|
433
|
+
return SQLQuery(sql, [limit])
|