metal-orm 1.1.28 → 1.1.29
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/dist/index.cjs +312 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +312 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ddl/dialects/render-reference.test.ts +13 -0
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +1 -0
- package/src/core/ddl/introspect/sqlite-foreign-key-ddl.ts +321 -0
- package/src/core/ddl/introspect/sqlite.ts +16 -2
- package/src/core/ddl/schema-diff.ts +26 -2
- package/src/core/ddl/schema-generator.ts +6 -1
- package/src/core/ddl/schema-types.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { composeSchemaDialect } from '../schema-dialect-composer.js';
|
|
3
3
|
import { PostgresSchemaDialect } from './postgres-schema-dialect.js';
|
|
4
|
+
import { SQLiteSchemaDialect } from './sqlite-schema-dialect.js';
|
|
4
5
|
import type { TableDef } from '../../../schema/table.js';
|
|
5
6
|
import type { ForeignKeyReference } from '../../../schema/column-types.js';
|
|
6
7
|
import { createLiteralFormatter } from '../sql-writing.js';
|
|
@@ -42,9 +43,21 @@ describe('renderReference deferrable handling', () => {
|
|
|
42
43
|
expect(sql).toContain('DEFERRABLE INITIALLY DEFERRED');
|
|
43
44
|
});
|
|
44
45
|
|
|
46
|
+
it('SQLite dialect renders the deferrable clause', () => {
|
|
47
|
+
const dialect = new SQLiteSchemaDialect();
|
|
48
|
+
const sql = dialect.renderReference(deferrableReference, table);
|
|
49
|
+
expect(sql).toContain('DEFERRABLE INITIALLY DEFERRED');
|
|
50
|
+
});
|
|
51
|
+
|
|
45
52
|
it('Postgres dialect skips the clause when the flag is missing', () => {
|
|
46
53
|
const dialect = new PostgresSchemaDialect();
|
|
47
54
|
const sql = dialect.renderReference({ table: 'parent', column: 'id' }, table);
|
|
48
55
|
expect(sql).not.toContain('DEFERRABLE INITIALLY DEFERRED');
|
|
49
56
|
});
|
|
57
|
+
|
|
58
|
+
it('SQLite dialect skips the clause when the flag is missing', () => {
|
|
59
|
+
const dialect = new SQLiteSchemaDialect();
|
|
60
|
+
const sql = dialect.renderReference({ table: 'parent', column: 'id' }, table);
|
|
61
|
+
expect(sql).not.toContain('DEFERRABLE INITIALLY DEFERRED');
|
|
62
|
+
});
|
|
50
63
|
});
|
|
@@ -79,6 +79,7 @@ export const createSqliteSchemaDialect = (): SchemaDialect =>
|
|
|
79
79
|
void table;
|
|
80
80
|
return !!(column.autoIncrement && primaryKey.length === 1 && primaryKey[0] === column.name);
|
|
81
81
|
},
|
|
82
|
+
renderReferenceSuffix: ref => ref.deferrable ? 'DEFERRABLE INITIALLY DEFERRED' : undefined,
|
|
82
83
|
renderIndex(table, index, services) {
|
|
83
84
|
const name = index.name || deriveIndexName(table, index);
|
|
84
85
|
const columns = renderIndexColumns(services, index.columns);
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
export interface SqliteForeignKeyModifier {
|
|
2
|
+
column: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
deferrable: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const isIdentifierChar = (value: string): boolean => /[A-Za-z0-9_]/.test(value);
|
|
8
|
+
|
|
9
|
+
const keywordAt = (text: string, position: number, keyword: string): boolean => {
|
|
10
|
+
if (position < 0 || position + keyword.length > text.length) return false;
|
|
11
|
+
if (position > 0 && isIdentifierChar(text[position - 1])) return false;
|
|
12
|
+
if (position + keyword.length < text.length && isIdentifierChar(text[position + keyword.length])) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return text.slice(position, position + keyword.length).toUpperCase() === keyword.toUpperCase();
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const skipWhitespace = (text: string, position: number): number => {
|
|
19
|
+
let pos = position;
|
|
20
|
+
while (pos < text.length && /\s/.test(text[pos])) pos += 1;
|
|
21
|
+
return pos;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const readIdentifier = (text: string, position: number): { value: string; next: number } | undefined => {
|
|
25
|
+
let pos = skipWhitespace(text, position);
|
|
26
|
+
if (pos >= text.length) return undefined;
|
|
27
|
+
|
|
28
|
+
const quote = text[pos];
|
|
29
|
+
if (quote === '"' || quote === '`' || quote === '[') {
|
|
30
|
+
const close = quote === '[' ? ']' : quote;
|
|
31
|
+
pos += 1;
|
|
32
|
+
let value = '';
|
|
33
|
+
while (pos < text.length) {
|
|
34
|
+
const current = text[pos++];
|
|
35
|
+
if (current === close) {
|
|
36
|
+
if (pos < text.length && text[pos] === close) {
|
|
37
|
+
value += close;
|
|
38
|
+
pos += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
return { value, next: pos };
|
|
42
|
+
}
|
|
43
|
+
value += current;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const start = pos;
|
|
49
|
+
while (
|
|
50
|
+
pos < text.length
|
|
51
|
+
&& !/\s/.test(text[pos])
|
|
52
|
+
&& text[pos] !== '('
|
|
53
|
+
&& text[pos] !== ')'
|
|
54
|
+
&& text[pos] !== ','
|
|
55
|
+
) {
|
|
56
|
+
pos += 1;
|
|
57
|
+
}
|
|
58
|
+
if (pos === start) return undefined;
|
|
59
|
+
return { value: text.slice(start, pos), next: pos };
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const splitTableBody = (createSql: string): string[] => {
|
|
63
|
+
const open = createSql.indexOf('(');
|
|
64
|
+
if (open < 0) return [];
|
|
65
|
+
|
|
66
|
+
let depth = 1;
|
|
67
|
+
let quote = '';
|
|
68
|
+
let bracket = false;
|
|
69
|
+
let lineComment = false;
|
|
70
|
+
let blockComment = false;
|
|
71
|
+
let close = -1;
|
|
72
|
+
|
|
73
|
+
for (let i = open + 1; i < createSql.length; i += 1) {
|
|
74
|
+
const current = createSql[i];
|
|
75
|
+
const next = createSql[i + 1] ?? '';
|
|
76
|
+
|
|
77
|
+
if (lineComment) {
|
|
78
|
+
if (current === '\n' || current === '\r') lineComment = false;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (blockComment) {
|
|
82
|
+
if (current === '*' && next === '/') {
|
|
83
|
+
blockComment = false;
|
|
84
|
+
i += 1;
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (quote) {
|
|
89
|
+
if (current === quote) {
|
|
90
|
+
if (next === quote) i += 1;
|
|
91
|
+
else quote = '';
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (bracket) {
|
|
96
|
+
if (current === ']') {
|
|
97
|
+
if (next === ']') i += 1;
|
|
98
|
+
else bracket = false;
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (current === '-' && next === '-') {
|
|
103
|
+
lineComment = true;
|
|
104
|
+
i += 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (current === '/' && next === '*') {
|
|
108
|
+
blockComment = true;
|
|
109
|
+
i += 1;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (current === '\'' || current === '"' || current === '`') {
|
|
113
|
+
quote = current;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (current === '[') {
|
|
117
|
+
bracket = true;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (current === '(') depth += 1;
|
|
121
|
+
else if (current === ')') {
|
|
122
|
+
depth -= 1;
|
|
123
|
+
if (depth === 0) {
|
|
124
|
+
close = i;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (close < 0) return [];
|
|
131
|
+
const body = createSql.slice(open + 1, close);
|
|
132
|
+
const segments: string[] = [];
|
|
133
|
+
let start = 0;
|
|
134
|
+
depth = 0;
|
|
135
|
+
quote = '';
|
|
136
|
+
bracket = false;
|
|
137
|
+
lineComment = false;
|
|
138
|
+
blockComment = false;
|
|
139
|
+
|
|
140
|
+
for (let i = 0; i < body.length; i += 1) {
|
|
141
|
+
const current = body[i];
|
|
142
|
+
const next = body[i + 1] ?? '';
|
|
143
|
+
|
|
144
|
+
if (lineComment) {
|
|
145
|
+
if (current === '\n' || current === '\r') lineComment = false;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (blockComment) {
|
|
149
|
+
if (current === '*' && next === '/') {
|
|
150
|
+
blockComment = false;
|
|
151
|
+
i += 1;
|
|
152
|
+
}
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (quote) {
|
|
156
|
+
if (current === quote) {
|
|
157
|
+
if (next === quote) i += 1;
|
|
158
|
+
else quote = '';
|
|
159
|
+
}
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (bracket) {
|
|
163
|
+
if (current === ']') {
|
|
164
|
+
if (next === ']') i += 1;
|
|
165
|
+
else bracket = false;
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (current === '-' && next === '-') {
|
|
170
|
+
lineComment = true;
|
|
171
|
+
i += 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (current === '/' && next === '*') {
|
|
175
|
+
blockComment = true;
|
|
176
|
+
i += 1;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (current === '\'' || current === '"' || current === '`') {
|
|
180
|
+
quote = current;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (current === '[') {
|
|
184
|
+
bracket = true;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (current === '(') depth += 1;
|
|
188
|
+
else if (current === ')' && depth > 0) depth -= 1;
|
|
189
|
+
else if (current === ',' && depth === 0) {
|
|
190
|
+
segments.push(body.slice(start, i).trim());
|
|
191
|
+
start = i + 1;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
segments.push(body.slice(start).trim());
|
|
196
|
+
return segments.filter(Boolean);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const findTopLevelKeyword = (text: string, keyword: string, start = 0): number => {
|
|
200
|
+
let depth = 0;
|
|
201
|
+
let quote = '';
|
|
202
|
+
let bracket = false;
|
|
203
|
+
|
|
204
|
+
for (let i = start; i < text.length; i += 1) {
|
|
205
|
+
const current = text[i];
|
|
206
|
+
const next = text[i + 1] ?? '';
|
|
207
|
+
if (quote) {
|
|
208
|
+
if (current === quote) {
|
|
209
|
+
if (next === quote) i += 1;
|
|
210
|
+
else quote = '';
|
|
211
|
+
}
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (bracket) {
|
|
215
|
+
if (current === ']') {
|
|
216
|
+
if (next === ']') i += 1;
|
|
217
|
+
else bracket = false;
|
|
218
|
+
}
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (current === '\'' || current === '"' || current === '`') {
|
|
222
|
+
quote = current;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (current === '[') {
|
|
226
|
+
bracket = true;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (current === '(') {
|
|
230
|
+
depth += 1;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (current === ')') {
|
|
234
|
+
if (depth > 0) depth -= 1;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (depth === 0 && keywordAt(text, i, keyword)) return i;
|
|
238
|
+
}
|
|
239
|
+
return -1;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const isInitiallyDeferred = (segment: string, start: number): boolean => {
|
|
243
|
+
const deferrablePos = findTopLevelKeyword(segment, 'DEFERRABLE', start);
|
|
244
|
+
if (deferrablePos < 0) return false;
|
|
245
|
+
|
|
246
|
+
const before = segment.slice(start, deferrablePos).trimEnd();
|
|
247
|
+
if (/\bNOT$/i.test(before)) return false;
|
|
248
|
+
|
|
249
|
+
return /^DEFERRABLE\s+INITIALLY\s+DEFERRED\b/i.test(segment.slice(deferrablePos));
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const parseTableForeignKey = (segment: string): SqliteForeignKeyModifier | undefined => {
|
|
253
|
+
let pos = skipWhitespace(segment, 0);
|
|
254
|
+
let name: string | undefined;
|
|
255
|
+
|
|
256
|
+
if (keywordAt(segment, pos, 'CONSTRAINT')) {
|
|
257
|
+
pos += 'CONSTRAINT'.length;
|
|
258
|
+
const parsedName = readIdentifier(segment, pos);
|
|
259
|
+
if (!parsedName) return undefined;
|
|
260
|
+
name = parsedName.value;
|
|
261
|
+
pos = skipWhitespace(segment, parsedName.next);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!keywordAt(segment, pos, 'FOREIGN')) return undefined;
|
|
265
|
+
pos += 'FOREIGN'.length;
|
|
266
|
+
pos = skipWhitespace(segment, pos);
|
|
267
|
+
if (!keywordAt(segment, pos, 'KEY')) return undefined;
|
|
268
|
+
pos += 'KEY'.length;
|
|
269
|
+
pos = skipWhitespace(segment, pos);
|
|
270
|
+
if (segment[pos] !== '(') return undefined;
|
|
271
|
+
pos += 1;
|
|
272
|
+
|
|
273
|
+
const source = readIdentifier(segment, pos);
|
|
274
|
+
if (!source) return undefined;
|
|
275
|
+
pos = skipWhitespace(segment, source.next);
|
|
276
|
+
if (segment[pos] === ',') return undefined;
|
|
277
|
+
|
|
278
|
+
const referencesPos = findTopLevelKeyword(segment, 'REFERENCES', pos);
|
|
279
|
+
if (referencesPos < 0) return undefined;
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
column: source.value,
|
|
283
|
+
...(name ? { name } : {}),
|
|
284
|
+
deferrable: isInitiallyDeferred(segment, referencesPos)
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const parseInlineForeignKey = (segment: string): SqliteForeignKeyModifier | undefined => {
|
|
289
|
+
const source = readIdentifier(segment, 0);
|
|
290
|
+
if (!source) return undefined;
|
|
291
|
+
|
|
292
|
+
const referencesPos = findTopLevelKeyword(segment, 'REFERENCES', source.next);
|
|
293
|
+
if (referencesPos < 0) return undefined;
|
|
294
|
+
|
|
295
|
+
let name: string | undefined;
|
|
296
|
+
const constraintPos = findTopLevelKeyword(segment, 'CONSTRAINT', source.next);
|
|
297
|
+
if (constraintPos >= 0 && constraintPos < referencesPos) {
|
|
298
|
+
const parsedName = readIdentifier(segment, constraintPos + 'CONSTRAINT'.length);
|
|
299
|
+
if (parsedName) name = parsedName.value;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
column: source.value,
|
|
304
|
+
...(name ? { name } : {}),
|
|
305
|
+
deferrable: isInitiallyDeferred(segment, referencesPos)
|
|
306
|
+
};
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
export const parseSqliteForeignKeyModifiers = (createSql: string): SqliteForeignKeyModifier[] => {
|
|
310
|
+
const result: SqliteForeignKeyModifier[] = [];
|
|
311
|
+
for (const segment of splitTableBody(createSql)) {
|
|
312
|
+
const tableConstraint = parseTableForeignKey(segment);
|
|
313
|
+
if (tableConstraint) {
|
|
314
|
+
result.push(tableConstraint);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const inlineConstraint = parseInlineForeignKey(segment);
|
|
318
|
+
if (inlineConstraint) result.push(inlineConstraint);
|
|
319
|
+
}
|
|
320
|
+
return result;
|
|
321
|
+
};
|
|
@@ -3,6 +3,7 @@ import { shouldIncludeTable, shouldIncludeView, queryRows } from './utils.js';
|
|
|
3
3
|
import { DatabaseSchema, DatabaseTable, DatabaseIndex, DatabaseColumn, DatabaseView } from '../schema-types.js';
|
|
4
4
|
import type { IntrospectContext } from './context.js';
|
|
5
5
|
import { runSelectNode } from './run-select.js';
|
|
6
|
+
import { parseSqliteForeignKeyModifiers } from './sqlite-foreign-key-ddl.js';
|
|
6
7
|
import type { SelectQueryNode, TableNode } from '../../ast/query.js';
|
|
7
8
|
import type { ColumnNode } from '../../ast/expression-nodes.js';
|
|
8
9
|
import { eq, notLike, and, valueToOperand } from '../../ast/expression-builders.js';
|
|
@@ -11,6 +12,7 @@ import type { ReferentialAction } from '../../../schema/column-types.js';
|
|
|
11
12
|
|
|
12
13
|
type SqliteTableRow = {
|
|
13
14
|
name: string;
|
|
15
|
+
sql: string | null;
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
type SqliteTableInfoRow = {
|
|
@@ -73,7 +75,7 @@ const buildPragmaQuery = (
|
|
|
73
75
|
columnAliases: string[]
|
|
74
76
|
): SelectQueryNode => ({
|
|
75
77
|
type: 'SelectQuery',
|
|
76
|
-
from: fnTable(name, [valueToOperand(table)], alias
|
|
78
|
+
from: fnTable(name, [valueToOperand(table)], alias),
|
|
77
79
|
columns: columnAliases.map(column => columnNode(alias, column)),
|
|
78
80
|
joins: []
|
|
79
81
|
});
|
|
@@ -151,7 +153,10 @@ export const sqliteIntrospector: SchemaIntrospector = {
|
|
|
151
153
|
const tablesQuery: SelectQueryNode = {
|
|
152
154
|
type: 'SelectQuery',
|
|
153
155
|
from: { type: 'Table', name: 'sqlite_master' } as TableNode,
|
|
154
|
-
columns: [
|
|
156
|
+
columns: [
|
|
157
|
+
columnNode(alias, 'name'),
|
|
158
|
+
columnNode(alias, 'sql')
|
|
159
|
+
],
|
|
155
160
|
joins: [],
|
|
156
161
|
where: and(
|
|
157
162
|
eq(columnNode(alias, 'type'), 'table'),
|
|
@@ -230,6 +235,15 @@ export const sqliteIntrospector: SchemaIntrospector = {
|
|
|
230
235
|
}
|
|
231
236
|
});
|
|
232
237
|
|
|
238
|
+
if (row.sql) {
|
|
239
|
+
for (const modifier of parseSqliteForeignKeyModifiers(row.sql)) {
|
|
240
|
+
const reference = tableEntry.columns.find(column => column.name === modifier.column)?.references;
|
|
241
|
+
if (!reference) continue;
|
|
242
|
+
if (modifier.name) reference.name = modifier.name;
|
|
243
|
+
reference.deferrable = modifier.deferrable;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
233
247
|
for (const idx of indexList) {
|
|
234
248
|
if (!idx.name) continue;
|
|
235
249
|
const indexColumns = await runPragma<SqliteIndexInfoRow>(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TableDef } from '../../schema/table.js';
|
|
2
|
-
import type { ColumnDef } from '../../schema/column-types.js';
|
|
2
|
+
import type { ColumnDef, ForeignKeyReference } from '../../schema/column-types.js';
|
|
3
3
|
import type { DbExecutor } from '../execution/db-executor.js';
|
|
4
4
|
import type { SchemaDialect } from './schema-dialect.js';
|
|
5
5
|
import { deriveIndexName } from './naming-strategy.js';
|
|
@@ -54,6 +54,22 @@ const normalizeDefault = (value: unknown): string | undefined => {
|
|
|
54
54
|
return String(value).trim();
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
+
const normalizeReferenceAction = (value: string | undefined): string =>
|
|
58
|
+
(value || 'NO ACTION').toUpperCase().replace(/\s+/g, ' ').trim();
|
|
59
|
+
|
|
60
|
+
const sameReference = (
|
|
61
|
+
expected: ForeignKeyReference | undefined,
|
|
62
|
+
actual: ForeignKeyReference | undefined
|
|
63
|
+
): boolean => {
|
|
64
|
+
if (!expected || !actual) return expected === actual;
|
|
65
|
+
return expected.table === actual.table
|
|
66
|
+
&& expected.column === actual.column
|
|
67
|
+
&& expected.name === actual.name
|
|
68
|
+
&& normalizeReferenceAction(expected.onDelete) === normalizeReferenceAction(actual.onDelete)
|
|
69
|
+
&& normalizeReferenceAction(expected.onUpdate) === normalizeReferenceAction(actual.onUpdate)
|
|
70
|
+
&& !!expected.deferrable === !!actual.deferrable;
|
|
71
|
+
};
|
|
72
|
+
|
|
57
73
|
const diffColumn = (
|
|
58
74
|
expected: ColumnDef,
|
|
59
75
|
actual: DatabaseColumn,
|
|
@@ -69,7 +85,8 @@ const diffColumn = (
|
|
|
69
85
|
typeChanged: expectedType !== actualType,
|
|
70
86
|
nullabilityChanged: !!expected.notNull !== !!actual.notNull,
|
|
71
87
|
defaultChanged: expectedDefault !== actualDefault,
|
|
72
|
-
autoIncrementChanged: !!expected.autoIncrement !== !!actual.autoIncrement
|
|
88
|
+
autoIncrementChanged: !!expected.autoIncrement !== !!actual.autoIncrement,
|
|
89
|
+
referenceChanged: !sameReference(expected.references, actual.references)
|
|
73
90
|
};
|
|
74
91
|
};
|
|
75
92
|
|
|
@@ -121,6 +138,13 @@ export const diffSchema = (
|
|
|
121
138
|
const expectedColumn = table.columns[columnName];
|
|
122
139
|
const actualColumn = actualColumns.get(columnName)!;
|
|
123
140
|
const columnDiff = diffColumn(expectedColumn, actualColumn, dialect);
|
|
141
|
+
|
|
142
|
+
if (columnDiff.referenceChanged) {
|
|
143
|
+
plan.warnings.push(
|
|
144
|
+
`Foreign key definition on ${key}.${columnName} differs from the expected schema; manual constraint migration is required.`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
124
148
|
const shouldAlter =
|
|
125
149
|
columnDiff.typeChanged
|
|
126
150
|
|| columnDiff.nullabilityChanged
|
|
@@ -50,7 +50,12 @@ export const renderColumnDefinition = (
|
|
|
50
50
|
parts.push(`CHECK (${col.check})`);
|
|
51
51
|
}
|
|
52
52
|
if (col.references) {
|
|
53
|
-
|
|
53
|
+
const referenceSql = dialect.renderReference(col.references, table);
|
|
54
|
+
parts.push(
|
|
55
|
+
col.references.name
|
|
56
|
+
? `CONSTRAINT ${dialect.quoteIdentifier(col.references.name)} ${referenceSql}`
|
|
57
|
+
: referenceSql
|
|
58
|
+
);
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
return { sql: parts.join(' '), inlinePrimary: !!(options.includePrimary && col.primary) };
|