dbtk 0.8.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.
- dbtk/__init__.py +54 -0
- dbtk/cli.py +169 -0
- dbtk/config.py +1194 -0
- dbtk/cursors.py +566 -0
- dbtk/database.py +959 -0
- dbtk/dbtk_sample.yml +90 -0
- dbtk/defaults.py +29 -0
- dbtk/etl/__init__.py +61 -0
- dbtk/etl/base_surge.py +257 -0
- dbtk/etl/bulk_surge.py +783 -0
- dbtk/etl/config_generators.py +458 -0
- dbtk/etl/data_surge.py +294 -0
- dbtk/etl/managers.py +734 -0
- dbtk/etl/table.py +1215 -0
- dbtk/etl/transforms/__init__.py +107 -0
- dbtk/etl/transforms/address.py +560 -0
- dbtk/etl/transforms/core.py +594 -0
- dbtk/etl/transforms/database.py +506 -0
- dbtk/etl/transforms/datetime.py +497 -0
- dbtk/etl/transforms/email.py +72 -0
- dbtk/etl/transforms/phone.py +670 -0
- dbtk/formats/__init__.py +18 -0
- dbtk/formats/edi.py +182 -0
- dbtk/logging_utils.py +310 -0
- dbtk/readers/__init__.py +26 -0
- dbtk/readers/base.py +644 -0
- dbtk/readers/csv.py +195 -0
- dbtk/readers/data_frame.py +119 -0
- dbtk/readers/excel.py +260 -0
- dbtk/readers/fixed_width.py +359 -0
- dbtk/readers/json.py +323 -0
- dbtk/readers/utils.py +394 -0
- dbtk/readers/xml.py +260 -0
- dbtk/record.py +710 -0
- dbtk/utils.py +537 -0
- dbtk/writers/__init__.py +46 -0
- dbtk/writers/base.py +718 -0
- dbtk/writers/csv.py +107 -0
- dbtk/writers/database.py +158 -0
- dbtk/writers/excel.py +1086 -0
- dbtk/writers/fixed_width.py +290 -0
- dbtk/writers/json.py +156 -0
- dbtk/writers/utils.py +41 -0
- dbtk/writers/xml.py +387 -0
- dbtk-0.8.0.dist-info/METADATA +305 -0
- dbtk-0.8.0.dist-info/RECORD +50 -0
- dbtk-0.8.0.dist-info/WHEEL +5 -0
- dbtk-0.8.0.dist-info/entry_points.txt +2 -0
- dbtk-0.8.0.dist-info/licenses/LICENSE.txt +7 -0
- dbtk-0.8.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
# dbtk/etl/config_generators.py
|
|
2
|
+
"""
|
|
3
|
+
Generate column definitions for Table class from database schema.
|
|
4
|
+
|
|
5
|
+
Extracts table metadata and formats it as Python dictionary code
|
|
6
|
+
that can be copied into Table() constructor calls.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Dict, Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def column_defs_from_db(cursor, table_name: str, add_comments: bool = False) -> str:
|
|
13
|
+
"""
|
|
14
|
+
Generate column definitions from database table schema.
|
|
15
|
+
|
|
16
|
+
Inspects the database table structure and returns a Python dictionary
|
|
17
|
+
string representation of column configurations ready to use with the
|
|
18
|
+
Table class.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
cursor: Database cursor (any cursor type)
|
|
22
|
+
table_name: Name of table to analyze (supports schema.table format)
|
|
23
|
+
add_comments: Include table/column comments from database metadata
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
String containing Python dict of column definitions
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> print(column_defs_from_db(cursor, 'users'))
|
|
30
|
+
{
|
|
31
|
+
'id': {'field': 'id', 'primary_key': True},
|
|
32
|
+
'name': {'field': 'name', 'nullable': False},
|
|
33
|
+
'email': {'field': 'email'},
|
|
34
|
+
'created_at': {'db_fn': 'CURRENT_TIMESTAMP'}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
>>> # Copy output into your code:
|
|
38
|
+
>>> table = Table('users', columns={
|
|
39
|
+
... 'id': {'field': 'id', 'primary_key': True},
|
|
40
|
+
... 'name': {'field': 'name', 'nullable': False},
|
|
41
|
+
... 'email': {'field': 'email'},
|
|
42
|
+
... 'created_at': {'db_fn': 'CURRENT_TIMESTAMP'}
|
|
43
|
+
... }, cursor=cursor)
|
|
44
|
+
"""
|
|
45
|
+
db_type = cursor.connection.database_type
|
|
46
|
+
|
|
47
|
+
# Dispatch to database-specific metadata extractor
|
|
48
|
+
if db_type == 'oracle':
|
|
49
|
+
metadata = _get_oracle_metadata(cursor, table_name, add_comments)
|
|
50
|
+
elif db_type == 'postgres':
|
|
51
|
+
metadata = _get_postgres_metadata(cursor, table_name, add_comments)
|
|
52
|
+
elif db_type == 'mysql':
|
|
53
|
+
metadata = _get_mysql_metadata(cursor, table_name, add_comments)
|
|
54
|
+
elif db_type in ('sqlserver', 'mssql'):
|
|
55
|
+
metadata = _get_sqlserver_metadata(cursor, table_name, add_comments)
|
|
56
|
+
else:
|
|
57
|
+
raise ValueError(f"Column generation not supported for database type: {db_type}")
|
|
58
|
+
|
|
59
|
+
# Format metadata dict as Python code string
|
|
60
|
+
return _format_columns_dict(metadata, add_comments)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _format_columns_dict(metadata: Dict[str, Any], add_comments: bool) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Format metadata dictionary as Python code string.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
metadata: Dict with 'columns', 'table_comment', 'column_comments' keys
|
|
69
|
+
add_comments: Whether to include comments in output
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Formatted Python dictionary string
|
|
73
|
+
"""
|
|
74
|
+
lines = []
|
|
75
|
+
|
|
76
|
+
# Add table comment if present
|
|
77
|
+
if add_comments and metadata.get('table_comment'):
|
|
78
|
+
lines.append(f"# {metadata['table_comment']}")
|
|
79
|
+
|
|
80
|
+
lines.append("{")
|
|
81
|
+
|
|
82
|
+
for col_name, col_def in metadata['columns'].items():
|
|
83
|
+
# Add column comment if present
|
|
84
|
+
if add_comments and col_name in metadata.get('column_comments', {}):
|
|
85
|
+
comment = metadata['column_comments'][col_name]
|
|
86
|
+
lines.append(f" # {comment}")
|
|
87
|
+
|
|
88
|
+
# Build column definition
|
|
89
|
+
parts = []
|
|
90
|
+
for key, value in col_def.items():
|
|
91
|
+
if isinstance(value, str):
|
|
92
|
+
# String values need quotes
|
|
93
|
+
parts.append(f"'{key}': '{value}'")
|
|
94
|
+
elif isinstance(value, bool):
|
|
95
|
+
# Boolean values
|
|
96
|
+
parts.append(f"'{key}': {value}")
|
|
97
|
+
else:
|
|
98
|
+
# Numbers or other types
|
|
99
|
+
parts.append(f"'{key}': {value}")
|
|
100
|
+
|
|
101
|
+
col_str = "{" + ", ".join(parts) + "}"
|
|
102
|
+
lines.append(f" '{col_name}': {col_str},")
|
|
103
|
+
|
|
104
|
+
lines.append("}")
|
|
105
|
+
|
|
106
|
+
return "\n".join(lines)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _get_oracle_metadata(cursor, table_name: str, add_comments: bool = False) -> Dict[str, Any]:
|
|
110
|
+
"""Extract table and column metadata from Oracle database."""
|
|
111
|
+
table_name = table_name.upper()
|
|
112
|
+
tab_info = table_name.split('.')
|
|
113
|
+
schema_name = None
|
|
114
|
+
if len(tab_info) == 2:
|
|
115
|
+
schema_name = tab_info[0]
|
|
116
|
+
table_name = tab_info[1]
|
|
117
|
+
|
|
118
|
+
# Get table comment if requested
|
|
119
|
+
table_comment = None
|
|
120
|
+
if add_comments:
|
|
121
|
+
cmt_query = '''SELECT cmt.comments FROM all_tab_comments cmt
|
|
122
|
+
WHERE cmt.table_name = :table_name AND cmt.owner = COALESCE(:schema_name, cmt.owner)'''
|
|
123
|
+
cursor.execute(cmt_query, {'table_name': table_name, 'schema_name': schema_name})
|
|
124
|
+
row = cursor.fetchone()
|
|
125
|
+
if row and row[0]:
|
|
126
|
+
table_comment = row[0]
|
|
127
|
+
|
|
128
|
+
# Query column information
|
|
129
|
+
col_query = '''
|
|
130
|
+
SELECT LOWER(atc.column_name) column_name, atc.data_type, atc.nullable,
|
|
131
|
+
CASE WHEN pkc.position IS NOT NULL THEN 'Y' ELSE 'N' END key_column,
|
|
132
|
+
cc.comments
|
|
133
|
+
FROM all_tab_cols atc
|
|
134
|
+
LEFT JOIN all_constraints pk ON atc.owner = pk.owner
|
|
135
|
+
AND atc.table_name = pk.table_name
|
|
136
|
+
AND pk.constraint_type = 'P'
|
|
137
|
+
LEFT JOIN all_col_comments cc ON atc.owner = cc.owner
|
|
138
|
+
AND atc.table_name = cc.table_name
|
|
139
|
+
AND atc.column_name = cc.column_name
|
|
140
|
+
LEFT JOIN all_cons_columns pkc ON atc.owner = pkc.owner
|
|
141
|
+
AND atc.table_name = pkc.table_name
|
|
142
|
+
AND atc.column_name = pkc.column_name
|
|
143
|
+
AND pk.constraint_name = pkc.constraint_name
|
|
144
|
+
WHERE atc.table_name = :table_name
|
|
145
|
+
AND atc.owner = COALESCE(:schema_name, atc.owner)
|
|
146
|
+
AND atc.virtual_column = 'NO'
|
|
147
|
+
ORDER BY atc.column_id
|
|
148
|
+
'''
|
|
149
|
+
|
|
150
|
+
cursor.execute(col_query, {'table_name': table_name, 'schema_name': schema_name})
|
|
151
|
+
|
|
152
|
+
columns = {}
|
|
153
|
+
column_comments = {}
|
|
154
|
+
|
|
155
|
+
for row in cursor:
|
|
156
|
+
col_name = row[0]
|
|
157
|
+
data_type = row[1]
|
|
158
|
+
is_nullable = row[2]
|
|
159
|
+
is_key = row[3]
|
|
160
|
+
comment = row[4]
|
|
161
|
+
|
|
162
|
+
# Store comment if present
|
|
163
|
+
if add_comments and comment:
|
|
164
|
+
column_comments[col_name] = comment
|
|
165
|
+
|
|
166
|
+
# Build column config
|
|
167
|
+
col_config = {'field': col_name}
|
|
168
|
+
|
|
169
|
+
# Add transform function based on data type
|
|
170
|
+
if data_type == 'DATE':
|
|
171
|
+
col_config['fn'] = 'parse_datetime' # Oracle DATE includes time
|
|
172
|
+
elif data_type in ('TIMESTAMP', 'TIMESTAMP WITH TIME ZONE', 'TIMESTAMP WITH LOCAL TIME ZONE'):
|
|
173
|
+
col_config['fn'] = 'parse_timestamp'
|
|
174
|
+
|
|
175
|
+
# Add primary key or nullable constraint
|
|
176
|
+
if is_key == 'Y':
|
|
177
|
+
col_config['primary_key'] = True
|
|
178
|
+
elif is_nullable == 'N':
|
|
179
|
+
col_config['nullable'] = False
|
|
180
|
+
|
|
181
|
+
columns[col_name] = col_config
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
'name': table_name,
|
|
185
|
+
'columns': columns,
|
|
186
|
+
'table_comment': table_comment,
|
|
187
|
+
'column_comments': column_comments
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _get_postgres_metadata(cursor, table_name: str, add_comments: bool = False) -> Dict[str, Any]:
|
|
192
|
+
"""Extract table and column metadata from PostgreSQL database."""
|
|
193
|
+
tab_info = table_name.lower().split('.')
|
|
194
|
+
schema = None
|
|
195
|
+
if len(tab_info) == 2:
|
|
196
|
+
schema = tab_info[0]
|
|
197
|
+
table_name = tab_info[1]
|
|
198
|
+
|
|
199
|
+
# Get table comment if requested
|
|
200
|
+
table_comment = None
|
|
201
|
+
if add_comments:
|
|
202
|
+
cmt_query = '''
|
|
203
|
+
SELECT obj_description(c.oid) as comments
|
|
204
|
+
FROM pg_class c
|
|
205
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
206
|
+
WHERE c.relname = %(table_name)s
|
|
207
|
+
AND n.nspname = COALESCE(%(schema)s, n.nspname)
|
|
208
|
+
'''
|
|
209
|
+
cursor.execute(cmt_query, {'table_name': table_name, 'schema': schema})
|
|
210
|
+
row = cursor.fetchone()
|
|
211
|
+
if row and row[0]:
|
|
212
|
+
table_comment = row[0]
|
|
213
|
+
|
|
214
|
+
# Query column information
|
|
215
|
+
col_query = '''
|
|
216
|
+
SELECT
|
|
217
|
+
c.column_name,
|
|
218
|
+
c.data_type,
|
|
219
|
+
c.is_nullable,
|
|
220
|
+
CASE WHEN kcu.column_name IS NOT NULL THEN 'Y' ELSE 'N' END as key_column,
|
|
221
|
+
COALESCE(col_description(pgc.oid, c.ordinal_position), '') as comments
|
|
222
|
+
FROM information_schema.columns c
|
|
223
|
+
LEFT JOIN information_schema.table_constraints tc
|
|
224
|
+
ON c.table_name = tc.table_name
|
|
225
|
+
AND tc.constraint_type = 'PRIMARY KEY'
|
|
226
|
+
LEFT JOIN information_schema.key_column_usage kcu
|
|
227
|
+
ON c.column_name = kcu.column_name
|
|
228
|
+
AND c.table_name = kcu.table_name
|
|
229
|
+
AND tc.constraint_name = kcu.constraint_name
|
|
230
|
+
LEFT JOIN pg_class pgc ON pgc.relname = c.table_name
|
|
231
|
+
WHERE c.table_name = %(table_name)s
|
|
232
|
+
AND c.table_schema = COALESCE(%(schema)s::varchar, c.table_schema)
|
|
233
|
+
ORDER BY c.ordinal_position
|
|
234
|
+
'''
|
|
235
|
+
|
|
236
|
+
cursor.execute(col_query, {'table_name': table_name, 'schema': schema})
|
|
237
|
+
|
|
238
|
+
columns = {}
|
|
239
|
+
column_comments = {}
|
|
240
|
+
|
|
241
|
+
for row in cursor:
|
|
242
|
+
col_name = row[0]
|
|
243
|
+
data_type = row[1]
|
|
244
|
+
is_nullable = row[2]
|
|
245
|
+
is_key = row[3]
|
|
246
|
+
comment = row[4]
|
|
247
|
+
|
|
248
|
+
# Store comment if present
|
|
249
|
+
if add_comments and comment:
|
|
250
|
+
column_comments[col_name] = comment
|
|
251
|
+
|
|
252
|
+
# Handle special timestamp columns (Rails/Django pattern)
|
|
253
|
+
if col_name.endswith('_at') and data_type in ('timestamp', 'timestamptz', 'timestamp without time zone', 'timestamp with time zone'):
|
|
254
|
+
columns[col_name] = {'db_fn': 'CURRENT_TIMESTAMP'}
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
# Build column config
|
|
258
|
+
col_config = {'field': col_name}
|
|
259
|
+
|
|
260
|
+
# Add transform function based on data type
|
|
261
|
+
if data_type == 'date':
|
|
262
|
+
col_config['fn'] = 'parse_date'
|
|
263
|
+
elif data_type in ('timestamp', 'timestamp without time zone'):
|
|
264
|
+
col_config['fn'] = 'parse_datetime'
|
|
265
|
+
elif data_type in ('timestamptz', 'timestamp with time zone'):
|
|
266
|
+
col_config['fn'] = 'parse_timestamp'
|
|
267
|
+
elif data_type in ('time', 'time without time zone', 'time with time zone'):
|
|
268
|
+
col_config['fn'] = 'parse_time'
|
|
269
|
+
|
|
270
|
+
# Add primary key or nullable constraint
|
|
271
|
+
if is_key == 'Y':
|
|
272
|
+
col_config['primary_key'] = True
|
|
273
|
+
elif is_nullable == 'NO':
|
|
274
|
+
col_config['nullable'] = False
|
|
275
|
+
|
|
276
|
+
columns[col_name] = col_config
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
'name': table_name,
|
|
280
|
+
'columns': columns,
|
|
281
|
+
'table_comment': table_comment,
|
|
282
|
+
'column_comments': column_comments
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _get_mysql_metadata(cursor, table_name: str, add_comments: bool = False) -> Dict[str, Any]:
|
|
287
|
+
"""Extract table and column metadata from MySQL database."""
|
|
288
|
+
# Get table comment if requested
|
|
289
|
+
table_comment = None
|
|
290
|
+
if add_comments:
|
|
291
|
+
cmt_query = '''
|
|
292
|
+
SELECT table_comment
|
|
293
|
+
FROM information_schema.tables
|
|
294
|
+
WHERE table_name = %s AND table_schema = DATABASE()
|
|
295
|
+
'''
|
|
296
|
+
cursor.execute(cmt_query, (table_name,))
|
|
297
|
+
row = cursor.fetchone()
|
|
298
|
+
if row and row[0]:
|
|
299
|
+
table_comment = row[0]
|
|
300
|
+
|
|
301
|
+
# Query column information
|
|
302
|
+
col_query = '''
|
|
303
|
+
SELECT
|
|
304
|
+
c.column_name,
|
|
305
|
+
c.data_type,
|
|
306
|
+
c.is_nullable,
|
|
307
|
+
CASE WHEN c.column_key = 'PRI' THEN 'Y' ELSE 'N' END as key_column,
|
|
308
|
+
COALESCE(c.column_comment, '') as comments
|
|
309
|
+
FROM information_schema.columns c
|
|
310
|
+
WHERE c.table_name = %s
|
|
311
|
+
AND c.table_schema = DATABASE()
|
|
312
|
+
ORDER BY c.ordinal_position
|
|
313
|
+
'''
|
|
314
|
+
|
|
315
|
+
cursor.execute(col_query, (table_name,))
|
|
316
|
+
|
|
317
|
+
columns = {}
|
|
318
|
+
column_comments = {}
|
|
319
|
+
|
|
320
|
+
for row in cursor:
|
|
321
|
+
col_name = row[0]
|
|
322
|
+
data_type = row[1]
|
|
323
|
+
is_nullable = row[2]
|
|
324
|
+
is_key = row[3]
|
|
325
|
+
comment = row[4]
|
|
326
|
+
|
|
327
|
+
# Store comment if present
|
|
328
|
+
if add_comments and comment:
|
|
329
|
+
column_comments[col_name] = comment
|
|
330
|
+
|
|
331
|
+
# Handle special timestamp columns (Laravel/Rails pattern)
|
|
332
|
+
if col_name in ('created_at', 'updated_at') and data_type in ('datetime', 'timestamp'):
|
|
333
|
+
columns[col_name] = {'db_fn': 'CURRENT_TIMESTAMP'}
|
|
334
|
+
continue
|
|
335
|
+
|
|
336
|
+
# Build column config
|
|
337
|
+
col_config = {'field': col_name}
|
|
338
|
+
|
|
339
|
+
# Add transform function based on data type
|
|
340
|
+
if data_type == 'date':
|
|
341
|
+
col_config['fn'] = 'parse_date'
|
|
342
|
+
elif data_type in ('datetime', 'timestamp'):
|
|
343
|
+
col_config['fn'] = 'parse_datetime'
|
|
344
|
+
elif data_type == 'time':
|
|
345
|
+
col_config['fn'] = 'parse_time'
|
|
346
|
+
|
|
347
|
+
# Add primary key or nullable constraint
|
|
348
|
+
if is_key == 'Y':
|
|
349
|
+
col_config['primary_key'] = True
|
|
350
|
+
elif is_nullable == 'NO':
|
|
351
|
+
col_config['nullable'] = False
|
|
352
|
+
|
|
353
|
+
columns[col_name] = col_config
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
'name': table_name,
|
|
357
|
+
'columns': columns,
|
|
358
|
+
'table_comment': table_comment,
|
|
359
|
+
'column_comments': column_comments
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _get_sqlserver_metadata(cursor, table_name: str, add_comments: bool = False) -> Dict[str, Any]:
|
|
364
|
+
"""Extract table and column metadata from SQL Server database."""
|
|
365
|
+
# Get table comment if requested
|
|
366
|
+
table_comment = None
|
|
367
|
+
if add_comments:
|
|
368
|
+
cmt_query = '''
|
|
369
|
+
SELECT ep.value as comments
|
|
370
|
+
FROM sys.tables t
|
|
371
|
+
LEFT JOIN sys.extended_properties ep
|
|
372
|
+
ON ep.major_id = t.object_id
|
|
373
|
+
AND ep.minor_id = 0
|
|
374
|
+
AND ep.name = 'MS_Description'
|
|
375
|
+
WHERE t.name = ?
|
|
376
|
+
'''
|
|
377
|
+
cursor.execute(cmt_query, (table_name,))
|
|
378
|
+
row = cursor.fetchone()
|
|
379
|
+
if row and row[0]:
|
|
380
|
+
table_comment = row[0]
|
|
381
|
+
|
|
382
|
+
# Query column information
|
|
383
|
+
col_query = '''
|
|
384
|
+
SELECT
|
|
385
|
+
c.column_name,
|
|
386
|
+
c.data_type,
|
|
387
|
+
c.is_nullable,
|
|
388
|
+
CASE WHEN pk.column_name IS NOT NULL THEN 'Y' ELSE 'N' END as key_column,
|
|
389
|
+
COALESCE(ep.value, '') as comments
|
|
390
|
+
FROM information_schema.columns c
|
|
391
|
+
LEFT JOIN (
|
|
392
|
+
SELECT kcu.column_name, kcu.table_name
|
|
393
|
+
FROM information_schema.table_constraints tc
|
|
394
|
+
JOIN information_schema.key_column_usage kcu
|
|
395
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
396
|
+
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
397
|
+
) pk ON c.column_name = pk.column_name AND c.table_name = pk.table_name
|
|
398
|
+
LEFT JOIN sys.tables t ON t.name = c.table_name
|
|
399
|
+
LEFT JOIN sys.columns sc ON sc.object_id = t.object_id AND sc.name = c.column_name
|
|
400
|
+
LEFT JOIN sys.extended_properties ep
|
|
401
|
+
ON ep.major_id = t.object_id
|
|
402
|
+
AND ep.minor_id = sc.column_id
|
|
403
|
+
AND ep.name = 'MS_Description'
|
|
404
|
+
WHERE c.table_name = ?
|
|
405
|
+
ORDER BY c.ordinal_position
|
|
406
|
+
'''
|
|
407
|
+
|
|
408
|
+
cursor.execute(col_query, (table_name,))
|
|
409
|
+
|
|
410
|
+
columns = {}
|
|
411
|
+
column_comments = {}
|
|
412
|
+
|
|
413
|
+
for row in cursor:
|
|
414
|
+
col_name = row[0]
|
|
415
|
+
data_type = row[1]
|
|
416
|
+
is_nullable = row[2]
|
|
417
|
+
is_key = row[3]
|
|
418
|
+
comment = row[4]
|
|
419
|
+
|
|
420
|
+
# Store comment if present
|
|
421
|
+
if add_comments and comment:
|
|
422
|
+
column_comments[col_name] = comment
|
|
423
|
+
|
|
424
|
+
# Handle special timestamp columns (SQL Server pattern)
|
|
425
|
+
if col_name in ('CreatedDate', 'ModifiedDate') and data_type in ('datetime', 'datetime2'):
|
|
426
|
+
columns[col_name] = {
|
|
427
|
+
'value': 'GETDATE()',
|
|
428
|
+
'db_fn': 'GETDATE()'
|
|
429
|
+
}
|
|
430
|
+
continue
|
|
431
|
+
|
|
432
|
+
# Build column config
|
|
433
|
+
col_config = {'field': col_name}
|
|
434
|
+
|
|
435
|
+
# Add transform function based on data type
|
|
436
|
+
if data_type == 'date':
|
|
437
|
+
col_config['fn'] = 'parse_date'
|
|
438
|
+
elif data_type in ('datetime', 'datetime2', 'smalldatetime'):
|
|
439
|
+
col_config['fn'] = 'parse_datetime'
|
|
440
|
+
elif data_type == 'datetimeoffset':
|
|
441
|
+
col_config['fn'] = 'parse_timestamp'
|
|
442
|
+
elif data_type == 'time':
|
|
443
|
+
col_config['fn'] = 'parse_time'
|
|
444
|
+
|
|
445
|
+
# Add primary key or nullable constraint
|
|
446
|
+
if is_key == 'Y':
|
|
447
|
+
col_config['primary_key'] = True
|
|
448
|
+
elif is_nullable == 'NO':
|
|
449
|
+
col_config['nullable'] = False
|
|
450
|
+
|
|
451
|
+
columns[col_name] = col_config
|
|
452
|
+
|
|
453
|
+
return {
|
|
454
|
+
'name': table_name,
|
|
455
|
+
'columns': columns,
|
|
456
|
+
'table_comment': table_comment,
|
|
457
|
+
'column_comments': column_comments
|
|
458
|
+
}
|