@mikro-orm/knex 6.0.0-dev.8 → 6.0.0-dev.80
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/AbstractSqlConnection.d.ts +5 -2
- package/AbstractSqlConnection.js +25 -17
- package/AbstractSqlDriver.d.ts +18 -18
- package/AbstractSqlDriver.js +117 -48
- package/AbstractSqlPlatform.d.ts +1 -0
- package/AbstractSqlPlatform.js +20 -5
- package/MonkeyPatchable.d.ts +1 -0
- package/MonkeyPatchable.js +3 -0
- package/README.md +64 -107
- package/SqlEntityManager.d.ts +2 -5
- package/SqlEntityManager.js +5 -9
- package/SqlEntityRepository.d.ts +6 -4
- package/SqlEntityRepository.js +10 -8
- package/index.mjs +19 -6
- package/package.json +7 -7
- package/query/ArrayCriteriaNode.d.ts +3 -3
- package/query/CriteriaNode.d.ts +8 -8
- package/query/CriteriaNode.js +9 -9
- package/query/CriteriaNodeFactory.d.ts +6 -6
- package/query/CriteriaNodeFactory.js +10 -13
- package/query/ObjectCriteriaNode.d.ts +3 -3
- package/query/ObjectCriteriaNode.js +12 -8
- package/query/QueryBuilder.d.ts +25 -7
- package/query/QueryBuilder.js +237 -61
- package/query/QueryBuilderHelper.d.ts +3 -5
- package/query/QueryBuilderHelper.js +96 -74
- package/query/ScalarCriteriaNode.d.ts +3 -2
- package/query/ScalarCriteriaNode.js +9 -6
- package/schema/DatabaseSchema.d.ts +4 -1
- package/schema/DatabaseSchema.js +22 -6
- package/schema/DatabaseTable.d.ts +2 -1
- package/schema/DatabaseTable.js +25 -24
- package/schema/SchemaComparator.js +9 -2
- package/schema/SchemaHelper.d.ts +5 -3
- package/schema/SchemaHelper.js +10 -4
- package/schema/SqlSchemaGenerator.d.ts +3 -5
- package/schema/SqlSchemaGenerator.js +41 -20
- package/typings.d.ts +8 -5
|
@@ -19,6 +19,10 @@ class QueryBuilderHelper {
|
|
|
19
19
|
this.metadata = this.driver.getMetadata();
|
|
20
20
|
}
|
|
21
21
|
mapper(field, type = enums_1.QueryType.SELECT, value, alias) {
|
|
22
|
+
if (core_1.Utils.isRawSql(field)) {
|
|
23
|
+
return this.knex.raw(field.sql, field.params);
|
|
24
|
+
}
|
|
25
|
+
/* istanbul ignore next */
|
|
22
26
|
if (typeof field !== 'string') {
|
|
23
27
|
return field;
|
|
24
28
|
}
|
|
@@ -36,12 +40,26 @@ class QueryBuilderHelper {
|
|
|
36
40
|
parts.push(this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias));
|
|
37
41
|
}
|
|
38
42
|
}
|
|
43
|
+
// flatten the value if we see we are expanding nested composite key
|
|
44
|
+
// hackish, but cleaner solution would require quite a lot of refactoring
|
|
45
|
+
if (fields.length !== parts.length && Array.isArray(value)) {
|
|
46
|
+
value.forEach(row => {
|
|
47
|
+
if (Array.isArray(row)) {
|
|
48
|
+
const tmp = core_1.Utils.flatten(row);
|
|
49
|
+
row.length = 0;
|
|
50
|
+
row.push(...tmp);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
39
54
|
return this.knex.raw('(' + parts.map(part => this.knex.ref(part)).join(', ') + ')');
|
|
40
55
|
}
|
|
41
|
-
|
|
42
|
-
|
|
56
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(field);
|
|
57
|
+
if (rawField) {
|
|
58
|
+
return this.knex.raw(rawField.sql, rawField.params);
|
|
59
|
+
}
|
|
43
60
|
const [a, f] = this.splitField(field);
|
|
44
61
|
const prop = this.getProperty(f, a);
|
|
62
|
+
let ret = field;
|
|
45
63
|
// embeddable nested path instead of a regular property with table alias, reset alias
|
|
46
64
|
if (prop?.name === a && prop.embeddedProps[f]) {
|
|
47
65
|
return this.alias + '.' + prop.fieldNames[0];
|
|
@@ -56,7 +74,7 @@ class QueryBuilderHelper {
|
|
|
56
74
|
const as = alias === null ? '' : ` as ${aliased}`;
|
|
57
75
|
return this.knex.raw(`${prop.formula(alias2)}${as}`);
|
|
58
76
|
}
|
|
59
|
-
if (prop?.
|
|
77
|
+
if (prop?.hasConvertToJSValueSQL) {
|
|
60
78
|
const prefixed = this.prefix(field, isTableNameAliasRequired, true);
|
|
61
79
|
const valueSQL = prop.customType.convertToJSValueSQL(prefixed, this.platform);
|
|
62
80
|
if (alias === null) {
|
|
@@ -65,15 +83,12 @@ class QueryBuilderHelper {
|
|
|
65
83
|
return this.knex.raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[0])}`);
|
|
66
84
|
}
|
|
67
85
|
// do not wrap custom expressions
|
|
68
|
-
if (!
|
|
86
|
+
if (!rawField) {
|
|
69
87
|
ret = this.prefix(field);
|
|
70
88
|
}
|
|
71
89
|
if (alias) {
|
|
72
90
|
ret += ' as ' + alias;
|
|
73
91
|
}
|
|
74
|
-
if (customExpression) {
|
|
75
|
-
return this.knex.raw(ret, value);
|
|
76
|
-
}
|
|
77
92
|
if (!isTableNameAliasRequired || this.isPrefixed(ret) || noPrefix) {
|
|
78
93
|
return ret;
|
|
79
94
|
}
|
|
@@ -162,6 +177,11 @@ class QueryBuilderHelper {
|
|
|
162
177
|
const right = `${join.alias}.${join.joinColumns[idx]}`;
|
|
163
178
|
conditions.push(`${this.knex.ref(left)} = ${this.knex.ref(right)}`);
|
|
164
179
|
});
|
|
180
|
+
if (join.prop.targetMeta.discriminatorValue && !join.path?.endsWith('[pivot]')) {
|
|
181
|
+
const typeProperty = join.prop.targetMeta.root.discriminatorColumn;
|
|
182
|
+
const alias = !join.prop.owner ? join.inverseAlias ?? join.alias : join.ownerAlias;
|
|
183
|
+
join.cond[`${alias}.${typeProperty}`] = join.prop.targetMeta.discriminatorValue;
|
|
184
|
+
}
|
|
165
185
|
Object.keys(join.cond).forEach(key => {
|
|
166
186
|
conditions.push(this.processJoinClause(key, join.cond[key], params));
|
|
167
187
|
});
|
|
@@ -212,20 +232,14 @@ class QueryBuilderHelper {
|
|
|
212
232
|
if (operator === '$exists') {
|
|
213
233
|
value = null;
|
|
214
234
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const { sql, bindings } = k.toSQL().toNative();
|
|
223
|
-
if (params2.length > 0) {
|
|
224
|
-
params.push(...bindings);
|
|
225
|
-
params.push(...params2);
|
|
226
|
-
return sql + ' = ?';
|
|
235
|
+
const rawField = core_1.RawQueryFragment.getKnownFragment(key);
|
|
236
|
+
if (rawField) {
|
|
237
|
+
let sql = rawField.sql;
|
|
238
|
+
params.push(...rawField.params);
|
|
239
|
+
params.push(...core_1.Utils.asArray(value));
|
|
240
|
+
if (core_1.Utils.asArray(value).length > 0) {
|
|
241
|
+
sql += ' = ?';
|
|
227
242
|
}
|
|
228
|
-
params.push(...bindings);
|
|
229
243
|
return sql;
|
|
230
244
|
}
|
|
231
245
|
const sql = this.mapper(key);
|
|
@@ -241,7 +255,7 @@ class QueryBuilderHelper {
|
|
|
241
255
|
return `(${parts.join(` ${core_1.GroupOperator[operator]} `)})`;
|
|
242
256
|
}
|
|
243
257
|
mapJoinColumns(type, join) {
|
|
244
|
-
if (join.prop && join.prop.
|
|
258
|
+
if (join.prop && join.prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !join.prop.owner) {
|
|
245
259
|
return join.prop.fieldNames.map((fieldName, idx) => {
|
|
246
260
|
return this.mapper(`${join.alias}.${join.inverseJoinColumns[idx]}`, type, undefined, fieldName);
|
|
247
261
|
});
|
|
@@ -254,7 +268,7 @@ class QueryBuilderHelper {
|
|
|
254
268
|
isOneToOneInverse(field) {
|
|
255
269
|
const meta = this.metadata.find(this.entityName);
|
|
256
270
|
const prop = meta.properties[field];
|
|
257
|
-
return prop && prop.
|
|
271
|
+
return prop && prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner;
|
|
258
272
|
}
|
|
259
273
|
getTableName(entityName) {
|
|
260
274
|
const meta = this.metadata.find(entityName);
|
|
@@ -293,13 +307,13 @@ class QueryBuilderHelper {
|
|
|
293
307
|
}
|
|
294
308
|
appendOnConflictClause(type, onConflict, qb) {
|
|
295
309
|
onConflict.forEach(item => {
|
|
296
|
-
const sub = qb.onConflict(item.fields);
|
|
310
|
+
const sub = item.fields.length > 0 ? qb.onConflict(item.fields) : qb.onConflict();
|
|
297
311
|
core_1.Utils.runIfNotEmpty(() => sub.ignore(), item.ignore);
|
|
298
312
|
core_1.Utils.runIfNotEmpty(() => {
|
|
299
313
|
let mergeParam = item.merge;
|
|
300
314
|
if (core_1.Utils.isObject(item.merge)) {
|
|
301
315
|
mergeParam = {};
|
|
302
|
-
|
|
316
|
+
core_1.Utils.keys(item.merge).forEach(key => {
|
|
303
317
|
const k = this.mapper(key, type);
|
|
304
318
|
mergeParam[k] = item.merge[key];
|
|
305
319
|
});
|
|
@@ -336,28 +350,20 @@ class QueryBuilderHelper {
|
|
|
336
350
|
if (core_1.Utils.isPlainObject(cond[key]) || cond[key] instanceof RegExp) {
|
|
337
351
|
return this.processObjectSubCondition(cond, key, qb, method, m, type);
|
|
338
352
|
}
|
|
339
|
-
if (QueryBuilderHelper.isCustomExpression(key)) {
|
|
340
|
-
return this.processCustomExpression(qb, m, key, cond, type);
|
|
341
|
-
}
|
|
342
353
|
const op = cond[key] === null ? 'is' : '=';
|
|
354
|
+
const raw = core_1.RawQueryFragment.getKnownFragment(key);
|
|
355
|
+
if (raw) {
|
|
356
|
+
const value = core_1.Utils.asArray(cond[key]);
|
|
357
|
+
if (value.length > 0) {
|
|
358
|
+
return void qb[m](this.knex.raw(raw.sql, raw.params), op, value[0]);
|
|
359
|
+
}
|
|
360
|
+
return void qb[m](this.knex.raw(raw.sql, raw.params));
|
|
361
|
+
}
|
|
343
362
|
if (this.subQueries[key]) {
|
|
344
363
|
return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), op, cond[key]);
|
|
345
364
|
}
|
|
346
365
|
qb[m](this.mapper(key, type, cond[key], null), op, cond[key]);
|
|
347
366
|
}
|
|
348
|
-
processCustomExpression(clause, m, key, cond, type) {
|
|
349
|
-
// unwind parameters when ? found in field name
|
|
350
|
-
const count = key.concat('?').match(/\?/g).length - 1;
|
|
351
|
-
const value = core_1.Utils.asArray(cond[key]);
|
|
352
|
-
const params1 = value.slice(0, count).map((c) => core_1.Utils.isObject(c) ? JSON.stringify(c) : c);
|
|
353
|
-
const params2 = value.slice(count);
|
|
354
|
-
const k = this.mapper(key, type, params1);
|
|
355
|
-
if (params2.length > 0) {
|
|
356
|
-
const val = params2.length === 1 && params2[0] === null ? null : this.knex.raw('?', params2);
|
|
357
|
-
return void clause[m](k, val);
|
|
358
|
-
}
|
|
359
|
-
clause[m](k);
|
|
360
|
-
}
|
|
361
367
|
processObjectSubCondition(cond, key, qb, method, m, type) {
|
|
362
368
|
// grouped condition for one field
|
|
363
369
|
let value = cond[key];
|
|
@@ -394,7 +400,8 @@ class QueryBuilderHelper {
|
|
|
394
400
|
}));
|
|
395
401
|
}
|
|
396
402
|
else {
|
|
397
|
-
|
|
403
|
+
const mappedKey = this.mapper(key, type, value[op], null);
|
|
404
|
+
qb[m](mappedKey, replacement, value[op]);
|
|
398
405
|
}
|
|
399
406
|
}
|
|
400
407
|
getOperatorReplacement(op, value) {
|
|
@@ -422,20 +429,21 @@ class QueryBuilderHelper {
|
|
|
422
429
|
}
|
|
423
430
|
getQueryOrderFromObject(type, orderBy, populate) {
|
|
424
431
|
const ret = [];
|
|
425
|
-
Object.keys(orderBy).forEach(
|
|
426
|
-
const direction = orderBy[
|
|
432
|
+
Object.keys(orderBy).forEach(key => {
|
|
433
|
+
const direction = orderBy[key];
|
|
427
434
|
const order = core_1.Utils.isNumber(direction) ? core_1.QueryOrderNumeric[direction] : direction;
|
|
428
|
-
|
|
429
|
-
|
|
435
|
+
const raw = core_1.RawQueryFragment.getKnownFragment(key);
|
|
436
|
+
if (raw) {
|
|
437
|
+
ret.push(`${this.platform.formatQuery(raw.sql, raw.params)} ${order.toLowerCase()}`);
|
|
430
438
|
return;
|
|
431
439
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const prop = this.getProperty(
|
|
437
|
-
const noPrefix = (prop && prop.persist === false && !prop.formula) ||
|
|
438
|
-
const column = this.mapper(noPrefix ?
|
|
440
|
+
core_1.Utils.splitPrimaryKeys(key).forEach(f => {
|
|
441
|
+
// eslint-disable-next-line prefer-const
|
|
442
|
+
let [alias, field] = this.splitField(f, true);
|
|
443
|
+
alias = populate[alias] || alias;
|
|
444
|
+
const prop = this.getProperty(field, alias);
|
|
445
|
+
const noPrefix = (prop && prop.persist === false && !prop.formula) || core_1.RawQueryFragment.isKnownFragment(f);
|
|
446
|
+
const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
|
|
439
447
|
/* istanbul ignore next */
|
|
440
448
|
const rawColumn = core_1.Utils.isString(column) ? column.split('.').map(e => this.knex.ref(e)).join('.') : column;
|
|
441
449
|
const customOrder = prop?.customOrder;
|
|
@@ -447,11 +455,29 @@ class QueryBuilderHelper {
|
|
|
447
455
|
});
|
|
448
456
|
return ret.join(', ');
|
|
449
457
|
}
|
|
450
|
-
finalize(type, qb, meta) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
458
|
+
finalize(type, qb, meta, data, returning) {
|
|
459
|
+
if (!meta || !data || !this.platform.usesReturningStatement()) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
// always respect explicit returning hint
|
|
463
|
+
if (returning && returning.length > 0) {
|
|
464
|
+
qb.returning(returning.map(field => this.mapper(field, type)));
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (type === enums_1.QueryType.INSERT) {
|
|
468
|
+
const returningProps = meta.hydrateProps
|
|
469
|
+
.filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
|
|
470
|
+
.filter(prop => !(prop.name in data));
|
|
471
|
+
if (returningProps.length > 0) {
|
|
472
|
+
qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
|
|
473
|
+
}
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (type === enums_1.QueryType.UPDATE) {
|
|
477
|
+
const returningProps = meta.hydrateProps.filter(prop => core_1.Utils.isRawSql(data[prop.name]));
|
|
478
|
+
if (returningProps.length > 0) {
|
|
479
|
+
qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
|
|
480
|
+
}
|
|
455
481
|
}
|
|
456
482
|
}
|
|
457
483
|
splitField(field, greedyAlias = false) {
|
|
@@ -494,18 +520,13 @@ class QueryBuilderHelper {
|
|
|
494
520
|
}
|
|
495
521
|
qb.update(versionProperty.fieldNames[0], this.knex.raw(sql));
|
|
496
522
|
}
|
|
497
|
-
static isCustomExpression(field, hasAlias = false) {
|
|
498
|
-
// if we do not have alias, we don't consider spaces as custom expressions
|
|
499
|
-
const re = hasAlias ? /[ ?<>=()'"`:]|^\d/ : /[?<>=()'"`:]|^\d/;
|
|
500
|
-
return !!field.match(re);
|
|
501
|
-
}
|
|
502
523
|
prefix(field, always = false, quote = false) {
|
|
503
524
|
let ret;
|
|
504
525
|
if (!this.isPrefixed(field)) {
|
|
505
526
|
const alias = always ? (quote ? this.alias : this.platform.quoteIdentifier(this.alias)) + '.' : '';
|
|
506
527
|
const fieldName = this.fieldName(field, this.alias, always);
|
|
507
|
-
if (
|
|
508
|
-
return fieldName;
|
|
528
|
+
if (fieldName instanceof core_1.RawQueryFragment) {
|
|
529
|
+
return fieldName.sql;
|
|
509
530
|
}
|
|
510
531
|
ret = alias + fieldName;
|
|
511
532
|
}
|
|
@@ -545,15 +566,15 @@ class QueryBuilderHelper {
|
|
|
545
566
|
}
|
|
546
567
|
if (prop.fieldNameRaw) {
|
|
547
568
|
if (!always) {
|
|
548
|
-
return prop.fieldNameRaw
|
|
549
|
-
.replace(
|
|
550
|
-
.replace(this.platform.quoteIdentifier('') + '.', '');
|
|
569
|
+
return (0, core_1.raw)(prop.fieldNameRaw
|
|
570
|
+
.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE + '\\.?', 'g'), '')
|
|
571
|
+
.replace(this.platform.quoteIdentifier('') + '.', ''));
|
|
551
572
|
}
|
|
552
573
|
if (alias) {
|
|
553
|
-
return prop.fieldNameRaw.replace(
|
|
574
|
+
return (0, core_1.raw)(prop.fieldNameRaw.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE, 'g'), alias));
|
|
554
575
|
}
|
|
555
576
|
/* istanbul ignore next */
|
|
556
|
-
return prop.fieldNameRaw;
|
|
577
|
+
return (0, core_1.raw)(prop.fieldNameRaw);
|
|
557
578
|
}
|
|
558
579
|
/* istanbul ignore next */
|
|
559
580
|
return prop.fieldNames?.[0] ?? field;
|
|
@@ -564,7 +585,7 @@ class QueryBuilderHelper {
|
|
|
564
585
|
// check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
|
|
565
586
|
if (alias && meta) {
|
|
566
587
|
const prop = meta.properties[alias];
|
|
567
|
-
if (prop?.
|
|
588
|
+
if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
568
589
|
// we want to select the full object property so hydration works as expected
|
|
569
590
|
if (prop.object) {
|
|
570
591
|
return prop;
|
|
@@ -596,17 +617,18 @@ class QueryBuilderHelper {
|
|
|
596
617
|
return;
|
|
597
618
|
}
|
|
598
619
|
if (prop.joinColumns && Array.isArray(data[k])) {
|
|
599
|
-
const copy = data[k];
|
|
620
|
+
const copy = core_1.Utils.flatten(data[k]);
|
|
600
621
|
delete data[k];
|
|
601
622
|
prop.joinColumns.forEach((joinColumn, idx) => data[joinColumn] = copy[idx]);
|
|
602
623
|
return;
|
|
603
624
|
}
|
|
604
625
|
if (prop.customType && convertCustomTypes && !this.platform.isRaw(data[k])) {
|
|
605
|
-
data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, { fromQuery: true, key: k, mode: 'query' });
|
|
626
|
+
data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, { fromQuery: true, key: k, mode: 'query-data' });
|
|
606
627
|
}
|
|
607
|
-
if (prop.
|
|
628
|
+
if (prop.hasConvertToDatabaseValueSQL && !this.platform.isRaw(data[k])) {
|
|
608
629
|
const quoted = this.platform.quoteValue(data[k]);
|
|
609
|
-
|
|
630
|
+
const sql = prop.customType.convertToDatabaseValueSQL(quoted, this.platform);
|
|
631
|
+
data[k] = this.knex.raw(sql.replace(/\?/, '\\?'));
|
|
610
632
|
}
|
|
611
633
|
if (!prop.customType && (Array.isArray(data[k]) || core_1.Utils.isPlainObject(data[k]))) {
|
|
612
634
|
data[k] = JSON.stringify(data[k]);
|
|
@@ -3,7 +3,8 @@ import type { IQueryBuilder } from '../typings';
|
|
|
3
3
|
/**
|
|
4
4
|
* @internal
|
|
5
5
|
*/
|
|
6
|
-
export declare class ScalarCriteriaNode extends CriteriaNode {
|
|
7
|
-
process
|
|
6
|
+
export declare class ScalarCriteriaNode<T extends object> extends CriteriaNode<T> {
|
|
7
|
+
process(qb: IQueryBuilder<T>, alias?: string): any;
|
|
8
|
+
willAutoJoin<T>(qb: IQueryBuilder<T>, alias?: string): boolean;
|
|
8
9
|
shouldJoin(): boolean;
|
|
9
10
|
}
|
|
@@ -13,23 +13,26 @@ class ScalarCriteriaNode extends CriteriaNode_1.CriteriaNode {
|
|
|
13
13
|
const parentPath = this.parent.getPath(); // the parent is always there, otherwise `shouldJoin` would return `false`
|
|
14
14
|
const nestedAlias = qb.getAliasForJoinPath(path) || qb.getNextAlias(this.prop?.pivotTable ?? this.entityName);
|
|
15
15
|
const field = `${alias}.${this.prop.name}`;
|
|
16
|
-
const type = this.prop.
|
|
16
|
+
const type = this.prop.kind === core_1.ReferenceKind.MANY_TO_MANY ? 'pivotJoin' : 'leftJoin';
|
|
17
17
|
qb.join(field, nestedAlias, undefined, type, path);
|
|
18
18
|
// select the owner as virtual property when joining from 1:1 inverse side, but only if the parent is root entity
|
|
19
|
-
if (this.prop.
|
|
19
|
+
if (this.prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !parentPath.includes('.')) {
|
|
20
20
|
qb.addSelect(field);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
return this.payload;
|
|
24
24
|
}
|
|
25
|
+
willAutoJoin(qb, alias) {
|
|
26
|
+
return this.shouldJoin();
|
|
27
|
+
}
|
|
25
28
|
shouldJoin() {
|
|
26
29
|
if (!this.parent || !this.prop) {
|
|
27
30
|
return false;
|
|
28
31
|
}
|
|
29
|
-
switch (this.prop.
|
|
30
|
-
case core_1.
|
|
31
|
-
case core_1.
|
|
32
|
-
case core_1.
|
|
32
|
+
switch (this.prop.kind) {
|
|
33
|
+
case core_1.ReferenceKind.ONE_TO_MANY: return true;
|
|
34
|
+
case core_1.ReferenceKind.MANY_TO_MANY: return true;
|
|
35
|
+
case core_1.ReferenceKind.ONE_TO_ONE: return !this.prop.owner;
|
|
33
36
|
default: return false; // SCALAR, MANY_TO_ONE
|
|
34
37
|
}
|
|
35
38
|
}
|
|
@@ -10,14 +10,17 @@ export declare class DatabaseSchema {
|
|
|
10
10
|
readonly name: string;
|
|
11
11
|
private tables;
|
|
12
12
|
private namespaces;
|
|
13
|
+
private nativeEnums;
|
|
13
14
|
constructor(platform: AbstractSqlPlatform, name: string);
|
|
14
15
|
addTable(name: string, schema: string | undefined | null, comment?: string): DatabaseTable;
|
|
15
16
|
getTables(): DatabaseTable[];
|
|
16
17
|
getTable(name: string): DatabaseTable | undefined;
|
|
17
18
|
hasTable(name: string): boolean;
|
|
19
|
+
setNativeEnums(nativeEnums: Dictionary<unknown[]>): void;
|
|
20
|
+
getNativeEnums(): Dictionary<unknown[]>;
|
|
18
21
|
hasNamespace(namespace: string): boolean;
|
|
19
22
|
getNamespaces(): string[];
|
|
20
|
-
static create(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, config: Configuration, schemaName?: string): Promise<DatabaseSchema>;
|
|
23
|
+
static create(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, schemas?: string[]): Promise<DatabaseSchema>;
|
|
21
24
|
static fromMetadata(metadata: EntityMetadata[], platform: AbstractSqlPlatform, config: Configuration, schemaName?: string): DatabaseSchema;
|
|
22
25
|
private static getSchemaName;
|
|
23
26
|
private static shouldHaveColumn;
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -12,10 +12,12 @@ class DatabaseSchema {
|
|
|
12
12
|
this.name = name;
|
|
13
13
|
this.tables = [];
|
|
14
14
|
this.namespaces = new Set();
|
|
15
|
+
this.nativeEnums = {}; // for postgres
|
|
15
16
|
}
|
|
16
17
|
addTable(name, schema, comment) {
|
|
17
18
|
const namespaceName = schema ?? this.name;
|
|
18
19
|
const table = new DatabaseTable_1.DatabaseTable(this.platform, name, namespaceName);
|
|
20
|
+
table.nativeEnums = this.nativeEnums;
|
|
19
21
|
table.comment = comment;
|
|
20
22
|
this.tables.push(table);
|
|
21
23
|
if (namespaceName != null) {
|
|
@@ -32,24 +34,38 @@ class DatabaseSchema {
|
|
|
32
34
|
hasTable(name) {
|
|
33
35
|
return !!this.getTable(name);
|
|
34
36
|
}
|
|
37
|
+
setNativeEnums(nativeEnums) {
|
|
38
|
+
this.nativeEnums = nativeEnums;
|
|
39
|
+
this.tables.forEach(t => t.nativeEnums = nativeEnums);
|
|
40
|
+
}
|
|
41
|
+
getNativeEnums() {
|
|
42
|
+
return this.nativeEnums;
|
|
43
|
+
}
|
|
35
44
|
hasNamespace(namespace) {
|
|
36
45
|
return this.namespaces.has(namespace);
|
|
37
46
|
}
|
|
38
47
|
getNamespaces() {
|
|
39
48
|
return [...this.namespaces];
|
|
40
49
|
}
|
|
41
|
-
static async create(connection, platform, config, schemaName) {
|
|
42
|
-
const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema'));
|
|
50
|
+
static async create(connection, platform, config, schemaName, schemas) {
|
|
51
|
+
const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema') ?? platform.getDefaultSchemaName());
|
|
43
52
|
const allTables = await connection.execute(platform.getSchemaHelper().getListTablesSQL());
|
|
44
53
|
const parts = config.get('migrations').tableName.split('.');
|
|
45
54
|
const migrationsTableName = parts[1] ?? parts[0];
|
|
46
55
|
const migrationsSchemaName = parts.length > 1 ? parts[0] : config.get('schema', platform.getDefaultSchemaName());
|
|
47
56
|
const tables = allTables.filter(t => t.table_name !== migrationsTableName || (t.schema_name && t.schema_name !== migrationsSchemaName));
|
|
48
|
-
await platform.getSchemaHelper().loadInformationSchema(schema, connection, tables);
|
|
57
|
+
await platform.getSchemaHelper().loadInformationSchema(schema, connection, tables, schemas && schemas.length > 0 ? schemas : undefined);
|
|
49
58
|
return schema;
|
|
50
59
|
}
|
|
51
60
|
static fromMetadata(metadata, platform, config, schemaName) {
|
|
52
61
|
const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema'));
|
|
62
|
+
const nativeEnums = {};
|
|
63
|
+
for (const meta of metadata) {
|
|
64
|
+
meta.props
|
|
65
|
+
.filter(prop => prop.nativeEnumName)
|
|
66
|
+
.forEach(prop => nativeEnums[prop.nativeEnumName] = prop.items?.map(val => '' + val) ?? []);
|
|
67
|
+
}
|
|
68
|
+
schema.setNativeEnums(nativeEnums);
|
|
53
69
|
for (const meta of metadata) {
|
|
54
70
|
const table = schema.addTable(meta.collection, this.getSchemaName(meta, config, schemaName));
|
|
55
71
|
table.comment = meta.comment;
|
|
@@ -78,15 +94,15 @@ class DatabaseSchema {
|
|
|
78
94
|
if (prop.persist === false || !prop.fieldNames) {
|
|
79
95
|
return false;
|
|
80
96
|
}
|
|
81
|
-
if (meta.pivotTable || (core_1.
|
|
97
|
+
if (meta.pivotTable || (core_1.ReferenceKind.EMBEDDED && prop.object)) {
|
|
82
98
|
return true;
|
|
83
99
|
}
|
|
84
100
|
const getRootProperty = (prop) => prop.embedded ? getRootProperty(meta.properties[prop.embedded[0]]) : prop;
|
|
85
101
|
const rootProp = getRootProperty(prop);
|
|
86
|
-
if (rootProp.
|
|
102
|
+
if (rootProp.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
87
103
|
return prop === rootProp || !rootProp.object;
|
|
88
104
|
}
|
|
89
|
-
return [core_1.
|
|
105
|
+
return [core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.MANY_TO_ONE].includes(prop.kind) || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && prop.owner);
|
|
90
106
|
}
|
|
91
107
|
toJSON() {
|
|
92
108
|
const { platform, namespaces, ...rest } = this;
|
|
@@ -13,6 +13,7 @@ export declare class DatabaseTable {
|
|
|
13
13
|
private indexes;
|
|
14
14
|
private checks;
|
|
15
15
|
private foreignKeys;
|
|
16
|
+
nativeEnums: Dictionary<unknown[]>;
|
|
16
17
|
comment?: string;
|
|
17
18
|
constructor(platform: AbstractSqlPlatform, name: string, schema?: string | undefined);
|
|
18
19
|
getColumns(): Column[];
|
|
@@ -37,7 +38,7 @@ export declare class DatabaseTable {
|
|
|
37
38
|
getPrimaryKey(): IndexDef | undefined;
|
|
38
39
|
hasPrimaryKey(): boolean;
|
|
39
40
|
private getPropertyDeclaration;
|
|
40
|
-
private
|
|
41
|
+
private getReferenceKind;
|
|
41
42
|
private getPropertyName;
|
|
42
43
|
private getPropertyType;
|
|
43
44
|
private getPropertyDefaultValue;
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -14,6 +14,7 @@ class DatabaseTable {
|
|
|
14
14
|
this.indexes = [];
|
|
15
15
|
this.checks = [];
|
|
16
16
|
this.foreignKeys = {};
|
|
17
|
+
this.nativeEnums = {}; // for postgres
|
|
17
18
|
Object.defineProperties(this, {
|
|
18
19
|
platform: { enumerable: false, writable: true },
|
|
19
20
|
});
|
|
@@ -41,7 +42,7 @@ class DatabaseTable {
|
|
|
41
42
|
const type = v.name in enums ? 'enum' : v.type;
|
|
42
43
|
v.mappedType = this.platform.getMappedType(type);
|
|
43
44
|
v.default = v.default?.toString().startsWith('nextval(') ? null : v.default;
|
|
44
|
-
v.enumItems
|
|
45
|
+
v.enumItems ??= enums[v.name] || [];
|
|
45
46
|
o[v.name] = v;
|
|
46
47
|
return o;
|
|
47
48
|
}, {});
|
|
@@ -51,7 +52,6 @@ class DatabaseTable {
|
|
|
51
52
|
}
|
|
52
53
|
addColumnFromProperty(prop, meta) {
|
|
53
54
|
prop.fieldNames.forEach((field, idx) => {
|
|
54
|
-
var _a;
|
|
55
55
|
const type = prop.enum ? 'enum' : prop.columnTypes[idx];
|
|
56
56
|
const mappedType = this.platform.getMappedType(type);
|
|
57
57
|
if (mappedType instanceof core_1.DecimalType) {
|
|
@@ -64,9 +64,9 @@ class DatabaseTable {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
if (mappedType instanceof core_1.DateTimeType) {
|
|
67
|
-
prop.length
|
|
67
|
+
prop.length ??= this.platform.getDefaultDateTimeLength();
|
|
68
68
|
}
|
|
69
|
-
const primary = !meta.compositePK && !!prop.primary && prop.
|
|
69
|
+
const primary = !meta.compositePK && !!prop.primary && prop.kind === core_1.ReferenceKind.SCALAR && this.platform.isNumericColumn(mappedType);
|
|
70
70
|
this.columns[field] = {
|
|
71
71
|
name: prop.fieldNames[idx],
|
|
72
72
|
type: prop.columnTypes[idx],
|
|
@@ -74,21 +74,22 @@ class DatabaseTable {
|
|
|
74
74
|
unsigned: prop.unsigned && this.platform.isNumericColumn(mappedType),
|
|
75
75
|
autoincrement: prop.autoincrement ?? primary,
|
|
76
76
|
primary,
|
|
77
|
+
nativeEnumName: prop.nativeEnumName,
|
|
77
78
|
nullable: !!prop.nullable,
|
|
78
79
|
length: prop.length,
|
|
79
80
|
precision: prop.precision,
|
|
80
81
|
scale: prop.scale,
|
|
81
82
|
default: prop.defaultRaw,
|
|
82
|
-
enumItems: prop.items?.every(core_1.Utils.isString) ? prop.items : undefined,
|
|
83
|
+
enumItems: prop.nativeEnumName || prop.items?.every(core_1.Utils.isString) ? prop.items : undefined,
|
|
83
84
|
comment: prop.comment,
|
|
84
85
|
extra: prop.extra,
|
|
85
86
|
ignoreSchemaChanges: prop.ignoreSchemaChanges,
|
|
86
87
|
};
|
|
87
|
-
|
|
88
|
+
this.columns[field].unsigned ||= this.columns[field].autoincrement;
|
|
88
89
|
const defaultValue = this.platform.getSchemaHelper().normalizeDefaultValue(prop.defaultRaw, prop.length);
|
|
89
90
|
this.columns[field].default = defaultValue;
|
|
90
91
|
});
|
|
91
|
-
if ([core_1.
|
|
92
|
+
if ([core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
92
93
|
const constraintName = this.getIndexName(true, prop.fieldNames, 'foreign');
|
|
93
94
|
let schema = prop.targetMeta.schema === '*' ? this.schema : prop.targetMeta.schema ?? this.schema;
|
|
94
95
|
if (prop.referencedTableName.includes('.')) {
|
|
@@ -102,11 +103,11 @@ class DatabaseTable {
|
|
|
102
103
|
referencedTableName: schema ? `${schema}.${prop.referencedTableName}` : prop.referencedTableName,
|
|
103
104
|
};
|
|
104
105
|
const cascade = prop.cascade.includes(core_1.Cascade.REMOVE) || prop.cascade.includes(core_1.Cascade.ALL);
|
|
105
|
-
if (prop.
|
|
106
|
-
this.foreignKeys[constraintName].deleteRule = prop.
|
|
106
|
+
if (prop.deleteRule || cascade || prop.nullable) {
|
|
107
|
+
this.foreignKeys[constraintName].deleteRule = prop.deleteRule || (cascade ? 'cascade' : 'set null');
|
|
107
108
|
}
|
|
108
|
-
if (prop.
|
|
109
|
-
this.foreignKeys[constraintName].updateRule = prop.
|
|
109
|
+
if (prop.updateRule || prop.cascade.includes(core_1.Cascade.PERSIST) || prop.cascade.includes(core_1.Cascade.ALL)) {
|
|
110
|
+
this.foreignKeys[constraintName].updateRule = prop.updateRule || 'cascade';
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
if (prop.index) {
|
|
@@ -164,8 +165,8 @@ class DatabaseTable {
|
|
|
164
165
|
}
|
|
165
166
|
const meta = schema.init().meta;
|
|
166
167
|
meta.relations
|
|
167
|
-
.filter(prop => prop.primary && prop.
|
|
168
|
-
.forEach(prop => prop.
|
|
168
|
+
.filter(prop => prop.primary && prop.kind === core_1.ReferenceKind.MANY_TO_ONE && !meta.compositePK)
|
|
169
|
+
.forEach(prop => prop.kind = core_1.ReferenceKind.ONE_TO_ONE);
|
|
169
170
|
return meta;
|
|
170
171
|
}
|
|
171
172
|
/**
|
|
@@ -206,20 +207,20 @@ class DatabaseTable {
|
|
|
206
207
|
const prop = this.getPropertyName(namingStrategy, column);
|
|
207
208
|
const index = compositeFkIndexes[prop] || this.indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && !idx.unique && !idx.primary);
|
|
208
209
|
const unique = compositeFkUniques[prop] || this.indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && idx.unique && !idx.primary);
|
|
209
|
-
const
|
|
210
|
+
const kind = this.getReferenceKind(fk, unique);
|
|
210
211
|
const type = this.getPropertyType(namingStrategy, column, fk);
|
|
211
212
|
const fkOptions = {};
|
|
212
213
|
if (fk) {
|
|
213
214
|
fkOptions.fieldNames = fk.columnNames;
|
|
214
215
|
fkOptions.referencedTableName = fk.referencedTableName;
|
|
215
216
|
fkOptions.referencedColumnNames = fk.referencedColumnNames;
|
|
216
|
-
fkOptions.
|
|
217
|
-
fkOptions.
|
|
217
|
+
fkOptions.updateRule = fk.updateRule?.toLowerCase();
|
|
218
|
+
fkOptions.deleteRule = fk.deleteRule?.toLowerCase();
|
|
218
219
|
}
|
|
219
220
|
return {
|
|
220
221
|
name: prop,
|
|
221
222
|
type,
|
|
222
|
-
|
|
223
|
+
kind,
|
|
223
224
|
columnType: column.type,
|
|
224
225
|
default: this.getPropertyDefaultValue(schemaHelper, column, type),
|
|
225
226
|
defaultRaw: this.getPropertyDefaultValue(schemaHelper, column, type, true),
|
|
@@ -234,14 +235,14 @@ class DatabaseTable {
|
|
|
234
235
|
...fkOptions,
|
|
235
236
|
};
|
|
236
237
|
}
|
|
237
|
-
|
|
238
|
+
getReferenceKind(fk, unique) {
|
|
238
239
|
if (fk && unique) {
|
|
239
|
-
return core_1.
|
|
240
|
+
return core_1.ReferenceKind.ONE_TO_ONE;
|
|
240
241
|
}
|
|
241
242
|
if (fk) {
|
|
242
|
-
return core_1.
|
|
243
|
+
return core_1.ReferenceKind.MANY_TO_ONE;
|
|
243
244
|
}
|
|
244
|
-
return core_1.
|
|
245
|
+
return core_1.ReferenceKind.SCALAR;
|
|
245
246
|
}
|
|
246
247
|
getPropertyName(namingStrategy, column) {
|
|
247
248
|
const fk = Object.values(this.foreignKeys).find(fk => fk.columnNames.includes(column.name));
|
|
@@ -288,7 +289,7 @@ class DatabaseTable {
|
|
|
288
289
|
return '' + val;
|
|
289
290
|
}
|
|
290
291
|
addIndex(meta, index, type) {
|
|
291
|
-
const properties = core_1.Utils.flatten(core_1.Utils.asArray(index.properties).map(prop => meta.properties[prop].fieldNames));
|
|
292
|
+
const properties = core_1.Utils.unique(core_1.Utils.flatten(core_1.Utils.asArray(index.properties).map(prop => meta.properties[prop].fieldNames)));
|
|
292
293
|
if (properties.length === 0 && !index.expression) {
|
|
293
294
|
return;
|
|
294
295
|
}
|
|
@@ -308,10 +309,10 @@ class DatabaseTable {
|
|
|
308
309
|
}
|
|
309
310
|
toJSON() {
|
|
310
311
|
const { platform, columns, ...rest } = this;
|
|
311
|
-
const columnsMapped =
|
|
312
|
+
const columnsMapped = core_1.Utils.keys(columns).reduce((o, col) => {
|
|
312
313
|
const { mappedType, ...restCol } = columns[col];
|
|
313
314
|
o[col] = restCol;
|
|
314
|
-
o[col].mappedType =
|
|
315
|
+
o[col].mappedType = core_1.Utils.keys(core_1.t).find(k => core_1.t[k] === mappedType.constructor);
|
|
315
316
|
return o;
|
|
316
317
|
}, {});
|
|
317
318
|
return { columns: columnsMapped, ...rest };
|