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,338 @@
|
|
|
1
|
+
from typing import Any, Mapping
|
|
2
|
+
|
|
3
|
+
import pymysql
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
from pymysql.constants import CLIENT
|
|
6
|
+
|
|
7
|
+
from databao_context_engine.plugins.base_db_plugin import BaseDatabaseConfigFile
|
|
8
|
+
from databao_context_engine.plugins.databases.base_introspector import BaseIntrospector, SQLQuery
|
|
9
|
+
from databao_context_engine.plugins.databases.databases_types import DatabaseSchema, DatabaseTable
|
|
10
|
+
from databao_context_engine.plugins.databases.introspection_model_builder import IntrospectionModelBuilder
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MySQLConfigFile(BaseDatabaseConfigFile):
|
|
14
|
+
connection: dict[str, Any]
|
|
15
|
+
type: str = Field(default="databases/mysql")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MySQLIntrospector(BaseIntrospector[MySQLConfigFile]):
|
|
19
|
+
_IGNORED_SCHEMAS = {"information_schema", "mysql", "performance_schema", "sys"}
|
|
20
|
+
|
|
21
|
+
supports_catalogs = True
|
|
22
|
+
|
|
23
|
+
def _connect(self, file_config: MySQLConfigFile):
|
|
24
|
+
connection = file_config.connection
|
|
25
|
+
if not isinstance(connection, Mapping):
|
|
26
|
+
raise ValueError("Invalid YAML config: 'connection' must be a mapping of connection parameters")
|
|
27
|
+
|
|
28
|
+
return pymysql.connect(
|
|
29
|
+
**connection,
|
|
30
|
+
cursorclass=pymysql.cursors.DictCursor,
|
|
31
|
+
client_flag=CLIENT.MULTI_STATEMENTS | CLIENT.MULTI_RESULTS,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def _connect_to_catalog(self, file_config: MySQLConfigFile, catalog: str):
|
|
35
|
+
cfg = dict(file_config.connection or {})
|
|
36
|
+
cfg["database"] = catalog
|
|
37
|
+
return self._connect(MySQLConfigFile(connection=cfg))
|
|
38
|
+
|
|
39
|
+
def _get_catalogs(self, connection, file_config: MySQLConfigFile) -> list[str]:
|
|
40
|
+
with connection.cursor() as cur:
|
|
41
|
+
cur.execute(
|
|
42
|
+
"""
|
|
43
|
+
SELECT schema_name
|
|
44
|
+
FROM information_schema.schemata
|
|
45
|
+
ORDER BY schema_name
|
|
46
|
+
"""
|
|
47
|
+
)
|
|
48
|
+
dbs = [row["SCHEMA_NAME"] for row in cur.fetchall()]
|
|
49
|
+
return [d for d in dbs if d.lower() not in self._IGNORED_SCHEMAS]
|
|
50
|
+
|
|
51
|
+
def _sql_list_schemas(self, catalogs: list[str] | None) -> SQLQuery:
|
|
52
|
+
return SQLQuery(
|
|
53
|
+
"SELECT DATABASE() AS schema_name, DATABASE() AS catalog_name",
|
|
54
|
+
None,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def collect_catalog_model(self, connection, catalog: str, schemas: list[str]) -> list[DatabaseSchema] | None:
|
|
58
|
+
if not schemas:
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
comps = self._component_queries()
|
|
62
|
+
results: dict[str, list[dict]] = {name: [] for name in comps}
|
|
63
|
+
|
|
64
|
+
schemas_sql = ", ".join(self._quote_literal(s) for s in schemas)
|
|
65
|
+
|
|
66
|
+
batch = ";\n".join(sql.replace("{SCHEMAS}", schemas_sql).rstrip().rstrip(";") for sql in comps.values())
|
|
67
|
+
|
|
68
|
+
with connection.cursor(pymysql.cursors.DictCursor) as cur:
|
|
69
|
+
cur.execute(batch)
|
|
70
|
+
|
|
71
|
+
for ix, name in enumerate(comps.keys(), start=1):
|
|
72
|
+
raw_rows = cur.fetchall() if cur.description else ()
|
|
73
|
+
|
|
74
|
+
if raw_rows and isinstance(raw_rows[0], dict):
|
|
75
|
+
rows_list = [{k.lower(): v for k, v in row.items()} for row in raw_rows]
|
|
76
|
+
else:
|
|
77
|
+
if cur.description:
|
|
78
|
+
cols = [d[0].lower() for d in cur.description]
|
|
79
|
+
rows_list = [dict(zip(cols, r)) for r in raw_rows]
|
|
80
|
+
else:
|
|
81
|
+
rows_list = []
|
|
82
|
+
|
|
83
|
+
results[name] = rows_list
|
|
84
|
+
|
|
85
|
+
if ix < len(comps):
|
|
86
|
+
ok = cur.nextset()
|
|
87
|
+
if not ok:
|
|
88
|
+
raise RuntimeError(f"MySQL batch ended early after component #{ix} '{name}'")
|
|
89
|
+
|
|
90
|
+
return IntrospectionModelBuilder.build_schemas_from_components(
|
|
91
|
+
schemas=schemas,
|
|
92
|
+
rels=results.get("relations", []),
|
|
93
|
+
cols=results.get("columns", []),
|
|
94
|
+
pk_cols=results.get("pk", []),
|
|
95
|
+
uq_cols=results.get("uq", []),
|
|
96
|
+
checks=results.get("checks", []),
|
|
97
|
+
fk_cols=results.get("fks", []),
|
|
98
|
+
idx_cols=results.get("idx", []),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def collect_schema_model(self, connection, catalog: str, schema: str) -> list[DatabaseTable] | None:
|
|
102
|
+
comps = self._component_queries()
|
|
103
|
+
results: dict[str, list[dict]] = {name: [] for name in comps}
|
|
104
|
+
|
|
105
|
+
batch = ";\n".join(
|
|
106
|
+
sql.replace("{SCHEMA}", self._quote_literal(schema)).rstrip().rstrip(";") for sql in comps.values()
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
with connection.cursor(pymysql.cursors.DictCursor) as cur:
|
|
110
|
+
cur.execute(batch)
|
|
111
|
+
|
|
112
|
+
for ix, name in enumerate(comps.keys(), start=1):
|
|
113
|
+
raw_rows = cur.fetchall() if cur.description else ()
|
|
114
|
+
|
|
115
|
+
rows_list: list[dict]
|
|
116
|
+
# TODO: simplify this
|
|
117
|
+
if raw_rows and isinstance(raw_rows[0], dict):
|
|
118
|
+
rows_list = [{k.lower(): v for k, v in row.items()} for row in raw_rows]
|
|
119
|
+
else:
|
|
120
|
+
if cur.description:
|
|
121
|
+
cols = [d[0].lower() for d in cur.description]
|
|
122
|
+
rows_list = [dict(zip(cols, r)) for r in raw_rows]
|
|
123
|
+
else:
|
|
124
|
+
rows_list = []
|
|
125
|
+
|
|
126
|
+
results[name] = rows_list
|
|
127
|
+
|
|
128
|
+
if ix < len(comps):
|
|
129
|
+
ok = cur.nextset()
|
|
130
|
+
if not ok:
|
|
131
|
+
raise RuntimeError(f"MySQL batch ended early after component #{ix} '{name}'")
|
|
132
|
+
|
|
133
|
+
return IntrospectionModelBuilder.build_tables_from_components(
|
|
134
|
+
rels=results.get("relations", []),
|
|
135
|
+
cols=results.get("columns", []),
|
|
136
|
+
pk_cols=results.get("pk", []),
|
|
137
|
+
uq_cols=results.get("uq", []),
|
|
138
|
+
checks=results.get("checks", []),
|
|
139
|
+
fk_cols=results.get("fks", []),
|
|
140
|
+
idx_cols=results.get("idx", []),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def _component_queries(self) -> dict[str, str]:
|
|
144
|
+
return {
|
|
145
|
+
"relations": self._sql_relations(),
|
|
146
|
+
"columns": self._sql_columns(),
|
|
147
|
+
"pk": self._sql_primary_keys(),
|
|
148
|
+
"uq": self._sql_uniques(),
|
|
149
|
+
"checks": self._sql_checks(),
|
|
150
|
+
"fks": self._sql_foreign_keys(),
|
|
151
|
+
"idx": self._sql_indexes(),
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
def _sql_relations(self) -> str:
|
|
155
|
+
return r"""
|
|
156
|
+
SELECT
|
|
157
|
+
t.TABLE_SCHEMA AS schema_name,
|
|
158
|
+
t.TABLE_NAME AS table_name,
|
|
159
|
+
CASE t.TABLE_TYPE
|
|
160
|
+
WHEN 'BASE TABLE' THEN 'table'
|
|
161
|
+
WHEN 'VIEW' THEN 'view'
|
|
162
|
+
ELSE LOWER(t.TABLE_TYPE)
|
|
163
|
+
END AS kind,
|
|
164
|
+
CASE t.TABLE_TYPE
|
|
165
|
+
WHEN 'VIEW' THEN NULL
|
|
166
|
+
ELSE NULLIF(t.TABLE_COMMENT, '')
|
|
167
|
+
END AS description
|
|
168
|
+
FROM
|
|
169
|
+
INFORMATION_SCHEMA.TABLES t
|
|
170
|
+
WHERE
|
|
171
|
+
t.TABLE_SCHEMA IN ({SCHEMAS})
|
|
172
|
+
ORDER BY
|
|
173
|
+
t.TABLE_SCHEMA,
|
|
174
|
+
t.TABLE_NAME
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
def _sql_columns(self) -> str:
|
|
178
|
+
return r"""
|
|
179
|
+
SELECT
|
|
180
|
+
c.TABLE_SCHEMA AS schema_name,
|
|
181
|
+
c.TABLE_NAME AS table_name,
|
|
182
|
+
c.COLUMN_NAME AS column_name,
|
|
183
|
+
c.ORDINAL_POSITION AS ordinal_position,
|
|
184
|
+
c.COLUMN_TYPE AS data_type,
|
|
185
|
+
CASE
|
|
186
|
+
WHEN c.IS_NULLABLE = 'YES' THEN TRUE
|
|
187
|
+
ELSE FALSE
|
|
188
|
+
END AS is_nullable,
|
|
189
|
+
CASE
|
|
190
|
+
WHEN c.EXTRA RLIKE '\\b(VIRTUAL|STORED) GENERATED\\b' THEN NULLIF(c.GENERATION_EXPRESSION, '')
|
|
191
|
+
ELSE c.COLUMN_DEFAULT
|
|
192
|
+
END AS default_expression,
|
|
193
|
+
CASE
|
|
194
|
+
WHEN c.EXTRA LIKE '%auto_increment%' THEN 'identity'
|
|
195
|
+
WHEN c.EXTRA RLIKE '\\b(VIRTUAL|STORED) GENERATED\\b' THEN 'computed'
|
|
196
|
+
ELSE NULL
|
|
197
|
+
END AS "generated",
|
|
198
|
+
NULLIF(c.COLUMN_COMMENT, '') AS description
|
|
199
|
+
FROM
|
|
200
|
+
INFORMATION_SCHEMA.COLUMNS c
|
|
201
|
+
WHERE
|
|
202
|
+
c.TABLE_SCHEMA IN ({SCHEMAS})
|
|
203
|
+
ORDER BY
|
|
204
|
+
c.TABLE_SCHEMA,
|
|
205
|
+
c.TABLE_NAME,
|
|
206
|
+
c.ORDINAL_POSITION
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
def _sql_primary_keys(self) -> str:
|
|
210
|
+
return r"""
|
|
211
|
+
SELECT
|
|
212
|
+
tc.TABLE_SCHEMA AS schema_name,
|
|
213
|
+
tc.TABLE_NAME AS table_name,
|
|
214
|
+
tc.CONSTRAINT_NAME AS constraint_name,
|
|
215
|
+
kcu.COLUMN_NAME AS column_name,
|
|
216
|
+
kcu.ORDINAL_POSITION AS position
|
|
217
|
+
FROM
|
|
218
|
+
INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
|
219
|
+
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
|
|
220
|
+
ON kcu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME AND kcu.TABLE_SCHEMA = tc.TABLE_SCHEMA AND kcu.TABLE_NAME = tc.TABLE_NAME
|
|
221
|
+
WHERE
|
|
222
|
+
tc.TABLE_SCHEMA IN ({SCHEMAS})
|
|
223
|
+
AND tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
|
|
224
|
+
ORDER BY
|
|
225
|
+
tc.TABLE_SCHEMA,
|
|
226
|
+
tc.TABLE_NAME,
|
|
227
|
+
tc.CONSTRAINT_NAME,
|
|
228
|
+
kcu.ORDINAL_POSITION
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
def _sql_uniques(self) -> str:
|
|
232
|
+
return r"""
|
|
233
|
+
SELECT
|
|
234
|
+
tc.TABLE_SCHEMA AS schema_name,
|
|
235
|
+
tc.TABLE_NAME AS table_name,
|
|
236
|
+
tc.CONSTRAINT_NAME AS constraint_name,
|
|
237
|
+
kcu.COLUMN_NAME AS column_name,
|
|
238
|
+
kcu.ORDINAL_POSITION AS position
|
|
239
|
+
FROM
|
|
240
|
+
INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
|
241
|
+
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON kcu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME AND kcu.TABLE_SCHEMA = tc.TABLE_SCHEMA AND kcu.TABLE_NAME = tc.TABLE_NAME
|
|
242
|
+
WHERE
|
|
243
|
+
tc.TABLE_SCHEMA IN ({SCHEMAS})
|
|
244
|
+
AND tc.CONSTRAINT_TYPE = 'UNIQUE'
|
|
245
|
+
ORDER BY
|
|
246
|
+
tc.TABLE_SCHEMA,
|
|
247
|
+
tc.TABLE_NAME,
|
|
248
|
+
tc.CONSTRAINT_NAME,
|
|
249
|
+
kcu.ORDINAL_POSITION
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
def _sql_checks(self) -> str:
|
|
253
|
+
return r"""
|
|
254
|
+
SELECT
|
|
255
|
+
tc.TABLE_SCHEMA AS schema_name,
|
|
256
|
+
tc.TABLE_NAME AS table_name,
|
|
257
|
+
tc.CONSTRAINT_NAME AS constraint_name,
|
|
258
|
+
cc.CHECK_CLAUSE AS expression,
|
|
259
|
+
TRUE AS validated
|
|
260
|
+
FROM
|
|
261
|
+
INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
|
262
|
+
JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS cc ON cc.CONSTRAINT_SCHEMA = tc.TABLE_SCHEMA AND cc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
|
|
263
|
+
WHERE
|
|
264
|
+
tc.TABLE_SCHEMA IN ({SCHEMAS})
|
|
265
|
+
AND tc.CONSTRAINT_TYPE = 'CHECK'
|
|
266
|
+
ORDER BY
|
|
267
|
+
tc.TABLE_SCHEMA,
|
|
268
|
+
tc.TABLE_NAME,
|
|
269
|
+
tc.CONSTRAINT_NAME
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
def _sql_foreign_keys(self) -> str:
|
|
273
|
+
return r"""
|
|
274
|
+
SELECT
|
|
275
|
+
kcu.TABLE_SCHEMA AS schema_name,
|
|
276
|
+
kcu.TABLE_NAME AS table_name,
|
|
277
|
+
kcu.CONSTRAINT_NAME AS constraint_name,
|
|
278
|
+
kcu.ORDINAL_POSITION AS position,
|
|
279
|
+
kcu.COLUMN_NAME AS from_column,
|
|
280
|
+
kcu.REFERENCED_TABLE_SCHEMA AS ref_schema,
|
|
281
|
+
kcu.REFERENCED_TABLE_NAME AS ref_table,
|
|
282
|
+
kcu.REFERENCED_COLUMN_NAME AS to_column,
|
|
283
|
+
LOWER(rc.UPDATE_RULE) AS on_update,
|
|
284
|
+
LOWER(rc.DELETE_RULE) AS on_delete,
|
|
285
|
+
TRUE AS enforced,
|
|
286
|
+
TRUE AS validated
|
|
287
|
+
FROM
|
|
288
|
+
INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
|
|
289
|
+
JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND tc.TABLE_SCHEMA = kcu.TABLE_SCHEMA AND tc.TABLE_NAME = kcu.TABLE_NAME
|
|
290
|
+
JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc ON rc.CONSTRAINT_SCHEMA = kcu.TABLE_SCHEMA AND rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
|
|
291
|
+
WHERE
|
|
292
|
+
kcu.TABLE_SCHEMA IN ({SCHEMAS})
|
|
293
|
+
AND tc.CONSTRAINT_TYPE = 'FOREIGN KEY'
|
|
294
|
+
ORDER BY
|
|
295
|
+
kcu.TABLE_SCHEMA,
|
|
296
|
+
kcu.TABLE_NAME,
|
|
297
|
+
kcu.CONSTRAINT_NAME,
|
|
298
|
+
kcu.ORDINAL_POSITION
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
def _sql_indexes(self) -> str:
|
|
302
|
+
return r"""
|
|
303
|
+
SELECT
|
|
304
|
+
s.TABLE_SCHEMA AS schema_name,
|
|
305
|
+
s.TABLE_NAME AS table_name,
|
|
306
|
+
s.INDEX_NAME AS index_name,
|
|
307
|
+
s.SEQ_IN_INDEX AS position,
|
|
308
|
+
COALESCE(s.EXPRESSION, s.COLUMN_NAME) AS expr,
|
|
309
|
+
(s.NON_UNIQUE = 0) AS is_unique,
|
|
310
|
+
s.INDEX_TYPE AS method,
|
|
311
|
+
NULL AS predicate
|
|
312
|
+
FROM
|
|
313
|
+
INFORMATION_SCHEMA.STATISTICS s
|
|
314
|
+
WHERE
|
|
315
|
+
s.TABLE_SCHEMA IN ({SCHEMAS})
|
|
316
|
+
AND s.INDEX_NAME <> 'PRIMARY'
|
|
317
|
+
ORDER BY
|
|
318
|
+
s.TABLE_SCHEMA,
|
|
319
|
+
s.TABLE_NAME,
|
|
320
|
+
s.INDEX_NAME,
|
|
321
|
+
s.SEQ_IN_INDEX
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
def _sql_sample_rows(self, catalog: str, schema: str, table: str, limit: int) -> SQLQuery:
|
|
325
|
+
sql = f"SELECT * FROM `{schema}`.`{table}` LIMIT %s"
|
|
326
|
+
return SQLQuery(sql, (limit,))
|
|
327
|
+
|
|
328
|
+
def _fetchall_dicts(self, connection, sql: str, params) -> list[dict]:
|
|
329
|
+
with connection.cursor(pymysql.cursors.DictCursor) as cur:
|
|
330
|
+
cur.execute(sql, params)
|
|
331
|
+
rows = cur.fetchall()
|
|
332
|
+
return [{k.lower(): v for k, v in row.items()} for row in rows]
|
|
333
|
+
|
|
334
|
+
def _quote_literal(self, value: str) -> str:
|
|
335
|
+
return "'" + str(value).replace("\\", "\\\\").replace("'", "\\'") + "'"
|
|
336
|
+
|
|
337
|
+
def _quote_ident(self, ident: str) -> str:
|
|
338
|
+
return "`" + ident.replace("`", "``") + "`"
|