ormbridge 0.14.0 → 0.15.0
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/README.md +21 -21
- package/dist/cliRunner.js +2 -2
- package/dist/cliRunner.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/emitters/sequelize.d.ts +16 -3
- package/dist/emitters/sequelize.js +270 -45
- package/dist/emitters/sequelize.js.map +1 -1
- package/dist/emitters/sequelize7.d.ts +68 -0
- package/dist/emitters/sequelize7.js +199 -0
- package/dist/emitters/sequelize7.js.map +1 -0
- package/dist/emitters/sequelizeText.d.ts +12 -0
- package/dist/emitters/sequelizeText.js +37 -0
- package/dist/emitters/sequelizeText.js.map +1 -0
- package/dist/formats.d.ts +2 -2
- package/dist/formats.js +4 -3
- package/dist/formats.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/io.js +44 -6
- package/dist/io.js.map +1 -1
- package/dist/parsers/efcore.js +60 -3
- package/dist/parsers/efcore.js.map +1 -1
- package/dist/parsers/efcoreAnnotations.js +1 -1
- package/dist/parsers/efcoreAnnotations.js.map +1 -1
- package/dist/parsers/efcoreBuild.d.ts +1 -0
- package/dist/parsers/efcoreBuild.js +15 -4
- package/dist/parsers/efcoreBuild.js.map +1 -1
- package/dist/parsers/efcoreEntities.js +90 -22
- package/dist/parsers/efcoreEntities.js.map +1 -1
- package/dist/parsers/efcoreMigrationHistory.d.ts +17 -0
- package/dist/parsers/efcoreMigrationHistory.js +105 -0
- package/dist/parsers/efcoreMigrationHistory.js.map +1 -0
- package/dist/parsers/efcoreMigrationReplay.d.ts +25 -0
- package/dist/parsers/efcoreMigrationReplay.js +790 -0
- package/dist/parsers/efcoreMigrationReplay.js.map +1 -0
- package/dist/parsers/efcoreMigrationSchema.d.ts +14 -0
- package/dist/parsers/efcoreMigrationSchema.js +550 -0
- package/dist/parsers/efcoreMigrationSchema.js.map +1 -0
- package/dist/parsers/efcoreMigrationState.d.ts +82 -0
- package/dist/parsers/efcoreMigrationState.js +104 -0
- package/dist/parsers/efcoreMigrationState.js.map +1 -0
- package/dist/parsers/efcoreModel.d.ts +18 -2
- package/dist/parsers/efcoreOwned.d.ts +12 -0
- package/dist/parsers/efcoreOwned.js +139 -0
- package/dist/parsers/efcoreOwned.js.map +1 -0
- package/dist/parsers/efcoreSnapshot.d.ts +85 -0
- package/dist/parsers/efcoreSnapshot.js +791 -0
- package/dist/parsers/efcoreSnapshot.js.map +1 -0
- package/dist/parsers/efcoreSteps.js +28 -3
- package/dist/parsers/efcoreSteps.js.map +1 -1
- package/dist/parsers/sequelize.js +37 -10
- package/dist/parsers/sequelize.js.map +1 -1
- package/dist/parsers/sequelizeAssociations.js +13 -2
- package/dist/parsers/sequelizeAssociations.js.map +1 -1
- package/dist/parsers/sequelizeCollect.d.ts +7 -2
- package/dist/parsers/sequelizeCollect.js +120 -9
- package/dist/parsers/sequelizeCollect.js.map +1 -1
- package/dist/parsers/sequelizeMigrationState.d.ts +75 -0
- package/dist/parsers/sequelizeMigrationState.js +330 -0
- package/dist/parsers/sequelizeMigrationState.js.map +1 -0
- package/dist/parsers/sequelizeMigrations.d.ts +14 -0
- package/dist/parsers/sequelizeMigrations.js +1123 -0
- package/dist/parsers/sequelizeMigrations.js.map +1 -0
- package/dist/parsers/sequelizeModel.d.ts +19 -2
- package/dist/styles.d.ts +3 -3
- package/dist/styles.js +4 -4
- package/dist/styles.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
import { bodyStatements, flattenChain, readLambda, selectedMembers, } from './csharpChains.js';
|
|
2
|
+
import { evaluate, } from './csharpSyntax.js';
|
|
3
|
+
import { dropColumn, findColumn, referentialAction, renameColumn, renameTable, tableKey, } from './efcoreMigrationState.js';
|
|
4
|
+
const NO_CONSTANTS = () => undefined;
|
|
5
|
+
/** The parameter names of the `MigrationBuilder` methods in order, for calls that pass arguments by position. */
|
|
6
|
+
const PARAMETERS = {
|
|
7
|
+
CreateTable: ['name', 'columns', 'schema', 'constraints', 'comment'],
|
|
8
|
+
DropTable: ['name', 'schema'],
|
|
9
|
+
RenameTable: ['name', 'schema', 'newName', 'newSchema'],
|
|
10
|
+
AddColumn: [
|
|
11
|
+
'name',
|
|
12
|
+
'table',
|
|
13
|
+
'type',
|
|
14
|
+
'unicode',
|
|
15
|
+
'maxLength',
|
|
16
|
+
'rowVersion',
|
|
17
|
+
'schema',
|
|
18
|
+
'nullable',
|
|
19
|
+
'defaultValue',
|
|
20
|
+
'defaultValueSql',
|
|
21
|
+
'computedColumnSql',
|
|
22
|
+
'fixedLength',
|
|
23
|
+
'comment',
|
|
24
|
+
'collation',
|
|
25
|
+
'precision',
|
|
26
|
+
'scale',
|
|
27
|
+
'stored',
|
|
28
|
+
],
|
|
29
|
+
DropColumn: ['name', 'table', 'schema'],
|
|
30
|
+
RenameColumn: ['name', 'table', 'newName', 'schema'],
|
|
31
|
+
CreateIndex: [
|
|
32
|
+
'name',
|
|
33
|
+
'table',
|
|
34
|
+
'columns',
|
|
35
|
+
'schema',
|
|
36
|
+
'unique',
|
|
37
|
+
'filter',
|
|
38
|
+
'descending',
|
|
39
|
+
],
|
|
40
|
+
DropIndex: ['name', 'table', 'schema'],
|
|
41
|
+
RenameIndex: ['name', 'newName', 'table', 'schema'],
|
|
42
|
+
AddForeignKey: [
|
|
43
|
+
'name',
|
|
44
|
+
'table',
|
|
45
|
+
'columns',
|
|
46
|
+
'principalTable',
|
|
47
|
+
'schema',
|
|
48
|
+
'principalSchema',
|
|
49
|
+
'principalColumns',
|
|
50
|
+
'onUpdate',
|
|
51
|
+
'onDelete',
|
|
52
|
+
],
|
|
53
|
+
DropForeignKey: ['name', 'table', 'schema'],
|
|
54
|
+
AddPrimaryKey: ['name', 'table', 'columns', 'schema'],
|
|
55
|
+
DropPrimaryKey: ['name', 'table', 'schema'],
|
|
56
|
+
AddUniqueConstraint: ['name', 'table', 'columns', 'schema'],
|
|
57
|
+
DropUniqueConstraint: ['name', 'table', 'schema'],
|
|
58
|
+
AddCheckConstraint: ['name', 'table', 'sql', 'schema'],
|
|
59
|
+
DropCheckConstraint: ['name', 'table', 'schema'],
|
|
60
|
+
Sql: ['sql', 'suppressTransaction'],
|
|
61
|
+
// The column builder inside CreateTable.
|
|
62
|
+
Column: [
|
|
63
|
+
'type',
|
|
64
|
+
'unicode',
|
|
65
|
+
'maxLength',
|
|
66
|
+
'rowVersion',
|
|
67
|
+
'name',
|
|
68
|
+
'nullable',
|
|
69
|
+
'defaultValue',
|
|
70
|
+
'defaultValueSql',
|
|
71
|
+
'computedColumnSql',
|
|
72
|
+
'fixedLength',
|
|
73
|
+
'comment',
|
|
74
|
+
'collation',
|
|
75
|
+
'precision',
|
|
76
|
+
'scale',
|
|
77
|
+
'stored',
|
|
78
|
+
],
|
|
79
|
+
// The constraint builder inside CreateTable.
|
|
80
|
+
ForeignKey: [
|
|
81
|
+
'name',
|
|
82
|
+
'columns',
|
|
83
|
+
'principalTable',
|
|
84
|
+
'principalSchema',
|
|
85
|
+
'principalColumns',
|
|
86
|
+
'onUpdate',
|
|
87
|
+
'onDelete',
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
/** Calls that change nothing the shared model holds. */
|
|
91
|
+
const IGNORED_CALLS = new Set([
|
|
92
|
+
'EnsureSchema',
|
|
93
|
+
'DropSchema',
|
|
94
|
+
'AlterDatabase',
|
|
95
|
+
'AlterTable',
|
|
96
|
+
'Annotation',
|
|
97
|
+
]);
|
|
98
|
+
const SEQUENCE_CALLS = new Set([
|
|
99
|
+
'CreateSequence',
|
|
100
|
+
'AlterSequence',
|
|
101
|
+
'DropSequence',
|
|
102
|
+
'RenameSequence',
|
|
103
|
+
'RestartSequence',
|
|
104
|
+
]);
|
|
105
|
+
const DATA_CALLS = new Set([
|
|
106
|
+
'InsertData',
|
|
107
|
+
'UpdateData',
|
|
108
|
+
'DeleteData',
|
|
109
|
+
]);
|
|
110
|
+
const CONTROL_STATEMENTS = new Set([
|
|
111
|
+
'if_statement',
|
|
112
|
+
'for_statement',
|
|
113
|
+
'foreach_statement',
|
|
114
|
+
'while_statement',
|
|
115
|
+
'do_statement',
|
|
116
|
+
'switch_statement',
|
|
117
|
+
'try_statement',
|
|
118
|
+
'using_statement',
|
|
119
|
+
'lock_statement',
|
|
120
|
+
]);
|
|
121
|
+
const SQL_TABLE = /\b(?:alter\s+table|create\s+(?:or\s+replace\s+)?(?:table|view)(?:\s+if\s+not\s+exists)?|drop\s+(?:table|view)(?:\s+if\s+exists)?|truncate(?:\s+table)?|insert\s+into|update|delete\s+from|create\s+(?:unique\s+)?index\s+\S+\s+on)\s+[[`"']?([A-Za-z0-9_.]+)/i;
|
|
122
|
+
/** Where a migration statement is, for warnings: `Migrations/2024_Initial.cs:12`. */
|
|
123
|
+
function placeOf(path, node) {
|
|
124
|
+
return `${path}:${node.startPosition.row + 1}`;
|
|
125
|
+
}
|
|
126
|
+
function shorten(text, length = 60) {
|
|
127
|
+
const flat = text.replace(/\s+/g, ' ').trim();
|
|
128
|
+
return flat.length > length ? `${flat.slice(0, length - 3)}...` : flat;
|
|
129
|
+
}
|
|
130
|
+
/** The argument expressions of one call by parameter name, whether they were passed by name or by position. */
|
|
131
|
+
function readArguments(call, parameters) {
|
|
132
|
+
const found = new Map();
|
|
133
|
+
const list = call.node.childForFieldName('arguments');
|
|
134
|
+
let position = 0;
|
|
135
|
+
for (const argument of list?.namedChildren ?? []) {
|
|
136
|
+
if (argument.type !== 'argument') {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const label = argument.namedChildren.find((child) => child.type === 'name_colon');
|
|
140
|
+
const value = [...argument.namedChildren]
|
|
141
|
+
.reverse()
|
|
142
|
+
.find((child) => child.type !== 'name_colon');
|
|
143
|
+
if (value === undefined) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (label === undefined) {
|
|
147
|
+
const name = parameters[position];
|
|
148
|
+
position += 1;
|
|
149
|
+
if (name !== undefined) {
|
|
150
|
+
found.set(name, value);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
found.set(label.text.replace(/:\s*$/, '').trim(), value);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return found;
|
|
158
|
+
}
|
|
159
|
+
function valueOf(args, name) {
|
|
160
|
+
const node = args.get(name);
|
|
161
|
+
return node === undefined ? undefined : evaluate(node, NO_CONSTANTS);
|
|
162
|
+
}
|
|
163
|
+
function textArgument(args, name) {
|
|
164
|
+
const value = valueOf(args, name);
|
|
165
|
+
return value?.kind === 'string' ? value.value : undefined;
|
|
166
|
+
}
|
|
167
|
+
function boolArgument(args, name) {
|
|
168
|
+
const value = valueOf(args, name);
|
|
169
|
+
return value?.kind === 'bool' ? value.value : undefined;
|
|
170
|
+
}
|
|
171
|
+
function numberArgument(args, name) {
|
|
172
|
+
const value = valueOf(args, name);
|
|
173
|
+
return value?.kind === 'number' ? value.value : undefined;
|
|
174
|
+
}
|
|
175
|
+
/** A string or an array of strings (`column: "A"`, `columns: new[] { "A", "B" }`), as a list. */
|
|
176
|
+
function textList(args, single, many) {
|
|
177
|
+
for (const name of [many, single]) {
|
|
178
|
+
const value = valueOf(args, name);
|
|
179
|
+
if (value?.kind === 'string') {
|
|
180
|
+
return [value.value];
|
|
181
|
+
}
|
|
182
|
+
if (value?.kind === 'array' &&
|
|
183
|
+
value.items.every((item) => item.kind === 'string')) {
|
|
184
|
+
return value.items.map((item) => item.kind === 'string' ? item.value : '');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
function boolList(args, name) {
|
|
190
|
+
const value = valueOf(args, name);
|
|
191
|
+
if (value?.kind !== 'array') {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
return value.items.map((item) => item.kind === 'bool' && item.value);
|
|
195
|
+
}
|
|
196
|
+
/** The referential action of `onDelete: ReferentialAction.Cascade`; the database default when it is not given. */
|
|
197
|
+
function actionArgument(args, name) {
|
|
198
|
+
const value = valueOf(args, name);
|
|
199
|
+
if (value === undefined) {
|
|
200
|
+
return 'noAction';
|
|
201
|
+
}
|
|
202
|
+
if (value.kind === 'member') {
|
|
203
|
+
return referentialAction(value.path[value.path.length - 1] ?? '');
|
|
204
|
+
}
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
/** What `.Annotation("key", value)` calls after a column, index or table say. */
|
|
208
|
+
function annotationsOf(chain) {
|
|
209
|
+
const found = new Map();
|
|
210
|
+
for (const call of chain.calls) {
|
|
211
|
+
if (call.name !== 'Annotation') {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const key = call.args[0];
|
|
215
|
+
const value = call.args[1];
|
|
216
|
+
if (key === undefined || value === undefined) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const name = evaluate(key, NO_CONSTANTS);
|
|
220
|
+
if (name.kind === 'string') {
|
|
221
|
+
found.set(name.value, evaluate(value, NO_CONSTANTS));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return found;
|
|
225
|
+
}
|
|
226
|
+
/** True when the annotations of a column make it an identity, serial or autoincrement column. */
|
|
227
|
+
export function identityOf(annotations) {
|
|
228
|
+
for (const [key, value] of annotations) {
|
|
229
|
+
if (/:(?:Identity|Autoincrement)$/i.test(key)) {
|
|
230
|
+
return !(value.kind === 'bool' && !value.value);
|
|
231
|
+
}
|
|
232
|
+
if (/:ValueGenerationStrategy$/i.test(key)) {
|
|
233
|
+
const text = value.kind === 'member'
|
|
234
|
+
? value.path.join('.')
|
|
235
|
+
: value.kind === 'string'
|
|
236
|
+
? value.value
|
|
237
|
+
: '';
|
|
238
|
+
return /Identity|Serial/i.test(text);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
export function indexAnnotations(annotations) {
|
|
244
|
+
let method;
|
|
245
|
+
let includes = [];
|
|
246
|
+
for (const [key, value] of annotations) {
|
|
247
|
+
if (/:IndexMethod$/i.test(key) && value.kind === 'string') {
|
|
248
|
+
method = value.value;
|
|
249
|
+
}
|
|
250
|
+
if (/:(?:Include|IndexInclude)$/i.test(key) && value.kind === 'array') {
|
|
251
|
+
includes = value.items.map((item) => item.kind === 'string' ? item.value : '');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return { ...(method === undefined ? {} : { method }), includes };
|
|
255
|
+
}
|
|
256
|
+
/** The column named by the arguments of `Column<T>(...)`, `AddColumn<T>(...)` or `AlterColumn<T>(...)`. */
|
|
257
|
+
function columnOf(name, clrType, args, chain) {
|
|
258
|
+
const storeType = textArgument(args, 'type');
|
|
259
|
+
const maxLength = numberArgument(args, 'maxLength');
|
|
260
|
+
const precision = numberArgument(args, 'precision');
|
|
261
|
+
const scale = numberArgument(args, 'scale');
|
|
262
|
+
const isUnicode = boolArgument(args, 'unicode');
|
|
263
|
+
const defaultValue = valueOf(args, 'defaultValue');
|
|
264
|
+
const defaultSql = textArgument(args, 'defaultValueSql');
|
|
265
|
+
const computedSql = textArgument(args, 'computedColumnSql');
|
|
266
|
+
const isStored = boolArgument(args, 'stored');
|
|
267
|
+
return {
|
|
268
|
+
name,
|
|
269
|
+
clrType,
|
|
270
|
+
isNullable: boolArgument(args, 'nullable') === true,
|
|
271
|
+
isRowVersion: boolArgument(args, 'rowVersion') === true,
|
|
272
|
+
isIdentity: identityOf(annotationsOf(chain)),
|
|
273
|
+
...(storeType === undefined ? {} : { storeType }),
|
|
274
|
+
...(maxLength === undefined ? {} : { maxLength }),
|
|
275
|
+
...(precision === undefined ? {} : { precision }),
|
|
276
|
+
...(scale === undefined ? {} : { scale }),
|
|
277
|
+
...(isUnicode === undefined ? {} : { isUnicode }),
|
|
278
|
+
...(defaultValue === undefined || defaultValue.kind === 'null'
|
|
279
|
+
? {}
|
|
280
|
+
: { defaultValue }),
|
|
281
|
+
...(defaultSql === undefined ? {} : { defaultSql }),
|
|
282
|
+
...(computedSql === undefined ? {} : { computedSql }),
|
|
283
|
+
...(isStored === undefined ? {} : { isStored }),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/** Drops the CLR default (`""`, 0, false, 0m, an empty Guid) that EF Core writes to fill existing rows. */
|
|
287
|
+
function withoutBackfill(column) {
|
|
288
|
+
const value = column.defaultValue;
|
|
289
|
+
if (value === undefined || column.isNullable) {
|
|
290
|
+
return column;
|
|
291
|
+
}
|
|
292
|
+
const isZero = (value.kind === 'string' && value.value === '') ||
|
|
293
|
+
(value.kind === 'number' && value.value === 0) ||
|
|
294
|
+
(value.kind === 'bool' && !value.value) ||
|
|
295
|
+
(value.kind === 'other' &&
|
|
296
|
+
/^(?:new\s+(?:byte\[\s*0\s*\]|Guid\(\s*"0{8}-0{4}-0{4}-0{4}-0{12}"\s*\)|DateTime\(\s*1,\s*1,\s*1,\s*0,\s*0,\s*0,\s*0(?:,\s*DateTimeKind\.\w+)?\s*\)|TimeSpan\(\s*0(?:,\s*0)*\s*\)|DateTimeOffset\(\s*0\s*,\s*TimeSpan\.Zero\s*\))|Guid\.Empty|TimeSpan\.Zero)/.test(value.text));
|
|
297
|
+
if (!isZero) {
|
|
298
|
+
return column;
|
|
299
|
+
}
|
|
300
|
+
const rest = { ...column };
|
|
301
|
+
delete rest.defaultValue;
|
|
302
|
+
return rest;
|
|
303
|
+
}
|
|
304
|
+
function lookupTable(ctx, node, operation, schema, name) {
|
|
305
|
+
if (name === undefined) {
|
|
306
|
+
ctx.warn(`${placeOf(ctx.path, node)}: ${operation} names no table, so it was skipped.`);
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
const table = ctx.schema.get(tableKey(schema, name));
|
|
310
|
+
if (table === undefined) {
|
|
311
|
+
ctx.warn(`${placeOf(ctx.path, node)}: ${operation} refers to the table "${name}", which no earlier migration creates; the change was skipped.`);
|
|
312
|
+
}
|
|
313
|
+
return table;
|
|
314
|
+
}
|
|
315
|
+
/** Finds an index or constraint by name when the call does not name its table. */
|
|
316
|
+
function tableWith(ctx, schema, table, has) {
|
|
317
|
+
if (table !== undefined) {
|
|
318
|
+
return ctx.schema.get(tableKey(schema, table));
|
|
319
|
+
}
|
|
320
|
+
return [...ctx.schema.values()].find(has);
|
|
321
|
+
}
|
|
322
|
+
/** Reads `x => x.A` or `x => new { x.A, x.B }` into column names, through the property-to-column map. */
|
|
323
|
+
function selectColumns(node, columns) {
|
|
324
|
+
if (node === undefined) {
|
|
325
|
+
return undefined;
|
|
326
|
+
}
|
|
327
|
+
const members = selectedMembers(node, NO_CONSTANTS);
|
|
328
|
+
return members?.map((member) => columns.get(member) ?? member);
|
|
329
|
+
}
|
|
330
|
+
function createTable(call, ctx, statement) {
|
|
331
|
+
const place = placeOf(ctx.path, statement);
|
|
332
|
+
const args = readArguments(call, PARAMETERS['CreateTable'] ?? []);
|
|
333
|
+
const name = textArgument(args, 'name');
|
|
334
|
+
if (name === undefined) {
|
|
335
|
+
ctx.warn(`${place}: CreateTable has no table name, so it was skipped.`);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const schemaName = textArgument(args, 'schema');
|
|
339
|
+
const table = {
|
|
340
|
+
name,
|
|
341
|
+
columns: [],
|
|
342
|
+
uniqueKeys: [],
|
|
343
|
+
indexes: [],
|
|
344
|
+
foreignKeys: [],
|
|
345
|
+
checkConstraints: [],
|
|
346
|
+
...(schemaName === undefined ? {} : { schema: schemaName }),
|
|
347
|
+
};
|
|
348
|
+
const key = tableKey(schemaName, name);
|
|
349
|
+
if (ctx.schema.has(key)) {
|
|
350
|
+
ctx.warn(`${place}: CreateTable creates the table "${name}" again; the earlier definition was replaced.`);
|
|
351
|
+
}
|
|
352
|
+
const columnNames = new Map();
|
|
353
|
+
const columnsNode = args.get('columns');
|
|
354
|
+
const lambda = columnsNode === undefined ? undefined : readLambda(columnsNode);
|
|
355
|
+
const body = lambda?.body;
|
|
356
|
+
if (lambda === undefined ||
|
|
357
|
+
body === undefined ||
|
|
358
|
+
body.type !== 'anonymous_object_creation_expression') {
|
|
359
|
+
ctx.warn(`${place}: the columns of the table "${name}" are not written as table => new { ... }, so none were read.`);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
readColumnList(body, table, columnNames, ctx);
|
|
363
|
+
}
|
|
364
|
+
const constraints = args.get('constraints');
|
|
365
|
+
const constraintLambda = constraints === undefined ? undefined : readLambda(constraints);
|
|
366
|
+
if (constraintLambda !== undefined) {
|
|
367
|
+
for (const item of bodyStatements(constraintLambda.body)) {
|
|
368
|
+
const expression = item.type === 'expression_statement'
|
|
369
|
+
? (item.namedChild(0) ?? undefined)
|
|
370
|
+
: item;
|
|
371
|
+
const chain = expression === undefined ? undefined : flattenChain(expression);
|
|
372
|
+
const first = chain?.calls[0];
|
|
373
|
+
if (chain === undefined || first === undefined) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
readConstraint(first, table, columnNames, ctx, item);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
ctx.schema.set(key, table);
|
|
380
|
+
ctx.changes += 1;
|
|
381
|
+
}
|
|
382
|
+
/** Reads `table => new { Id = table.Column<int>(...), Name = table.Column<string>(...) }`. */
|
|
383
|
+
function readColumnList(body, table, columnNames, ctx) {
|
|
384
|
+
const children = body.namedChildren.flatMap((child) => child.type === 'anonymous_object_member_declarator'
|
|
385
|
+
? child.namedChildren
|
|
386
|
+
: [child]);
|
|
387
|
+
for (let at = 0; at < children.length; at += 1) {
|
|
388
|
+
const label = children[at];
|
|
389
|
+
const expression = children[at + 1];
|
|
390
|
+
if (label === undefined || label.type !== 'name_equals') {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
at += 1;
|
|
394
|
+
const propertyName = label.namedChild(0)?.text ?? label.text;
|
|
395
|
+
const chain = expression === undefined ? undefined : flattenChain(expression);
|
|
396
|
+
const call = chain?.calls[0];
|
|
397
|
+
if (chain === undefined || call === undefined || call.name !== 'Column') {
|
|
398
|
+
ctx.warn(`${placeOf(ctx.path, label)}: the column "${propertyName}" of the table "${table.name}" is not written as table.Column<T>(...), so it was skipped.`);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
const args = readArguments(call, PARAMETERS['Column'] ?? []);
|
|
402
|
+
const name = textArgument(args, 'name') ?? propertyName;
|
|
403
|
+
columnNames.set(propertyName, name);
|
|
404
|
+
table.columns.push(columnOf(name, call.typeArgs[0], args, chain));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function readConstraint(call, table, columnNames, ctx, statement) {
|
|
408
|
+
const place = placeOf(ctx.path, statement);
|
|
409
|
+
switch (call.name) {
|
|
410
|
+
case 'PrimaryKey':
|
|
411
|
+
case 'UniqueConstraint': {
|
|
412
|
+
const args = readArguments(call, ['name', 'columns']);
|
|
413
|
+
const columns = selectColumns(args.get('columns'), columnNames);
|
|
414
|
+
const name = textArgument(args, 'name');
|
|
415
|
+
if (columns === undefined) {
|
|
416
|
+
ctx.warn(`${place}: the columns of the ${call.name === 'PrimaryKey' ? 'primary key' : 'unique constraint'} of the table "${table.name}" could not be read, so it was skipped.`);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const key = {
|
|
420
|
+
columns,
|
|
421
|
+
...(name === undefined ? {} : { name }),
|
|
422
|
+
};
|
|
423
|
+
if (call.name === 'PrimaryKey') {
|
|
424
|
+
table.primaryKey = key;
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
table.uniqueKeys.push(key);
|
|
428
|
+
}
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
case 'ForeignKey': {
|
|
432
|
+
const args = readArguments(call, PARAMETERS['ForeignKey'] ?? []);
|
|
433
|
+
const columns = selectColumns(args.get('columns') ?? args.get('column'), columnNames);
|
|
434
|
+
const principalTable = textArgument(args, 'principalTable');
|
|
435
|
+
if (columns === undefined || principalTable === undefined) {
|
|
436
|
+
ctx.warn(`${place}: a foreign key of the table "${table.name}" could not be read, so it was skipped.`);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
pushForeignKey(table, args, columns, principalTable, ctx, statement);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
case 'CheckConstraint': {
|
|
443
|
+
const args = readArguments(call, ['name', 'sql']);
|
|
444
|
+
table.checkConstraints.push(textArgument(args, 'name') ?? 'check');
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
default:
|
|
448
|
+
ctx.warn(`${place}: table.${call.name}(...) in CreateTable of "${table.name}" is not known and was skipped.`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function pushForeignKey(table, args, columns, principalTable, ctx, statement) {
|
|
452
|
+
const place = placeOf(ctx.path, statement);
|
|
453
|
+
const principalSchema = textArgument(args, 'principalSchema');
|
|
454
|
+
const onDelete = actionArgument(args, 'onDelete');
|
|
455
|
+
const onUpdate = actionArgument(args, 'onUpdate');
|
|
456
|
+
if (onDelete === undefined || onUpdate === undefined) {
|
|
457
|
+
ctx.warn(`${place}: the referential action of a foreign key on "${table.name}" (${columns.join(', ')}) is not a ReferentialAction member; NO ACTION was used.`);
|
|
458
|
+
}
|
|
459
|
+
// Without column names the key references the primary key of the principal table.
|
|
460
|
+
const principalColumns = textList(args, 'principalColumn', 'principalColumns') ?? [];
|
|
461
|
+
const name = textArgument(args, 'name');
|
|
462
|
+
const foreignKey = {
|
|
463
|
+
name: name ?? '',
|
|
464
|
+
columns,
|
|
465
|
+
principalTable,
|
|
466
|
+
principalColumns,
|
|
467
|
+
onDelete: onDelete ?? 'noAction',
|
|
468
|
+
onUpdate: onUpdate ?? 'noAction',
|
|
469
|
+
...(principalSchema === undefined ? {} : { principalSchema }),
|
|
470
|
+
};
|
|
471
|
+
table.foreignKeys.push(foreignKey);
|
|
472
|
+
}
|
|
473
|
+
/** Applies one `migrationBuilder.X(...)` call. */
|
|
474
|
+
function applyCall(chain, ctx, statement) {
|
|
475
|
+
const call = chain.calls[0];
|
|
476
|
+
if (call === undefined) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
const place = placeOf(ctx.path, statement);
|
|
480
|
+
const parameters = PARAMETERS[call.name] ?? [];
|
|
481
|
+
const args = readArguments(call, parameters);
|
|
482
|
+
const schemaName = textArgument(args, 'schema');
|
|
483
|
+
const name = textArgument(args, 'name');
|
|
484
|
+
const tableName = textArgument(args, 'table');
|
|
485
|
+
ctx.changes += 1;
|
|
486
|
+
switch (call.name) {
|
|
487
|
+
case 'CreateTable':
|
|
488
|
+
createTable(call, ctx, statement);
|
|
489
|
+
return;
|
|
490
|
+
case 'DropTable': {
|
|
491
|
+
const table = lookupTable(ctx, statement, 'DropTable', schemaName, name);
|
|
492
|
+
if (table === undefined) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
ctx.schema.delete(tableKey(table.schema, table.name));
|
|
496
|
+
for (const other of ctx.schema.values()) {
|
|
497
|
+
const before = other.foreignKeys.length;
|
|
498
|
+
other.foreignKeys = other.foreignKeys.filter((key) => !(key.principalTable === table.name &&
|
|
499
|
+
key.principalSchema === table.schema));
|
|
500
|
+
if (other.foreignKeys.length !== before) {
|
|
501
|
+
ctx.warn(`${place}: DropTable "${table.name}" also removed the foreign keys of "${other.name}" that referenced it; the migration did not drop them first.`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
case 'RenameTable': {
|
|
507
|
+
const table = lookupTable(ctx, statement, 'RenameTable', schemaName, name);
|
|
508
|
+
const newName = textArgument(args, 'newName');
|
|
509
|
+
if (table === undefined) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (newName === undefined && !args.has('newSchema')) {
|
|
513
|
+
ctx.warn(`${place}: RenameTable "${table.name}" names neither a new name nor a new schema, so it changed nothing.`);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
renameTable(ctx.schema, table,
|
|
517
|
+
// A table stays in its schema only when the call says so: EF Core moves it to the default schema otherwise.
|
|
518
|
+
textArgument(args, 'newSchema'), newName ?? table.name);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
case 'AddColumn':
|
|
522
|
+
case 'AlterColumn': {
|
|
523
|
+
const table = lookupTable(ctx, statement, `${call.name} "${name ?? '?'}"`, schemaName, tableName);
|
|
524
|
+
if (table === undefined || name === undefined) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
const fullArgs = readArguments(call, PARAMETERS['AddColumn'] ?? []);
|
|
528
|
+
const made = columnOf(name, call.typeArgs[0], fullArgs, chain);
|
|
529
|
+
// EF Core fills the rows that exist with the CLR default when it adds a column that is not nullable or makes
|
|
530
|
+
// a nullable one so; that default is a step of the migration, not part of the model.
|
|
531
|
+
const column = call.name === 'AddColumn' ||
|
|
532
|
+
(boolArgument(args, 'oldNullable') === true && !made.isNullable)
|
|
533
|
+
? withoutBackfill(made)
|
|
534
|
+
: made;
|
|
535
|
+
const existing = findColumn(table, name);
|
|
536
|
+
if (call.name === 'AddColumn') {
|
|
537
|
+
if (existing !== undefined) {
|
|
538
|
+
ctx.warn(`${place}: AddColumn adds "${table.name}"."${name}", which already exists; the column was replaced.`);
|
|
539
|
+
table.columns[table.columns.indexOf(existing)] = column;
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
table.columns.push(column);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else if (existing === undefined) {
|
|
546
|
+
ctx.warn(`${place}: AlterColumn changes "${table.name}"."${name}", which no earlier migration adds; the column was added.`);
|
|
547
|
+
table.columns.push(column);
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
table.columns[table.columns.indexOf(existing)] = column;
|
|
551
|
+
}
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
case 'DropColumn': {
|
|
555
|
+
const table = lookupTable(ctx, statement, `DropColumn "${name ?? '?'}"`, schemaName, tableName);
|
|
556
|
+
if (table === undefined || name === undefined) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
if (findColumn(table, name) === undefined) {
|
|
560
|
+
ctx.warn(`${place}: DropColumn removes "${table.name}"."${name}", which no earlier migration adds.`);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
dropColumn(table, name);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
case 'RenameColumn': {
|
|
567
|
+
const table = lookupTable(ctx, statement, `RenameColumn "${name ?? '?'}"`, schemaName, tableName);
|
|
568
|
+
const newName = textArgument(args, 'newName');
|
|
569
|
+
if (table === undefined || name === undefined || newName === undefined) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (findColumn(table, name) === undefined) {
|
|
573
|
+
ctx.warn(`${place}: RenameColumn renames "${table.name}"."${name}", which no earlier migration adds.`);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
renameColumn(ctx.schema, table, name, newName);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
case 'CreateIndex': {
|
|
580
|
+
const table = lookupTable(ctx, statement, `CreateIndex "${name ?? '?'}"`, schemaName, tableName);
|
|
581
|
+
const columns = textList(args, 'column', 'columns');
|
|
582
|
+
if (table === undefined || name === undefined) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
if (columns === undefined) {
|
|
586
|
+
ctx.warn(`${place}: the columns of the index "${name}" on "${table.name}" could not be read, so it was skipped.`);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
const filter = textArgument(args, 'filter');
|
|
590
|
+
const extra = indexAnnotations(annotationsOf(chain));
|
|
591
|
+
const index = {
|
|
592
|
+
name,
|
|
593
|
+
columns,
|
|
594
|
+
isUnique: boolArgument(args, 'unique') === true,
|
|
595
|
+
includes: extra.includes,
|
|
596
|
+
descending: boolList(args, 'descending'),
|
|
597
|
+
...(filter === undefined ? {} : { filter }),
|
|
598
|
+
...(extra.method === undefined ? {} : { method: extra.method }),
|
|
599
|
+
};
|
|
600
|
+
table.indexes = table.indexes.filter((item) => item.name !== name);
|
|
601
|
+
table.indexes.push(index);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
case 'DropIndex': {
|
|
605
|
+
const table = tableWith(ctx, schemaName, tableName, (candidate) => candidate.indexes.some((item) => item.name === name));
|
|
606
|
+
if (table === undefined ||
|
|
607
|
+
name === undefined ||
|
|
608
|
+
!table.indexes.some((item) => item.name === name)) {
|
|
609
|
+
ctx.warn(`${place}: DropIndex "${name ?? '?'}" refers to an index that no earlier migration creates.`);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
table.indexes = table.indexes.filter((item) => item.name !== name);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
case 'RenameIndex': {
|
|
616
|
+
const newName = textArgument(args, 'newName');
|
|
617
|
+
const table = tableWith(ctx, schemaName, tableName, (candidate) => candidate.indexes.some((item) => item.name === name));
|
|
618
|
+
const index = table?.indexes.find((item) => item.name === name);
|
|
619
|
+
if (index === undefined || newName === undefined) {
|
|
620
|
+
ctx.warn(`${place}: RenameIndex "${name ?? '?'}" refers to an index that no earlier migration creates.`);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
index.name = newName;
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
case 'AddForeignKey': {
|
|
627
|
+
const table = lookupTable(ctx, statement, `AddForeignKey "${name ?? '?'}"`, schemaName, tableName);
|
|
628
|
+
const columns = textList(args, 'column', 'columns');
|
|
629
|
+
const principalTable = textArgument(args, 'principalTable');
|
|
630
|
+
if (table === undefined) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (columns === undefined || principalTable === undefined) {
|
|
634
|
+
ctx.warn(`${place}: the foreign key "${name ?? '?'}" on "${table.name}" could not be read, so it was skipped.`);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
table.foreignKeys = table.foreignKeys.filter((item) => item.name !== name);
|
|
638
|
+
pushForeignKey(table, args, columns, principalTable, ctx, statement);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
case 'DropForeignKey': {
|
|
642
|
+
const table = tableWith(ctx, schemaName, tableName, (candidate) => candidate.foreignKeys.some((item) => item.name === name));
|
|
643
|
+
if (table === undefined ||
|
|
644
|
+
!table.foreignKeys.some((item) => item.name === name)) {
|
|
645
|
+
ctx.warn(`${place}: DropForeignKey "${name ?? '?'}" refers to a foreign key that no earlier migration creates.`);
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
table.foreignKeys = table.foreignKeys.filter((item) => item.name !== name);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
case 'AddPrimaryKey':
|
|
652
|
+
case 'AddUniqueConstraint': {
|
|
653
|
+
const table = lookupTable(ctx, statement, `${call.name} "${name ?? '?'}"`, schemaName, tableName);
|
|
654
|
+
const columns = textList(args, 'column', 'columns');
|
|
655
|
+
if (table === undefined) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
if (columns === undefined) {
|
|
659
|
+
ctx.warn(`${place}: the columns of ${call.name} "${name ?? '?'}" on "${table.name}" could not be read, so it was skipped.`);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
const key = {
|
|
663
|
+
columns,
|
|
664
|
+
...(name === undefined ? {} : { name }),
|
|
665
|
+
};
|
|
666
|
+
if (call.name === 'AddPrimaryKey') {
|
|
667
|
+
table.primaryKey = key;
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
table.uniqueKeys.push(key);
|
|
671
|
+
}
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
case 'DropPrimaryKey': {
|
|
675
|
+
const table = lookupTable(ctx, statement, 'DropPrimaryKey', schemaName, tableName);
|
|
676
|
+
if (table !== undefined) {
|
|
677
|
+
delete table.primaryKey;
|
|
678
|
+
}
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
case 'DropUniqueConstraint': {
|
|
682
|
+
const table = tableWith(ctx, schemaName, tableName, (candidate) => candidate.uniqueKeys.some((item) => item.name === name));
|
|
683
|
+
if (table === undefined ||
|
|
684
|
+
!table.uniqueKeys.some((item) => item.name === name)) {
|
|
685
|
+
ctx.warn(`${place}: DropUniqueConstraint "${name ?? '?'}" refers to a constraint that no earlier migration creates.`);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
table.uniqueKeys = table.uniqueKeys.filter((item) => item.name !== name);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
case 'AddCheckConstraint': {
|
|
692
|
+
const table = lookupTable(ctx, statement, `AddCheckConstraint "${name ?? '?'}"`, schemaName, tableName);
|
|
693
|
+
table?.checkConstraints.push(name ?? 'check');
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
case 'DropCheckConstraint': {
|
|
697
|
+
const table = tableWith(ctx, schemaName, tableName, (candidate) => candidate.checkConstraints.includes(name ?? ''));
|
|
698
|
+
if (table !== undefined) {
|
|
699
|
+
table.checkConstraints = table.checkConstraints.filter((item) => item !== name);
|
|
700
|
+
}
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
case 'Sql': {
|
|
704
|
+
const sql = textArgument(args, 'sql');
|
|
705
|
+
const tableMatch = sql === undefined ? null : SQL_TABLE.exec(sql);
|
|
706
|
+
ctx.warn(`${place}: the raw SQL "${shorten(sql ?? statement.text)}"${tableMatch?.[1] === undefined ? '' : ` on the table "${tableMatch[1]}"`} (migrationBuilder.Sql) is not interpreted; the rebuilt schema may differ from the real database.`);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
default:
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
if (IGNORED_CALLS.has(call.name)) {
|
|
713
|
+
ctx.changes -= 1;
|
|
714
|
+
}
|
|
715
|
+
else if (SEQUENCE_CALLS.has(call.name)) {
|
|
716
|
+
ctx.warn(`${place}: ${call.name}${name === undefined ? '' : ` "${name}"`} changes a sequence, which the shared model cannot hold; it was skipped.`);
|
|
717
|
+
}
|
|
718
|
+
else if (DATA_CALLS.has(call.name)) {
|
|
719
|
+
const target = textArgument(args, 'table');
|
|
720
|
+
ctx.warn(`${place}: ${call.name}${target === undefined ? '' : ` on the table "${target}"`} changes rows, not the schema (seed data is not preserved).`);
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
ctx.warn(`${place}: migrationBuilder.${call.name}(...) is not known and was skipped.`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
/** Runs the statements of an `Up` body, descending into `if`, loops and `try` with a warning when they change the schema. */
|
|
727
|
+
function replayStatements(statements, builder, ctx) {
|
|
728
|
+
for (const statement of statements) {
|
|
729
|
+
if (statement.type === 'expression_statement') {
|
|
730
|
+
const expression = statement.namedChild(0);
|
|
731
|
+
const chain = expression === null ? undefined : flattenChain(expression);
|
|
732
|
+
if (chain !== undefined && chain.root.text === builder) {
|
|
733
|
+
applyCall(chain, ctx, statement);
|
|
734
|
+
}
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (CONTROL_STATEMENTS.has(statement.type)) {
|
|
738
|
+
const before = ctx.changes;
|
|
739
|
+
const nested = statement.namedChildren.filter((child) => child.type === 'block');
|
|
740
|
+
// Only the first block runs: the `if` branch, the loop body, the `try` body. An `else` is not applied.
|
|
741
|
+
const first = nested[0];
|
|
742
|
+
if (first !== undefined) {
|
|
743
|
+
replayStatements(bodyStatements(first), builder, ctx);
|
|
744
|
+
}
|
|
745
|
+
if (ctx.changes !== before || nested.length > 1) {
|
|
746
|
+
ctx.warn(`${placeOf(ctx.path, statement)}: ${statement.type.replace(/_statement$/, '')} contains schema changes; the first block was applied as if it always ran` +
|
|
747
|
+
`${nested.length > 1 ? ' and the other blocks were skipped' : ''}, so the result may differ from the real database.`);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
/** Finds the migration classes of a file: classes that derive from `Migration` and have an `Up` method. */
|
|
753
|
+
export function findMigrations(classes, path) {
|
|
754
|
+
const found = [];
|
|
755
|
+
for (const cls of classes) {
|
|
756
|
+
if (!cls.bases.some((base) => base.name === 'Migration')) {
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
const up = cls.methods.find((method) => method.name === 'Up');
|
|
760
|
+
if (up?.body === undefined) {
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
const attribute = cls.attributes.find((item) => item.name === 'Migration');
|
|
764
|
+
const declared = attribute?.args[0] === undefined
|
|
765
|
+
? undefined
|
|
766
|
+
: evaluate(attribute.args[0], NO_CONSTANTS);
|
|
767
|
+
const base = path.split(/[\\/]/).pop() ?? path;
|
|
768
|
+
found.push({
|
|
769
|
+
path,
|
|
770
|
+
className: cls.name,
|
|
771
|
+
id: declared?.kind === 'string'
|
|
772
|
+
? declared.value
|
|
773
|
+
: base.replace(/\.cs$/i, ''),
|
|
774
|
+
up: up.body,
|
|
775
|
+
builder: up.parameters[0]?.name ?? 'migrationBuilder',
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
return found;
|
|
779
|
+
}
|
|
780
|
+
/** Replays the `Up` method of one migration into the schema. */
|
|
781
|
+
export function replayMigration(migration, schema, warn) {
|
|
782
|
+
const ctx = {
|
|
783
|
+
path: migration.path,
|
|
784
|
+
schema,
|
|
785
|
+
warn,
|
|
786
|
+
changes: 0,
|
|
787
|
+
};
|
|
788
|
+
replayStatements(bodyStatements(migration.up), migration.builder, ctx);
|
|
789
|
+
}
|
|
790
|
+
//# sourceMappingURL=efcoreMigrationReplay.js.map
|