al-data-utils 0.3.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.
- al_data_utils/__init__.py +10 -0
- al_data_utils/airflow_utils/__init__.py +13 -0
- al_data_utils/airflow_utils/airflow_utils.py +83 -0
- al_data_utils/azure_as_utils/__init__.py +31 -0
- al_data_utils/azure_as_utils/auth.py +82 -0
- al_data_utils/azure_as_utils/azure_as_utils.py +223 -0
- al_data_utils/azure_as_utils/client.py +158 -0
- al_data_utils/azure_as_utils/errors.py +78 -0
- al_data_utils/azure_as_utils/models.py +265 -0
- al_data_utils/azure_as_utils/poller.py +121 -0
- al_data_utils/backup_utils/__init__.py +9 -0
- al_data_utils/backup_utils/mssql/dacpac/__init__.py +1 -0
- al_data_utils/backup_utils/mssql/dacpac/sqlpackage.py +207 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/__init__.py +8 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/config.py +150 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/emit/__init__.py +11 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/emit/foreign_keys.py +38 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/emit/indexes.py +37 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/emit/sql_objects.py +17 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/emit/tables.py +139 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/introspect/__init__.py +19 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/introspect/foreign_keys.py +173 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/introspect/functions.py +133 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/introspect/indexes.py +190 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/introspect/sequences.py +180 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/introspect/stored_procedures.py +313 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/introspect/synonyms.py +135 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/introspect/tables.py +251 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/introspect/views.py +244 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/io/__init__.py +10 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/io/layout.py +28 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/io/writer.py +40 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/models/__init__.py +14 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/models/foreign_key_def.py +19 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/models/index_def.py +19 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/models/sql_object_def.py +11 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/models/table_column_def.py +27 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/models/table_def.py +22 -0
- al_data_utils/backup_utils/mssql/schema_snapshot/snapshot_processor.py +754 -0
- al_data_utils/backup_utils/mssql/scripter/__init__.py +1 -0
- al_data_utils/config_utils/__init__.py +6 -0
- al_data_utils/config_utils/config_utils.py +31 -0
- al_data_utils/files_sync_utils/__init__.py +15 -0
- al_data_utils/files_sync_utils/files_sync_utils.py +567 -0
- al_data_utils/geo_utils/__init__.py +0 -0
- al_data_utils/git_utils/__init__.py +27 -0
- al_data_utils/git_utils/git_utils.py +142 -0
- al_data_utils/logging_utils/__init__.py +3 -0
- al_data_utils/logging_utils/logging_utils.py +39 -0
- al_data_utils/ones_api_utils/__init__.py +3 -0
- al_data_utils/ones_api_utils/ones_api_utils.py +61 -0
- al_data_utils/sql_utils/__init__.py +3 -0
- al_data_utils/sql_utils/sql_utils.py +158 -0
- al_data_utils/tg_utils/__init__.py +3 -0
- al_data_utils/tg_utils/tg_utils.py +45 -0
- al_data_utils-0.3.1.dist-info/METADATA +157 -0
- al_data_utils-0.3.1.dist-info/RECORD +60 -0
- al_data_utils-0.3.1.dist-info/WHEEL +5 -0
- al_data_utils-0.3.1.dist-info/licenses/LICENSE +21 -0
- al_data_utils-0.3.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from al_data_utils.logging_utils import warning, info
|
|
4
|
+
from al_data_utils.sql_utils.sql_utils import UnifiedSQLClient
|
|
5
|
+
from ..models import SqlObjectDef
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
_SQL_STORED_PROCEDURE_NAMES = r"""
|
|
9
|
+
select
|
|
10
|
+
s.name as schema_name,
|
|
11
|
+
p.name as object_name
|
|
12
|
+
from sys.procedures p
|
|
13
|
+
inner join sys.schemas s
|
|
14
|
+
on s.schema_id = p.schema_id
|
|
15
|
+
where p.is_ms_shipped = 0
|
|
16
|
+
order by s.name, p.name;
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_SQL_STORED_PROCEDURES_BULK = r"""
|
|
21
|
+
select
|
|
22
|
+
s.name as schema_name,
|
|
23
|
+
p.name as object_name,
|
|
24
|
+
convert(nvarchar(max), m.definition) as definition
|
|
25
|
+
from sys.procedures p
|
|
26
|
+
inner join sys.schemas s
|
|
27
|
+
on s.schema_id = p.schema_id
|
|
28
|
+
inner join sys.sql_modules m
|
|
29
|
+
on m.object_id = p.object_id
|
|
30
|
+
where p.is_ms_shipped = 0
|
|
31
|
+
order by s.name, p.name;
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
_SQL_STORED_PROCEDURE_DEFINITION = r"""
|
|
36
|
+
select
|
|
37
|
+
convert(nvarchar(max), object_definition(object_id(quotename(?) + '.' + quotename(?)))) as definition
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _normalized_set(values: set[str] | None) -> set[str]:
|
|
42
|
+
if not values:
|
|
43
|
+
return set()
|
|
44
|
+
return {str(v).strip().lower() for v in values if str(v).strip()}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _split_qualified(values: set[str]) -> list[tuple[str, str]]:
|
|
48
|
+
pairs: list[tuple[str, str]] = []
|
|
49
|
+
for v in sorted(values):
|
|
50
|
+
if '.' not in v:
|
|
51
|
+
continue
|
|
52
|
+
schema, name = v.split('.', 1)
|
|
53
|
+
schema = schema.strip().lower()
|
|
54
|
+
name = name.strip().lower()
|
|
55
|
+
if schema and name:
|
|
56
|
+
pairs.append((schema, name))
|
|
57
|
+
return pairs
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _build_filter_sql_and_params(
|
|
61
|
+
*,
|
|
62
|
+
include_schemas: set[str] | None = None,
|
|
63
|
+
exclude_schemas: set[str] | None = None,
|
|
64
|
+
include_objects: set[str] | None = None,
|
|
65
|
+
exclude_objects: set[str] | None = None,
|
|
66
|
+
) -> tuple[str, dict[str, str]]:
|
|
67
|
+
inc_schemas = sorted(_normalized_set(include_schemas))
|
|
68
|
+
exc_schemas = sorted(_normalized_set(exclude_schemas))
|
|
69
|
+
inc_objects = _split_qualified(_normalized_set(include_objects))
|
|
70
|
+
exc_objects = _split_qualified(_normalized_set(exclude_objects))
|
|
71
|
+
|
|
72
|
+
params: dict[str, str] = {}
|
|
73
|
+
clauses: list[str] = []
|
|
74
|
+
|
|
75
|
+
if inc_schemas:
|
|
76
|
+
names: list[str] = []
|
|
77
|
+
for i, schema in enumerate(inc_schemas):
|
|
78
|
+
key = f'inc_schema_{i}'
|
|
79
|
+
params[key] = schema
|
|
80
|
+
names.append(f':{key}')
|
|
81
|
+
clauses.append(f'lower(s.name) in ({", ".join(names)})')
|
|
82
|
+
|
|
83
|
+
if exc_schemas:
|
|
84
|
+
names = []
|
|
85
|
+
for i, schema in enumerate(exc_schemas):
|
|
86
|
+
key = f'exc_schema_{i}'
|
|
87
|
+
params[key] = schema
|
|
88
|
+
names.append(f':{key}')
|
|
89
|
+
clauses.append(f'lower(s.name) not in ({", ".join(names)})')
|
|
90
|
+
|
|
91
|
+
if inc_objects:
|
|
92
|
+
parts: list[str] = []
|
|
93
|
+
for i, (schema, name) in enumerate(inc_objects):
|
|
94
|
+
sk = f'inc_obj_schema_{i}'
|
|
95
|
+
nk = f'inc_obj_name_{i}'
|
|
96
|
+
params[sk] = schema
|
|
97
|
+
params[nk] = name
|
|
98
|
+
parts.append(f'(lower(s.name) = :{sk} and lower(p.name) = :{nk})')
|
|
99
|
+
clauses.append(f'({" or ".join(parts)})')
|
|
100
|
+
|
|
101
|
+
if exc_objects:
|
|
102
|
+
parts = []
|
|
103
|
+
for i, (schema, name) in enumerate(exc_objects):
|
|
104
|
+
sk = f'exc_obj_schema_{i}'
|
|
105
|
+
nk = f'exc_obj_name_{i}'
|
|
106
|
+
params[sk] = schema
|
|
107
|
+
params[nk] = name
|
|
108
|
+
parts.append(f'(lower(s.name) = :{sk} and lower(p.name) = :{nk})')
|
|
109
|
+
clauses.append(f'not ({" or ".join(parts)})')
|
|
110
|
+
|
|
111
|
+
if not clauses:
|
|
112
|
+
return '', {}
|
|
113
|
+
|
|
114
|
+
return '\n and ' + '\n and '.join(clauses), params
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _inject_filters(sql: str, filters_sql: str) -> str:
|
|
118
|
+
if not filters_sql:
|
|
119
|
+
return sql
|
|
120
|
+
return sql.replace(
|
|
121
|
+
'where p.is_ms_shipped = 0', f'where p.is_ms_shipped = 0{filters_sql}'
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _execute_direct(
|
|
126
|
+
sql_client: UnifiedSQLClient, query: str, params: tuple | None = None
|
|
127
|
+
) -> list[dict[str, object]]:
|
|
128
|
+
"""Execute a query via raw DBAPI cursor and return rows as dicts.
|
|
129
|
+
|
|
130
|
+
This path avoids SQLAlchemy transaction state issues after low-level TDS errors.
|
|
131
|
+
"""
|
|
132
|
+
conn = sql_client.engine.raw_connection()
|
|
133
|
+
cursor = conn.cursor()
|
|
134
|
+
try:
|
|
135
|
+
if params:
|
|
136
|
+
cursor.execute(query, params)
|
|
137
|
+
else:
|
|
138
|
+
cursor.execute(query)
|
|
139
|
+
|
|
140
|
+
columns = [c[0] for c in cursor.description or []]
|
|
141
|
+
rows = cursor.fetchall() if columns else []
|
|
142
|
+
return [dict(zip(columns, row)) for row in rows]
|
|
143
|
+
finally:
|
|
144
|
+
try:
|
|
145
|
+
cursor.close()
|
|
146
|
+
except Exception:
|
|
147
|
+
pass
|
|
148
|
+
try:
|
|
149
|
+
conn.close()
|
|
150
|
+
except Exception:
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def get_stored_procedures(
|
|
155
|
+
sql_client: UnifiedSQLClient,
|
|
156
|
+
*,
|
|
157
|
+
include_schemas: set[str] | None = None,
|
|
158
|
+
exclude_schemas: set[str] | None = None,
|
|
159
|
+
include_objects: set[str] | None = None,
|
|
160
|
+
exclude_objects: set[str] | None = None,
|
|
161
|
+
verbose: bool = True,
|
|
162
|
+
) -> list[SqlObjectDef]:
|
|
163
|
+
_info = info if verbose else (lambda _: None)
|
|
164
|
+
|
|
165
|
+
filters_sql, params = _build_filter_sql_and_params(
|
|
166
|
+
include_schemas=include_schemas,
|
|
167
|
+
exclude_schemas=exclude_schemas,
|
|
168
|
+
include_objects=include_objects,
|
|
169
|
+
exclude_objects=exclude_objects,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
include_pairs = _split_qualified(_normalized_set(include_objects))
|
|
173
|
+
|
|
174
|
+
# Exact include-list mode: fetch only requested procedure definitions.
|
|
175
|
+
if include_pairs:
|
|
176
|
+
total = len(include_pairs)
|
|
177
|
+
_info(
|
|
178
|
+
'[schema_snapshot..] stored procedures discovered: '
|
|
179
|
+
f'{total} (targeted include mode)'
|
|
180
|
+
)
|
|
181
|
+
result: list[SqlObjectDef] = []
|
|
182
|
+
for i, (schema_name, object_name) in enumerate(include_pairs, start=1):
|
|
183
|
+
_info(
|
|
184
|
+
f'[schema_snapshot..] fetching stored procedure definition [{i}/{total}]: '
|
|
185
|
+
f'{schema_name}.{object_name}'
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
def_rows = _execute_direct(
|
|
190
|
+
sql_client,
|
|
191
|
+
_SQL_STORED_PROCEDURE_DEFINITION,
|
|
192
|
+
params=(schema_name, object_name),
|
|
193
|
+
)
|
|
194
|
+
definition = ''
|
|
195
|
+
if def_rows:
|
|
196
|
+
definition = str(def_rows[0].get('definition') or '')
|
|
197
|
+
|
|
198
|
+
if not definition:
|
|
199
|
+
warning(
|
|
200
|
+
'[schema_snapshot..] stored procedure definition is empty '
|
|
201
|
+
f'for {schema_name}.{object_name} (not found or encrypted); skipping'
|
|
202
|
+
)
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
result.append(
|
|
206
|
+
SqlObjectDef(
|
|
207
|
+
schema_name=schema_name,
|
|
208
|
+
object_name=object_name,
|
|
209
|
+
definition=definition,
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
except Exception as one_err:
|
|
213
|
+
warning(
|
|
214
|
+
'[schema_snapshot..] failed to extract stored procedure '
|
|
215
|
+
f'[{i}/{total}] {schema_name}.{object_name}; skipping ({one_err})'
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return result
|
|
219
|
+
|
|
220
|
+
# Fast path: single bulk query for all procedure definitions.
|
|
221
|
+
# Falls back to per-procedure extraction only if the bulk query fails.
|
|
222
|
+
try:
|
|
223
|
+
rows = sql_client.execute_command_with_result_to_dicts(
|
|
224
|
+
_inject_filters(_SQL_STORED_PROCEDURES_BULK, filters_sql), params
|
|
225
|
+
)
|
|
226
|
+
total = len(rows)
|
|
227
|
+
_info(f'[schema_snapshot..] stored procedures discovered: {total} (bulk mode)')
|
|
228
|
+
|
|
229
|
+
result: list[SqlObjectDef] = []
|
|
230
|
+
for i, r in enumerate(rows, start=1):
|
|
231
|
+
schema_name = str(r['schema_name'])
|
|
232
|
+
object_name = str(r['object_name'])
|
|
233
|
+
definition = str(r.get('definition') or '')
|
|
234
|
+
|
|
235
|
+
if not definition:
|
|
236
|
+
warning(
|
|
237
|
+
'[schema_snapshot..] stored procedure definition is empty '
|
|
238
|
+
f'for {schema_name}.{object_name} (possibly encrypted); skipping'
|
|
239
|
+
)
|
|
240
|
+
continue
|
|
241
|
+
|
|
242
|
+
result.append(
|
|
243
|
+
SqlObjectDef(
|
|
244
|
+
schema_name=schema_name,
|
|
245
|
+
object_name=object_name,
|
|
246
|
+
definition=definition,
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
_info(
|
|
251
|
+
f'[schema_snapshot..] extracted stored procedure [{i}/{total}]: '
|
|
252
|
+
f'{schema_name}.{object_name}'
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
return result
|
|
256
|
+
except Exception as bulk_err:
|
|
257
|
+
warning(
|
|
258
|
+
'[schema_snapshot..] bulk stored procedure extraction failed; '
|
|
259
|
+
f'falling back to per-procedure mode ({bulk_err})'
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Fetch names first, then definition one by one.
|
|
263
|
+
# This avoids failing the whole snapshot because of one problematic procedure.
|
|
264
|
+
result: list[SqlObjectDef] = []
|
|
265
|
+
name_rows = sql_client.execute_command_with_result_to_dicts(
|
|
266
|
+
_inject_filters(_SQL_STORED_PROCEDURE_NAMES, filters_sql), params
|
|
267
|
+
)
|
|
268
|
+
total = len(name_rows)
|
|
269
|
+
_info(
|
|
270
|
+
f'[schema_snapshot..] stored procedures discovered: {total} (per-procedure mode)'
|
|
271
|
+
)
|
|
272
|
+
for i, r in enumerate(name_rows, start=1):
|
|
273
|
+
schema_name = str(r['schema_name'])
|
|
274
|
+
object_name = str(r['object_name'])
|
|
275
|
+
_info(
|
|
276
|
+
f'[schema_snapshot..] fetching stored procedure definition [{i}/{total}]: '
|
|
277
|
+
f'{schema_name}.{object_name}'
|
|
278
|
+
)
|
|
279
|
+
try:
|
|
280
|
+
def_rows = _execute_direct(
|
|
281
|
+
sql_client,
|
|
282
|
+
_SQL_STORED_PROCEDURE_DEFINITION,
|
|
283
|
+
params=(schema_name, object_name),
|
|
284
|
+
)
|
|
285
|
+
definition = ''
|
|
286
|
+
if def_rows:
|
|
287
|
+
definition = str(def_rows[0].get('definition') or '')
|
|
288
|
+
|
|
289
|
+
if not definition:
|
|
290
|
+
warning(
|
|
291
|
+
'[schema_snapshot..] stored procedure definition is empty '
|
|
292
|
+
f'for {schema_name}.{object_name} (possibly encrypted); skipping'
|
|
293
|
+
)
|
|
294
|
+
continue
|
|
295
|
+
|
|
296
|
+
result.append(
|
|
297
|
+
SqlObjectDef(
|
|
298
|
+
schema_name=schema_name,
|
|
299
|
+
object_name=object_name,
|
|
300
|
+
definition=definition,
|
|
301
|
+
)
|
|
302
|
+
)
|
|
303
|
+
_info(
|
|
304
|
+
f'[schema_snapshot..] extracted stored procedure [{i}/{total}]: '
|
|
305
|
+
f'{schema_name}.{object_name}'
|
|
306
|
+
)
|
|
307
|
+
except Exception as one_err:
|
|
308
|
+
warning(
|
|
309
|
+
'[schema_snapshot..] failed to extract stored procedure '
|
|
310
|
+
f'[{i}/{total}] {schema_name}.{object_name}; skipping ({one_err})'
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
return result
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from al_data_utils.sql_utils.sql_utils import UnifiedSQLClient
|
|
4
|
+
from ..models import SqlObjectDef
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
_SQL_SYNONYMS = r"""
|
|
8
|
+
select
|
|
9
|
+
s.name as schema_name,
|
|
10
|
+
sn.name as object_name,
|
|
11
|
+
sn.base_object_name
|
|
12
|
+
from sys.synonyms sn
|
|
13
|
+
inner join sys.schemas s
|
|
14
|
+
on s.schema_id = sn.schema_id
|
|
15
|
+
order by s.name, sn.name;
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _normalized_set(values: set[str] | None) -> set[str]:
|
|
20
|
+
if not values:
|
|
21
|
+
return set()
|
|
22
|
+
return {str(v).strip().lower() for v in values if str(v).strip()}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _split_qualified(values: set[str]) -> list[tuple[str, str]]:
|
|
26
|
+
pairs: list[tuple[str, str]] = []
|
|
27
|
+
for v in sorted(values):
|
|
28
|
+
if '.' not in v:
|
|
29
|
+
continue
|
|
30
|
+
schema, name = v.split('.', 1)
|
|
31
|
+
schema = schema.strip().lower()
|
|
32
|
+
name = name.strip().lower()
|
|
33
|
+
if schema and name:
|
|
34
|
+
pairs.append((schema, name))
|
|
35
|
+
return pairs
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _build_filter_sql_and_params(
|
|
39
|
+
*,
|
|
40
|
+
include_schemas: set[str] | None = None,
|
|
41
|
+
exclude_schemas: set[str] | None = None,
|
|
42
|
+
include_objects: set[str] | None = None,
|
|
43
|
+
exclude_objects: set[str] | None = None,
|
|
44
|
+
) -> tuple[str, dict[str, str]]:
|
|
45
|
+
inc_schemas = sorted(_normalized_set(include_schemas))
|
|
46
|
+
exc_schemas = sorted(_normalized_set(exclude_schemas))
|
|
47
|
+
inc_objects = _split_qualified(_normalized_set(include_objects))
|
|
48
|
+
exc_objects = _split_qualified(_normalized_set(exclude_objects))
|
|
49
|
+
|
|
50
|
+
params: dict[str, str] = {}
|
|
51
|
+
clauses: list[str] = []
|
|
52
|
+
|
|
53
|
+
if inc_schemas:
|
|
54
|
+
names: list[str] = []
|
|
55
|
+
for i, schema in enumerate(inc_schemas):
|
|
56
|
+
key = f'inc_schema_{i}'
|
|
57
|
+
params[key] = schema
|
|
58
|
+
names.append(f':{key}')
|
|
59
|
+
clauses.append(f'lower(s.name) in ({", ".join(names)})')
|
|
60
|
+
|
|
61
|
+
if exc_schemas:
|
|
62
|
+
names = []
|
|
63
|
+
for i, schema in enumerate(exc_schemas):
|
|
64
|
+
key = f'exc_schema_{i}'
|
|
65
|
+
params[key] = schema
|
|
66
|
+
names.append(f':{key}')
|
|
67
|
+
clauses.append(f'lower(s.name) not in ({", ".join(names)})')
|
|
68
|
+
|
|
69
|
+
if inc_objects:
|
|
70
|
+
parts: list[str] = []
|
|
71
|
+
for i, (schema, name) in enumerate(inc_objects):
|
|
72
|
+
sk = f'inc_obj_schema_{i}'
|
|
73
|
+
nk = f'inc_obj_name_{i}'
|
|
74
|
+
params[sk] = schema
|
|
75
|
+
params[nk] = name
|
|
76
|
+
parts.append(f'(lower(s.name) = :{sk} and lower(sn.name) = :{nk})')
|
|
77
|
+
clauses.append(f'({" or ".join(parts)})')
|
|
78
|
+
|
|
79
|
+
if exc_objects:
|
|
80
|
+
parts = []
|
|
81
|
+
for i, (schema, name) in enumerate(exc_objects):
|
|
82
|
+
sk = f'exc_obj_schema_{i}'
|
|
83
|
+
nk = f'exc_obj_name_{i}'
|
|
84
|
+
params[sk] = schema
|
|
85
|
+
params[nk] = name
|
|
86
|
+
parts.append(f'(lower(s.name) = :{sk} and lower(sn.name) = :{nk})')
|
|
87
|
+
clauses.append(f'not ({" or ".join(parts)})')
|
|
88
|
+
|
|
89
|
+
if not clauses:
|
|
90
|
+
return '', {}
|
|
91
|
+
|
|
92
|
+
return '\nwhere ' + '\n and '.join(clauses), params
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _inject_filters(sql: str, filters_sql: str) -> str:
|
|
96
|
+
if not filters_sql:
|
|
97
|
+
return sql
|
|
98
|
+
return sql.replace(
|
|
99
|
+
'order by s.name, sn.name;', f'{filters_sql}\norder by s.name, sn.name;'
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_synonyms(
|
|
104
|
+
sql_client: UnifiedSQLClient,
|
|
105
|
+
*,
|
|
106
|
+
include_schemas: set[str] | None = None,
|
|
107
|
+
exclude_schemas: set[str] | None = None,
|
|
108
|
+
include_objects: set[str] | None = None,
|
|
109
|
+
exclude_objects: set[str] | None = None,
|
|
110
|
+
) -> list[SqlObjectDef]:
|
|
111
|
+
filters_sql, params = _build_filter_sql_and_params(
|
|
112
|
+
include_schemas=include_schemas,
|
|
113
|
+
exclude_schemas=exclude_schemas,
|
|
114
|
+
include_objects=include_objects,
|
|
115
|
+
exclude_objects=exclude_objects,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
rows = sql_client.execute_command_with_result_to_dicts(
|
|
119
|
+
_inject_filters(_SQL_SYNONYMS, filters_sql), params
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
result: list[SqlObjectDef] = []
|
|
123
|
+
for r in rows:
|
|
124
|
+
schema_name = str(r['schema_name'])
|
|
125
|
+
object_name = str(r['object_name'])
|
|
126
|
+
base_object_name = str(r.get('base_object_name') or '')
|
|
127
|
+
definition = (
|
|
128
|
+
f'create synonym {schema_name}.{object_name} for {base_object_name}'
|
|
129
|
+
)
|
|
130
|
+
result.append(
|
|
131
|
+
SqlObjectDef(
|
|
132
|
+
schema_name=schema_name, object_name=object_name, definition=definition
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
return result
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
|
|
5
|
+
from al_data_utils.sql_utils.sql_utils import UnifiedSQLClient
|
|
6
|
+
from ..models import TableColumnDef, TableDef, PrimaryKeyDef
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_SQL_TABLE_COLUMNS = r"""
|
|
10
|
+
select
|
|
11
|
+
s.name as schema_name,
|
|
12
|
+
t.name as table_name,
|
|
13
|
+
c.name as column_name,
|
|
14
|
+
c.column_id as ordinal_position,
|
|
15
|
+
ty.name as data_type,
|
|
16
|
+
cast(c.max_length as int) as max_length,
|
|
17
|
+
cast(c.precision as int) as precision,
|
|
18
|
+
cast(c.scale as int) as scale,
|
|
19
|
+
cast(c.is_nullable as bit) as is_nullable,
|
|
20
|
+
cast(c.is_identity as bit) as is_identity,
|
|
21
|
+
cast(ic.seed_value as int) as identity_seed,
|
|
22
|
+
cast(ic.increment_value as int) as identity_increment,
|
|
23
|
+
cast(c.is_computed as bit) as is_computed,
|
|
24
|
+
cc.definition as computed_definition,
|
|
25
|
+
c.collation_name,
|
|
26
|
+
dc.name as default_constraint_name,
|
|
27
|
+
dc.definition as default_definition
|
|
28
|
+
from sys.tables t
|
|
29
|
+
inner join sys.schemas s
|
|
30
|
+
on s.schema_id = t.schema_id
|
|
31
|
+
inner join sys.columns c
|
|
32
|
+
on c.object_id = t.object_id
|
|
33
|
+
inner join sys.types ty
|
|
34
|
+
on ty.user_type_id = c.user_type_id
|
|
35
|
+
left outer join sys.identity_columns ic
|
|
36
|
+
on ic.object_id = c.object_id
|
|
37
|
+
and ic.column_id = c.column_id
|
|
38
|
+
left outer join sys.computed_columns cc
|
|
39
|
+
on cc.object_id = c.object_id
|
|
40
|
+
and cc.column_id = c.column_id
|
|
41
|
+
left outer join sys.default_constraints dc
|
|
42
|
+
on dc.parent_object_id = c.object_id
|
|
43
|
+
and dc.parent_column_id = c.column_id
|
|
44
|
+
where t.is_ms_shipped = 0
|
|
45
|
+
order by s.name, t.name, c.column_id;
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
_SQL_PRIMARY_KEYS = r"""
|
|
50
|
+
select
|
|
51
|
+
s.name as schema_name,
|
|
52
|
+
t.name as table_name,
|
|
53
|
+
kc.name as constraint_name,
|
|
54
|
+
i.type_desc,
|
|
55
|
+
stuff((
|
|
56
|
+
select
|
|
57
|
+
', ' + quotename(c.name) + case when ic2.is_descending_key = 1 then ' desc' else ' asc' end
|
|
58
|
+
from sys.index_columns ic2
|
|
59
|
+
inner join sys.columns c
|
|
60
|
+
on c.object_id = ic2.object_id and c.column_id = ic2.column_id
|
|
61
|
+
where ic2.object_id = i.object_id
|
|
62
|
+
and ic2.index_id = i.index_id
|
|
63
|
+
and ic2.key_ordinal > 0
|
|
64
|
+
order by ic2.key_ordinal
|
|
65
|
+
for xml path(''), type
|
|
66
|
+
).value('.', 'nvarchar(max)'), 1, 2, '') as key_columns
|
|
67
|
+
from sys.key_constraints kc
|
|
68
|
+
inner join sys.tables t
|
|
69
|
+
on t.object_id = kc.parent_object_id
|
|
70
|
+
inner join sys.schemas s
|
|
71
|
+
on s.schema_id = t.schema_id
|
|
72
|
+
inner join sys.indexes i
|
|
73
|
+
on i.object_id = kc.parent_object_id
|
|
74
|
+
and i.index_id = kc.unique_index_id
|
|
75
|
+
where kc.type = 'PK'
|
|
76
|
+
order by s.name, t.name;
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _normalized_set(values: set[str] | None) -> set[str]:
|
|
81
|
+
if not values:
|
|
82
|
+
return set()
|
|
83
|
+
return {str(v).strip().lower() for v in values if str(v).strip()}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _split_qualified(values: set[str]) -> list[tuple[str, str]]:
|
|
87
|
+
pairs: list[tuple[str, str]] = []
|
|
88
|
+
for v in sorted(values):
|
|
89
|
+
if '.' not in v:
|
|
90
|
+
continue
|
|
91
|
+
schema, table = v.split('.', 1)
|
|
92
|
+
schema = schema.strip().lower()
|
|
93
|
+
table = table.strip().lower()
|
|
94
|
+
if schema and table:
|
|
95
|
+
pairs.append((schema, table))
|
|
96
|
+
return pairs
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _build_table_filter_sql_and_params(
|
|
100
|
+
*,
|
|
101
|
+
include_schemas: set[str] | None = None,
|
|
102
|
+
exclude_schemas: set[str] | None = None,
|
|
103
|
+
include_tables: set[str] | None = None,
|
|
104
|
+
exclude_tables: set[str] | None = None,
|
|
105
|
+
) -> tuple[str, dict[str, str]]:
|
|
106
|
+
inc_schemas = sorted(_normalized_set(include_schemas))
|
|
107
|
+
exc_schemas = sorted(_normalized_set(exclude_schemas))
|
|
108
|
+
inc_tables = _split_qualified(_normalized_set(include_tables))
|
|
109
|
+
exc_tables = _split_qualified(_normalized_set(exclude_tables))
|
|
110
|
+
|
|
111
|
+
params: dict[str, str] = {}
|
|
112
|
+
clauses: list[str] = []
|
|
113
|
+
|
|
114
|
+
if inc_schemas:
|
|
115
|
+
names: list[str] = []
|
|
116
|
+
for i, schema in enumerate(inc_schemas):
|
|
117
|
+
key = f'inc_schema_{i}'
|
|
118
|
+
params[key] = schema
|
|
119
|
+
names.append(f':{key}')
|
|
120
|
+
clauses.append(f'lower(s.name) in ({", ".join(names)})')
|
|
121
|
+
|
|
122
|
+
if exc_schemas:
|
|
123
|
+
names = []
|
|
124
|
+
for i, schema in enumerate(exc_schemas):
|
|
125
|
+
key = f'exc_schema_{i}'
|
|
126
|
+
params[key] = schema
|
|
127
|
+
names.append(f':{key}')
|
|
128
|
+
clauses.append(f'lower(s.name) not in ({", ".join(names)})')
|
|
129
|
+
|
|
130
|
+
if inc_tables:
|
|
131
|
+
parts: list[str] = []
|
|
132
|
+
for i, (schema, table) in enumerate(inc_tables):
|
|
133
|
+
sk = f'inc_tbl_schema_{i}'
|
|
134
|
+
tk = f'inc_tbl_name_{i}'
|
|
135
|
+
params[sk] = schema
|
|
136
|
+
params[tk] = table
|
|
137
|
+
parts.append(f'(lower(s.name) = :{sk} and lower(t.name) = :{tk})')
|
|
138
|
+
clauses.append(f'({" or ".join(parts)})')
|
|
139
|
+
|
|
140
|
+
if exc_tables:
|
|
141
|
+
parts = []
|
|
142
|
+
for i, (schema, table) in enumerate(exc_tables):
|
|
143
|
+
sk = f'exc_tbl_schema_{i}'
|
|
144
|
+
tk = f'exc_tbl_name_{i}'
|
|
145
|
+
params[sk] = schema
|
|
146
|
+
params[tk] = table
|
|
147
|
+
parts.append(f'(lower(s.name) = :{sk} and lower(t.name) = :{tk})')
|
|
148
|
+
clauses.append(f'not ({" or ".join(parts)})')
|
|
149
|
+
|
|
150
|
+
if not clauses:
|
|
151
|
+
return '', {}
|
|
152
|
+
|
|
153
|
+
return '\n and ' + '\n and '.join(clauses), params
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _inject_table_filters(sql: str, filters_sql: str) -> str:
|
|
157
|
+
if not filters_sql:
|
|
158
|
+
return sql
|
|
159
|
+
if 'where t.is_ms_shipped = 0' in sql:
|
|
160
|
+
return sql.replace(
|
|
161
|
+
'where t.is_ms_shipped = 0', f'where t.is_ms_shipped = 0{filters_sql}'
|
|
162
|
+
)
|
|
163
|
+
if "where kc.type = 'PK'" in sql:
|
|
164
|
+
return sql.replace("where kc.type = 'PK'", f"where kc.type = 'PK'{filters_sql}")
|
|
165
|
+
return sql
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def get_tables(
|
|
169
|
+
sql_client: UnifiedSQLClient,
|
|
170
|
+
*,
|
|
171
|
+
include_schemas: set[str] | None = None,
|
|
172
|
+
exclude_schemas: set[str] | None = None,
|
|
173
|
+
include_tables: set[str] | None = None,
|
|
174
|
+
exclude_tables: set[str] | None = None,
|
|
175
|
+
) -> list[TableDef]:
|
|
176
|
+
filters_sql, params = _build_table_filter_sql_and_params(
|
|
177
|
+
include_schemas=include_schemas,
|
|
178
|
+
exclude_schemas=exclude_schemas,
|
|
179
|
+
include_tables=include_tables,
|
|
180
|
+
exclude_tables=exclude_tables,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
col_rows = sql_client.execute_command_with_result_to_dicts(
|
|
184
|
+
_inject_table_filters(_SQL_TABLE_COLUMNS, filters_sql), params
|
|
185
|
+
)
|
|
186
|
+
pk_rows = sql_client.execute_command_with_result_to_dicts(
|
|
187
|
+
_inject_table_filters(_SQL_PRIMARY_KEYS, filters_sql), params
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
columns_by_table: dict[tuple[str, str], list[TableColumnDef]] = defaultdict(list)
|
|
191
|
+
for r in col_rows:
|
|
192
|
+
col = TableColumnDef(
|
|
193
|
+
schema_name=str(r['schema_name']),
|
|
194
|
+
table_name=str(r['table_name']),
|
|
195
|
+
column_name=str(r['column_name']),
|
|
196
|
+
ordinal_position=int(r['ordinal_position']),
|
|
197
|
+
data_type=str(r['data_type']),
|
|
198
|
+
max_length=None if r['max_length'] is None else int(r['max_length']),
|
|
199
|
+
precision=None if r['precision'] is None else int(r['precision']),
|
|
200
|
+
scale=None if r['scale'] is None else int(r['scale']),
|
|
201
|
+
is_nullable=bool(r['is_nullable']),
|
|
202
|
+
is_identity=bool(r['is_identity']),
|
|
203
|
+
identity_seed=None
|
|
204
|
+
if r['identity_seed'] is None
|
|
205
|
+
else int(r['identity_seed']),
|
|
206
|
+
identity_increment=None
|
|
207
|
+
if r['identity_increment'] is None
|
|
208
|
+
else int(r['identity_increment']),
|
|
209
|
+
is_computed=bool(r['is_computed']),
|
|
210
|
+
computed_definition=None
|
|
211
|
+
if r['computed_definition'] is None
|
|
212
|
+
else str(r['computed_definition']),
|
|
213
|
+
collation_name=None
|
|
214
|
+
if r['collation_name'] is None
|
|
215
|
+
else str(r['collation_name']),
|
|
216
|
+
default_constraint_name=None
|
|
217
|
+
if r.get('default_constraint_name') is None
|
|
218
|
+
else str(r.get('default_constraint_name')),
|
|
219
|
+
default_definition=None
|
|
220
|
+
if r['default_definition'] is None
|
|
221
|
+
else str(r['default_definition']),
|
|
222
|
+
)
|
|
223
|
+
columns_by_table[(col.schema_name, col.table_name)].append(col)
|
|
224
|
+
|
|
225
|
+
pk_by_table: dict[tuple[str, str], PrimaryKeyDef] = {}
|
|
226
|
+
for r in pk_rows:
|
|
227
|
+
pk = PrimaryKeyDef(
|
|
228
|
+
schema_name=str(r['schema_name']),
|
|
229
|
+
table_name=str(r['table_name']),
|
|
230
|
+
constraint_name=str(r['constraint_name']),
|
|
231
|
+
key_columns=str(r['key_columns'] or ''),
|
|
232
|
+
type_desc=str(r['type_desc'] or ''),
|
|
233
|
+
)
|
|
234
|
+
pk_by_table[(pk.schema_name, pk.table_name)] = pk
|
|
235
|
+
|
|
236
|
+
result: list[TableDef] = []
|
|
237
|
+
for key in sorted(
|
|
238
|
+
columns_by_table.keys(), key=lambda x: (x[0].lower(), x[1].lower())
|
|
239
|
+
):
|
|
240
|
+
schema_name, table_name = key
|
|
241
|
+
cols = sorted(columns_by_table[key], key=lambda c: c.ordinal_position)
|
|
242
|
+
result.append(
|
|
243
|
+
TableDef(
|
|
244
|
+
schema_name=schema_name,
|
|
245
|
+
table_name=table_name,
|
|
246
|
+
columns=cols,
|
|
247
|
+
primary_key=pk_by_table.get((schema_name, table_name)),
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
return result
|