metal-orm 1.0.46 → 1.0.48

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.
@@ -1,210 +1,471 @@
1
- import { SchemaIntrospector, IntrospectOptions } from './types.js';
2
- import { queryRows, shouldIncludeTable } from './utils.js';
3
- import { DatabaseSchema, DatabaseTable, DatabaseIndex, DatabaseColumn } from '../schema-types.js';
4
- import { DbExecutor } from '../../execution/db-executor.js';
5
-
6
- /** Row type for MSSQL column information. */
7
- type MssqlColumnRow = {
8
- table_schema: string;
9
- table_name: string;
10
- column_name: string;
11
- data_type: string;
12
- is_nullable: boolean | number;
13
- is_identity: boolean | number;
14
- column_default: string | null;
15
- };
16
-
17
- /** Row type for MSSQL primary key information. */
18
- type MssqlPrimaryKeyRow = {
19
- table_schema: string;
20
- table_name: string;
21
- column_name: string;
22
- key_ordinal: number;
23
- };
24
-
25
- /** Row type for MSSQL index information. */
26
- type MssqlIndexRow = {
27
- table_schema: string;
28
- table_name: string;
29
- index_name: string;
30
- is_unique: boolean | number;
31
- has_filter: boolean | number;
32
- filter_definition: string | null;
33
- };
34
-
35
- /** Row type for MSSQL index column information. */
36
- type MssqlIndexColumnRow = {
37
- table_schema: string;
38
- table_name: string;
39
- index_name: string;
40
- column_name: string;
41
- key_ordinal: number;
42
- };
43
-
44
- /** MSSQL schema introspector implementation. */
45
- export const mssqlIntrospector: SchemaIntrospector = {
46
- /**
47
- * Introspects the MSSQL database schema.
48
- * @param ctx - The introspection context containing the database executor.
49
- * @param options - Options for introspection, such as schema filter.
50
- * @returns A promise that resolves to the introspected database schema.
51
- */
52
- async introspect(ctx: { executor: DbExecutor }, options: IntrospectOptions): Promise<DatabaseSchema> {
53
- const schema = options.schema;
54
- const filterSchema = schema ? 'sch.name = @p1' : '1=1';
55
- const params = schema ? [schema] : [];
56
-
57
- const columnRows = (await queryRows(
58
- ctx.executor,
59
- `
60
- SELECT
61
- sch.name AS table_schema,
62
- t.name AS table_name,
63
- c.name AS column_name,
64
- LOWER(ty.name)
65
- + CASE
66
- WHEN LOWER(ty.name) IN ('varchar', 'char', 'varbinary', 'binary', 'nvarchar', 'nchar') THEN
67
- '('
68
- + (
69
- CASE
70
- WHEN c.max_length = -1 THEN 'max'
71
- WHEN LOWER(ty.name) IN ('nvarchar', 'nchar') THEN CAST(c.max_length / 2 AS varchar(10))
72
- ELSE CAST(c.max_length AS varchar(10))
73
- END
74
- )
75
- + ')'
76
- WHEN LOWER(ty.name) IN ('decimal', 'numeric') THEN
77
- '(' + CAST(c.precision AS varchar(10)) + ',' + CAST(c.scale AS varchar(10)) + ')'
78
- ELSE
79
- ''
80
- END AS data_type,
81
- c.is_nullable,
82
- c.is_identity,
83
- object_definition(c.default_object_id) AS column_default
84
- FROM sys.columns c
85
- JOIN sys.tables t ON t.object_id = c.object_id
86
- JOIN sys.schemas sch ON sch.schema_id = t.schema_id
87
- JOIN sys.types ty ON ty.user_type_id = c.user_type_id
88
- WHERE t.is_ms_shipped = 0 AND ${filterSchema}
89
- `,
90
- params
91
- )) as MssqlColumnRow[];
92
-
93
- const pkRows = (await queryRows(
94
- ctx.executor,
95
- `
96
- SELECT
97
- sch.name AS table_schema,
98
- t.name AS table_name,
99
- c.name AS column_name,
100
- ic.key_ordinal
101
- FROM sys.indexes i
102
- JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
103
- JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
104
- JOIN sys.tables t ON t.object_id = i.object_id
105
- JOIN sys.schemas sch ON sch.schema_id = t.schema_id
106
- WHERE i.is_primary_key = 1 AND ${filterSchema}
107
- ORDER BY ic.key_ordinal
108
- `,
109
- params
110
- )) as MssqlPrimaryKeyRow[];
111
-
112
- const pkMap = new Map<string, string[]>();
113
- pkRows.forEach(r => {
114
- const key = `${r.table_schema}.${r.table_name}`;
115
- const list = pkMap.get(key) || [];
116
- list.push(r.column_name);
117
- pkMap.set(key, list);
118
- });
119
-
120
- const indexRows = (await queryRows(
121
- ctx.executor,
122
- `
123
- SELECT
124
- sch.name AS table_schema,
125
- t.name AS table_name,
126
- i.name AS index_name,
127
- i.is_unique,
128
- i.has_filter,
129
- i.filter_definition
130
- FROM sys.indexes i
131
- JOIN sys.tables t ON t.object_id = i.object_id
132
- JOIN sys.schemas sch ON sch.schema_id = t.schema_id
133
- WHERE i.is_primary_key = 0 AND i.is_hypothetical = 0 AND ${filterSchema}
134
- `,
135
- params
136
- )) as MssqlIndexRow[];
137
-
138
- const indexColsRows = (await queryRows(
139
- ctx.executor,
140
- `
141
- SELECT
142
- sch.name AS table_schema,
143
- t.name AS table_name,
144
- i.name AS index_name,
145
- c.name AS column_name,
146
- ic.key_ordinal
147
- FROM sys.index_columns ic
148
- JOIN sys.indexes i ON i.object_id = ic.object_id AND i.index_id = ic.index_id
149
- JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
150
- JOIN sys.tables t ON t.object_id = i.object_id
151
- JOIN sys.schemas sch ON sch.schema_id = t.schema_id
152
- WHERE i.is_primary_key = 0 AND ${filterSchema}
153
- ORDER BY ic.key_ordinal
154
- `,
155
- params
156
- )) as MssqlIndexColumnRow[];
157
-
158
- const indexColumnsMap = new Map<string, { column: string; order: number }[]>();
159
- indexColsRows.forEach(r => {
160
- const key = `${r.table_schema}.${r.table_name}.${r.index_name}`;
161
- const list = indexColumnsMap.get(key) || [];
162
- list.push({ column: r.column_name, order: r.key_ordinal });
163
- indexColumnsMap.set(key, list);
164
- });
165
-
166
- const tablesByKey = new Map<string, DatabaseTable>();
167
-
168
- columnRows.forEach(r => {
169
- if (!shouldIncludeTable(r.table_name, options)) return;
170
- const key = `${r.table_schema}.${r.table_name}`;
171
- if (!tablesByKey.has(key)) {
172
- tablesByKey.set(key, {
173
- name: r.table_name,
174
- schema: r.table_schema,
175
- columns: [],
176
- primaryKey: pkMap.get(key) || [],
177
- indexes: []
178
- });
179
- }
180
- const t = tablesByKey.get(key)!;
181
- const column: DatabaseColumn = {
182
- name: r.column_name,
183
- type: r.data_type,
184
- notNull: r.is_nullable === false || r.is_nullable === 0,
185
- default: r.column_default ?? undefined,
186
- autoIncrement: !!r.is_identity
187
- };
188
- t.columns.push(column);
189
- });
190
-
191
- indexRows.forEach(r => {
192
- const key = `${r.table_schema}.${r.table_name}`;
193
- const table = tablesByKey.get(key);
194
- if (!table) return;
195
- const cols = (indexColumnsMap.get(`${r.table_schema}.${r.table_name}.${r.index_name}`) || [])
196
- .sort((a, b) => a.order - b.order)
197
- .map(c => ({ column: c.column }));
198
- const idx: DatabaseIndex = {
199
- name: r.index_name,
200
- columns: cols,
201
- unique: !!r.is_unique,
202
- where: r.has_filter ? r.filter_definition : undefined
203
- };
204
- table.indexes = table.indexes || [];
205
- table.indexes.push(idx);
206
- });
207
-
208
- return { tables: Array.from(tablesByKey.values()) };
209
- }
210
- };
1
+ import type { ReferentialAction } from '../../../schema/column-types.js';
2
+ import { SchemaIntrospector, IntrospectOptions } from './types.js';
3
+ import { shouldIncludeTable } from './utils.js';
4
+ import { DatabaseSchema, DatabaseTable, DatabaseIndex, DatabaseColumn } from '../schema-types.js';
5
+ import type { IntrospectContext } from './context.js';
6
+ import { runSelectNode } from './run-select.js';
7
+ import type { SelectQueryNode, TableNode } from '../../ast/query.js';
8
+ import type { ColumnNode, ExpressionNode, FunctionNode } from '../../ast/expression-nodes.js';
9
+ import type { JoinNode } from '../../ast/join.js';
10
+ import { eq, and } from '../../ast/expression-builders.js';
11
+ import type { TableDef } from '../../../schema/table.js';
12
+ import {
13
+ SysColumns,
14
+ SysTables,
15
+ SysSchemas,
16
+ SysTypes,
17
+ SysIndexes,
18
+ SysIndexColumns,
19
+ SysForeignKeys,
20
+ SysForeignKeyColumns
21
+ } from './catalogs/mssql.js';
22
+ import { buildMssqlDataType, objectDefinition } from './functions/mssql.js';
23
+
24
+ type MssqlColumnRow = {
25
+ table_schema: string;
26
+ table_name: string;
27
+ column_name: string;
28
+ data_type: string;
29
+ is_nullable: boolean | number;
30
+ is_identity: boolean | number;
31
+ column_default: string | null;
32
+ };
33
+
34
+ type MssqlPrimaryKeyRow = {
35
+ table_schema: string;
36
+ table_name: string;
37
+ column_name: string;
38
+ key_ordinal: number;
39
+ };
40
+
41
+ type MssqlIndexRow = {
42
+ table_schema: string;
43
+ table_name: string;
44
+ index_name: string;
45
+ is_unique: boolean | number;
46
+ has_filter: boolean | number;
47
+ filter_definition: string | null;
48
+ };
49
+
50
+ type MssqlIndexColumnRow = {
51
+ table_schema: string;
52
+ table_name: string;
53
+ index_name: string;
54
+ column_name: string;
55
+ key_ordinal: number;
56
+ };
57
+
58
+ type MssqlForeignKeyRow = {
59
+ table_schema: string;
60
+ table_name: string;
61
+ column_name: string;
62
+ constraint_name: string;
63
+ referenced_schema: string;
64
+ referenced_table: string;
65
+ referenced_column: string;
66
+ delete_rule: string | null;
67
+ update_rule: string | null;
68
+ };
69
+
70
+ type ForeignKeyEntry = {
71
+ table: string;
72
+ column: string;
73
+ onDelete?: ReferentialAction;
74
+ onUpdate?: ReferentialAction;
75
+ name?: string;
76
+ };
77
+
78
+ const normalizeReferentialAction = (value: string | null | undefined): ReferentialAction | undefined => {
79
+ if (!value) return undefined;
80
+ const normalized = value.replace(/_/g, ' ').toUpperCase();
81
+ const allowed: ReferentialAction[] = ['NO ACTION', 'RESTRICT', 'CASCADE', 'SET NULL', 'SET DEFAULT'];
82
+ return allowed.includes(normalized as ReferentialAction) ? (normalized as ReferentialAction) : undefined;
83
+ };
84
+
85
+ const tableNode = (table: TableDef, alias: string): TableNode => ({
86
+ type: 'Table',
87
+ name: table.name,
88
+ schema: table.schema,
89
+ alias
90
+ });
91
+
92
+ const columnNode = (table: string, name: string, alias?: string): ColumnNode => ({
93
+ type: 'Column',
94
+ table,
95
+ name,
96
+ alias
97
+ });
98
+
99
+ const combineConditions = (...expressions: (ExpressionNode | undefined)[]): ExpressionNode | undefined => {
100
+ const filtered = expressions.filter(Boolean) as ExpressionNode[];
101
+ if (!filtered.length) return undefined;
102
+ if (filtered.length === 1) return filtered[0];
103
+ return and(...filtered);
104
+ };
105
+
106
+ export const mssqlIntrospector: SchemaIntrospector = {
107
+ async introspect(ctx: IntrospectContext, options: IntrospectOptions): Promise<DatabaseSchema> {
108
+ const schema = options.schema;
109
+ const schemaCondition = schema ? eq(columnNode('sch', 'name'), schema) : undefined;
110
+
111
+ const dataTypeExpression = buildMssqlDataType(
112
+ { table: 'ty', name: 'name' },
113
+ { table: 'c', name: 'max_length' },
114
+ { table: 'c', name: 'precision' },
115
+ { table: 'c', name: 'scale' }
116
+ ) as FunctionNode;
117
+
118
+ const defaultExpression = objectDefinition({ table: 'c', name: 'default_object_id' }) as FunctionNode;
119
+
120
+ const columnsQuery: SelectQueryNode = {
121
+ type: 'SelectQuery',
122
+ from: tableNode(SysColumns, 'c'),
123
+ columns: [
124
+ columnNode('sch', 'name', 'table_schema'),
125
+ columnNode('t', 'name', 'table_name'),
126
+ columnNode('c', 'name', 'column_name'),
127
+ { ...dataTypeExpression, alias: 'data_type' },
128
+ columnNode('c', 'is_nullable'),
129
+ columnNode('c', 'is_identity'),
130
+ { ...defaultExpression, alias: 'column_default' }
131
+ ],
132
+ joins: [
133
+ {
134
+ type: 'Join',
135
+ kind: 'INNER',
136
+ table: tableNode(SysTables, 't'),
137
+ condition: eq({ table: 't', name: 'object_id' }, { table: 'c', name: 'object_id' })
138
+ } as JoinNode,
139
+ {
140
+ type: 'Join',
141
+ kind: 'INNER',
142
+ table: tableNode(SysSchemas, 'sch'),
143
+ condition: eq({ table: 'sch', name: 'schema_id' }, { table: 't', name: 'schema_id' })
144
+ } as JoinNode,
145
+ {
146
+ type: 'Join',
147
+ kind: 'INNER',
148
+ table: tableNode(SysTypes, 'ty'),
149
+ condition: eq({ table: 'ty', name: 'user_type_id' }, { table: 'c', name: 'user_type_id' })
150
+ } as JoinNode
151
+ ],
152
+ where: combineConditions(
153
+ eq({ table: 't', name: 'is_ms_shipped' }, 0),
154
+ schemaCondition
155
+ )
156
+ };
157
+
158
+ const pkQuery: SelectQueryNode = {
159
+ type: 'SelectQuery',
160
+ from: tableNode(SysIndexes, 'i'),
161
+ columns: [
162
+ columnNode('sch', 'name', 'table_schema'),
163
+ columnNode('t', 'name', 'table_name'),
164
+ columnNode('c', 'name', 'column_name'),
165
+ columnNode('ic', 'key_ordinal', 'key_ordinal')
166
+ ],
167
+ joins: [
168
+ {
169
+ type: 'Join',
170
+ kind: 'INNER',
171
+ table: tableNode(SysIndexColumns, 'ic'),
172
+ condition: and(
173
+ eq({ table: 'ic', name: 'object_id' }, { table: 'i', name: 'object_id' }),
174
+ eq({ table: 'ic', name: 'index_id' }, { table: 'i', name: 'index_id' })
175
+ )
176
+ } as JoinNode,
177
+ {
178
+ type: 'Join',
179
+ kind: 'INNER',
180
+ table: tableNode(SysColumns, 'c'),
181
+ condition: and(
182
+ eq({ table: 'c', name: 'object_id' }, { table: 'ic', name: 'object_id' }),
183
+ eq({ table: 'c', name: 'column_id' }, { table: 'ic', name: 'column_id' })
184
+ )
185
+ } as JoinNode,
186
+ {
187
+ type: 'Join',
188
+ kind: 'INNER',
189
+ table: tableNode(SysTables, 't'),
190
+ condition: eq({ table: 't', name: 'object_id' }, { table: 'i', name: 'object_id' })
191
+ } as JoinNode,
192
+ {
193
+ type: 'Join',
194
+ kind: 'INNER',
195
+ table: tableNode(SysSchemas, 'sch'),
196
+ condition: eq({ table: 'sch', name: 'schema_id' }, { table: 't', name: 'schema_id' })
197
+ } as JoinNode
198
+ ],
199
+ where: combineConditions(
200
+ eq({ table: 'i', name: 'is_primary_key' }, 1),
201
+ schemaCondition
202
+ ),
203
+ orderBy: [
204
+ {
205
+ type: 'OrderBy',
206
+ term: columnNode('ic', 'key_ordinal'),
207
+ direction: 'ASC'
208
+ }
209
+ ]
210
+ };
211
+
212
+ const fkQuery: SelectQueryNode = {
213
+ type: 'SelectQuery',
214
+ from: tableNode(SysForeignKeyColumns, 'fkc'),
215
+ columns: [
216
+ columnNode('sch', 'name', 'table_schema'),
217
+ columnNode('t', 'name', 'table_name'),
218
+ columnNode('c', 'name', 'column_name'),
219
+ columnNode('fk', 'name', 'constraint_name'),
220
+ columnNode('rsch', 'name', 'referenced_schema'),
221
+ columnNode('rt', 'name', 'referenced_table'),
222
+ columnNode('rc', 'name', 'referenced_column'),
223
+ columnNode('fk', 'delete_referential_action_desc', 'delete_rule'),
224
+ columnNode('fk', 'update_referential_action_desc', 'update_rule')
225
+ ],
226
+ joins: [
227
+ {
228
+ type: 'Join',
229
+ kind: 'INNER',
230
+ table: tableNode(SysForeignKeys, 'fk'),
231
+ condition: eq({ table: 'fk', name: 'object_id' }, { table: 'fkc', name: 'constraint_object_id' })
232
+ } as JoinNode,
233
+ {
234
+ type: 'Join',
235
+ kind: 'INNER',
236
+ table: tableNode(SysTables, 't'),
237
+ condition: eq({ table: 't', name: 'object_id' }, { table: 'fkc', name: 'parent_object_id' })
238
+ } as JoinNode,
239
+ {
240
+ type: 'Join',
241
+ kind: 'INNER',
242
+ table: tableNode(SysSchemas, 'sch'),
243
+ condition: eq({ table: 'sch', name: 'schema_id' }, { table: 't', name: 'schema_id' })
244
+ } as JoinNode,
245
+ {
246
+ type: 'Join',
247
+ kind: 'INNER',
248
+ table: tableNode(SysColumns, 'c'),
249
+ condition: and(
250
+ eq({ table: 'c', name: 'object_id' }, { table: 'fkc', name: 'parent_object_id' }),
251
+ eq({ table: 'c', name: 'column_id' }, { table: 'fkc', name: 'parent_column_id' })
252
+ )
253
+ } as JoinNode,
254
+ {
255
+ type: 'Join',
256
+ kind: 'INNER',
257
+ table: tableNode(SysTables, 'rt'),
258
+ condition: eq({ table: 'rt', name: 'object_id' }, { table: 'fkc', name: 'referenced_object_id' })
259
+ } as JoinNode,
260
+ {
261
+ type: 'Join',
262
+ kind: 'INNER',
263
+ table: tableNode(SysSchemas, 'rsch'),
264
+ condition: eq({ table: 'rsch', name: 'schema_id' }, { table: 'rt', name: 'schema_id' })
265
+ } as JoinNode,
266
+ {
267
+ type: 'Join',
268
+ kind: 'INNER',
269
+ table: tableNode(SysColumns, 'rc'),
270
+ condition: and(
271
+ eq({ table: 'rc', name: 'object_id' }, { table: 'fkc', name: 'referenced_object_id' }),
272
+ eq({ table: 'rc', name: 'column_id' }, { table: 'fkc', name: 'referenced_column_id' })
273
+ )
274
+ } as JoinNode
275
+ ],
276
+ where: combineConditions(
277
+ eq({ table: 't', name: 'is_ms_shipped' }, 0),
278
+ schemaCondition
279
+ ),
280
+ orderBy: [
281
+ {
282
+ type: 'OrderBy',
283
+ term: columnNode('fk', 'name'),
284
+ direction: 'ASC'
285
+ },
286
+ {
287
+ type: 'OrderBy',
288
+ term: columnNode('fkc', 'constraint_column_id'),
289
+ direction: 'ASC'
290
+ }
291
+ ]
292
+ };
293
+
294
+ const indexQuery: SelectQueryNode = {
295
+ type: 'SelectQuery',
296
+ from: tableNode(SysIndexes, 'i'),
297
+ columns: [
298
+ columnNode('sch', 'name', 'table_schema'),
299
+ columnNode('t', 'name', 'table_name'),
300
+ columnNode('i', 'name', 'index_name'),
301
+ columnNode('i', 'is_unique'),
302
+ columnNode('i', 'has_filter'),
303
+ columnNode('i', 'filter_definition')
304
+ ],
305
+ joins: [
306
+ {
307
+ type: 'Join',
308
+ kind: 'INNER',
309
+ table: tableNode(SysTables, 't'),
310
+ condition: eq({ table: 't', name: 'object_id' }, { table: 'i', name: 'object_id' })
311
+ } as JoinNode,
312
+ {
313
+ type: 'Join',
314
+ kind: 'INNER',
315
+ table: tableNode(SysSchemas, 'sch'),
316
+ condition: eq({ table: 'sch', name: 'schema_id' }, { table: 't', name: 'schema_id' })
317
+ } as JoinNode
318
+ ],
319
+ where: combineConditions(
320
+ eq({ table: 'i', name: 'is_primary_key' }, 0),
321
+ eq({ table: 'i', name: 'is_hypothetical' }, 0),
322
+ schemaCondition
323
+ )
324
+ };
325
+
326
+ const indexColumnsQuery: SelectQueryNode = {
327
+ type: 'SelectQuery',
328
+ from: tableNode(SysIndexColumns, 'ic'),
329
+ columns: [
330
+ columnNode('sch', 'name', 'table_schema'),
331
+ columnNode('t', 'name', 'table_name'),
332
+ columnNode('i', 'name', 'index_name'),
333
+ columnNode('c', 'name', 'column_name'),
334
+ columnNode('ic', 'key_ordinal', 'key_ordinal')
335
+ ],
336
+ joins: [
337
+ {
338
+ type: 'Join',
339
+ kind: 'INNER',
340
+ table: tableNode(SysIndexes, 'i'),
341
+ condition: and(
342
+ eq({ table: 'ic', name: 'object_id' }, { table: 'i', name: 'object_id' }),
343
+ eq({ table: 'ic', name: 'index_id' }, { table: 'i', name: 'index_id' })
344
+ )
345
+ } as JoinNode,
346
+ {
347
+ type: 'Join',
348
+ kind: 'INNER',
349
+ table: tableNode(SysColumns, 'c'),
350
+ condition: and(
351
+ eq({ table: 'c', name: 'object_id' }, { table: 'ic', name: 'object_id' }),
352
+ eq({ table: 'c', name: 'column_id' }, { table: 'ic', name: 'column_id' })
353
+ )
354
+ } as JoinNode,
355
+ {
356
+ type: 'Join',
357
+ kind: 'INNER',
358
+ table: tableNode(SysTables, 't'),
359
+ condition: eq({ table: 't', name: 'object_id' }, { table: 'i', name: 'object_id' })
360
+ } as JoinNode,
361
+ {
362
+ type: 'Join',
363
+ kind: 'INNER',
364
+ table: tableNode(SysSchemas, 'sch'),
365
+ condition: eq({ table: 'sch', name: 'schema_id' }, { table: 't', name: 'schema_id' })
366
+ } as JoinNode
367
+ ],
368
+ where: combineConditions(
369
+ eq({ table: 'i', name: 'is_primary_key' }, 0),
370
+ schemaCondition
371
+ ),
372
+ orderBy: [
373
+ {
374
+ type: 'OrderBy',
375
+ term: columnNode('ic', 'key_ordinal'),
376
+ direction: 'ASC'
377
+ }
378
+ ]
379
+ };
380
+
381
+ const columnRows = (await runSelectNode<MssqlColumnRow>(columnsQuery, ctx)) as MssqlColumnRow[];
382
+ const pkRows = (await runSelectNode<MssqlPrimaryKeyRow>(pkQuery, ctx)) as MssqlPrimaryKeyRow[];
383
+ const fkRows = (await runSelectNode<MssqlForeignKeyRow>(fkQuery, ctx)) as MssqlForeignKeyRow[];
384
+ const indexRows = (await runSelectNode<MssqlIndexRow>(indexQuery, ctx)) as MssqlIndexRow[];
385
+ const indexColsRows = (await runSelectNode<MssqlIndexColumnRow>(indexColumnsQuery, ctx)) as MssqlIndexColumnRow[];
386
+
387
+ const pkMap = new Map<string, string[]>();
388
+ pkRows.forEach(r => {
389
+ const key = `${r.table_schema}.${r.table_name}`;
390
+ const list = pkMap.get(key) || [];
391
+ list.push(r.column_name);
392
+ pkMap.set(key, list);
393
+ });
394
+
395
+ const fkMap = new Map<string, ForeignKeyEntry[]>();
396
+ fkRows.forEach(r => {
397
+ const key = `${r.table_schema}.${r.table_name}.${r.column_name}`;
398
+ const list = fkMap.get(key) || [];
399
+ list.push({
400
+ table: `${r.referenced_schema}.${r.referenced_table}`,
401
+ column: r.referenced_column,
402
+ onDelete: normalizeReferentialAction(r.delete_rule),
403
+ onUpdate: normalizeReferentialAction(r.update_rule),
404
+ name: r.constraint_name
405
+ });
406
+ fkMap.set(key, list);
407
+ });
408
+
409
+ const indexColumnsMap = new Map<string, { column: string; order: number }[]>();
410
+ indexColsRows.forEach(r => {
411
+ const key = `${r.table_schema}.${r.table_name}.${r.index_name}`;
412
+ const list = indexColumnsMap.get(key) || [];
413
+ list.push({ column: r.column_name, order: r.key_ordinal });
414
+ indexColumnsMap.set(key, list);
415
+ });
416
+
417
+ const tablesByKey = new Map<string, DatabaseTable>();
418
+
419
+ columnRows.forEach(r => {
420
+ if (!shouldIncludeTable(r.table_name, options)) return;
421
+ const key = `${r.table_schema}.${r.table_name}`;
422
+ if (!tablesByKey.has(key)) {
423
+ tablesByKey.set(key, {
424
+ name: r.table_name,
425
+ schema: r.table_schema,
426
+ columns: [],
427
+ primaryKey: pkMap.get(key) || [],
428
+ indexes: []
429
+ });
430
+ }
431
+ const table = tablesByKey.get(key)!;
432
+ const column: DatabaseColumn = {
433
+ name: r.column_name,
434
+ type: r.data_type,
435
+ notNull: r.is_nullable === false || r.is_nullable === 0,
436
+ default: r.column_default ?? undefined,
437
+ autoIncrement: !!r.is_identity
438
+ };
439
+ const fk = fkMap.get(`${key}.${r.column_name}`)?.[0];
440
+ if (fk) {
441
+ column.references = {
442
+ table: fk.table,
443
+ column: fk.column,
444
+ onDelete: fk.onDelete,
445
+ onUpdate: fk.onUpdate,
446
+ name: fk.name
447
+ };
448
+ }
449
+ table.columns.push(column);
450
+ });
451
+
452
+ indexRows.forEach(r => {
453
+ const key = `${r.table_schema}.${r.table_name}`;
454
+ const table = tablesByKey.get(key);
455
+ if (!table) return;
456
+ const cols = (indexColumnsMap.get(`${r.table_schema}.${r.table_name}.${r.index_name}`) || [])
457
+ .sort((a, b) => a.order - b.order)
458
+ .map(c => ({ column: c.column }));
459
+ const idx: DatabaseIndex = {
460
+ name: r.index_name,
461
+ columns: cols,
462
+ unique: !!r.is_unique,
463
+ where: r.has_filter ? r.filter_definition ?? undefined : undefined
464
+ };
465
+ table.indexes = table.indexes || [];
466
+ table.indexes.push(idx);
467
+ });
468
+
469
+ return { tables: Array.from(tablesByKey.values()) };
470
+ }
471
+ };