ormbridge 0.13.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 +23 -22
- package/dist/cliRunner.js +20 -2
- package/dist/cliRunner.js.map +1 -1
- package/dist/config.d.ts +6 -1
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -1
- package/dist/emitters/jpa.d.ts +17 -2
- package/dist/emitters/jpa.js +55 -7
- package/dist/emitters/jpa.js.map +1 -1
- package/dist/emitters/jpaKeyClass.d.ts +18 -0
- package/dist/emitters/jpaKeyClass.js +88 -0
- package/dist/emitters/jpaKeyClass.js.map +1 -0
- package/dist/emitters/jpaKeys.d.ts +1 -7
- package/dist/emitters/jpaKeys.js +0 -43
- package/dist/emitters/jpaKeys.js.map +1 -1
- package/dist/emitters/jpaRelations.d.ts +5 -1
- package/dist/emitters/jpaRelations.js +7 -1
- package/dist/emitters/jpaRelations.js.map +1 -1
- package/dist/emitters/jpaRender.d.ts +5 -1
- package/dist/emitters/jpaRender.js +11 -4
- package/dist/emitters/jpaRender.js.map +1 -1
- package/dist/emitters/jpaTypes.d.ts +9 -3
- package/dist/emitters/jpaTypes.js +8 -0
- package/dist/emitters/jpaTypes.js.map +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 +13 -2
- package/dist/formats.js +17 -6
- package/dist/formats.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/io.js +105 -26
- 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/jpaBuild.js +2 -1
- package/dist/parsers/jpaBuild.js.map +1 -1
- package/dist/parsers/jpaClasses.js +7 -1
- package/dist/parsers/jpaClasses.js.map +1 -1
- package/dist/parsers/jpaColumns.d.ts +4 -1
- package/dist/parsers/jpaColumns.js +8 -2
- package/dist/parsers/jpaColumns.js.map +1 -1
- package/dist/parsers/jpaEntities.js +15 -0
- package/dist/parsers/jpaEntities.js.map +1 -1
- package/dist/parsers/jpaModel.d.ts +6 -0
- 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/parsers/sqlDdl.js +1306 -65
- package/dist/parsers/sqlDdl.js.map +1 -1
- package/dist/parsers/sqlMigrationOrder.d.ts +8 -0
- package/dist/parsers/sqlMigrationOrder.js +110 -0
- package/dist/parsers/sqlMigrationOrder.js.map +1 -0
- package/dist/parsers/sqlSyntax.d.ts +6 -0
- package/dist/parsers/sqlSyntax.js +15 -2
- package/dist/parsers/sqlSyntax.js.map +1 -1
- package/dist/parsers/zod.d.ts +11 -0
- package/dist/parsers/zod.js +796 -0
- package/dist/parsers/zod.js.map +1 -0
- package/dist/parsers/zodFields.d.ts +73 -0
- package/dist/parsers/zodFields.js +681 -0
- package/dist/parsers/zodFields.js.map +1 -0
- package/dist/parsers/zodSyntax.d.ts +72 -0
- package/dist/parsers/zodSyntax.js +347 -0
- package/dist/parsers/zodSyntax.js.map +1 -0
- 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,791 @@
|
|
|
1
|
+
import { bodyStatements, flattenChain, readLambda, } from './csharpChains.js';
|
|
2
|
+
import { evaluate, parseTypeText, } from './csharpSyntax.js';
|
|
3
|
+
import { identityOf, indexAnnotations, } from './efcoreMigrationReplay.js';
|
|
4
|
+
import { conventionalName, tableKey, } from './efcoreMigrationState.js';
|
|
5
|
+
import { deleteBehavior, isValueType } from './efcoreTypes.js';
|
|
6
|
+
/**
|
|
7
|
+
* Reads a `*ModelSnapshot.cs`: the model EF Core wrote when it added the last migration. The snapshot is C# that
|
|
8
|
+
* rebuilds the model with string names (`modelBuilder.Entity("App.Blog", b => { b.Property<int>("Id") ... })`), so
|
|
9
|
+
* it is read here without the entity classes, into the same state the replay of the migrations fills.
|
|
10
|
+
*/
|
|
11
|
+
const NO_CONSTANTS = () => undefined;
|
|
12
|
+
function newEntity(name) {
|
|
13
|
+
return {
|
|
14
|
+
name,
|
|
15
|
+
properties: new Map(),
|
|
16
|
+
alternateKeys: [],
|
|
17
|
+
indexes: [],
|
|
18
|
+
isView: false,
|
|
19
|
+
owned: [],
|
|
20
|
+
requiredNavigations: new Set(),
|
|
21
|
+
relations: [],
|
|
22
|
+
ownerKey: [],
|
|
23
|
+
checks: [],
|
|
24
|
+
isComplex: false,
|
|
25
|
+
isRequired: false,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** `App.Models.Blog` is `Blog`; `PostTag` stays; `App.Post.Address#Address` is `Address`. */
|
|
29
|
+
export function shortEntityName(name) {
|
|
30
|
+
const afterHash = name.split('#').pop() ?? name;
|
|
31
|
+
return afterHash.split('.').pop() ?? afterHash;
|
|
32
|
+
}
|
|
33
|
+
function text(node) {
|
|
34
|
+
if (node === undefined) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const value = evaluate(node, NO_CONSTANTS);
|
|
38
|
+
return value.kind === 'string' ? value.value : undefined;
|
|
39
|
+
}
|
|
40
|
+
function number(node) {
|
|
41
|
+
if (node === undefined) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const value = evaluate(node, NO_CONSTANTS);
|
|
45
|
+
return value.kind === 'number' ? value.value : undefined;
|
|
46
|
+
}
|
|
47
|
+
function flag(node) {
|
|
48
|
+
if (node === undefined) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const value = evaluate(node, NO_CONSTANTS);
|
|
52
|
+
return value.kind === 'bool' ? value.value : undefined;
|
|
53
|
+
}
|
|
54
|
+
function texts(nodes) {
|
|
55
|
+
return nodes.flatMap((node) => {
|
|
56
|
+
const value = evaluate(node, NO_CONSTANTS);
|
|
57
|
+
if (value.kind === 'string') {
|
|
58
|
+
return [value.value];
|
|
59
|
+
}
|
|
60
|
+
if (value.kind === 'array') {
|
|
61
|
+
return value.items.flatMap((item) => item.kind === 'string' ? [item.value] : []);
|
|
62
|
+
}
|
|
63
|
+
return [];
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function readProperty(chain, first) {
|
|
67
|
+
const name = text(first.args[0]);
|
|
68
|
+
if (name === undefined) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
const property = {
|
|
72
|
+
name,
|
|
73
|
+
clrType: first.typeArgs[0],
|
|
74
|
+
isRequired: false,
|
|
75
|
+
isIdentity: false,
|
|
76
|
+
generatedOnAdd: false,
|
|
77
|
+
isRowVersion: false,
|
|
78
|
+
};
|
|
79
|
+
const annotations = new Map();
|
|
80
|
+
for (const call of chain.calls.slice(1)) {
|
|
81
|
+
const argument = call.args[0];
|
|
82
|
+
switch (call.name) {
|
|
83
|
+
case 'IsRequired':
|
|
84
|
+
property.isRequired = flag(argument) !== false;
|
|
85
|
+
break;
|
|
86
|
+
case 'ValueGeneratedOnAdd':
|
|
87
|
+
property.generatedOnAdd = true;
|
|
88
|
+
break;
|
|
89
|
+
case 'HasColumnType': {
|
|
90
|
+
const value = text(argument);
|
|
91
|
+
if (value !== undefined) {
|
|
92
|
+
property.storeType = value;
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case 'HasColumnName': {
|
|
97
|
+
const value = text(argument);
|
|
98
|
+
if (value !== undefined) {
|
|
99
|
+
property.column = value;
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
case 'HasMaxLength': {
|
|
104
|
+
const value = number(argument);
|
|
105
|
+
if (value !== undefined) {
|
|
106
|
+
property.maxLength = value;
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case 'HasPrecision': {
|
|
111
|
+
const value = number(argument);
|
|
112
|
+
if (value !== undefined) {
|
|
113
|
+
property.precision = value;
|
|
114
|
+
}
|
|
115
|
+
const scale = number(call.args[1]);
|
|
116
|
+
if (scale !== undefined) {
|
|
117
|
+
property.scale = scale;
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case 'IsUnicode': {
|
|
122
|
+
property.isUnicode = flag(argument) !== false;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case 'IsRowVersion':
|
|
126
|
+
property.isRowVersion = true;
|
|
127
|
+
break;
|
|
128
|
+
case 'HasDefaultValue':
|
|
129
|
+
if (argument !== undefined) {
|
|
130
|
+
const value = evaluate(argument, NO_CONSTANTS);
|
|
131
|
+
if (value.kind !== 'null') {
|
|
132
|
+
property.defaultValue = value;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
case 'HasDefaultValueSql': {
|
|
137
|
+
const value = text(argument);
|
|
138
|
+
if (value !== undefined) {
|
|
139
|
+
property.defaultSql = value;
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case 'HasComputedColumnSql': {
|
|
144
|
+
const value = text(argument);
|
|
145
|
+
if (value !== undefined) {
|
|
146
|
+
property.computedSql = value;
|
|
147
|
+
const stored = flag(call.args[1]);
|
|
148
|
+
if (stored !== undefined) {
|
|
149
|
+
property.isStored = stored;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case 'HasAnnotation': {
|
|
155
|
+
const key = text(argument);
|
|
156
|
+
const second = call.args[1];
|
|
157
|
+
if (key !== undefined && second !== undefined) {
|
|
158
|
+
annotations.set(key, evaluate(second, NO_CONSTANTS));
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
default:
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
property.isIdentity = identityOf(annotations);
|
|
167
|
+
return property;
|
|
168
|
+
}
|
|
169
|
+
function readIndex(chain, first) {
|
|
170
|
+
const index = {
|
|
171
|
+
properties: texts(first.args),
|
|
172
|
+
isUnique: false,
|
|
173
|
+
descending: [],
|
|
174
|
+
includes: [],
|
|
175
|
+
};
|
|
176
|
+
const annotations = new Map();
|
|
177
|
+
for (const call of chain.calls.slice(1)) {
|
|
178
|
+
const argument = call.args[0];
|
|
179
|
+
switch (call.name) {
|
|
180
|
+
case 'IsUnique':
|
|
181
|
+
index.isUnique = flag(argument) !== false;
|
|
182
|
+
break;
|
|
183
|
+
case 'HasDatabaseName': {
|
|
184
|
+
const value = text(argument);
|
|
185
|
+
if (value !== undefined) {
|
|
186
|
+
index.name = value;
|
|
187
|
+
}
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
case 'HasFilter': {
|
|
191
|
+
const value = text(argument);
|
|
192
|
+
if (value !== undefined) {
|
|
193
|
+
index.filter = value;
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case 'IsDescending':
|
|
198
|
+
index.descending = call.args.map((item) => flag(item) !== false);
|
|
199
|
+
if (call.args.length === 0) {
|
|
200
|
+
index.descending = index.properties.map(() => true);
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
case 'HasMethod': {
|
|
204
|
+
const value = text(argument);
|
|
205
|
+
if (value !== undefined) {
|
|
206
|
+
index.method = value;
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case 'IncludeProperties':
|
|
211
|
+
index.includes = texts(call.args);
|
|
212
|
+
break;
|
|
213
|
+
case 'HasAnnotation': {
|
|
214
|
+
const key = text(argument);
|
|
215
|
+
const second = call.args[1];
|
|
216
|
+
if (key !== undefined && second !== undefined) {
|
|
217
|
+
annotations.set(key, evaluate(second, NO_CONSTANTS));
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
default:
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const extra = indexAnnotations(annotations);
|
|
226
|
+
if (index.method === undefined && extra.method !== undefined) {
|
|
227
|
+
index.method = extra.method;
|
|
228
|
+
}
|
|
229
|
+
if (index.includes.length === 0) {
|
|
230
|
+
index.includes = extra.includes;
|
|
231
|
+
}
|
|
232
|
+
return index;
|
|
233
|
+
}
|
|
234
|
+
function readRelation(chain, first) {
|
|
235
|
+
const target = text(first.args[0]);
|
|
236
|
+
if (target === undefined) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
const navigation = text(first.args[1]);
|
|
240
|
+
const relation = {
|
|
241
|
+
target,
|
|
242
|
+
foreignKey: [],
|
|
243
|
+
principalKey: [],
|
|
244
|
+
isRequired: false,
|
|
245
|
+
...(navigation === undefined ? {} : { navigation }),
|
|
246
|
+
};
|
|
247
|
+
for (const call of chain.calls.slice(1)) {
|
|
248
|
+
switch (call.name) {
|
|
249
|
+
case 'WithMany':
|
|
250
|
+
case 'WithOne': {
|
|
251
|
+
const inverse = text(call.args[0]);
|
|
252
|
+
if (inverse !== undefined) {
|
|
253
|
+
relation.inverse = inverse;
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
case 'HasForeignKey':
|
|
258
|
+
relation.foreignKey = texts(call.args);
|
|
259
|
+
break;
|
|
260
|
+
case 'HasPrincipalKey':
|
|
261
|
+
relation.principalKey = texts(call.args);
|
|
262
|
+
break;
|
|
263
|
+
case 'OnDelete': {
|
|
264
|
+
const value = call.args[0] === undefined
|
|
265
|
+
? undefined
|
|
266
|
+
: evaluate(call.args[0], NO_CONSTANTS);
|
|
267
|
+
if (value?.kind === 'member') {
|
|
268
|
+
const action = deleteBehavior(value.path[value.path.length - 1] ?? '');
|
|
269
|
+
if (action !== undefined) {
|
|
270
|
+
relation.onDelete = action;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
case 'IsRequired':
|
|
276
|
+
relation.isRequired = flag(call.args[0]) !== false;
|
|
277
|
+
break;
|
|
278
|
+
case 'HasConstraintName': {
|
|
279
|
+
const value = text(call.args[0]);
|
|
280
|
+
if (value !== undefined) {
|
|
281
|
+
relation.constraintName = value;
|
|
282
|
+
}
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
default:
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return relation;
|
|
290
|
+
}
|
|
291
|
+
/** Reads the calls inside `modelBuilder.Entity("X", b => { ... })` or an `OwnsOne` / `OwnsMany` lambda. */
|
|
292
|
+
function readBlock(entity, lambda, warn, path) {
|
|
293
|
+
const builder = lambda.parameter ?? 'b';
|
|
294
|
+
for (const statement of bodyStatements(lambda.body)) {
|
|
295
|
+
const expression = statement.type === 'expression_statement'
|
|
296
|
+
? statement.namedChild(0)
|
|
297
|
+
: statement;
|
|
298
|
+
const chain = expression === null ? undefined : flattenChain(expression);
|
|
299
|
+
const first = chain?.calls[0];
|
|
300
|
+
if (chain === undefined || first === undefined) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (chain.root.text !== builder) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
switch (first.name) {
|
|
307
|
+
case 'Property': {
|
|
308
|
+
const property = readProperty(chain, first);
|
|
309
|
+
if (property !== undefined) {
|
|
310
|
+
entity.properties.set(property.name, property);
|
|
311
|
+
}
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
case 'HasKey':
|
|
315
|
+
entity.key = texts(first.args);
|
|
316
|
+
for (const call of chain.calls.slice(1)) {
|
|
317
|
+
if (call.name === 'HasName') {
|
|
318
|
+
const value = text(call.args[0]);
|
|
319
|
+
if (value !== undefined) {
|
|
320
|
+
entity.keyName = value;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
case 'HasAlternateKey': {
|
|
326
|
+
const name = chain.calls
|
|
327
|
+
.filter((call) => call.name === 'HasName')
|
|
328
|
+
.map((call) => text(call.args[0]))[0];
|
|
329
|
+
entity.alternateKeys.push({
|
|
330
|
+
properties: texts(first.args),
|
|
331
|
+
...(name === undefined ? {} : { name }),
|
|
332
|
+
});
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
case 'HasIndex':
|
|
336
|
+
entity.indexes.push(readIndex(chain, first));
|
|
337
|
+
break;
|
|
338
|
+
case 'ToTable':
|
|
339
|
+
case 'ToView': {
|
|
340
|
+
const name = text(first.args[0]);
|
|
341
|
+
if (first.name === 'ToView') {
|
|
342
|
+
entity.isView = true;
|
|
343
|
+
}
|
|
344
|
+
if (name !== undefined) {
|
|
345
|
+
entity.table = name;
|
|
346
|
+
}
|
|
347
|
+
const schemaNode = first.args[1];
|
|
348
|
+
if (schemaNode !== undefined) {
|
|
349
|
+
const schema = evaluate(schemaNode, NO_CONSTANTS);
|
|
350
|
+
if (schema.kind === 'string') {
|
|
351
|
+
entity.schema = schema.value;
|
|
352
|
+
}
|
|
353
|
+
else if (schema.kind === 'null') {
|
|
354
|
+
entity.schema = null;
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
const nested = readLambda(schemaNode);
|
|
358
|
+
for (const item of nested === undefined
|
|
359
|
+
? []
|
|
360
|
+
: bodyStatements(nested.body)) {
|
|
361
|
+
const inner = item.type === 'expression_statement'
|
|
362
|
+
? item.namedChild(0)
|
|
363
|
+
: item;
|
|
364
|
+
const innerChain = inner === null ? undefined : flattenChain(inner);
|
|
365
|
+
const check = innerChain?.calls[0];
|
|
366
|
+
if (check?.name === 'HasCheckConstraint') {
|
|
367
|
+
entity.checks.push(text(check.args[0]) ?? 'check');
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
case 'HasBaseType': {
|
|
375
|
+
const base = text(first.args[0]);
|
|
376
|
+
if (base !== undefined) {
|
|
377
|
+
entity.baseType = base;
|
|
378
|
+
}
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
case 'HasOne': {
|
|
382
|
+
const relation = readRelation(chain, first);
|
|
383
|
+
if (relation !== undefined) {
|
|
384
|
+
entity.relations.push(relation);
|
|
385
|
+
}
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
case 'WithOwner':
|
|
389
|
+
for (const call of chain.calls.slice(1)) {
|
|
390
|
+
if (call.name === 'HasForeignKey') {
|
|
391
|
+
entity.ownerKey = texts(call.args);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
case 'Navigation':
|
|
396
|
+
if (chain.calls.some((call) => call.name === 'IsRequired')) {
|
|
397
|
+
const nav = text(first.args[0]);
|
|
398
|
+
if (nav !== undefined) {
|
|
399
|
+
entity.requiredNavigations.add(nav);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
403
|
+
case 'OwnsOne':
|
|
404
|
+
case 'OwnsMany':
|
|
405
|
+
case 'ComplexProperty': {
|
|
406
|
+
// OwnsOne("Type", "Nav", b1 => ...); ComplexProperty<T>("Nav", "Type", b1 => ...).
|
|
407
|
+
const isComplex = first.name === 'ComplexProperty';
|
|
408
|
+
const typeName = text(first.args[isComplex ? 1 : 0]);
|
|
409
|
+
const navigation = text(first.args[isComplex ? 0 : 1]);
|
|
410
|
+
const block = first.args[2];
|
|
411
|
+
const nestedLambda = block === undefined ? undefined : readLambda(block);
|
|
412
|
+
if (typeName === undefined || navigation === undefined) {
|
|
413
|
+
warn(`${path}: ${first.name} in the model snapshot names no owned type or navigation, so it was skipped.`);
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
const owned = newEntity(typeName);
|
|
417
|
+
owned.isComplex = isComplex;
|
|
418
|
+
if (nestedLambda !== undefined) {
|
|
419
|
+
readBlock(owned, nestedLambda, warn, path);
|
|
420
|
+
}
|
|
421
|
+
entity.owned.push({
|
|
422
|
+
navigation,
|
|
423
|
+
many: first.name === 'OwnsMany',
|
|
424
|
+
entity: owned,
|
|
425
|
+
});
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
case 'ToJson': {
|
|
429
|
+
entity.jsonColumn = text(first.args[0]) ?? '';
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
case 'IsRequired':
|
|
433
|
+
entity.isRequired = true;
|
|
434
|
+
break;
|
|
435
|
+
default:
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/** Finds the snapshot class and reads its `BuildModel` method into entities. */
|
|
441
|
+
export function readSnapshotClass(cls, path, warn) {
|
|
442
|
+
const method = cls.methods.find((item) => item.name === 'BuildModel');
|
|
443
|
+
if (method?.body === undefined) {
|
|
444
|
+
return undefined;
|
|
445
|
+
}
|
|
446
|
+
const builder = method.parameters[0]?.name ?? 'modelBuilder';
|
|
447
|
+
const entities = new Map();
|
|
448
|
+
let defaultSchema;
|
|
449
|
+
for (const statement of bodyStatements(method.body)) {
|
|
450
|
+
const expression = statement.type === 'expression_statement'
|
|
451
|
+
? statement.namedChild(0)
|
|
452
|
+
: null;
|
|
453
|
+
const chain = expression === null ? undefined : flattenChain(expression);
|
|
454
|
+
if (chain === undefined || chain.root.text !== builder) {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
for (const call of chain.calls) {
|
|
458
|
+
if (call.name === 'HasDefaultSchema') {
|
|
459
|
+
defaultSchema = text(call.args[0]) ?? defaultSchema;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const first = chain.calls[0];
|
|
463
|
+
if (first === undefined || first.name !== 'Entity') {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
const name = text(first.args[0]) ?? first.typeArgs[0]?.text;
|
|
467
|
+
const lambda = first.args[1] === undefined ? undefined : readLambda(first.args[1]);
|
|
468
|
+
if (name === undefined || lambda === undefined) {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const entity = entities.get(name) ?? newEntity(name);
|
|
472
|
+
entities.set(name, entity);
|
|
473
|
+
readBlock(entity, lambda, warn, path);
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
entities,
|
|
477
|
+
...(defaultSchema === undefined ? {} : { defaultSchema }),
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function schemaOf(entity, defaultSchema) {
|
|
481
|
+
if (entity.schema === null) {
|
|
482
|
+
return undefined;
|
|
483
|
+
}
|
|
484
|
+
return entity.schema ?? defaultSchema;
|
|
485
|
+
}
|
|
486
|
+
/** True when the CLR type is a reference type, which allows null unless the property says `IsRequired()`. */
|
|
487
|
+
function isReferenceType(type) {
|
|
488
|
+
if (type === undefined) {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
return (type.arrayDepth > 0 ||
|
|
492
|
+
type.name === 'Dictionary' ||
|
|
493
|
+
(!isValueType(type, new Set()) &&
|
|
494
|
+
/^(?:string|String|JsonDocument|JsonNode|JsonObject|JsonArray|Uri|IPAddress|PhysicalAddress|Version|CultureInfo)$/.test(type.name)));
|
|
495
|
+
}
|
|
496
|
+
function isIntegerType(type) {
|
|
497
|
+
return (type !== undefined &&
|
|
498
|
+
type.arrayDepth === 0 &&
|
|
499
|
+
/^(?:int|Int32|long|Int64|short|Int16|byte|Byte|sbyte|SByte)$/.test(type.name));
|
|
500
|
+
}
|
|
501
|
+
function columnOfProperty(property, columnName, propertyName, forceNullable, isKey) {
|
|
502
|
+
const type = property.clrType;
|
|
503
|
+
// The snapshot does not repeat IsRequired() for the properties of a key; a key never allows null.
|
|
504
|
+
const isNullable = !isKey &&
|
|
505
|
+
(forceNullable ||
|
|
506
|
+
type?.nullable === true ||
|
|
507
|
+
(isReferenceType(type) && !property.isRequired));
|
|
508
|
+
const hasDefault = property.defaultValue !== undefined || property.defaultSql !== undefined;
|
|
509
|
+
return {
|
|
510
|
+
name: columnName,
|
|
511
|
+
clrType: type,
|
|
512
|
+
isNullable,
|
|
513
|
+
isRowVersion: property.isRowVersion,
|
|
514
|
+
isIdentity: property.isIdentity ||
|
|
515
|
+
(property.generatedOnAdd &&
|
|
516
|
+
isIntegerType(type) &&
|
|
517
|
+
!hasDefault &&
|
|
518
|
+
property.computedSql === undefined),
|
|
519
|
+
propertyName,
|
|
520
|
+
...(property.storeType === undefined
|
|
521
|
+
? {}
|
|
522
|
+
: { storeType: property.storeType }),
|
|
523
|
+
...(property.maxLength === undefined
|
|
524
|
+
? {}
|
|
525
|
+
: { maxLength: property.maxLength }),
|
|
526
|
+
...(property.precision === undefined
|
|
527
|
+
? {}
|
|
528
|
+
: { precision: property.precision }),
|
|
529
|
+
...(property.scale === undefined ? {} : { scale: property.scale }),
|
|
530
|
+
...(property.isUnicode === undefined
|
|
531
|
+
? {}
|
|
532
|
+
: { isUnicode: property.isUnicode }),
|
|
533
|
+
...(property.defaultValue === undefined
|
|
534
|
+
? {}
|
|
535
|
+
: { defaultValue: property.defaultValue }),
|
|
536
|
+
...(property.defaultSql === undefined
|
|
537
|
+
? {}
|
|
538
|
+
: { defaultSql: property.defaultSql }),
|
|
539
|
+
...(property.computedSql === undefined
|
|
540
|
+
? {}
|
|
541
|
+
: { computedSql: property.computedSql }),
|
|
542
|
+
...(property.isStored === undefined ? {} : { isStored: property.isStored }),
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function newTable(entity, state) {
|
|
546
|
+
const name = entity.table ?? shortEntityName(entity.name);
|
|
547
|
+
const schema = schemaOf(entity, state.defaultSchema);
|
|
548
|
+
return {
|
|
549
|
+
name,
|
|
550
|
+
columns: [],
|
|
551
|
+
uniqueKeys: [],
|
|
552
|
+
indexes: [],
|
|
553
|
+
foreignKeys: [],
|
|
554
|
+
checkConstraints: [...entity.checks],
|
|
555
|
+
entityName: shortEntityName(entity.name),
|
|
556
|
+
...(schema === undefined ? {} : { schema }),
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/** Adds the columns of an entity (or of an owned type sharing its table) to a table. */
|
|
560
|
+
function addColumns(table, entity, prefix, pathPrefix, forceNullable, skip, state) {
|
|
561
|
+
const columns = new Map();
|
|
562
|
+
for (const property of entity.properties.values()) {
|
|
563
|
+
if (skip.has(property.name)) {
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
const columnName = property.column ?? `${prefix}${property.name}`;
|
|
567
|
+
columns.set(property.name, columnName);
|
|
568
|
+
if (table.columns.some((item) => item.name === columnName)) {
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
table.columns.push(columnOfProperty(property, columnName, `${pathPrefix}${property.name}`, forceNullable, (entity.key ?? []).includes(property.name) ||
|
|
572
|
+
entity.alternateKeys.some((key) => key.properties.includes(property.name))));
|
|
573
|
+
}
|
|
574
|
+
for (const owned of entity.owned) {
|
|
575
|
+
addOwned(table, entity, owned, prefix, pathPrefix, forceNullable, state);
|
|
576
|
+
}
|
|
577
|
+
return columns;
|
|
578
|
+
}
|
|
579
|
+
/** Table-splitting owned types and complex properties become columns with the navigation as a prefix. */
|
|
580
|
+
function addOwned(table, owner, owned, prefix, pathPrefix, forceNullable, state) {
|
|
581
|
+
const entity = owned.entity;
|
|
582
|
+
const ownedTable = entity.table;
|
|
583
|
+
const isSeparate = owned.many ||
|
|
584
|
+
(ownedTable !== undefined &&
|
|
585
|
+
!entity.isComplex &&
|
|
586
|
+
(ownedTable !== table.name ||
|
|
587
|
+
schemaOf(entity, state.defaultSchema) !== table.schema));
|
|
588
|
+
if (isSeparate) {
|
|
589
|
+
addOwnedTable(table, owner, owned, state);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const required = entity.isComplex
|
|
593
|
+
? entity.isRequired
|
|
594
|
+
: owner.requiredNavigations.has(owned.navigation);
|
|
595
|
+
const nullable = forceNullable || !required;
|
|
596
|
+
if (entity.jsonColumn !== undefined) {
|
|
597
|
+
const column = entity.jsonColumn === '' ? owned.navigation : entity.jsonColumn;
|
|
598
|
+
table.columns.push({
|
|
599
|
+
name: column,
|
|
600
|
+
clrType: parseTypeText('JsonDocument'),
|
|
601
|
+
isNullable: nullable,
|
|
602
|
+
isRowVersion: false,
|
|
603
|
+
isIdentity: false,
|
|
604
|
+
propertyName: `${pathPrefix}${owned.navigation}`,
|
|
605
|
+
});
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const skip = new Set([
|
|
609
|
+
...entity.ownerKey,
|
|
610
|
+
...(entity.key ?? []),
|
|
611
|
+
]);
|
|
612
|
+
// A complex property has no key; an owned type's key is the foreign key to the owner, which is the owner's key.
|
|
613
|
+
if (entity.isComplex) {
|
|
614
|
+
skip.clear();
|
|
615
|
+
}
|
|
616
|
+
addColumns(table, entity, `${prefix}${owned.navigation}_`, `${pathPrefix}${owned.navigation}.`, nullable, skip, state);
|
|
617
|
+
}
|
|
618
|
+
/** An owned collection (or an owned type with its own table) is a table with a foreign key to the owner. */
|
|
619
|
+
function addOwnedTable(ownerTable, owner, owned, state) {
|
|
620
|
+
const entity = owned.entity;
|
|
621
|
+
const table = newTable(entity, state);
|
|
622
|
+
state.keyColumns.set(entity, (entity.key ?? []).map((name) => entity.properties.get(name)?.column ?? name));
|
|
623
|
+
const columns = addColumns(table, entity, '', '', false, new Set(), state);
|
|
624
|
+
const key = (entity.key ?? []).map((name) => columns.get(name) ?? name);
|
|
625
|
+
if (key.length > 0) {
|
|
626
|
+
table.primaryKey = {
|
|
627
|
+
columns: key,
|
|
628
|
+
...(entity.keyName === undefined ? {} : { name: entity.keyName }),
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
addIndexes(table, entity, columns);
|
|
632
|
+
const foreignColumns = entity.ownerKey.map((name) => columns.get(name) ?? name);
|
|
633
|
+
const principalColumns = state.keyColumns.get(owner) ?? [];
|
|
634
|
+
if (foreignColumns.length > 0) {
|
|
635
|
+
table.foreignKeys.push({
|
|
636
|
+
name: conventionalName('FK', table.name, foreignColumns, ownerTable.name),
|
|
637
|
+
columns: foreignColumns,
|
|
638
|
+
principalTable: ownerTable.name,
|
|
639
|
+
principalColumns,
|
|
640
|
+
onDelete: 'cascade',
|
|
641
|
+
onUpdate: 'noAction',
|
|
642
|
+
...(ownerTable.schema === undefined
|
|
643
|
+
? {}
|
|
644
|
+
: { principalSchema: ownerTable.schema }),
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
state.schema.set(tableKey(table.schema, table.name), table);
|
|
648
|
+
}
|
|
649
|
+
function addIndexes(table, entity, columns) {
|
|
650
|
+
const toColumns = (names) => names.map((name) => columns.get(name) ?? name);
|
|
651
|
+
for (const index of entity.indexes) {
|
|
652
|
+
const names = toColumns(index.properties);
|
|
653
|
+
const entry = {
|
|
654
|
+
name: index.name ?? conventionalName('IX', table.name, names),
|
|
655
|
+
columns: names,
|
|
656
|
+
isUnique: index.isUnique,
|
|
657
|
+
includes: toColumns(index.includes),
|
|
658
|
+
descending: index.descending,
|
|
659
|
+
...(index.filter === undefined ? {} : { filter: index.filter }),
|
|
660
|
+
...(index.method === undefined ? {} : { method: index.method }),
|
|
661
|
+
};
|
|
662
|
+
table.indexes.push(entry);
|
|
663
|
+
}
|
|
664
|
+
for (const alternate of entity.alternateKeys) {
|
|
665
|
+
const names = toColumns(alternate.properties);
|
|
666
|
+
const key = {
|
|
667
|
+
columns: names,
|
|
668
|
+
name: alternate.name ?? conventionalName('AK', table.name, names),
|
|
669
|
+
};
|
|
670
|
+
table.uniqueKeys.push(key);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
/** The root entity of a table-per-hierarchy chain, or the entity itself. */
|
|
674
|
+
function rootOf(entity, entities) {
|
|
675
|
+
let current = entity;
|
|
676
|
+
for (let guard = 0; guard < 32; guard += 1) {
|
|
677
|
+
const base = current.baseType === undefined
|
|
678
|
+
? undefined
|
|
679
|
+
: entities.get(current.baseType);
|
|
680
|
+
if (base === undefined || current.table !== undefined) {
|
|
681
|
+
return current;
|
|
682
|
+
}
|
|
683
|
+
current = base;
|
|
684
|
+
}
|
|
685
|
+
return current;
|
|
686
|
+
}
|
|
687
|
+
/** Turns the entities of the snapshot into tables. */
|
|
688
|
+
export function snapshotSchema(entities, defaultSchema, schema, path, warn) {
|
|
689
|
+
const state = {
|
|
690
|
+
schema,
|
|
691
|
+
entities,
|
|
692
|
+
defaultSchema,
|
|
693
|
+
warn,
|
|
694
|
+
path,
|
|
695
|
+
tableOf: new Map(),
|
|
696
|
+
keyColumns: new Map(),
|
|
697
|
+
};
|
|
698
|
+
const columnMaps = new Map();
|
|
699
|
+
// Root entities first, so that a table-per-hierarchy table exists before its derived types add columns.
|
|
700
|
+
const ordered = [...entities.values()].sort((first, second) => Number(rootOf(first, entities) !== first) -
|
|
701
|
+
Number(rootOf(second, entities) !== second));
|
|
702
|
+
for (const entity of ordered) {
|
|
703
|
+
if (entity.isView) {
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
const root = rootOf(entity, entities);
|
|
707
|
+
if (root !== entity) {
|
|
708
|
+
const rootTable = state.tableOf.get(root.name);
|
|
709
|
+
if (rootTable === undefined) {
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
const columns = addColumns(rootTable, entity, '', '', true, new Set(), state);
|
|
713
|
+
state.tableOf.set(entity.name, rootTable);
|
|
714
|
+
columnMaps.set(entity.name, columns);
|
|
715
|
+
addIndexes(rootTable, entity, columns);
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
const table = newTable(entity, state);
|
|
719
|
+
state.tableOf.set(entity.name, table);
|
|
720
|
+
// The key columns are needed by owned collections, so the key is resolved before the columns are added.
|
|
721
|
+
const keyNames = entity.key ?? [];
|
|
722
|
+
state.keyColumns.set(entity, keyNames.map((name) => entity.properties.get(name)?.column ?? name));
|
|
723
|
+
const columns = addColumns(table, entity, '', '', false, new Set(), state);
|
|
724
|
+
columnMaps.set(entity.name, columns);
|
|
725
|
+
if (keyNames.length > 0) {
|
|
726
|
+
table.primaryKey = {
|
|
727
|
+
columns: keyNames.map((name) => columns.get(name) ?? name),
|
|
728
|
+
...(entity.keyName === undefined ? {} : { name: entity.keyName }),
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
addIndexes(table, entity, columns);
|
|
732
|
+
schema.set(tableKey(table.schema, table.name), table);
|
|
733
|
+
}
|
|
734
|
+
// Relationships need every table, so they come last.
|
|
735
|
+
for (const entity of entities.values()) {
|
|
736
|
+
const table = state.tableOf.get(entity.name);
|
|
737
|
+
const columns = columnMaps.get(entity.name);
|
|
738
|
+
if (table === undefined || columns === undefined) {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
for (const relation of entity.relations) {
|
|
742
|
+
const principal = entities.get(relation.target);
|
|
743
|
+
const principalTable = state.tableOf.get(relation.target);
|
|
744
|
+
if (principal === undefined || principalTable === undefined) {
|
|
745
|
+
warn(`${path}: the relationship from ${shortEntityName(entity.name)} to ${shortEntityName(relation.target)} points at an entity that is not in the model snapshot, so it was skipped.`);
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
const principalColumns = columnMaps.get(principal.name) ?? new Map();
|
|
749
|
+
// A one-to-one relationship names the dependent type first: HasForeignKey("App.Profile", "OwnerId").
|
|
750
|
+
const foreignNames = relation.foreignKey.filter((name) => columns.has(name));
|
|
751
|
+
const foreignColumns = (foreignNames.length > 0 ? foreignNames : relation.foreignKey).map((name) => columns.get(name) ?? name);
|
|
752
|
+
const principalNames = relation.principalKey.filter((name) => principalColumns.has(name));
|
|
753
|
+
const referenced = relation.principalKey.length > 0
|
|
754
|
+
? (principalNames.length > 0
|
|
755
|
+
? principalNames
|
|
756
|
+
: relation.principalKey).map((name) => principalColumns.get(name) ?? name)
|
|
757
|
+
: (state.keyColumns.get(principal) ?? []);
|
|
758
|
+
// The snapshot leaves out an alternate key that only a foreign key uses; the migration creates it.
|
|
759
|
+
if (relation.principalKey.length > 0 &&
|
|
760
|
+
!principalTable.uniqueKeys.some((key) => key.columns.join(',') === referenced.join(','))) {
|
|
761
|
+
principalTable.uniqueKeys.push({
|
|
762
|
+
columns: referenced,
|
|
763
|
+
name: conventionalName('AK', principalTable.name, referenced),
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
const optionalColumns = foreignColumns.some((name) => table.columns.find((column) => column.name === name)
|
|
767
|
+
?.isNullable === true);
|
|
768
|
+
const required = relation.isRequired || !optionalColumns;
|
|
769
|
+
const foreignKey = {
|
|
770
|
+
name: relation.constraintName ??
|
|
771
|
+
conventionalName('FK', table.name, foreignColumns, principalTable.name),
|
|
772
|
+
columns: foreignColumns,
|
|
773
|
+
principalTable: principalTable.name,
|
|
774
|
+
principalColumns: referenced,
|
|
775
|
+
onDelete: relation.onDelete ?? (required ? 'cascade' : 'noAction'),
|
|
776
|
+
onUpdate: 'noAction',
|
|
777
|
+
...(principalTable.schema === undefined
|
|
778
|
+
? {}
|
|
779
|
+
: { principalSchema: principalTable.schema }),
|
|
780
|
+
...(relation.navigation === undefined
|
|
781
|
+
? {}
|
|
782
|
+
: { navigation: relation.navigation }),
|
|
783
|
+
...(relation.inverse === undefined
|
|
784
|
+
? {}
|
|
785
|
+
: { inverseNavigation: relation.inverse }),
|
|
786
|
+
};
|
|
787
|
+
table.foreignKeys.push(foreignKey);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
//# sourceMappingURL=efcoreSnapshot.js.map
|