@zmdb/mssql 1.0.0-beta.1
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.
- package/LICENSE +674 -0
- package/README.md +110 -0
- package/dist/compiler.d.ts +3 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +82 -0
- package/dist/compiler.js.map +1 -0
- package/dist/driver.d.ts +28 -0
- package/dist/driver.d.ts.map +1 -0
- package/dist/driver.js +47 -0
- package/dist/driver.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/introspect.d.ts +45 -0
- package/dist/introspect.d.ts.map +1 -0
- package/dist/introspect.js +567 -0
- package/dist/introspect.js.map +1 -0
- package/dist/migrations.d.ts +3 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +296 -0
- package/dist/migrations.js.map +1 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +27 -0
- package/dist/types.js.map +1 -0
- package/package.json +58 -0
- package/src/compiler.ts +109 -0
- package/src/driver.ts +87 -0
- package/src/index.ts +131 -0
- package/src/introspect.ts +790 -0
- package/src/migrations.ts +405 -0
- package/src/types.ts +34 -0
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
import type { SchemaSnapshot } from '@zmdb/migrations';
|
|
2
|
+
import {
|
|
3
|
+
CatalogRowError,
|
|
4
|
+
normalizeDriftSnapshot,
|
|
5
|
+
type CatalogColumnSnapshot,
|
|
6
|
+
type CatalogForeignKeySnapshot,
|
|
7
|
+
type CatalogIndexColumn,
|
|
8
|
+
type CatalogIndexSnapshot,
|
|
9
|
+
type CatalogSchemaSnapshot,
|
|
10
|
+
type CatalogTableSnapshot,
|
|
11
|
+
type CatalogWarning,
|
|
12
|
+
type ReferentialAction,
|
|
13
|
+
} from '@zmdb/migrations/introspect/runtime';
|
|
14
|
+
import { type CompiledQuery, type IntrospectionDriver, type Introspector, type IntrospectOptions } from '@zmdb/sql';
|
|
15
|
+
|
|
16
|
+
export interface MssqlIdentity {
|
|
17
|
+
readonly seed: string;
|
|
18
|
+
readonly increment: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MssqlComputedColumn {
|
|
22
|
+
readonly expression: string;
|
|
23
|
+
readonly persisted: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface MssqlCatalogColumnSnapshot extends CatalogColumnSnapshot {
|
|
27
|
+
readonly identity?: MssqlIdentity;
|
|
28
|
+
readonly computed?: MssqlComputedColumn;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MssqlCatalogForeignKeySnapshot extends CatalogForeignKeySnapshot {
|
|
32
|
+
readonly disabled: boolean;
|
|
33
|
+
readonly trusted: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface MssqlCatalogIndexSnapshot extends CatalogIndexSnapshot {
|
|
37
|
+
readonly clustered: boolean;
|
|
38
|
+
readonly includedColumns: readonly string[];
|
|
39
|
+
readonly disabled: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface MssqlCatalogTableSnapshot extends Omit<CatalogTableSnapshot, 'columns' | 'foreignKeys' | 'indexes'> {
|
|
43
|
+
readonly columns: readonly MssqlCatalogColumnSnapshot[];
|
|
44
|
+
readonly foreignKeys: readonly MssqlCatalogForeignKeySnapshot[];
|
|
45
|
+
readonly indexes: readonly MssqlCatalogIndexSnapshot[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface MssqlCatalogSequenceSnapshot {
|
|
49
|
+
readonly name: string;
|
|
50
|
+
readonly schema?: string;
|
|
51
|
+
readonly catalogType: string;
|
|
52
|
+
readonly start: string;
|
|
53
|
+
readonly increment: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface MssqlCatalogSchemaSnapshot extends Omit<CatalogSchemaSnapshot, 'tables'> {
|
|
57
|
+
readonly tables: readonly MssqlCatalogTableSnapshot[];
|
|
58
|
+
readonly sequences: readonly MssqlCatalogSequenceSnapshot[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface MssqlIntrospector extends Introspector<'mssql'> {
|
|
62
|
+
readonly dialect: 'mssql';
|
|
63
|
+
snapshot(driver: IntrospectionDriver, options?: IntrospectOptions): Promise<MssqlCatalogSchemaSnapshot>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface MssqlTable {
|
|
67
|
+
readonly schema: string;
|
|
68
|
+
readonly name: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface MssqlColumn {
|
|
72
|
+
readonly schema: string;
|
|
73
|
+
readonly table: string;
|
|
74
|
+
readonly ordinal: number;
|
|
75
|
+
readonly name: string;
|
|
76
|
+
readonly nullable: boolean;
|
|
77
|
+
readonly dataType: string;
|
|
78
|
+
readonly maxLength: number;
|
|
79
|
+
readonly precision: number;
|
|
80
|
+
readonly scale: number;
|
|
81
|
+
readonly default: string | null;
|
|
82
|
+
readonly identity: boolean;
|
|
83
|
+
readonly seed: string | null;
|
|
84
|
+
readonly increment: string | null;
|
|
85
|
+
readonly computed: boolean;
|
|
86
|
+
readonly computedDefinition: string | null;
|
|
87
|
+
readonly persisted: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface MssqlPrimaryKey {
|
|
91
|
+
readonly schema: string;
|
|
92
|
+
readonly table: string;
|
|
93
|
+
readonly ordinal: number;
|
|
94
|
+
readonly column: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface MssqlForeignKey {
|
|
98
|
+
readonly schema: string;
|
|
99
|
+
readonly table: string;
|
|
100
|
+
readonly constraint: string;
|
|
101
|
+
readonly ordinal: number;
|
|
102
|
+
readonly column: string;
|
|
103
|
+
readonly targetSchema: string;
|
|
104
|
+
readonly targetTable: string;
|
|
105
|
+
readonly targetColumn: string;
|
|
106
|
+
readonly onUpdate: string;
|
|
107
|
+
readonly onDelete: string;
|
|
108
|
+
readonly disabled: boolean;
|
|
109
|
+
readonly trusted: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface MssqlIndexRow {
|
|
113
|
+
readonly schema: string;
|
|
114
|
+
readonly table: string;
|
|
115
|
+
readonly name: string;
|
|
116
|
+
readonly unique: boolean;
|
|
117
|
+
readonly primary: boolean;
|
|
118
|
+
readonly type: string;
|
|
119
|
+
readonly filtered: boolean;
|
|
120
|
+
readonly predicate: string | null;
|
|
121
|
+
readonly ordinal: number;
|
|
122
|
+
readonly included: boolean;
|
|
123
|
+
readonly column: string;
|
|
124
|
+
readonly disabled: boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface MssqlSequence {
|
|
128
|
+
readonly schema: string;
|
|
129
|
+
readonly name: string;
|
|
130
|
+
readonly dataType: string;
|
|
131
|
+
readonly precision: number;
|
|
132
|
+
readonly scale: number;
|
|
133
|
+
readonly start: string;
|
|
134
|
+
readonly increment: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function valueAt(row: Readonly<Record<string, unknown>>, field: string): unknown {
|
|
138
|
+
return Reflect.get(row, field);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function invalid(catalog: string, row: number, field: string, expected: string, value: unknown): never {
|
|
142
|
+
throw new CatalogRowError(catalog, row, field, expected, value);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function textField(row: Readonly<Record<string, unknown>>, field: string, catalog: string, index: number): string {
|
|
146
|
+
const value = valueAt(row, field);
|
|
147
|
+
return typeof value === 'string' ? value : invalid(catalog, index, field, 'a string', value);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function nullableTextField(
|
|
151
|
+
row: Readonly<Record<string, unknown>>,
|
|
152
|
+
field: string,
|
|
153
|
+
catalog: string,
|
|
154
|
+
index: number,
|
|
155
|
+
): string | null {
|
|
156
|
+
const value = valueAt(row, field);
|
|
157
|
+
if (value === null) return null;
|
|
158
|
+
return typeof value === 'string' ? value : invalid(catalog, index, field, 'a string or null', value);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function integerField(row: Readonly<Record<string, unknown>>, field: string, catalog: string, index: number): number {
|
|
162
|
+
const value = valueAt(row, field);
|
|
163
|
+
if (typeof value === 'number' && Number.isSafeInteger(value)) return value;
|
|
164
|
+
if (typeof value === 'string' && /^-?\d+$/.test(value)) {
|
|
165
|
+
const parsed = Number(value);
|
|
166
|
+
if (Number.isSafeInteger(parsed)) return parsed;
|
|
167
|
+
}
|
|
168
|
+
return invalid(catalog, index, field, 'a safe integer or decimal integer string', value);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function booleanField(row: Readonly<Record<string, unknown>>, field: string, catalog: string, index: number): boolean {
|
|
172
|
+
const value = valueAt(row, field);
|
|
173
|
+
if (typeof value === 'boolean') return value;
|
|
174
|
+
if (value === 0 || value === '0') return false;
|
|
175
|
+
if (value === 1 || value === '1') return true;
|
|
176
|
+
return invalid(catalog, index, field, 'a boolean or 0/1 flag', value);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function scalarText(row: Readonly<Record<string, unknown>>, field: string, catalog: string, index: number): string {
|
|
180
|
+
const value = valueAt(row, field);
|
|
181
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'bigint') return String(value);
|
|
182
|
+
return invalid(catalog, index, field, 'a string or numeric scalar', value);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function nullableScalarText(
|
|
186
|
+
row: Readonly<Record<string, unknown>>,
|
|
187
|
+
field: string,
|
|
188
|
+
catalog: string,
|
|
189
|
+
index: number,
|
|
190
|
+
): string | null {
|
|
191
|
+
return valueAt(row, field) === null ? null : scalarText(row, field, catalog, index);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function query(text: string, parameters: readonly unknown[] = []): CompiledQuery {
|
|
195
|
+
return { text, parameters };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function globExpression(glob: string): RegExp {
|
|
199
|
+
let source = '^';
|
|
200
|
+
for (const character of glob) {
|
|
201
|
+
if (character === '*') source += '.*';
|
|
202
|
+
else if (character === '?') source += '.';
|
|
203
|
+
else source += character.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
|
|
204
|
+
}
|
|
205
|
+
return new RegExp(`${source}$`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function selected(name: string, options: IntrospectOptions): boolean {
|
|
209
|
+
const include = options.include;
|
|
210
|
+
if (include !== undefined && include.length > 0 && !include.some(glob => globExpression(glob).test(name))) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
const exclude = options.exclude ?? ['_zmdb_migrations'];
|
|
214
|
+
return !exclude.some(glob => globExpression(glob).test(name));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function schemas(options: IntrospectOptions): readonly string[] {
|
|
218
|
+
const configured = options.schemas ?? ['dbo'];
|
|
219
|
+
return [...new Set(configured.length === 0 ? ['dbo'] : configured)].toSorted();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function placeholders(count: number): string {
|
|
223
|
+
return Array.from({ length: count }, (_, index) => `@p${String(index + 1)}`).join(', ');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function action(value: string, catalog: string, row: number, field: string): ReferentialAction {
|
|
227
|
+
switch (value.toUpperCase()) {
|
|
228
|
+
case 'NO_ACTION':
|
|
229
|
+
return 'no action';
|
|
230
|
+
case 'CASCADE':
|
|
231
|
+
return 'cascade';
|
|
232
|
+
case 'SET_NULL':
|
|
233
|
+
return 'set null';
|
|
234
|
+
case 'SET_DEFAULT':
|
|
235
|
+
return 'set default';
|
|
236
|
+
default:
|
|
237
|
+
return invalid(catalog, row, field, 'a SQL Server referential action', value);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function parseTable(row: Readonly<Record<string, unknown>>, index: number): MssqlTable {
|
|
242
|
+
const catalog = 'mssql sys.tables';
|
|
243
|
+
return {
|
|
244
|
+
schema: textField(row, 'schema_name', catalog, index),
|
|
245
|
+
name: textField(row, 'table_name', catalog, index),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function parseColumn(row: Readonly<Record<string, unknown>>, index: number): MssqlColumn {
|
|
250
|
+
const catalog = 'mssql sys.columns';
|
|
251
|
+
return {
|
|
252
|
+
schema: textField(row, 'schema_name', catalog, index),
|
|
253
|
+
table: textField(row, 'table_name', catalog, index),
|
|
254
|
+
ordinal: integerField(row, 'ordinal_position', catalog, index),
|
|
255
|
+
name: textField(row, 'column_name', catalog, index),
|
|
256
|
+
nullable: booleanField(row, 'is_nullable', catalog, index),
|
|
257
|
+
dataType: textField(row, 'data_type', catalog, index),
|
|
258
|
+
maxLength: integerField(row, 'max_length', catalog, index),
|
|
259
|
+
precision: integerField(row, 'numeric_precision', catalog, index),
|
|
260
|
+
scale: integerField(row, 'numeric_scale', catalog, index),
|
|
261
|
+
default: nullableTextField(row, 'column_default', catalog, index),
|
|
262
|
+
identity: booleanField(row, 'is_identity', catalog, index),
|
|
263
|
+
seed: nullableScalarText(row, 'seed_value', catalog, index),
|
|
264
|
+
increment: nullableScalarText(row, 'increment_value', catalog, index),
|
|
265
|
+
computed: booleanField(row, 'is_computed', catalog, index),
|
|
266
|
+
computedDefinition: nullableTextField(row, 'computed_definition', catalog, index),
|
|
267
|
+
persisted: booleanField(row, 'is_persisted', catalog, index),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function parsePrimaryKey(row: Readonly<Record<string, unknown>>, index: number): MssqlPrimaryKey {
|
|
272
|
+
const catalog = 'mssql sys.key_constraints';
|
|
273
|
+
return {
|
|
274
|
+
schema: textField(row, 'schema_name', catalog, index),
|
|
275
|
+
table: textField(row, 'table_name', catalog, index),
|
|
276
|
+
ordinal: integerField(row, 'ordinal_position', catalog, index),
|
|
277
|
+
column: textField(row, 'column_name', catalog, index),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function parseForeignKey(row: Readonly<Record<string, unknown>>, index: number): MssqlForeignKey {
|
|
282
|
+
const catalog = 'mssql sys.foreign_keys';
|
|
283
|
+
return {
|
|
284
|
+
schema: textField(row, 'schema_name', catalog, index),
|
|
285
|
+
table: textField(row, 'table_name', catalog, index),
|
|
286
|
+
constraint: textField(row, 'constraint_name', catalog, index),
|
|
287
|
+
ordinal: integerField(row, 'ordinal_position', catalog, index),
|
|
288
|
+
column: textField(row, 'column_name', catalog, index),
|
|
289
|
+
targetSchema: textField(row, 'target_schema', catalog, index),
|
|
290
|
+
targetTable: textField(row, 'target_table', catalog, index),
|
|
291
|
+
targetColumn: textField(row, 'target_column', catalog, index),
|
|
292
|
+
onUpdate: textField(row, 'update_action', catalog, index),
|
|
293
|
+
onDelete: textField(row, 'delete_action', catalog, index),
|
|
294
|
+
disabled: booleanField(row, 'is_disabled', catalog, index),
|
|
295
|
+
trusted: !booleanField(row, 'is_not_trusted', catalog, index),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function parseIndex(row: Readonly<Record<string, unknown>>, index: number): MssqlIndexRow {
|
|
300
|
+
const catalog = 'mssql sys.indexes';
|
|
301
|
+
return {
|
|
302
|
+
schema: textField(row, 'schema_name', catalog, index),
|
|
303
|
+
table: textField(row, 'table_name', catalog, index),
|
|
304
|
+
name: textField(row, 'index_name', catalog, index),
|
|
305
|
+
unique: booleanField(row, 'is_unique', catalog, index),
|
|
306
|
+
primary: booleanField(row, 'is_primary_key', catalog, index),
|
|
307
|
+
type: textField(row, 'index_type', catalog, index),
|
|
308
|
+
filtered: booleanField(row, 'has_filter', catalog, index),
|
|
309
|
+
predicate: nullableTextField(row, 'filter_definition', catalog, index),
|
|
310
|
+
ordinal: integerField(row, 'ordinal_position', catalog, index),
|
|
311
|
+
included: booleanField(row, 'is_included_column', catalog, index),
|
|
312
|
+
column: textField(row, 'column_name', catalog, index),
|
|
313
|
+
disabled: booleanField(row, 'is_disabled', catalog, index),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function parseSequence(row: Readonly<Record<string, unknown>>, index: number): MssqlSequence {
|
|
318
|
+
const catalog = 'mssql sys.sequences';
|
|
319
|
+
return {
|
|
320
|
+
schema: textField(row, 'schema_name', catalog, index),
|
|
321
|
+
name: textField(row, 'sequence_name', catalog, index),
|
|
322
|
+
dataType: textField(row, 'data_type', catalog, index),
|
|
323
|
+
precision: integerField(row, 'numeric_precision', catalog, index),
|
|
324
|
+
scale: integerField(row, 'numeric_scale', catalog, index),
|
|
325
|
+
start: scalarText(row, 'start_value', catalog, index),
|
|
326
|
+
increment: scalarText(row, 'increment_value', catalog, index),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function tableKey(schema: string, table: string): string {
|
|
331
|
+
return `${schema}\0${table}`;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function catalogType(column: MssqlColumn): string {
|
|
335
|
+
const type = column.dataType.toUpperCase();
|
|
336
|
+
if (type === 'NVARCHAR' || type === 'NCHAR') {
|
|
337
|
+
return column.maxLength === -1 ? `${type}(MAX)` : `${type}(${String(column.maxLength / 2)})`;
|
|
338
|
+
}
|
|
339
|
+
if (type === 'VARCHAR' || type === 'CHAR' || type === 'VARBINARY' || type === 'BINARY') {
|
|
340
|
+
return column.maxLength === -1 ? `${type}(MAX)` : `${type}(${String(column.maxLength)})`;
|
|
341
|
+
}
|
|
342
|
+
if (type === 'DECIMAL' || type === 'NUMERIC') {
|
|
343
|
+
return `${type}(${String(column.precision)},${String(column.scale)})`;
|
|
344
|
+
}
|
|
345
|
+
if (type === 'DATETIME2' || type === 'DATETIMEOFFSET' || type === 'TIME') {
|
|
346
|
+
return `${type}(${String(column.scale)})`;
|
|
347
|
+
}
|
|
348
|
+
return type;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function mappedType(column: MssqlColumn): {
|
|
352
|
+
readonly type: string;
|
|
353
|
+
readonly length?: number;
|
|
354
|
+
readonly warnings: readonly string[];
|
|
355
|
+
} {
|
|
356
|
+
const type = column.dataType.toLowerCase();
|
|
357
|
+
const source = catalogType(column);
|
|
358
|
+
const warnings: string[] = [];
|
|
359
|
+
|
|
360
|
+
if (column.identity) {
|
|
361
|
+
if (column.seed === null || column.increment === null) {
|
|
362
|
+
throw new TypeError(`mssql identity column "${column.table}"."${column.name}" has no seed or increment`);
|
|
363
|
+
}
|
|
364
|
+
if (type === 'int' && column.seed === '1' && column.increment === '1') {
|
|
365
|
+
return { type: 'serial', warnings };
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
type: `${source} IDENTITY(${column.seed},${column.increment})`,
|
|
369
|
+
warnings,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (type === 'int') return { type: 'integer', warnings };
|
|
374
|
+
if (type === 'bigint') return { type: 'bigint', warnings };
|
|
375
|
+
if (type === 'smallint' || type === 'tinyint') {
|
|
376
|
+
warnings.push(`SQL Server type ${source} was widened to integer`);
|
|
377
|
+
return { type: 'integer', warnings };
|
|
378
|
+
}
|
|
379
|
+
if (type === 'decimal' || type === 'numeric') return { type: source, warnings };
|
|
380
|
+
if (type === 'float' || type === 'real' || type === 'money' || type === 'smallmoney') {
|
|
381
|
+
return { type: source, warnings };
|
|
382
|
+
}
|
|
383
|
+
if (type === 'nvarchar') {
|
|
384
|
+
return column.maxLength === -1
|
|
385
|
+
? { type: 'text', warnings }
|
|
386
|
+
: { type: 'varchar', length: column.maxLength / 2, warnings };
|
|
387
|
+
}
|
|
388
|
+
if (type === 'varchar') {
|
|
389
|
+
warnings.push(`SQL Server ${source} uses a database code page; forward zmdb DDL emits Unicode NVARCHAR`);
|
|
390
|
+
return column.maxLength === -1
|
|
391
|
+
? { type: 'text', warnings }
|
|
392
|
+
: { type: 'varchar', length: column.maxLength, warnings };
|
|
393
|
+
}
|
|
394
|
+
if (type === 'nchar' || type === 'char') {
|
|
395
|
+
warnings.push(`SQL Server fixed-width type ${source} was normalized to varchar`);
|
|
396
|
+
const length = type === 'nchar' ? column.maxLength / 2 : column.maxLength;
|
|
397
|
+
return { type: 'varchar', length, warnings };
|
|
398
|
+
}
|
|
399
|
+
if (type === 'ntext' || type === 'text') {
|
|
400
|
+
if (type === 'text') {
|
|
401
|
+
warnings.push('SQL Server TEXT uses a database code page; forward zmdb DDL emits Unicode NVARCHAR(MAX)');
|
|
402
|
+
}
|
|
403
|
+
return { type: 'text', warnings };
|
|
404
|
+
}
|
|
405
|
+
if (type === 'bit') return { type: 'boolean', warnings };
|
|
406
|
+
if (type === 'datetimeoffset') {
|
|
407
|
+
if (column.scale !== 3) {
|
|
408
|
+
warnings.push(`SQL Server ${source} was normalized to timestamp; forward zmdb DDL emits DATETIMEOFFSET(3)`);
|
|
409
|
+
}
|
|
410
|
+
return { type: 'timestamp', warnings };
|
|
411
|
+
}
|
|
412
|
+
if (type === 'datetime2' || type === 'datetime' || type === 'smalldatetime') {
|
|
413
|
+
warnings.push(
|
|
414
|
+
`SQL Server ${source} has no offset; it was preserved as a custom SQL type instead of being called timestamp`,
|
|
415
|
+
);
|
|
416
|
+
return { type: source, warnings };
|
|
417
|
+
}
|
|
418
|
+
if (type === 'uniqueidentifier') return { type: 'UNIQUEIDENTIFIER', warnings };
|
|
419
|
+
if (type === 'xml') return { type: 'XML', warnings };
|
|
420
|
+
if (type === 'varbinary' || type === 'binary' || type === 'image' || type === 'date' || type === 'time') {
|
|
421
|
+
return { type: source, warnings };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
warnings.push(`SQL Server type ${source} cannot be represented by the current declared SQL type vocabulary`);
|
|
425
|
+
return { type: source, warnings };
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function primaryKeys(rows: readonly MssqlPrimaryKey[]): ReadonlyMap<string, readonly string[]> {
|
|
429
|
+
const grouped = new Map<string, MssqlPrimaryKey[]>();
|
|
430
|
+
for (const row of rows) {
|
|
431
|
+
const key = tableKey(row.schema, row.table);
|
|
432
|
+
const values = grouped.get(key);
|
|
433
|
+
if (values) values.push(row);
|
|
434
|
+
else grouped.set(key, [row]);
|
|
435
|
+
}
|
|
436
|
+
return new Map(
|
|
437
|
+
[...grouped].map(([key, values]) => [
|
|
438
|
+
key,
|
|
439
|
+
values.toSorted((left, right) => left.ordinal - right.ordinal).map(value => value.column),
|
|
440
|
+
]),
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function foreignKeys(
|
|
445
|
+
rows: readonly MssqlForeignKey[],
|
|
446
|
+
warnings: CatalogWarning[],
|
|
447
|
+
): ReadonlyMap<string, readonly MssqlCatalogForeignKeySnapshot[]> {
|
|
448
|
+
const grouped = new Map<string, MssqlForeignKey[]>();
|
|
449
|
+
for (const row of rows) {
|
|
450
|
+
const key = `${tableKey(row.schema, row.table)}\0${row.constraint}`;
|
|
451
|
+
const values = grouped.get(key);
|
|
452
|
+
if (values) values.push(row);
|
|
453
|
+
else grouped.set(key, [row]);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const byTable = new Map<string, MssqlCatalogForeignKeySnapshot[]>();
|
|
457
|
+
for (const values of grouped.values()) {
|
|
458
|
+
values.sort((left, right) => left.ordinal - right.ordinal);
|
|
459
|
+
const first = values[0];
|
|
460
|
+
if (first === undefined) continue;
|
|
461
|
+
if (
|
|
462
|
+
values.some(
|
|
463
|
+
value =>
|
|
464
|
+
value.targetSchema !== first.targetSchema ||
|
|
465
|
+
value.targetTable !== first.targetTable ||
|
|
466
|
+
value.disabled !== first.disabled ||
|
|
467
|
+
value.trusted !== first.trusted,
|
|
468
|
+
)
|
|
469
|
+
) {
|
|
470
|
+
throw new TypeError(`mssql foreign key "${first.constraint}" has inconsistent catalog rows`);
|
|
471
|
+
}
|
|
472
|
+
if (first.targetSchema !== first.schema) {
|
|
473
|
+
warnings.push({
|
|
474
|
+
table: first.table,
|
|
475
|
+
reason:
|
|
476
|
+
`SQL Server foreign key "${first.constraint}" targets schema "${first.targetSchema}"; ` +
|
|
477
|
+
'the schema-neutral drift model preserves the target table name but not its schema',
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
if (first.disabled) {
|
|
481
|
+
warnings.push({
|
|
482
|
+
table: first.table,
|
|
483
|
+
reason: `SQL Server foreign key "${first.constraint}" is disabled`,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
if (!first.trusted) {
|
|
487
|
+
warnings.push({
|
|
488
|
+
table: first.table,
|
|
489
|
+
reason: `SQL Server foreign key "${first.constraint}" is not trusted`,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
const snapshot: MssqlCatalogForeignKeySnapshot = {
|
|
493
|
+
name: first.constraint,
|
|
494
|
+
columns: values.map(value => value.column),
|
|
495
|
+
targetTable: first.targetTable,
|
|
496
|
+
targetColumns: values.map(value => value.targetColumn),
|
|
497
|
+
onDelete: action(first.onDelete, 'mssql sys.foreign_keys', 0, 'delete_action'),
|
|
498
|
+
onUpdate: action(first.onUpdate, 'mssql sys.foreign_keys', 0, 'update_action'),
|
|
499
|
+
disabled: first.disabled,
|
|
500
|
+
trusted: first.trusted,
|
|
501
|
+
};
|
|
502
|
+
const key = tableKey(first.schema, first.table);
|
|
503
|
+
const tableValues = byTable.get(key);
|
|
504
|
+
if (tableValues) tableValues.push(snapshot);
|
|
505
|
+
else byTable.set(key, [snapshot]);
|
|
506
|
+
}
|
|
507
|
+
return new Map(
|
|
508
|
+
[...byTable].map(([key, values]) => [key, values.toSorted((left, right) => left.name.localeCompare(right.name))]),
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function indexes(
|
|
513
|
+
rows: readonly MssqlIndexRow[],
|
|
514
|
+
warnings: CatalogWarning[],
|
|
515
|
+
): ReadonlyMap<string, readonly MssqlCatalogIndexSnapshot[]> {
|
|
516
|
+
const grouped = new Map<string, MssqlIndexRow[]>();
|
|
517
|
+
for (const row of rows) {
|
|
518
|
+
if (row.primary) continue;
|
|
519
|
+
const key = `${tableKey(row.schema, row.table)}\0${row.name}`;
|
|
520
|
+
const values = grouped.get(key);
|
|
521
|
+
if (values) values.push(row);
|
|
522
|
+
else grouped.set(key, [row]);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const byTable = new Map<string, MssqlCatalogIndexSnapshot[]>();
|
|
526
|
+
for (const values of grouped.values()) {
|
|
527
|
+
values.sort((left, right) => left.ordinal - right.ordinal);
|
|
528
|
+
const first = values[0];
|
|
529
|
+
if (first === undefined) continue;
|
|
530
|
+
if (
|
|
531
|
+
values.some(
|
|
532
|
+
value =>
|
|
533
|
+
value.unique !== first.unique ||
|
|
534
|
+
value.type !== first.type ||
|
|
535
|
+
value.filtered !== first.filtered ||
|
|
536
|
+
value.predicate !== first.predicate ||
|
|
537
|
+
value.disabled !== first.disabled,
|
|
538
|
+
)
|
|
539
|
+
) {
|
|
540
|
+
throw new TypeError(`mssql index "${first.name}" has inconsistent catalog rows`);
|
|
541
|
+
}
|
|
542
|
+
if (first.filtered !== (first.predicate !== null)) {
|
|
543
|
+
throw new TypeError(
|
|
544
|
+
`mssql index "${first.name}" reports has_filter=${String(first.filtered)} with ` +
|
|
545
|
+
`${first.predicate === null ? 'no' : 'a'} filter definition`,
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
const type = first.type.toUpperCase();
|
|
549
|
+
const clustered = type === 'CLUSTERED';
|
|
550
|
+
if (type !== 'CLUSTERED' && type !== 'NONCLUSTERED') {
|
|
551
|
+
warnings.push({
|
|
552
|
+
table: first.table,
|
|
553
|
+
reason: `SQL Server index "${first.name}" uses ${first.type}, which the current DDL vocabulary cannot recreate`,
|
|
554
|
+
});
|
|
555
|
+
} else if (clustered) {
|
|
556
|
+
warnings.push({
|
|
557
|
+
table: first.table,
|
|
558
|
+
reason: `SQL Server index "${first.name}" is clustered; clustering is preserved as catalog evidence but not emitted by the current DDL vocabulary`,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
const includedColumns = values.filter(value => value.included).map(value => value.column);
|
|
562
|
+
if (includedColumns.length > 0) {
|
|
563
|
+
warnings.push({
|
|
564
|
+
table: first.table,
|
|
565
|
+
reason:
|
|
566
|
+
`SQL Server index "${first.name}" includes ${includedColumns.join(', ')}; ` +
|
|
567
|
+
'included columns are preserved as catalog evidence but not emitted by the current DDL vocabulary',
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
if (first.disabled) {
|
|
571
|
+
warnings.push({ table: first.table, reason: `SQL Server index "${first.name}" is disabled` });
|
|
572
|
+
}
|
|
573
|
+
const columns: CatalogIndexColumn[] = values.filter(value => !value.included).map(value => value.column);
|
|
574
|
+
const snapshot: MssqlCatalogIndexSnapshot = {
|
|
575
|
+
name: first.name,
|
|
576
|
+
columns,
|
|
577
|
+
unique: first.unique,
|
|
578
|
+
...(type === 'NONCLUSTERED' ? {} : { method: first.type.toLowerCase() }),
|
|
579
|
+
...(first.predicate === null ? {} : { where: first.predicate }),
|
|
580
|
+
clustered,
|
|
581
|
+
includedColumns,
|
|
582
|
+
disabled: first.disabled,
|
|
583
|
+
};
|
|
584
|
+
const key = tableKey(first.schema, first.table);
|
|
585
|
+
const tableValues = byTable.get(key);
|
|
586
|
+
if (tableValues) tableValues.push(snapshot);
|
|
587
|
+
else byTable.set(key, [snapshot]);
|
|
588
|
+
}
|
|
589
|
+
return new Map(
|
|
590
|
+
[...byTable].map(([key, values]) => [key, values.toSorted((left, right) => left.name.localeCompare(right.name))]),
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function sequenceType(sequence: MssqlSequence): string {
|
|
595
|
+
const type = sequence.dataType.toUpperCase();
|
|
596
|
+
return type === 'DECIMAL' || type === 'NUMERIC'
|
|
597
|
+
? `${type}(${String(sequence.precision)},${String(sequence.scale)})`
|
|
598
|
+
: type;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async function mssqlSnapshot(
|
|
602
|
+
driver: IntrospectionDriver,
|
|
603
|
+
options: IntrospectOptions = {},
|
|
604
|
+
): Promise<MssqlCatalogSchemaSnapshot> {
|
|
605
|
+
const selectedSchemas = schemas(options);
|
|
606
|
+
const slots = placeholders(selectedSchemas.length);
|
|
607
|
+
const [tableRows, columnRows, primaryKeyRows, foreignKeyRows, indexRows, sequenceRows] = await Promise.all([
|
|
608
|
+
driver.execute(
|
|
609
|
+
query(
|
|
610
|
+
`SELECT s.name AS schema_name, t.name AS table_name ` +
|
|
611
|
+
`FROM sys.tables t JOIN sys.schemas s ON s.schema_id = t.schema_id ` +
|
|
612
|
+
`WHERE t.is_ms_shipped = 0 AND s.name IN (${slots}) ORDER BY s.name, t.name`,
|
|
613
|
+
selectedSchemas,
|
|
614
|
+
),
|
|
615
|
+
),
|
|
616
|
+
driver.execute(
|
|
617
|
+
query(
|
|
618
|
+
`SELECT s.name AS schema_name, t.name AS table_name, c.column_id AS ordinal_position, ` +
|
|
619
|
+
`c.name AS column_name, c.is_nullable, ty.name AS data_type, c.max_length, ` +
|
|
620
|
+
`c.precision AS numeric_precision, c.scale AS numeric_scale, dc.definition AS column_default, ` +
|
|
621
|
+
`c.is_identity, ic.seed_value, ic.increment_value, c.is_computed, ` +
|
|
622
|
+
`cc.definition AS computed_definition, COALESCE(cc.is_persisted, 0) AS is_persisted ` +
|
|
623
|
+
`FROM sys.tables t JOIN sys.schemas s ON s.schema_id = t.schema_id ` +
|
|
624
|
+
`JOIN sys.columns c ON c.object_id = t.object_id ` +
|
|
625
|
+
`JOIN sys.types ty ON ty.user_type_id = c.user_type_id ` +
|
|
626
|
+
`LEFT JOIN sys.default_constraints dc ON dc.object_id = c.default_object_id ` +
|
|
627
|
+
`LEFT JOIN sys.identity_columns ic ON ic.object_id = c.object_id AND ic.column_id = c.column_id ` +
|
|
628
|
+
`LEFT JOIN sys.computed_columns cc ON cc.object_id = c.object_id AND cc.column_id = c.column_id ` +
|
|
629
|
+
`WHERE t.is_ms_shipped = 0 AND s.name IN (${slots}) ` +
|
|
630
|
+
`ORDER BY s.name, t.name, c.column_id`,
|
|
631
|
+
selectedSchemas,
|
|
632
|
+
),
|
|
633
|
+
),
|
|
634
|
+
driver.execute(
|
|
635
|
+
query(
|
|
636
|
+
`SELECT s.name AS schema_name, t.name AS table_name, ic.key_ordinal AS ordinal_position, ` +
|
|
637
|
+
`c.name AS column_name FROM sys.key_constraints kc ` +
|
|
638
|
+
`JOIN sys.tables t ON t.object_id = kc.parent_object_id ` +
|
|
639
|
+
`JOIN sys.schemas s ON s.schema_id = t.schema_id ` +
|
|
640
|
+
`JOIN sys.index_columns ic ON ic.object_id = t.object_id AND ic.index_id = kc.unique_index_id ` +
|
|
641
|
+
`JOIN sys.columns c ON c.object_id = t.object_id AND c.column_id = ic.column_id ` +
|
|
642
|
+
`WHERE kc.type = 'PK' AND s.name IN (${slots}) ORDER BY s.name, t.name, ic.key_ordinal`,
|
|
643
|
+
selectedSchemas,
|
|
644
|
+
),
|
|
645
|
+
),
|
|
646
|
+
driver.execute(
|
|
647
|
+
query(
|
|
648
|
+
`SELECT s.name AS schema_name, t.name AS table_name, fk.name AS constraint_name, ` +
|
|
649
|
+
`fkc.constraint_column_id AS ordinal_position, pc.name AS column_name, ` +
|
|
650
|
+
`rs.name AS target_schema, rt.name AS target_table, rc.name AS target_column, ` +
|
|
651
|
+
`fk.update_referential_action_desc AS update_action, ` +
|
|
652
|
+
`fk.delete_referential_action_desc AS delete_action, fk.is_disabled, fk.is_not_trusted ` +
|
|
653
|
+
`FROM sys.foreign_keys fk JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id ` +
|
|
654
|
+
`JOIN sys.tables t ON t.object_id = fk.parent_object_id ` +
|
|
655
|
+
`JOIN sys.schemas s ON s.schema_id = t.schema_id ` +
|
|
656
|
+
`JOIN sys.columns pc ON pc.object_id = t.object_id AND pc.column_id = fkc.parent_column_id ` +
|
|
657
|
+
`JOIN sys.tables rt ON rt.object_id = fk.referenced_object_id ` +
|
|
658
|
+
`JOIN sys.schemas rs ON rs.schema_id = rt.schema_id ` +
|
|
659
|
+
`JOIN sys.columns rc ON rc.object_id = rt.object_id AND rc.column_id = fkc.referenced_column_id ` +
|
|
660
|
+
`WHERE s.name IN (${slots}) ORDER BY s.name, t.name, fk.name, fkc.constraint_column_id`,
|
|
661
|
+
selectedSchemas,
|
|
662
|
+
),
|
|
663
|
+
),
|
|
664
|
+
driver.execute(
|
|
665
|
+
query(
|
|
666
|
+
`SELECT s.name AS schema_name, t.name AS table_name, i.name AS index_name, i.is_unique, ` +
|
|
667
|
+
`i.is_primary_key, i.type_desc AS index_type, i.has_filter, i.filter_definition, ` +
|
|
668
|
+
`CASE WHEN ic.is_included_column = 1 THEN ic.index_column_id ELSE ic.key_ordinal END AS ordinal_position, ` +
|
|
669
|
+
`ic.is_included_column, c.name AS column_name, i.is_disabled ` +
|
|
670
|
+
`FROM sys.indexes i JOIN sys.tables t ON t.object_id = i.object_id ` +
|
|
671
|
+
`JOIN sys.schemas s ON s.schema_id = t.schema_id ` +
|
|
672
|
+
`JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id ` +
|
|
673
|
+
`JOIN sys.columns c ON c.object_id = i.object_id AND c.column_id = ic.column_id ` +
|
|
674
|
+
`WHERE i.name IS NOT NULL AND i.is_hypothetical = 0 AND s.name IN (${slots}) ` +
|
|
675
|
+
`ORDER BY s.name, t.name, i.name, ic.is_included_column, ordinal_position`,
|
|
676
|
+
selectedSchemas,
|
|
677
|
+
),
|
|
678
|
+
),
|
|
679
|
+
driver.execute(
|
|
680
|
+
query(
|
|
681
|
+
`SELECT s.name AS schema_name, seq.name AS sequence_name, ty.name AS data_type, ` +
|
|
682
|
+
`seq.precision AS numeric_precision, seq.scale AS numeric_scale, seq.start_value, seq.increment AS increment_value ` +
|
|
683
|
+
`FROM sys.sequences seq JOIN sys.schemas s ON s.schema_id = seq.schema_id ` +
|
|
684
|
+
`JOIN sys.types ty ON ty.user_type_id = seq.user_type_id ` +
|
|
685
|
+
`WHERE s.name IN (${slots}) ORDER BY s.name, seq.name`,
|
|
686
|
+
selectedSchemas,
|
|
687
|
+
),
|
|
688
|
+
),
|
|
689
|
+
]);
|
|
690
|
+
|
|
691
|
+
const tables = tableRows.map(parseTable).filter(table => selected(table.name, options));
|
|
692
|
+
const duplicate = tables.find(
|
|
693
|
+
(table, index) => tables.findIndex(candidate => candidate.name === table.name) !== index,
|
|
694
|
+
);
|
|
695
|
+
if (duplicate !== undefined) {
|
|
696
|
+
throw new TypeError(
|
|
697
|
+
`mssql introspection cannot represent table "${duplicate.name}" from more than one schema in a schema-neutral snapshot`,
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const parsedColumns = columnRows.map(parseColumn);
|
|
702
|
+
const parsedPrimaryKeys = primaryKeyRows.map(parsePrimaryKey);
|
|
703
|
+
const parsedForeignKeys = foreignKeyRows.map(parseForeignKey);
|
|
704
|
+
const parsedIndexes = indexRows.map(parseIndex);
|
|
705
|
+
const parsedSequences = sequenceRows.map(parseSequence);
|
|
706
|
+
const primaryKeyByTable = primaryKeys(parsedPrimaryKeys);
|
|
707
|
+
const warnings: CatalogWarning[] = [];
|
|
708
|
+
const foreignKeyByTable = foreignKeys(parsedForeignKeys, warnings);
|
|
709
|
+
const indexByTable = indexes(parsedIndexes, warnings);
|
|
710
|
+
const snapshots: MssqlCatalogTableSnapshot[] = [];
|
|
711
|
+
|
|
712
|
+
for (const table of tables) {
|
|
713
|
+
const key = tableKey(table.schema, table.name);
|
|
714
|
+
const primaryKey = primaryKeyByTable.get(key) ?? [];
|
|
715
|
+
const columns: MssqlCatalogColumnSnapshot[] = parsedColumns
|
|
716
|
+
.filter(column => column.schema === table.schema && column.table === table.name)
|
|
717
|
+
.map(column => {
|
|
718
|
+
const mapped = mappedType(column);
|
|
719
|
+
for (const reason of mapped.warnings) warnings.push({ table: table.name, column: column.name, reason });
|
|
720
|
+
if (column.computed && column.computedDefinition === null) {
|
|
721
|
+
throw new TypeError(`mssql computed column "${table.name}"."${column.name}" has no definition`);
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
name: column.name,
|
|
725
|
+
type: mapped.type,
|
|
726
|
+
catalogType: catalogType(column),
|
|
727
|
+
nullable: column.nullable,
|
|
728
|
+
primaryKey: primaryKey.includes(column.name),
|
|
729
|
+
...(mapped.length === undefined ? {} : { length: mapped.length }),
|
|
730
|
+
...(column.default === null ? {} : { default: column.default }),
|
|
731
|
+
...(column.identity
|
|
732
|
+
? {
|
|
733
|
+
identity: {
|
|
734
|
+
seed: column.seed ?? '',
|
|
735
|
+
increment: column.increment ?? '',
|
|
736
|
+
},
|
|
737
|
+
}
|
|
738
|
+
: {}),
|
|
739
|
+
...(column.computed && column.computedDefinition !== null
|
|
740
|
+
? {
|
|
741
|
+
computed: {
|
|
742
|
+
expression: column.computedDefinition,
|
|
743
|
+
persisted: column.persisted,
|
|
744
|
+
},
|
|
745
|
+
}
|
|
746
|
+
: {}),
|
|
747
|
+
};
|
|
748
|
+
})
|
|
749
|
+
.toSorted((left, right) => left.name.localeCompare(right.name));
|
|
750
|
+
|
|
751
|
+
snapshots.push({
|
|
752
|
+
name: table.name,
|
|
753
|
+
columns,
|
|
754
|
+
primaryKey,
|
|
755
|
+
foreignKeys: foreignKeyByTable.get(key) ?? [],
|
|
756
|
+
indexes: indexByTable.get(key) ?? [],
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const sequences: MssqlCatalogSequenceSnapshot[] = parsedSequences
|
|
761
|
+
.filter(sequence => selected(sequence.name, options))
|
|
762
|
+
.map(sequence => ({
|
|
763
|
+
name: sequence.name,
|
|
764
|
+
...(sequence.schema === 'dbo' ? {} : { schema: sequence.schema }),
|
|
765
|
+
catalogType: sequenceType(sequence),
|
|
766
|
+
start: sequence.start,
|
|
767
|
+
increment: sequence.increment,
|
|
768
|
+
}))
|
|
769
|
+
.toSorted((left, right) => left.name.localeCompare(right.name));
|
|
770
|
+
|
|
771
|
+
return {
|
|
772
|
+
version: 1,
|
|
773
|
+
tables: snapshots.toSorted((left, right) => left.name.localeCompare(right.name)),
|
|
774
|
+
extensions: [],
|
|
775
|
+
warnings: warnings.toSorted(
|
|
776
|
+
(left, right) =>
|
|
777
|
+
left.table.localeCompare(right.table) ||
|
|
778
|
+
(left.column ?? '').localeCompare(right.column ?? '') ||
|
|
779
|
+
left.reason.localeCompare(right.reason),
|
|
780
|
+
),
|
|
781
|
+
sequences,
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
export const mssqlIntrospector: MssqlIntrospector = Object.freeze({
|
|
786
|
+
name: 'mssql',
|
|
787
|
+
dialect: 'mssql',
|
|
788
|
+
snapshot: mssqlSnapshot,
|
|
789
|
+
normalizeForDrift: (snapshot: SchemaSnapshot, role: 'live' | 'declared') => normalizeDriftSnapshot(snapshot, role),
|
|
790
|
+
});
|