@mikro-orm/sql 7.0.0-rc.1 → 7.0.0-rc.3
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.js +2 -1
- package/AbstractSqlDriver.d.ts +18 -12
- package/AbstractSqlDriver.js +187 -38
- package/AbstractSqlPlatform.d.ts +1 -0
- package/AbstractSqlPlatform.js +5 -3
- package/PivotCollectionPersister.js +2 -2
- package/SqlEntityManager.d.ts +5 -4
- package/SqlEntityManager.js +5 -5
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +8 -4
- package/dialects/mysql/BaseMySqlPlatform.js +1 -2
- package/dialects/mysql/MySqlSchemaHelper.js +21 -10
- package/dialects/postgresql/BasePostgreSqlPlatform.js +38 -30
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +63 -47
- package/dialects/sqlite/BaseSqliteConnection.d.ts +4 -1
- package/dialects/sqlite/BaseSqliteConnection.js +4 -0
- package/dialects/sqlite/NodeSqliteDialect.d.ts +21 -0
- package/dialects/sqlite/NodeSqliteDialect.js +43 -0
- package/dialects/sqlite/SqliteDriver.d.ts +12 -0
- package/dialects/sqlite/SqliteDriver.js +14 -0
- package/dialects/sqlite/{BaseSqlitePlatform.d.ts → SqlitePlatform.d.ts} +5 -2
- package/dialects/sqlite/{BaseSqlitePlatform.js → SqlitePlatform.js} +30 -4
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +5 -0
- package/dialects/sqlite/SqliteSchemaHelper.js +31 -10
- package/dialects/sqlite/index.d.ts +3 -1
- package/dialects/sqlite/index.js +3 -1
- package/package.json +30 -30
- package/plugin/transformer.js +17 -16
- package/query/CriteriaNode.js +28 -10
- package/query/CriteriaNodeFactory.js +5 -1
- package/query/NativeQueryBuilder.d.ts +25 -0
- package/query/NativeQueryBuilder.js +61 -1
- package/query/ObjectCriteriaNode.js +71 -27
- package/query/QueryBuilder.d.ts +177 -48
- package/query/QueryBuilder.js +233 -54
- package/query/QueryBuilderHelper.d.ts +4 -3
- package/query/QueryBuilderHelper.js +47 -17
- package/query/ScalarCriteriaNode.js +14 -7
- package/query/raw.js +1 -1
- package/schema/DatabaseSchema.js +21 -15
- package/schema/DatabaseTable.js +114 -54
- package/schema/SchemaComparator.js +56 -32
- package/schema/SchemaHelper.js +28 -8
- package/schema/SqlSchemaGenerator.d.ts +2 -1
- package/schema/SqlSchemaGenerator.js +15 -8
- package/tsconfig.build.tsbuildinfo +1 -1
- package/typings.d.ts +15 -4
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ALIAS_REPLACEMENT, ALIAS_REPLACEMENT_RE, ArrayType, inspect, isRaw, LockMode, OptimisticLockError, QueryOperator, QueryOrderNumeric, raw, Raw, QueryHelper, ReferenceKind, Utils, ValidationError, } from '@mikro-orm/core';
|
|
2
2
|
import { JoinType, QueryType } from './enums.js';
|
|
3
|
-
import { NativeQueryBuilder } from './NativeQueryBuilder.js';
|
|
4
3
|
/**
|
|
5
4
|
* @internal
|
|
6
5
|
*/
|
|
@@ -175,13 +174,25 @@ export class QueryBuilderHelper {
|
|
|
175
174
|
}
|
|
176
175
|
}
|
|
177
176
|
return {
|
|
178
|
-
prop,
|
|
179
|
-
|
|
177
|
+
prop,
|
|
178
|
+
type,
|
|
179
|
+
cond,
|
|
180
|
+
ownerAlias,
|
|
181
|
+
alias,
|
|
182
|
+
table,
|
|
183
|
+
schema,
|
|
184
|
+
joinColumns,
|
|
185
|
+
inverseJoinColumns,
|
|
186
|
+
primaryKeys,
|
|
180
187
|
};
|
|
181
188
|
}
|
|
182
189
|
joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}, schema) {
|
|
183
190
|
return {
|
|
184
|
-
prop,
|
|
191
|
+
prop,
|
|
192
|
+
type,
|
|
193
|
+
cond,
|
|
194
|
+
ownerAlias,
|
|
195
|
+
alias,
|
|
185
196
|
table: this.getTableName(prop.targetMeta.class),
|
|
186
197
|
schema: prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta, { schema }),
|
|
187
198
|
joinColumns: prop.referencedColumnNames,
|
|
@@ -194,7 +205,9 @@ export class QueryBuilderHelper {
|
|
|
194
205
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
195
206
|
const ret = {
|
|
196
207
|
[`${ownerAlias}.${prop.name}#${pivotAlias}`]: {
|
|
197
|
-
prop,
|
|
208
|
+
prop,
|
|
209
|
+
type,
|
|
210
|
+
ownerAlias,
|
|
198
211
|
alias: pivotAlias,
|
|
199
212
|
inverseAlias: alias,
|
|
200
213
|
joinColumns: prop.joinColumns,
|
|
@@ -256,7 +269,9 @@ export class QueryBuilderHelper {
|
|
|
256
269
|
conditions.push(`${left} = ${this.platform.quoteIdentifier(right)}`);
|
|
257
270
|
});
|
|
258
271
|
}
|
|
259
|
-
if (join.prop.targetMeta?.root.inheritanceType === 'sti' &&
|
|
272
|
+
if (join.prop.targetMeta?.root.inheritanceType === 'sti' &&
|
|
273
|
+
join.prop.targetMeta?.discriminatorValue &&
|
|
274
|
+
!join.path?.endsWith('[pivot]')) {
|
|
260
275
|
const typeProperty = join.prop.targetMeta.root.discriminatorColumn;
|
|
261
276
|
const alias = join.inverseAlias ?? join.alias;
|
|
262
277
|
join.cond[`${alias}.${typeProperty}`] = join.prop.targetMeta.discriminatorValue;
|
|
@@ -455,7 +470,7 @@ export class QueryBuilderHelper {
|
|
|
455
470
|
// grouped condition for one field, e.g. `{ age: { $gte: 10, $lt: 50 } }`
|
|
456
471
|
if (size > 1) {
|
|
457
472
|
const subCondition = Object.entries(value).map(([subKey, subValue]) => {
|
|
458
|
-
return
|
|
473
|
+
return { [key]: { [subKey]: subValue } };
|
|
459
474
|
});
|
|
460
475
|
for (const sub of subCondition) {
|
|
461
476
|
this.append(() => this._appendQueryCondition(type, sub, '$and'), parts, params);
|
|
@@ -521,8 +536,8 @@ export class QueryBuilderHelper {
|
|
|
521
536
|
else if (['$in', '$nin'].includes(op) && Array.isArray(value[op]) && value[op].length === 0) {
|
|
522
537
|
parts.push(`1 = ${op === '$in' ? 0 : 1}`);
|
|
523
538
|
}
|
|
524
|
-
else if (value[op] instanceof Raw || value[op]
|
|
525
|
-
const query = value[op] instanceof
|
|
539
|
+
else if (value[op] instanceof Raw || typeof value[op]?.toRaw === 'function') {
|
|
540
|
+
const query = value[op] instanceof Raw ? value[op] : value[op].toRaw();
|
|
526
541
|
const mappedKey = this.mapper(key, type, query, null);
|
|
527
542
|
let sql = query.sql;
|
|
528
543
|
if (['$in', '$nin'].includes(op)) {
|
|
@@ -549,7 +564,11 @@ export class QueryBuilderHelper {
|
|
|
549
564
|
return `(${tmp.join(', ')})`;
|
|
550
565
|
}
|
|
551
566
|
if (prop?.customType instanceof ArrayType) {
|
|
552
|
-
const item = prop.customType.convertToDatabaseValue(value, this.platform, {
|
|
567
|
+
const item = prop.customType.convertToDatabaseValue(value, this.platform, {
|
|
568
|
+
fromQuery: true,
|
|
569
|
+
key,
|
|
570
|
+
mode: 'query',
|
|
571
|
+
});
|
|
553
572
|
params.push(item);
|
|
554
573
|
}
|
|
555
574
|
else {
|
|
@@ -629,11 +648,14 @@ export class QueryBuilderHelper {
|
|
|
629
648
|
const noPrefix = (prop?.persist === false && !prop.formula && !prop.embedded) || Raw.isKnownFragment(f);
|
|
630
649
|
const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
|
|
631
650
|
/* v8 ignore next */
|
|
632
|
-
const rawColumn = typeof column === 'string'
|
|
651
|
+
const rawColumn = typeof column === 'string'
|
|
652
|
+
? column
|
|
653
|
+
.split('.')
|
|
654
|
+
.map(e => this.platform.quoteIdentifier(e))
|
|
655
|
+
.join('.')
|
|
656
|
+
: column;
|
|
633
657
|
const customOrder = prop?.customOrder;
|
|
634
|
-
let colPart = customOrder
|
|
635
|
-
? this.platform.generateCustomOrder(rawColumn, customOrder)
|
|
636
|
-
: rawColumn;
|
|
658
|
+
let colPart = customOrder ? this.platform.generateCustomOrder(rawColumn, customOrder) : rawColumn;
|
|
637
659
|
if (isRaw(colPart)) {
|
|
638
660
|
colPart = this.platform.formatQuery(colPart.sql, colPart.params);
|
|
639
661
|
}
|
|
@@ -672,7 +694,9 @@ export class QueryBuilderHelper {
|
|
|
672
694
|
const fields = returningProps.flatMap((prop) => {
|
|
673
695
|
if (prop.hasConvertToJSValueSQL) {
|
|
674
696
|
const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
675
|
-
const sql = prop.customType.convertToJSValueSQL(aliased, this.platform) +
|
|
697
|
+
const sql = prop.customType.convertToJSValueSQL(aliased, this.platform) +
|
|
698
|
+
' as ' +
|
|
699
|
+
this.platform.quoteIdentifier(prop.fieldNames[0]);
|
|
676
700
|
return [raw(sql)];
|
|
677
701
|
}
|
|
678
702
|
return prop.fieldNames;
|
|
@@ -770,7 +794,9 @@ export class QueryBuilderHelper {
|
|
|
770
794
|
// skip nesting parens if the value is simple = scalar or object without operators or with only single key, being the operator
|
|
771
795
|
const keys = Utils.getObjectQueryKeys(sub);
|
|
772
796
|
const val = sub[keys[0]];
|
|
773
|
-
const simple = !Utils.isPlainObject(val) ||
|
|
797
|
+
const simple = !Utils.isPlainObject(val) ||
|
|
798
|
+
Utils.getObjectKeysSize(val) === 1 ||
|
|
799
|
+
Object.keys(val).every(k => !Utils.isOperator(k));
|
|
774
800
|
if (keys.length === 1 && simple) {
|
|
775
801
|
this.append(() => this._appendQueryCondition(type, sub, operator), parts, params);
|
|
776
802
|
continue;
|
|
@@ -804,7 +830,11 @@ export class QueryBuilderHelper {
|
|
|
804
830
|
}
|
|
805
831
|
getProperty(field, alias) {
|
|
806
832
|
const entityName = this.aliasMap[alias]?.entityName || this.entityName;
|
|
807
|
-
const meta = this.metadata.
|
|
833
|
+
const meta = this.metadata.find(entityName);
|
|
834
|
+
// raw table name (e.g. CTE) — no metadata available
|
|
835
|
+
if (!meta) {
|
|
836
|
+
return undefined;
|
|
837
|
+
}
|
|
808
838
|
// check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
|
|
809
839
|
if (alias) {
|
|
810
840
|
const prop = meta.properties[alias];
|
|
@@ -7,7 +7,8 @@ import { QueryBuilder } from './QueryBuilder.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export class ScalarCriteriaNode extends CriteriaNode {
|
|
9
9
|
process(qb, options) {
|
|
10
|
-
const matchPopulateJoins = options?.matchPopulateJoins ||
|
|
10
|
+
const matchPopulateJoins = options?.matchPopulateJoins ||
|
|
11
|
+
(this.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind));
|
|
11
12
|
const nestedAlias = qb.getAliasForJoinPath(this.getPath(options), { ...options, matchPopulateJoins });
|
|
12
13
|
if (this.shouldJoin(qb, nestedAlias)) {
|
|
13
14
|
const path = this.getPath();
|
|
@@ -22,7 +23,7 @@ export class ScalarCriteriaNode extends CriteriaNode {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
if (this.payload instanceof QueryBuilder) {
|
|
25
|
-
return this.payload.
|
|
26
|
+
return this.payload.toRaw();
|
|
26
27
|
}
|
|
27
28
|
if (this.payload && typeof this.payload === 'object') {
|
|
28
29
|
const keys = Object.keys(this.payload).filter(key => ARRAY_OPERATORS.includes(key) && Array.isArray(this.payload[key]));
|
|
@@ -36,14 +37,20 @@ export class ScalarCriteriaNode extends CriteriaNode {
|
|
|
36
37
|
return this.shouldJoin(qb, alias);
|
|
37
38
|
}
|
|
38
39
|
shouldJoin(qb, nestedAlias) {
|
|
39
|
-
if (!this.parent ||
|
|
40
|
+
if (!this.parent ||
|
|
41
|
+
!this.prop ||
|
|
42
|
+
(nestedAlias && [QueryType.SELECT, QueryType.COUNT].includes(qb.type ?? QueryType.SELECT))) {
|
|
40
43
|
return false;
|
|
41
44
|
}
|
|
42
45
|
switch (this.prop.kind) {
|
|
43
|
-
case ReferenceKind.ONE_TO_MANY:
|
|
44
|
-
|
|
45
|
-
case ReferenceKind.
|
|
46
|
-
|
|
46
|
+
case ReferenceKind.ONE_TO_MANY:
|
|
47
|
+
return true;
|
|
48
|
+
case ReferenceKind.MANY_TO_MANY:
|
|
49
|
+
return true;
|
|
50
|
+
case ReferenceKind.ONE_TO_ONE:
|
|
51
|
+
return !this.prop.owner;
|
|
52
|
+
default:
|
|
53
|
+
return false; // SCALAR, MANY_TO_ONE
|
|
47
54
|
}
|
|
48
55
|
}
|
|
49
56
|
}
|
package/query/raw.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { raw as raw_, Utils } from '@mikro-orm/core';
|
|
1
|
+
import { raw as raw_, Utils, } from '@mikro-orm/core';
|
|
2
2
|
/**
|
|
3
3
|
* Creates raw SQL query fragment that can be assigned to a property or part of a filter. This fragment is represented
|
|
4
4
|
* by `RawQueryFragment` class instance that can be serialized to a string, so it can be used both as an object value
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -81,8 +81,11 @@ export class DatabaseSchema {
|
|
|
81
81
|
const parts = config.get('migrations').tableName.split('.');
|
|
82
82
|
const migrationsTableName = parts[1] ?? parts[0];
|
|
83
83
|
const migrationsSchemaName = parts.length > 1 ? parts[0] : config.get('schema', platform.getDefaultSchemaName());
|
|
84
|
-
const tables = allTables.filter(t => this.isTableNameAllowed(t.table_name, takeTables, skipTables) &&
|
|
85
|
-
|
|
84
|
+
const tables = allTables.filter(t => this.isTableNameAllowed(t.table_name, takeTables, skipTables) &&
|
|
85
|
+
(t.table_name !== migrationsTableName || (t.schema_name && t.schema_name !== migrationsSchemaName)));
|
|
86
|
+
await platform
|
|
87
|
+
.getSchemaHelper()
|
|
88
|
+
.loadInformationSchema(schema, connection, tables, schemas && schemas.length > 0 ? schemas : undefined);
|
|
86
89
|
// Load views from database
|
|
87
90
|
await platform.getSchemaHelper().loadViews(schema, connection);
|
|
88
91
|
// Load materialized views (PostgreSQL only)
|
|
@@ -140,9 +143,7 @@ export class DatabaseSchema {
|
|
|
140
143
|
table.comment = meta.comment;
|
|
141
144
|
// For TPT child entities, only use ownProps (properties defined in this entity only)
|
|
142
145
|
// For all other entities (including TPT root), use all props
|
|
143
|
-
const propsToProcess = meta.inheritanceType === 'tpt' && meta.tptParent && meta.ownProps
|
|
144
|
-
? meta.ownProps
|
|
145
|
-
: meta.props;
|
|
146
|
+
const propsToProcess = meta.inheritanceType === 'tpt' && meta.tptParent && meta.ownProps ? meta.ownProps : meta.props;
|
|
146
147
|
for (const prop of propsToProcess) {
|
|
147
148
|
if (!this.shouldHaveColumn(meta, prop, skipColumns)) {
|
|
148
149
|
continue;
|
|
@@ -177,7 +178,9 @@ export class DatabaseSchema {
|
|
|
177
178
|
table.addIndex(meta, { properties: pkPropsForIndex.map(prop => prop.name) }, 'primary');
|
|
178
179
|
for (const check of meta.checks) {
|
|
179
180
|
const columnName = check.property ? meta.properties[check.property].fieldNames[0] : undefined;
|
|
180
|
-
const expression = isRaw(check.expression)
|
|
181
|
+
const expression = isRaw(check.expression)
|
|
182
|
+
? platform.formatQuery(check.expression.sql, check.expression.params)
|
|
183
|
+
: check.expression;
|
|
181
184
|
table.addCheck({
|
|
182
185
|
name: check.name,
|
|
183
186
|
expression,
|
|
@@ -229,7 +232,7 @@ export class DatabaseSchema {
|
|
|
229
232
|
const pkColumnNames = meta.primaryKeys.flatMap(pk => meta.properties[pk].fieldNames);
|
|
230
233
|
const parentPkColumnNames = parent.primaryKeys.flatMap(pk => parent.properties[pk].fieldNames);
|
|
231
234
|
// Determine the parent table name with schema
|
|
232
|
-
const parentSchema = parent.schema === '*' ? undefined : parent.schema ?? config.get('schema', platform.getDefaultSchemaName());
|
|
235
|
+
const parentSchema = parent.schema === '*' ? undefined : (parent.schema ?? config.get('schema', platform.getDefaultSchemaName()));
|
|
233
236
|
const parentTableName = parentSchema ? `${parentSchema}.${parent.tableName}` : parent.tableName;
|
|
234
237
|
// Create FK constraint name
|
|
235
238
|
const constraintName = platform.getIndexName(table.name, pkColumnNames, 'foreign');
|
|
@@ -284,7 +287,8 @@ export class DatabaseSchema {
|
|
|
284
287
|
if (rootProp.kind === ReferenceKind.EMBEDDED) {
|
|
285
288
|
return prop === rootProp || !rootProp.object;
|
|
286
289
|
}
|
|
287
|
-
return [ReferenceKind.SCALAR, ReferenceKind.MANY_TO_ONE].includes(prop.kind) ||
|
|
290
|
+
return ([ReferenceKind.SCALAR, ReferenceKind.MANY_TO_ONE].includes(prop.kind) ||
|
|
291
|
+
(prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner));
|
|
288
292
|
}
|
|
289
293
|
toJSON() {
|
|
290
294
|
const { platform, namespaces, ...rest } = this;
|
|
@@ -293,19 +297,21 @@ export class DatabaseSchema {
|
|
|
293
297
|
prune(schema, wildcardSchemaTables) {
|
|
294
298
|
const hasWildcardSchema = wildcardSchemaTables.length > 0;
|
|
295
299
|
this.tables = this.tables.filter(table => {
|
|
296
|
-
return (!schema && !hasWildcardSchema) // no schema specified and we don't have any multi-schema entity
|
|
297
|
-
|
|
298
|
-
|
|
300
|
+
return ((!schema && !hasWildcardSchema) || // no schema specified and we don't have any multi-schema entity
|
|
301
|
+
table.schema === schema || // specified schema matches the table's one
|
|
302
|
+
(!schema && !wildcardSchemaTables.includes(table.name))); // no schema specified and the table has fixed one provided
|
|
299
303
|
});
|
|
300
304
|
this.views = this.views.filter(view => {
|
|
301
305
|
/* v8 ignore next */
|
|
302
|
-
return (!schema && !hasWildcardSchema)
|
|
303
|
-
|
|
304
|
-
|
|
306
|
+
return ((!schema && !hasWildcardSchema) ||
|
|
307
|
+
view.schema === schema ||
|
|
308
|
+
(!schema && !wildcardSchemaTables.includes(view.name)));
|
|
305
309
|
});
|
|
306
310
|
// remove namespaces of ignored tables and views
|
|
307
311
|
for (const ns of this.namespaces) {
|
|
308
|
-
if (!this.tables.some(t => t.schema === ns) &&
|
|
312
|
+
if (!this.tables.some(t => t.schema === ns) &&
|
|
313
|
+
!this.views.some(v => v.schema === ns) &&
|
|
314
|
+
!Object.values(this.nativeEnums).some(e => e.schema === ns)) {
|
|
309
315
|
this.namespaces.delete(ns);
|
|
310
316
|
}
|
|
311
317
|
}
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -80,10 +80,13 @@ export class DatabaseTable {
|
|
|
80
80
|
this.columns[field] = {
|
|
81
81
|
name: prop.fieldNames[idx],
|
|
82
82
|
type: prop.columnTypes[idx],
|
|
83
|
-
generated: prop.generated instanceof RawQueryFragment
|
|
83
|
+
generated: prop.generated instanceof RawQueryFragment
|
|
84
|
+
? this.platform.formatQuery(prop.generated.sql, prop.generated.params)
|
|
85
|
+
: prop.generated,
|
|
84
86
|
mappedType,
|
|
85
87
|
unsigned: prop.unsigned && this.platform.isNumericColumn(mappedType),
|
|
86
|
-
autoincrement: prop.autoincrement ??
|
|
88
|
+
autoincrement: prop.autoincrement ??
|
|
89
|
+
(primary && prop.kind === ReferenceKind.SCALAR && this.platform.isNumericColumn(mappedType)),
|
|
87
90
|
primary,
|
|
88
91
|
nullable: this.columns[field]?.nullable ?? !!prop.nullable,
|
|
89
92
|
nativeEnumName: prop.nativeEnumName,
|
|
@@ -105,7 +108,9 @@ export class DatabaseTable {
|
|
|
105
108
|
});
|
|
106
109
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.polymorphic) {
|
|
107
110
|
const constraintName = this.getIndexName(prop.foreignKeyName ?? true, prop.fieldNames, 'foreign');
|
|
108
|
-
let schema = prop.targetMeta.root.schema === '*'
|
|
111
|
+
let schema = prop.targetMeta.root.schema === '*'
|
|
112
|
+
? this.schema
|
|
113
|
+
: (prop.targetMeta.root.schema ?? config.get('schema', this.platform.getDefaultSchemaName()));
|
|
109
114
|
if (prop.referencedTableName.includes('.')) {
|
|
110
115
|
schema = undefined;
|
|
111
116
|
}
|
|
@@ -154,19 +159,20 @@ export class DatabaseTable {
|
|
|
154
159
|
return this.platform.getIndexName(this.name, columnNames, type);
|
|
155
160
|
}
|
|
156
161
|
getEntityDeclaration(namingStrategy, schemaHelper, scalarPropertiesForRelations) {
|
|
157
|
-
const { fksOnColumnProps, fksOnStandaloneProps, columnFks, fkIndexes, nullableForeignKeys, skippedColumnNames
|
|
162
|
+
const { fksOnColumnProps, fksOnStandaloneProps, columnFks, fkIndexes, nullableForeignKeys, skippedColumnNames } = this.foreignKeysToProps(namingStrategy, scalarPropertiesForRelations);
|
|
158
163
|
const name = namingStrategy.getEntityName(this.name, this.schema);
|
|
159
164
|
const schema = new EntitySchema({ name, collection: this.name, schema: this.schema, comment: this.comment });
|
|
160
165
|
const compositeFkIndexes = {};
|
|
161
166
|
const compositeFkUniques = {};
|
|
162
|
-
const potentiallyUnmappedIndexes = this.indexes.filter(index => !index.primary // Skip primary index. Whether it's in use by scalar column or FK, it's already mapped.
|
|
163
|
-
|
|
164
|
-
index.columnNames.length > 1 // All composite indexes are to be mapped to entity decorators or FK props.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
const potentiallyUnmappedIndexes = this.indexes.filter(index => !index.primary && // Skip primary index. Whether it's in use by scalar column or FK, it's already mapped.
|
|
168
|
+
// Non-trivial non-composite indexes will be declared at the entity's metadata, though later outputted in the property
|
|
169
|
+
(index.columnNames.length > 1 || // All composite indexes are to be mapped to entity decorators or FK props.
|
|
170
|
+
skippedColumnNames.includes(index.columnNames[0]) || // Non-composite indexes for skipped columns are to be mapped as entity decorators.
|
|
171
|
+
index.deferMode ||
|
|
172
|
+
index.expression ||
|
|
173
|
+
!(index.columnNames[0] in columnFks)) && // Trivial non-composite indexes for scalar props are to be mapped to the column.
|
|
168
174
|
// ignore indexes that don't have all column names (this can happen in sqlite where there is no way to infer this for expressions)
|
|
169
|
-
|
|
175
|
+
!(index.columnNames.some(col => !col) && !index.expression));
|
|
170
176
|
// Helper to map column name to property name
|
|
171
177
|
const columnToPropertyName = (colName) => this.getPropertyName(namingStrategy, colName);
|
|
172
178
|
for (const index of potentiallyUnmappedIndexes) {
|
|
@@ -198,8 +204,13 @@ export class DatabaseTable {
|
|
|
198
204
|
}
|
|
199
205
|
}
|
|
200
206
|
// An index is trivial if it has no special options that require entity-level declaration
|
|
201
|
-
const hasAdvancedOptions = index.columns?.length ||
|
|
202
|
-
index.
|
|
207
|
+
const hasAdvancedOptions = index.columns?.length ||
|
|
208
|
+
index.include?.length ||
|
|
209
|
+
index.fillFactor ||
|
|
210
|
+
index.type ||
|
|
211
|
+
index.invisible ||
|
|
212
|
+
index.disabled ||
|
|
213
|
+
index.clustered;
|
|
203
214
|
const isTrivial = !index.deferMode && !index.expression && !hasAdvancedOptions;
|
|
204
215
|
if (isTrivial) {
|
|
205
216
|
// Index is for FK. Map to the FK prop and move on.
|
|
@@ -213,7 +224,8 @@ export class DatabaseTable {
|
|
|
213
224
|
}
|
|
214
225
|
}
|
|
215
226
|
}
|
|
216
|
-
const properties = ret.properties ??
|
|
227
|
+
const properties = ret.properties ??
|
|
228
|
+
this.getIndexProperties(index, columnFks, fksOnColumnProps, fksOnStandaloneProps, namingStrategy);
|
|
217
229
|
// If there is a column that cannot be unambiguously mapped to a prop, render an expression.
|
|
218
230
|
if (typeof properties === 'undefined') {
|
|
219
231
|
ret.expression ??= schemaHelper.getCreateIndexSQL(this.name, index);
|
|
@@ -237,7 +249,7 @@ export class DatabaseTable {
|
|
|
237
249
|
}
|
|
238
250
|
schema.addIndex(ret);
|
|
239
251
|
}
|
|
240
|
-
const addedStandaloneFkPropsBasedOnColumn = new Set;
|
|
252
|
+
const addedStandaloneFkPropsBasedOnColumn = new Set();
|
|
241
253
|
const nonSkippedColumns = this.getColumns().filter(column => !skippedColumnNames.includes(column.name));
|
|
242
254
|
for (const column of nonSkippedColumns) {
|
|
243
255
|
const columnName = column.name;
|
|
@@ -259,10 +271,10 @@ export class DatabaseTable {
|
|
|
259
271
|
schema.addProperty(prop.name, prop.type, prop);
|
|
260
272
|
}
|
|
261
273
|
const meta = schema.init().meta;
|
|
262
|
-
const oneToOneCandidateProperties = meta.relations
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
274
|
+
const oneToOneCandidateProperties = meta.relations.filter(prop => prop.primary && prop.kind === ReferenceKind.MANY_TO_ONE);
|
|
275
|
+
if (oneToOneCandidateProperties.length === 1 &&
|
|
276
|
+
oneToOneCandidateProperties[0].fieldNames.length ===
|
|
277
|
+
new Set(meta.getPrimaryProps().flatMap(prop => prop.fieldNames)).size) {
|
|
266
278
|
oneToOneCandidateProperties[0].kind = ReferenceKind.ONE_TO_ONE;
|
|
267
279
|
}
|
|
268
280
|
return meta;
|
|
@@ -277,7 +289,8 @@ export class DatabaseTable {
|
|
|
277
289
|
const standaloneFksBasedOnColumnNames = new Map();
|
|
278
290
|
for (const currentFk of fks) {
|
|
279
291
|
const fkIndex = this.findFkIndex(currentFk);
|
|
280
|
-
if (currentFk.columnNames.length === 1 &&
|
|
292
|
+
if (currentFk.columnNames.length === 1 &&
|
|
293
|
+
!fks.some(fk => fk !== currentFk && fk.columnNames.length === 1 && currentFk.columnNames[0] === fk.columnNames[0])) {
|
|
281
294
|
// Non-composite FK is the only possible one for a column. Render the column with it.
|
|
282
295
|
const columnName = currentFk.columnNames[0];
|
|
283
296
|
columnFks[columnName] ??= [];
|
|
@@ -316,7 +329,10 @@ export class DatabaseTable {
|
|
|
316
329
|
if (nullableColumnsInFk.length > 0) {
|
|
317
330
|
nullableForeignKeys.add(currentFk);
|
|
318
331
|
}
|
|
319
|
-
if (specificColumnNames.length === 1 &&
|
|
332
|
+
if (specificColumnNames.length === 1 &&
|
|
333
|
+
(nullableColumnsInFk.length === currentFk.columnNames.length ||
|
|
334
|
+
nullableColumnsInFk.length === 0 ||
|
|
335
|
+
(nullableColumnsInFk.length === 1 && nullableColumnsInFk[0] === specificColumnNames[0]))) {
|
|
320
336
|
// Composite FK has exactly one column which is not used in any other FK.
|
|
321
337
|
// The FK also doesn't have a mix of nullable and non-nullable columns,
|
|
322
338
|
// or its only nullable column is this very one.
|
|
@@ -391,14 +407,16 @@ export class DatabaseTable {
|
|
|
391
407
|
// But also does not skip if the column is not nullable, and yet all involved FKs are nullable,
|
|
392
408
|
// or if one or more FKs involved has multiple nullable columns.
|
|
393
409
|
smart: (column) => {
|
|
394
|
-
return columnsInFks.includes(column.name)
|
|
395
|
-
|
|
396
|
-
|
|
410
|
+
return (columnsInFks.includes(column.name) &&
|
|
411
|
+
!fksOnColumnProps.has(column.name) &&
|
|
412
|
+
(column.nullable
|
|
397
413
|
? columnFks[column.name].some(fk => !fk.columnNames.some(fkColumnName => fkColumnName !== column.name && this.getColumn(fkColumnName)?.nullable))
|
|
398
|
-
: columnFks[column.name].some(fk => !nullableForeignKeys.has(fk)));
|
|
414
|
+
: columnFks[column.name].some(fk => !nullableForeignKeys.has(fk))));
|
|
399
415
|
},
|
|
400
416
|
};
|
|
401
|
-
const skippedColumnNames = this.getColumns()
|
|
417
|
+
const skippedColumnNames = this.getColumns()
|
|
418
|
+
.filter(skippingHandlers[scalarPropertiesForRelations])
|
|
419
|
+
.map(column => column.name);
|
|
402
420
|
// Check standalone FKs named after columns for potential conflicts among themselves.
|
|
403
421
|
// This typically happens when two standalone FKs named after a column resolve to the same prop name
|
|
404
422
|
// because the respective columns include the referenced table in the name.
|
|
@@ -439,7 +457,8 @@ export class DatabaseTable {
|
|
|
439
457
|
findFkIndex(currentFk) {
|
|
440
458
|
const fkColumnsLength = currentFk.columnNames.length;
|
|
441
459
|
const possibleIndexes = this.indexes.filter(index => {
|
|
442
|
-
return index.columnNames.length === fkColumnsLength &&
|
|
460
|
+
return (index.columnNames.length === fkColumnsLength &&
|
|
461
|
+
!currentFk.columnNames.some((columnName, i) => index.columnNames[i] !== columnName));
|
|
443
462
|
});
|
|
444
463
|
possibleIndexes.sort((a, b) => {
|
|
445
464
|
if (a.primary !== b.primary) {
|
|
@@ -502,7 +521,8 @@ export class DatabaseTable {
|
|
|
502
521
|
return Array.from(propBaseNames).map(baseName => this.getPropertyName(namingStrategy, baseName, fksOnColumnProps.get(baseName)));
|
|
503
522
|
}
|
|
504
523
|
getSafeBaseNameForFkProp(namingStrategy, currentFk, fks, columnName) {
|
|
505
|
-
if (columnName &&
|
|
524
|
+
if (columnName &&
|
|
525
|
+
this.getPropertyName(namingStrategy, columnName, currentFk) !== this.getPropertyName(namingStrategy, columnName)) {
|
|
506
526
|
// The eligible scalar column name is different from the name of the FK prop of the same column.
|
|
507
527
|
// Both can be safely rendered.
|
|
508
528
|
// Use the column name as a base for the FK prop.
|
|
@@ -538,7 +558,9 @@ export class DatabaseTable {
|
|
|
538
558
|
*/
|
|
539
559
|
getShortestName(skipDefaultSchema = true) {
|
|
540
560
|
const defaultSchema = this.platform.getDefaultSchemaName();
|
|
541
|
-
if (!this.schema ||
|
|
561
|
+
if (!this.schema ||
|
|
562
|
+
this.name.startsWith(defaultSchema + '.') ||
|
|
563
|
+
(this.schema === defaultSchema && skipDefaultSchema)) {
|
|
542
564
|
return this.name;
|
|
543
565
|
}
|
|
544
566
|
return `${this.schema}.${this.name}`;
|
|
@@ -569,7 +591,7 @@ export class DatabaseTable {
|
|
|
569
591
|
}
|
|
570
592
|
getForeignKeyDeclaration(fk, namingStrategy, schemaHelper, fkIndex, nullable, propNameBase, fksOnColumnProps) {
|
|
571
593
|
const prop = this.getPropertyName(namingStrategy, propNameBase, fk);
|
|
572
|
-
const kind =
|
|
594
|
+
const kind = fkIndex?.unique && !fkIndex.primary ? this.getReferenceKind(fk, fkIndex) : this.getReferenceKind(fk);
|
|
573
595
|
const runtimeType = this.getPropertyTypeForForeignKey(namingStrategy, fk);
|
|
574
596
|
const fkOptions = {};
|
|
575
597
|
fkOptions.fieldNames = fk.columnNames;
|
|
@@ -584,8 +606,8 @@ export class DatabaseTable {
|
|
|
584
606
|
const column = this.getColumn(fk.columnNames[0]);
|
|
585
607
|
const defaultRaw = this.getPropertyDefaultValue(schemaHelper, column, column.type, true);
|
|
586
608
|
const defaultTs = this.getPropertyDefaultValue(schemaHelper, column, column.type);
|
|
587
|
-
columnOptions.default =
|
|
588
|
-
columnOptions.defaultRaw =
|
|
609
|
+
columnOptions.default = defaultRaw !== defaultTs || defaultRaw === '' ? defaultTs : undefined;
|
|
610
|
+
columnOptions.defaultRaw = column.nullable && defaultRaw === 'null' ? undefined : defaultRaw;
|
|
589
611
|
columnOptions.optional = typeof column.generated !== 'undefined' || defaultRaw !== 'null';
|
|
590
612
|
columnOptions.generated = column.generated;
|
|
591
613
|
columnOptions.nullable = column.nullable;
|
|
@@ -607,25 +629,29 @@ export class DatabaseTable {
|
|
|
607
629
|
nullable,
|
|
608
630
|
primary: fkIndex?.primary || !fk.columnNames.some(columnName => !this.getPrimaryKey()?.columnNames.includes(columnName)),
|
|
609
631
|
index: !fkIndex?.unique ? fkIndex?.keyName : undefined,
|
|
610
|
-
unique:
|
|
632
|
+
unique: fkIndex?.unique && !fkIndex.primary ? fkIndex.keyName : undefined,
|
|
611
633
|
...fkOptions,
|
|
612
634
|
};
|
|
613
635
|
}
|
|
614
636
|
getPropertyDeclaration(column, namingStrategy, schemaHelper, compositeFkIndexes, compositeFkUniques, columnFks, fk) {
|
|
615
637
|
const prop = this.getPropertyName(namingStrategy, column.name, fk);
|
|
616
638
|
const persist = !(column.name in columnFks && typeof fk === 'undefined');
|
|
617
|
-
const index = compositeFkIndexes[prop] ||
|
|
618
|
-
|
|
639
|
+
const index = compositeFkIndexes[prop] ||
|
|
640
|
+
this.indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && !idx.unique && !idx.primary);
|
|
641
|
+
const unique = compositeFkUniques[prop] ||
|
|
642
|
+
this.indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && idx.unique && !idx.primary);
|
|
619
643
|
const kind = this.getReferenceKind(fk, unique);
|
|
620
644
|
const runtimeType = this.getPropertyTypeForColumn(namingStrategy, column, fk);
|
|
621
|
-
const type = fk
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
645
|
+
const type = fk
|
|
646
|
+
? runtimeType
|
|
647
|
+
: (Utils.keys(t).find(k => {
|
|
648
|
+
const typeInCoreMap = this.platform.getMappedType(k);
|
|
649
|
+
return ((typeInCoreMap !== Type.getType(UnknownType) || k === 'unknown') && typeInCoreMap === column.mappedType);
|
|
650
|
+
}) ?? runtimeType);
|
|
651
|
+
const ignoreSchemaChanges = type === 'unknown' && column.length ? (column.extra ? ['type', 'extra'] : ['type']) : undefined;
|
|
626
652
|
const defaultRaw = this.getPropertyDefaultValue(schemaHelper, column, runtimeType, true);
|
|
627
653
|
const defaultParsed = this.getPropertyDefaultValue(schemaHelper, column, runtimeType);
|
|
628
|
-
const defaultTs =
|
|
654
|
+
const defaultTs = defaultRaw !== defaultParsed || defaultParsed === '' ? defaultParsed : undefined;
|
|
629
655
|
const fkOptions = {};
|
|
630
656
|
if (fk) {
|
|
631
657
|
fkOptions.fieldNames = fk.columnNames;
|
|
@@ -646,7 +672,7 @@ export class DatabaseTable {
|
|
|
646
672
|
optional: defaultRaw !== 'null' || defaultTs != null || typeof column.generated !== 'undefined',
|
|
647
673
|
columnType: column.type,
|
|
648
674
|
default: defaultTs,
|
|
649
|
-
defaultRaw:
|
|
675
|
+
defaultRaw: column.nullable && defaultRaw === 'null' ? undefined : defaultRaw,
|
|
650
676
|
nullable: column.nullable,
|
|
651
677
|
primary: column.primary && persist,
|
|
652
678
|
autoincrement: column.autoincrement,
|
|
@@ -720,7 +746,7 @@ export class DatabaseTable {
|
|
|
720
746
|
const defaultValue = column.default ?? 'null';
|
|
721
747
|
const val = schemaHelper.normalizeDefaultValue(defaultValue, column.length);
|
|
722
748
|
if (val === 'null') {
|
|
723
|
-
return raw ? 'null' :
|
|
749
|
+
return raw ? 'null' : column.nullable ? null : undefined;
|
|
724
750
|
}
|
|
725
751
|
if (propType === 'boolean' && !raw) {
|
|
726
752
|
return !['0', 'false', 'f', 'n', 'no', 'off'].includes('' + column.default);
|
|
@@ -752,7 +778,9 @@ export class DatabaseTable {
|
|
|
752
778
|
}
|
|
753
779
|
addIndex(meta, index, type) {
|
|
754
780
|
// If columns are specified but properties are not, derive properties from column names
|
|
755
|
-
if (index.columns?.length &&
|
|
781
|
+
if (index.columns?.length &&
|
|
782
|
+
!index.expression &&
|
|
783
|
+
(!index.properties || Utils.asArray(index.properties).length === 0)) {
|
|
756
784
|
index = { ...index, properties: index.columns.map(c => c.name) };
|
|
757
785
|
}
|
|
758
786
|
const properties = Utils.unique(Utils.flatten(Utils.asArray(index.properties).map(prop => {
|
|
@@ -787,13 +815,15 @@ export class DatabaseTable {
|
|
|
787
815
|
}
|
|
788
816
|
const name = this.getIndexName(index.name, properties, type);
|
|
789
817
|
// Process include columns (map property names to field names)
|
|
790
|
-
const includeColumns = index.include
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
818
|
+
const includeColumns = index.include
|
|
819
|
+
? Utils.unique(Utils.flatten(Utils.asArray(index.include).map(prop => {
|
|
820
|
+
if (meta.properties[prop]) {
|
|
821
|
+
return meta.properties[prop].fieldNames;
|
|
822
|
+
}
|
|
823
|
+
/* v8 ignore next */
|
|
824
|
+
return [prop];
|
|
825
|
+
})))
|
|
826
|
+
: undefined;
|
|
797
827
|
// Process columns with advanced options (map property names to field names)
|
|
798
828
|
const columns = index.columns?.map(col => {
|
|
799
829
|
const fieldName = meta.properties[col.name]?.fieldNames[0] ?? col.name;
|
|
@@ -843,9 +873,39 @@ export class DatabaseTable {
|
|
|
843
873
|
toJSON() {
|
|
844
874
|
const { platform, columns, ...rest } = this;
|
|
845
875
|
const columnsMapped = Utils.keys(columns).reduce((o, col) => {
|
|
846
|
-
const
|
|
847
|
-
|
|
848
|
-
|
|
876
|
+
const c = columns[col];
|
|
877
|
+
const normalized = {
|
|
878
|
+
name: c.name,
|
|
879
|
+
type: c.type,
|
|
880
|
+
unsigned: !!c.unsigned,
|
|
881
|
+
autoincrement: !!c.autoincrement,
|
|
882
|
+
primary: !!c.primary,
|
|
883
|
+
nullable: !!c.nullable,
|
|
884
|
+
unique: !!c.unique,
|
|
885
|
+
length: c.length ?? null,
|
|
886
|
+
precision: c.precision ?? null,
|
|
887
|
+
scale: c.scale ?? null,
|
|
888
|
+
default: c.default ?? null,
|
|
889
|
+
comment: c.comment ?? null,
|
|
890
|
+
enumItems: c.enumItems ?? [],
|
|
891
|
+
mappedType: Utils.keys(t).find(k => t[k] === c.mappedType.constructor),
|
|
892
|
+
};
|
|
893
|
+
if (c.generated) {
|
|
894
|
+
normalized.generated = c.generated;
|
|
895
|
+
}
|
|
896
|
+
if (c.nativeEnumName) {
|
|
897
|
+
normalized.nativeEnumName = c.nativeEnumName;
|
|
898
|
+
}
|
|
899
|
+
if (c.extra) {
|
|
900
|
+
normalized.extra = c.extra;
|
|
901
|
+
}
|
|
902
|
+
if (c.ignoreSchemaChanges) {
|
|
903
|
+
normalized.ignoreSchemaChanges = c.ignoreSchemaChanges;
|
|
904
|
+
}
|
|
905
|
+
if (c.defaultConstraint) {
|
|
906
|
+
normalized.defaultConstraint = c.defaultConstraint;
|
|
907
|
+
}
|
|
908
|
+
o[col] = normalized;
|
|
849
909
|
return o;
|
|
850
910
|
}, {});
|
|
851
911
|
return { columns: columnsMapped, ...rest };
|