@mikro-orm/sql 7.0.0-rc.2 → 7.0.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/AbstractSqlConnection.d.ts +5 -4
- package/AbstractSqlConnection.js +20 -6
- package/AbstractSqlDriver.d.ts +19 -13
- package/AbstractSqlDriver.js +225 -47
- package/AbstractSqlPlatform.d.ts +35 -0
- package/AbstractSqlPlatform.js +51 -5
- package/PivotCollectionPersister.d.ts +2 -11
- package/PivotCollectionPersister.js +59 -59
- package/README.md +5 -4
- package/SqlEntityManager.d.ts +2 -2
- package/SqlEntityManager.js +5 -5
- package/dialects/index.d.ts +1 -0
- package/dialects/index.js +1 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +8 -4
- package/dialects/mysql/BaseMySqlPlatform.d.ts +6 -0
- package/dialects/mysql/BaseMySqlPlatform.js +18 -2
- package/dialects/mysql/MySqlSchemaHelper.d.ts +1 -1
- package/dialects/mysql/MySqlSchemaHelper.js +25 -14
- package/dialects/oracledb/OracleDialect.d.ts +78 -0
- package/dialects/oracledb/OracleDialect.js +166 -0
- package/dialects/oracledb/OracleNativeQueryBuilder.d.ts +19 -0
- package/dialects/oracledb/OracleNativeQueryBuilder.js +249 -0
- package/dialects/oracledb/index.d.ts +2 -0
- package/dialects/oracledb/index.js +2 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +6 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.js +49 -37
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +75 -59
- package/dialects/sqlite/BaseSqliteConnection.js +2 -2
- package/dialects/sqlite/NodeSqliteDialect.js +3 -1
- package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
- package/dialects/sqlite/SqlitePlatform.js +7 -1
- package/dialects/sqlite/SqliteSchemaHelper.js +23 -17
- package/index.d.ts +1 -1
- package/index.js +0 -1
- package/package.json +30 -30
- package/plugin/index.d.ts +1 -14
- package/plugin/index.js +13 -13
- package/plugin/transformer.d.ts +6 -22
- package/plugin/transformer.js +91 -82
- package/query/ArrayCriteriaNode.d.ts +1 -1
- package/query/CriteriaNode.js +28 -10
- package/query/CriteriaNodeFactory.js +20 -4
- package/query/NativeQueryBuilder.d.ts +28 -3
- package/query/NativeQueryBuilder.js +65 -3
- package/query/ObjectCriteriaNode.js +75 -31
- package/query/QueryBuilder.d.ts +199 -100
- package/query/QueryBuilder.js +544 -358
- package/query/QueryBuilderHelper.d.ts +18 -14
- package/query/QueryBuilderHelper.js +364 -147
- package/query/ScalarCriteriaNode.js +17 -8
- package/query/enums.d.ts +2 -0
- package/query/enums.js +2 -0
- package/query/raw.js +1 -1
- package/schema/DatabaseSchema.d.ts +7 -5
- package/schema/DatabaseSchema.js +68 -45
- package/schema/DatabaseTable.d.ts +8 -6
- package/schema/DatabaseTable.js +191 -107
- package/schema/SchemaComparator.d.ts +1 -3
- package/schema/SchemaComparator.js +76 -50
- package/schema/SchemaHelper.d.ts +2 -13
- package/schema/SchemaHelper.js +30 -9
- package/schema/SqlSchemaGenerator.d.ts +4 -14
- package/schema/SqlSchemaGenerator.js +26 -12
- package/typings.d.ts +10 -5
- package/tsconfig.build.tsbuildinfo +0 -1
package/schema/DatabaseTable.js
CHANGED
|
@@ -3,51 +3,60 @@ import { DecimalType, EntitySchema, RawQueryFragment, ReferenceKind, t, Type, Un
|
|
|
3
3
|
* @internal
|
|
4
4
|
*/
|
|
5
5
|
export class DatabaseTable {
|
|
6
|
-
platform;
|
|
7
6
|
name;
|
|
8
7
|
schema;
|
|
9
|
-
columns = {};
|
|
10
|
-
indexes = [];
|
|
11
|
-
checks = [];
|
|
12
|
-
foreignKeys = {};
|
|
8
|
+
#columns = {};
|
|
9
|
+
#indexes = [];
|
|
10
|
+
#checks = [];
|
|
11
|
+
#foreignKeys = {};
|
|
12
|
+
#platform;
|
|
13
13
|
nativeEnums = {}; // for postgres
|
|
14
14
|
comment;
|
|
15
15
|
constructor(platform, name, schema) {
|
|
16
|
-
this.platform = platform;
|
|
17
16
|
this.name = name;
|
|
18
17
|
this.schema = schema;
|
|
19
|
-
|
|
20
|
-
platform: { enumerable: false, writable: true },
|
|
21
|
-
});
|
|
18
|
+
this.#platform = platform;
|
|
22
19
|
}
|
|
23
20
|
getQuotedName() {
|
|
24
|
-
return this
|
|
21
|
+
return this.#platform.quoteIdentifier(this.getShortestName());
|
|
25
22
|
}
|
|
26
23
|
getColumns() {
|
|
27
|
-
return Object.values(this
|
|
24
|
+
return Object.values(this.#columns);
|
|
28
25
|
}
|
|
29
26
|
getColumn(name) {
|
|
30
|
-
return this
|
|
27
|
+
return this.#columns[name];
|
|
31
28
|
}
|
|
32
29
|
removeColumn(name) {
|
|
33
|
-
delete this
|
|
30
|
+
delete this.#columns[name];
|
|
34
31
|
}
|
|
35
32
|
getIndexes() {
|
|
36
|
-
return Utils.removeDuplicates(this
|
|
33
|
+
return Utils.removeDuplicates(this.#indexes);
|
|
37
34
|
}
|
|
38
35
|
getChecks() {
|
|
39
|
-
return this
|
|
36
|
+
return this.#checks;
|
|
37
|
+
}
|
|
38
|
+
/** @internal */
|
|
39
|
+
setIndexes(indexes) {
|
|
40
|
+
this.#indexes = indexes;
|
|
41
|
+
}
|
|
42
|
+
/** @internal */
|
|
43
|
+
setChecks(checks) {
|
|
44
|
+
this.#checks = checks;
|
|
45
|
+
}
|
|
46
|
+
/** @internal */
|
|
47
|
+
setForeignKeys(fks) {
|
|
48
|
+
this.#foreignKeys = fks;
|
|
40
49
|
}
|
|
41
50
|
init(cols, indexes = [], checks = [], pks, fks = {}, enums = {}) {
|
|
42
|
-
this
|
|
43
|
-
this
|
|
44
|
-
this
|
|
45
|
-
this
|
|
51
|
+
this.#indexes = indexes;
|
|
52
|
+
this.#checks = checks;
|
|
53
|
+
this.#foreignKeys = fks;
|
|
54
|
+
this.#columns = cols.reduce((o, v) => {
|
|
46
55
|
const index = indexes.filter(i => i.columnNames[0] === v.name);
|
|
47
56
|
v.primary = v.primary || pks.includes(v.name);
|
|
48
57
|
v.unique = index.some(i => i.unique && !i.primary);
|
|
49
58
|
const type = v.name in enums ? 'enum' : v.type;
|
|
50
|
-
v.mappedType = this
|
|
59
|
+
v.mappedType = this.#platform.getMappedType(type);
|
|
51
60
|
v.default = v.default?.toString().startsWith('nextval(') ? null : v.default;
|
|
52
61
|
v.enumItems ??= enums[v.name] || [];
|
|
53
62
|
o[v.name] = v;
|
|
@@ -55,14 +64,14 @@ export class DatabaseTable {
|
|
|
55
64
|
}, {});
|
|
56
65
|
}
|
|
57
66
|
addColumn(column) {
|
|
58
|
-
this
|
|
67
|
+
this.#columns[column.name] = column;
|
|
59
68
|
}
|
|
60
69
|
addColumnFromProperty(prop, meta, config) {
|
|
61
70
|
prop.fieldNames?.forEach((field, idx) => {
|
|
62
71
|
const type = prop.enum ? 'enum' : prop.columnTypes[idx];
|
|
63
|
-
const mappedType = this
|
|
72
|
+
const mappedType = this.#platform.getMappedType(type);
|
|
64
73
|
if (mappedType instanceof DecimalType) {
|
|
65
|
-
const match =
|
|
74
|
+
const match = /\w+\((\d+), ?(\d+)\)/.exec(prop.columnTypes[idx]);
|
|
66
75
|
/* v8 ignore next */
|
|
67
76
|
if (match) {
|
|
68
77
|
prop.precision ??= +match[1];
|
|
@@ -71,21 +80,24 @@ export class DatabaseTable {
|
|
|
71
80
|
}
|
|
72
81
|
}
|
|
73
82
|
if (prop.length == null && prop.columnTypes[idx]) {
|
|
74
|
-
prop.length = this
|
|
83
|
+
prop.length = this.#platform.getSchemaHelper().inferLengthFromColumnType(prop.columnTypes[idx]);
|
|
75
84
|
if (typeof mappedType.getDefaultLength !== 'undefined') {
|
|
76
|
-
prop.length ??= mappedType.getDefaultLength(this
|
|
85
|
+
prop.length ??= mappedType.getDefaultLength(this.#platform);
|
|
77
86
|
}
|
|
78
87
|
}
|
|
79
88
|
const primary = !meta.compositePK && prop.fieldNames.length === 1 && !!prop.primary;
|
|
80
|
-
this
|
|
89
|
+
this.#columns[field] = {
|
|
81
90
|
name: prop.fieldNames[idx],
|
|
82
91
|
type: prop.columnTypes[idx],
|
|
83
|
-
generated: prop.generated instanceof RawQueryFragment
|
|
92
|
+
generated: prop.generated instanceof RawQueryFragment
|
|
93
|
+
? this.#platform.formatQuery(prop.generated.sql, prop.generated.params)
|
|
94
|
+
: prop.generated,
|
|
84
95
|
mappedType,
|
|
85
|
-
unsigned: prop.unsigned && this
|
|
86
|
-
autoincrement: prop.autoincrement ??
|
|
96
|
+
unsigned: prop.unsigned && this.#platform.isNumericColumn(mappedType),
|
|
97
|
+
autoincrement: prop.autoincrement ??
|
|
98
|
+
(primary && prop.kind === ReferenceKind.SCALAR && this.#platform.isNumericColumn(mappedType)),
|
|
87
99
|
primary,
|
|
88
|
-
nullable: this
|
|
100
|
+
nullable: this.#columns[field]?.nullable ?? !!prop.nullable,
|
|
89
101
|
nativeEnumName: prop.nativeEnumName,
|
|
90
102
|
length: prop.length,
|
|
91
103
|
precision: prop.precision,
|
|
@@ -96,21 +108,29 @@ export class DatabaseTable {
|
|
|
96
108
|
extra: prop.extra,
|
|
97
109
|
ignoreSchemaChanges: prop.ignoreSchemaChanges,
|
|
98
110
|
};
|
|
99
|
-
this
|
|
111
|
+
this.#columns[field].unsigned ??= this.#columns[field].autoincrement;
|
|
100
112
|
if (this.nativeEnums[type]) {
|
|
101
|
-
this
|
|
113
|
+
this.#columns[field].enumItems ??= this.nativeEnums[type].items;
|
|
102
114
|
}
|
|
103
|
-
const defaultValue = this
|
|
104
|
-
this
|
|
115
|
+
const defaultValue = this.#platform.getSchemaHelper().normalizeDefaultValue(prop.defaultRaw, prop.length);
|
|
116
|
+
this.#columns[field].default = defaultValue;
|
|
105
117
|
});
|
|
106
118
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.polymorphic) {
|
|
107
119
|
const constraintName = this.getIndexName(prop.foreignKeyName ?? true, prop.fieldNames, 'foreign');
|
|
108
|
-
let schema = prop.targetMeta.root.schema === '*'
|
|
120
|
+
let schema = prop.targetMeta.root.schema === '*'
|
|
121
|
+
? this.schema
|
|
122
|
+
: (prop.targetMeta.root.schema ?? config.get('schema', this.#platform.getDefaultSchemaName()));
|
|
109
123
|
if (prop.referencedTableName.includes('.')) {
|
|
110
124
|
schema = undefined;
|
|
111
125
|
}
|
|
126
|
+
// For cross-schema FKs on MySQL/MariaDB (where schema = database), when the referenced
|
|
127
|
+
// table has no explicit schema but the current table does, qualify with dbName so the
|
|
128
|
+
// FK can resolve the referenced table in the correct database
|
|
129
|
+
if (!schema && this.schema && !this.#platform.getDefaultSchemaName()) {
|
|
130
|
+
schema = config.get('dbName');
|
|
131
|
+
}
|
|
112
132
|
if (prop.createForeignKeyConstraint) {
|
|
113
|
-
this
|
|
133
|
+
this.#foreignKeys[constraintName] = {
|
|
114
134
|
constraintName,
|
|
115
135
|
columnNames: prop.fieldNames,
|
|
116
136
|
localTableName: this.getShortestName(false),
|
|
@@ -118,15 +138,15 @@ export class DatabaseTable {
|
|
|
118
138
|
referencedTableName: schema ? `${schema}.${prop.referencedTableName}` : prop.referencedTableName,
|
|
119
139
|
};
|
|
120
140
|
const schemaConfig = config.get('schemaGenerator');
|
|
121
|
-
this
|
|
122
|
-
this
|
|
141
|
+
this.#foreignKeys[constraintName].deleteRule = prop.deleteRule ?? schemaConfig.defaultDeleteRule;
|
|
142
|
+
this.#foreignKeys[constraintName].updateRule = prop.updateRule ?? schemaConfig.defaultUpdateRule;
|
|
123
143
|
if (prop.deferMode) {
|
|
124
|
-
this
|
|
144
|
+
this.#foreignKeys[constraintName].deferMode = prop.deferMode;
|
|
125
145
|
}
|
|
126
146
|
}
|
|
127
147
|
}
|
|
128
148
|
if (prop.index) {
|
|
129
|
-
this
|
|
149
|
+
this.#indexes.push({
|
|
130
150
|
columnNames: prop.fieldNames,
|
|
131
151
|
composite: prop.fieldNames.length > 1,
|
|
132
152
|
keyName: this.getIndexName(prop.index, prop.fieldNames, 'index'),
|
|
@@ -136,7 +156,7 @@ export class DatabaseTable {
|
|
|
136
156
|
});
|
|
137
157
|
}
|
|
138
158
|
if (prop.unique && !(prop.primary && !meta.compositePK)) {
|
|
139
|
-
this
|
|
159
|
+
this.#indexes.push({
|
|
140
160
|
columnNames: prop.fieldNames,
|
|
141
161
|
composite: prop.fieldNames.length > 1,
|
|
142
162
|
keyName: this.getIndexName(prop.unique, prop.fieldNames, 'unique'),
|
|
@@ -151,22 +171,23 @@ export class DatabaseTable {
|
|
|
151
171
|
if (typeof value === 'string') {
|
|
152
172
|
return value;
|
|
153
173
|
}
|
|
154
|
-
return this
|
|
174
|
+
return this.#platform.getIndexName(this.name, columnNames, type);
|
|
155
175
|
}
|
|
156
176
|
getEntityDeclaration(namingStrategy, schemaHelper, scalarPropertiesForRelations) {
|
|
157
|
-
const { fksOnColumnProps, fksOnStandaloneProps, columnFks, fkIndexes, nullableForeignKeys, skippedColumnNames
|
|
177
|
+
const { fksOnColumnProps, fksOnStandaloneProps, columnFks, fkIndexes, nullableForeignKeys, skippedColumnNames } = this.foreignKeysToProps(namingStrategy, scalarPropertiesForRelations);
|
|
158
178
|
const name = namingStrategy.getEntityName(this.name, this.schema);
|
|
159
179
|
const schema = new EntitySchema({ name, collection: this.name, schema: this.schema, comment: this.comment });
|
|
160
180
|
const compositeFkIndexes = {};
|
|
161
181
|
const compositeFkUniques = {};
|
|
162
|
-
const potentiallyUnmappedIndexes = this
|
|
163
|
-
|
|
164
|
-
index.columnNames.length > 1 // All composite indexes are to be mapped to entity decorators or FK props.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
182
|
+
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.
|
|
183
|
+
// Non-trivial non-composite indexes will be declared at the entity's metadata, though later outputted in the property
|
|
184
|
+
(index.columnNames.length > 1 || // All composite indexes are to be mapped to entity decorators or FK props.
|
|
185
|
+
skippedColumnNames.includes(index.columnNames[0]) || // Non-composite indexes for skipped columns are to be mapped as entity decorators.
|
|
186
|
+
index.deferMode ||
|
|
187
|
+
index.expression ||
|
|
188
|
+
!(index.columnNames[0] in columnFks)) && // Trivial non-composite indexes for scalar props are to be mapped to the column.
|
|
168
189
|
// 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
|
-
|
|
190
|
+
!(index.columnNames.some(col => !col) && !index.expression));
|
|
170
191
|
// Helper to map column name to property name
|
|
171
192
|
const columnToPropertyName = (colName) => this.getPropertyName(namingStrategy, colName);
|
|
172
193
|
for (const index of potentiallyUnmappedIndexes) {
|
|
@@ -198,8 +219,13 @@ export class DatabaseTable {
|
|
|
198
219
|
}
|
|
199
220
|
}
|
|
200
221
|
// An index is trivial if it has no special options that require entity-level declaration
|
|
201
|
-
const hasAdvancedOptions = index.columns?.length ||
|
|
202
|
-
index.
|
|
222
|
+
const hasAdvancedOptions = index.columns?.length ||
|
|
223
|
+
index.include?.length ||
|
|
224
|
+
index.fillFactor ||
|
|
225
|
+
index.type ||
|
|
226
|
+
index.invisible ||
|
|
227
|
+
index.disabled ||
|
|
228
|
+
index.clustered;
|
|
203
229
|
const isTrivial = !index.deferMode && !index.expression && !hasAdvancedOptions;
|
|
204
230
|
if (isTrivial) {
|
|
205
231
|
// Index is for FK. Map to the FK prop and move on.
|
|
@@ -213,7 +239,8 @@ export class DatabaseTable {
|
|
|
213
239
|
}
|
|
214
240
|
}
|
|
215
241
|
}
|
|
216
|
-
const properties = ret.properties ??
|
|
242
|
+
const properties = ret.properties ??
|
|
243
|
+
this.getIndexProperties(index, columnFks, fksOnColumnProps, fksOnStandaloneProps, namingStrategy);
|
|
217
244
|
// If there is a column that cannot be unambiguously mapped to a prop, render an expression.
|
|
218
245
|
if (typeof properties === 'undefined') {
|
|
219
246
|
ret.expression ??= schemaHelper.getCreateIndexSQL(this.name, index);
|
|
@@ -237,7 +264,7 @@ export class DatabaseTable {
|
|
|
237
264
|
}
|
|
238
265
|
schema.addIndex(ret);
|
|
239
266
|
}
|
|
240
|
-
const addedStandaloneFkPropsBasedOnColumn = new Set;
|
|
267
|
+
const addedStandaloneFkPropsBasedOnColumn = new Set();
|
|
241
268
|
const nonSkippedColumns = this.getColumns().filter(column => !skippedColumnNames.includes(column.name));
|
|
242
269
|
for (const column of nonSkippedColumns) {
|
|
243
270
|
const columnName = column.name;
|
|
@@ -259,10 +286,10 @@ export class DatabaseTable {
|
|
|
259
286
|
schema.addProperty(prop.name, prop.type, prop);
|
|
260
287
|
}
|
|
261
288
|
const meta = schema.init().meta;
|
|
262
|
-
const oneToOneCandidateProperties = meta.relations
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
289
|
+
const oneToOneCandidateProperties = meta.relations.filter(prop => prop.primary && prop.kind === ReferenceKind.MANY_TO_ONE);
|
|
290
|
+
if (oneToOneCandidateProperties.length === 1 &&
|
|
291
|
+
oneToOneCandidateProperties[0].fieldNames.length ===
|
|
292
|
+
new Set(meta.getPrimaryProps().flatMap(prop => prop.fieldNames)).size) {
|
|
266
293
|
oneToOneCandidateProperties[0].kind = ReferenceKind.ONE_TO_ONE;
|
|
267
294
|
}
|
|
268
295
|
return meta;
|
|
@@ -277,7 +304,8 @@ export class DatabaseTable {
|
|
|
277
304
|
const standaloneFksBasedOnColumnNames = new Map();
|
|
278
305
|
for (const currentFk of fks) {
|
|
279
306
|
const fkIndex = this.findFkIndex(currentFk);
|
|
280
|
-
if (currentFk.columnNames.length === 1 &&
|
|
307
|
+
if (currentFk.columnNames.length === 1 &&
|
|
308
|
+
!fks.some(fk => fk !== currentFk && fk.columnNames.length === 1 && currentFk.columnNames[0] === fk.columnNames[0])) {
|
|
281
309
|
// Non-composite FK is the only possible one for a column. Render the column with it.
|
|
282
310
|
const columnName = currentFk.columnNames[0];
|
|
283
311
|
columnFks[columnName] ??= [];
|
|
@@ -316,7 +344,10 @@ export class DatabaseTable {
|
|
|
316
344
|
if (nullableColumnsInFk.length > 0) {
|
|
317
345
|
nullableForeignKeys.add(currentFk);
|
|
318
346
|
}
|
|
319
|
-
if (specificColumnNames.length === 1 &&
|
|
347
|
+
if (specificColumnNames.length === 1 &&
|
|
348
|
+
(nullableColumnsInFk.length === currentFk.columnNames.length ||
|
|
349
|
+
nullableColumnsInFk.length === 0 ||
|
|
350
|
+
(nullableColumnsInFk.length === 1 && nullableColumnsInFk[0] === specificColumnNames[0]))) {
|
|
320
351
|
// Composite FK has exactly one column which is not used in any other FK.
|
|
321
352
|
// The FK also doesn't have a mix of nullable and non-nullable columns,
|
|
322
353
|
// or its only nullable column is this very one.
|
|
@@ -391,14 +422,16 @@ export class DatabaseTable {
|
|
|
391
422
|
// But also does not skip if the column is not nullable, and yet all involved FKs are nullable,
|
|
392
423
|
// or if one or more FKs involved has multiple nullable columns.
|
|
393
424
|
smart: (column) => {
|
|
394
|
-
return columnsInFks.includes(column.name)
|
|
395
|
-
|
|
396
|
-
|
|
425
|
+
return (columnsInFks.includes(column.name) &&
|
|
426
|
+
!fksOnColumnProps.has(column.name) &&
|
|
427
|
+
(column.nullable
|
|
397
428
|
? 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)));
|
|
429
|
+
: columnFks[column.name].some(fk => !nullableForeignKeys.has(fk))));
|
|
399
430
|
},
|
|
400
431
|
};
|
|
401
|
-
const skippedColumnNames = this.getColumns()
|
|
432
|
+
const skippedColumnNames = this.getColumns()
|
|
433
|
+
.filter(skippingHandlers[scalarPropertiesForRelations])
|
|
434
|
+
.map(column => column.name);
|
|
402
435
|
// Check standalone FKs named after columns for potential conflicts among themselves.
|
|
403
436
|
// This typically happens when two standalone FKs named after a column resolve to the same prop name
|
|
404
437
|
// because the respective columns include the referenced table in the name.
|
|
@@ -438,8 +471,9 @@ export class DatabaseTable {
|
|
|
438
471
|
}
|
|
439
472
|
findFkIndex(currentFk) {
|
|
440
473
|
const fkColumnsLength = currentFk.columnNames.length;
|
|
441
|
-
const possibleIndexes = this
|
|
442
|
-
return index.columnNames.length === fkColumnsLength &&
|
|
474
|
+
const possibleIndexes = this.#indexes.filter(index => {
|
|
475
|
+
return (index.columnNames.length === fkColumnsLength &&
|
|
476
|
+
!currentFk.columnNames.some((columnName, i) => index.columnNames[i] !== columnName));
|
|
443
477
|
});
|
|
444
478
|
possibleIndexes.sort((a, b) => {
|
|
445
479
|
if (a.primary !== b.primary) {
|
|
@@ -502,7 +536,8 @@ export class DatabaseTable {
|
|
|
502
536
|
return Array.from(propBaseNames).map(baseName => this.getPropertyName(namingStrategy, baseName, fksOnColumnProps.get(baseName)));
|
|
503
537
|
}
|
|
504
538
|
getSafeBaseNameForFkProp(namingStrategy, currentFk, fks, columnName) {
|
|
505
|
-
if (columnName &&
|
|
539
|
+
if (columnName &&
|
|
540
|
+
this.getPropertyName(namingStrategy, columnName, currentFk) !== this.getPropertyName(namingStrategy, columnName)) {
|
|
506
541
|
// The eligible scalar column name is different from the name of the FK prop of the same column.
|
|
507
542
|
// Both can be safely rendered.
|
|
508
543
|
// Use the column name as a base for the FK prop.
|
|
@@ -537,39 +572,41 @@ export class DatabaseTable {
|
|
|
537
572
|
* The shortest name is stripped of the default namespace. All other namespaced elements are returned as full-qualified names.
|
|
538
573
|
*/
|
|
539
574
|
getShortestName(skipDefaultSchema = true) {
|
|
540
|
-
const defaultSchema = this
|
|
541
|
-
if (!this.schema ||
|
|
575
|
+
const defaultSchema = this.#platform.getDefaultSchemaName();
|
|
576
|
+
if (!this.schema ||
|
|
577
|
+
this.name.startsWith(defaultSchema + '.') ||
|
|
578
|
+
(this.schema === defaultSchema && skipDefaultSchema)) {
|
|
542
579
|
return this.name;
|
|
543
580
|
}
|
|
544
581
|
return `${this.schema}.${this.name}`;
|
|
545
582
|
}
|
|
546
583
|
getForeignKeys() {
|
|
547
|
-
return this
|
|
584
|
+
return this.#foreignKeys;
|
|
548
585
|
}
|
|
549
586
|
hasColumn(columnName) {
|
|
550
|
-
return columnName in this
|
|
587
|
+
return columnName in this.#columns;
|
|
551
588
|
}
|
|
552
589
|
getIndex(indexName) {
|
|
553
|
-
return this
|
|
590
|
+
return this.#indexes.find(i => i.keyName === indexName);
|
|
554
591
|
}
|
|
555
592
|
hasIndex(indexName) {
|
|
556
593
|
return !!this.getIndex(indexName);
|
|
557
594
|
}
|
|
558
595
|
getCheck(checkName) {
|
|
559
|
-
return this
|
|
596
|
+
return this.#checks.find(i => i.name === checkName);
|
|
560
597
|
}
|
|
561
598
|
hasCheck(checkName) {
|
|
562
599
|
return !!this.getCheck(checkName);
|
|
563
600
|
}
|
|
564
601
|
getPrimaryKey() {
|
|
565
|
-
return this
|
|
602
|
+
return this.#indexes.find(i => i.primary);
|
|
566
603
|
}
|
|
567
604
|
hasPrimaryKey() {
|
|
568
605
|
return !!this.getPrimaryKey();
|
|
569
606
|
}
|
|
570
607
|
getForeignKeyDeclaration(fk, namingStrategy, schemaHelper, fkIndex, nullable, propNameBase, fksOnColumnProps) {
|
|
571
608
|
const prop = this.getPropertyName(namingStrategy, propNameBase, fk);
|
|
572
|
-
const kind =
|
|
609
|
+
const kind = fkIndex?.unique && !fkIndex.primary ? this.getReferenceKind(fk, fkIndex) : this.getReferenceKind(fk);
|
|
573
610
|
const runtimeType = this.getPropertyTypeForForeignKey(namingStrategy, fk);
|
|
574
611
|
const fkOptions = {};
|
|
575
612
|
fkOptions.fieldNames = fk.columnNames;
|
|
@@ -584,8 +621,8 @@ export class DatabaseTable {
|
|
|
584
621
|
const column = this.getColumn(fk.columnNames[0]);
|
|
585
622
|
const defaultRaw = this.getPropertyDefaultValue(schemaHelper, column, column.type, true);
|
|
586
623
|
const defaultTs = this.getPropertyDefaultValue(schemaHelper, column, column.type);
|
|
587
|
-
columnOptions.default =
|
|
588
|
-
columnOptions.defaultRaw =
|
|
624
|
+
columnOptions.default = defaultRaw !== defaultTs || defaultRaw === '' ? defaultTs : undefined;
|
|
625
|
+
columnOptions.defaultRaw = column.nullable && defaultRaw === 'null' ? undefined : defaultRaw;
|
|
589
626
|
columnOptions.optional = typeof column.generated !== 'undefined' || defaultRaw !== 'null';
|
|
590
627
|
columnOptions.generated = column.generated;
|
|
591
628
|
columnOptions.nullable = column.nullable;
|
|
@@ -607,25 +644,29 @@ export class DatabaseTable {
|
|
|
607
644
|
nullable,
|
|
608
645
|
primary: fkIndex?.primary || !fk.columnNames.some(columnName => !this.getPrimaryKey()?.columnNames.includes(columnName)),
|
|
609
646
|
index: !fkIndex?.unique ? fkIndex?.keyName : undefined,
|
|
610
|
-
unique:
|
|
647
|
+
unique: fkIndex?.unique && !fkIndex.primary ? fkIndex.keyName : undefined,
|
|
611
648
|
...fkOptions,
|
|
612
649
|
};
|
|
613
650
|
}
|
|
614
651
|
getPropertyDeclaration(column, namingStrategy, schemaHelper, compositeFkIndexes, compositeFkUniques, columnFks, fk) {
|
|
615
652
|
const prop = this.getPropertyName(namingStrategy, column.name, fk);
|
|
616
653
|
const persist = !(column.name in columnFks && typeof fk === 'undefined');
|
|
617
|
-
const index = compositeFkIndexes[prop] ||
|
|
618
|
-
|
|
654
|
+
const index = compositeFkIndexes[prop] ||
|
|
655
|
+
this.#indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && !idx.unique && !idx.primary);
|
|
656
|
+
const unique = compositeFkUniques[prop] ||
|
|
657
|
+
this.#indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && idx.unique && !idx.primary);
|
|
619
658
|
const kind = this.getReferenceKind(fk, unique);
|
|
620
659
|
const runtimeType = this.getPropertyTypeForColumn(namingStrategy, column, fk);
|
|
621
|
-
const type = fk
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
660
|
+
const type = fk
|
|
661
|
+
? runtimeType
|
|
662
|
+
: (Utils.keys(t).find(k => {
|
|
663
|
+
const typeInCoreMap = this.#platform.getMappedType(k);
|
|
664
|
+
return ((typeInCoreMap !== Type.getType(UnknownType) || k === 'unknown') && typeInCoreMap === column.mappedType);
|
|
665
|
+
}) ?? runtimeType);
|
|
666
|
+
const ignoreSchemaChanges = type === 'unknown' && column.length ? (column.extra ? ['type', 'extra'] : ['type']) : undefined;
|
|
626
667
|
const defaultRaw = this.getPropertyDefaultValue(schemaHelper, column, runtimeType, true);
|
|
627
668
|
const defaultParsed = this.getPropertyDefaultValue(schemaHelper, column, runtimeType);
|
|
628
|
-
const defaultTs =
|
|
669
|
+
const defaultTs = defaultRaw !== defaultParsed || defaultParsed === '' ? defaultParsed : undefined;
|
|
629
670
|
const fkOptions = {};
|
|
630
671
|
if (fk) {
|
|
631
672
|
fkOptions.fieldNames = fk.columnNames;
|
|
@@ -646,7 +687,7 @@ export class DatabaseTable {
|
|
|
646
687
|
optional: defaultRaw !== 'null' || defaultTs != null || typeof column.generated !== 'undefined',
|
|
647
688
|
columnType: column.type,
|
|
648
689
|
default: defaultTs,
|
|
649
|
-
defaultRaw:
|
|
690
|
+
defaultRaw: column.nullable && defaultRaw === 'null' ? undefined : defaultRaw,
|
|
650
691
|
nullable: column.nullable,
|
|
651
692
|
primary: column.primary && persist,
|
|
652
693
|
autoincrement: column.autoincrement,
|
|
@@ -702,7 +743,7 @@ export class DatabaseTable {
|
|
|
702
743
|
if (fk) {
|
|
703
744
|
return this.getPropertyTypeForForeignKey(namingStrategy, fk);
|
|
704
745
|
}
|
|
705
|
-
const enumMode = this
|
|
746
|
+
const enumMode = this.#platform.getConfig().get('entityGenerator').enumMode;
|
|
706
747
|
// If this column is using an enum.
|
|
707
748
|
if (column.enumItems?.length) {
|
|
708
749
|
const name = column.nativeEnumName ?? column.name;
|
|
@@ -720,7 +761,7 @@ export class DatabaseTable {
|
|
|
720
761
|
const defaultValue = column.default ?? 'null';
|
|
721
762
|
const val = schemaHelper.normalizeDefaultValue(defaultValue, column.length);
|
|
722
763
|
if (val === 'null') {
|
|
723
|
-
return raw ? 'null' :
|
|
764
|
+
return raw ? 'null' : column.nullable ? null : undefined;
|
|
724
765
|
}
|
|
725
766
|
if (propType === 'boolean' && !raw) {
|
|
726
767
|
return !['0', 'false', 'f', 'n', 'no', 'off'].includes('' + column.default);
|
|
@@ -729,7 +770,7 @@ export class DatabaseTable {
|
|
|
729
770
|
return +defaultValue;
|
|
730
771
|
}
|
|
731
772
|
// unquote string defaults if `raw = false`
|
|
732
|
-
const match = ('' + val)
|
|
773
|
+
const match = /^'(.*)'$/.exec('' + val);
|
|
733
774
|
if (!raw && match) {
|
|
734
775
|
return match[1];
|
|
735
776
|
}
|
|
@@ -746,13 +787,15 @@ export class DatabaseTable {
|
|
|
746
787
|
};
|
|
747
788
|
const columns = meta.createSchemaColumnMappingObject();
|
|
748
789
|
const exp = expression(columns, table, indexName);
|
|
749
|
-
return exp instanceof RawQueryFragment ? this
|
|
790
|
+
return exp instanceof RawQueryFragment ? this.#platform.formatQuery(exp.sql, exp.params) : exp;
|
|
750
791
|
}
|
|
751
792
|
return expression;
|
|
752
793
|
}
|
|
753
794
|
addIndex(meta, index, type) {
|
|
754
795
|
// If columns are specified but properties are not, derive properties from column names
|
|
755
|
-
if (index.columns?.length &&
|
|
796
|
+
if (index.columns?.length &&
|
|
797
|
+
!index.expression &&
|
|
798
|
+
(!index.properties || Utils.asArray(index.properties).length === 0)) {
|
|
756
799
|
index = { ...index, properties: index.columns.map(c => c.name) };
|
|
757
800
|
}
|
|
758
801
|
const properties = Utils.unique(Utils.flatten(Utils.asArray(index.properties).map(prop => {
|
|
@@ -787,13 +830,15 @@ export class DatabaseTable {
|
|
|
787
830
|
}
|
|
788
831
|
const name = this.getIndexName(index.name, properties, type);
|
|
789
832
|
// Process include columns (map property names to field names)
|
|
790
|
-
const includeColumns = index.include
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
833
|
+
const includeColumns = index.include
|
|
834
|
+
? Utils.unique(Utils.flatten(Utils.asArray(index.include).map(prop => {
|
|
835
|
+
if (meta.properties[prop]) {
|
|
836
|
+
return meta.properties[prop].fieldNames;
|
|
837
|
+
}
|
|
838
|
+
/* v8 ignore next */
|
|
839
|
+
return [prop];
|
|
840
|
+
})))
|
|
841
|
+
: undefined;
|
|
797
842
|
// Process columns with advanced options (map property names to field names)
|
|
798
843
|
const columns = index.columns?.map(col => {
|
|
799
844
|
const fieldName = meta.properties[col.name]?.fieldNames[0] ?? col.name;
|
|
@@ -817,7 +862,7 @@ export class DatabaseTable {
|
|
|
817
862
|
if (index.fillFactor != null && (index.fillFactor < 0 || index.fillFactor > 100)) {
|
|
818
863
|
throw new Error(`fillFactor must be between 0 and 100, got ${index.fillFactor} for index '${name}' on entity '${meta.className}'`);
|
|
819
864
|
}
|
|
820
|
-
this
|
|
865
|
+
this.#indexes.push({
|
|
821
866
|
keyName: name,
|
|
822
867
|
columnNames: properties,
|
|
823
868
|
composite: properties.length > 1,
|
|
@@ -838,16 +883,55 @@ export class DatabaseTable {
|
|
|
838
883
|
});
|
|
839
884
|
}
|
|
840
885
|
addCheck(check) {
|
|
841
|
-
this
|
|
886
|
+
this.#checks.push(check);
|
|
842
887
|
}
|
|
843
888
|
toJSON() {
|
|
844
|
-
const
|
|
889
|
+
const columns = this.#columns;
|
|
845
890
|
const columnsMapped = Utils.keys(columns).reduce((o, col) => {
|
|
846
|
-
const
|
|
847
|
-
|
|
848
|
-
|
|
891
|
+
const c = columns[col];
|
|
892
|
+
const normalized = {
|
|
893
|
+
name: c.name,
|
|
894
|
+
type: c.type,
|
|
895
|
+
unsigned: !!c.unsigned,
|
|
896
|
+
autoincrement: !!c.autoincrement,
|
|
897
|
+
primary: !!c.primary,
|
|
898
|
+
nullable: !!c.nullable,
|
|
899
|
+
unique: !!c.unique,
|
|
900
|
+
length: c.length ?? null,
|
|
901
|
+
precision: c.precision ?? null,
|
|
902
|
+
scale: c.scale ?? null,
|
|
903
|
+
default: c.default ?? null,
|
|
904
|
+
comment: c.comment ?? null,
|
|
905
|
+
enumItems: c.enumItems ?? [],
|
|
906
|
+
mappedType: Utils.keys(t).find(k => t[k] === c.mappedType.constructor),
|
|
907
|
+
};
|
|
908
|
+
if (c.generated) {
|
|
909
|
+
normalized.generated = c.generated;
|
|
910
|
+
}
|
|
911
|
+
if (c.nativeEnumName) {
|
|
912
|
+
normalized.nativeEnumName = c.nativeEnumName;
|
|
913
|
+
}
|
|
914
|
+
if (c.extra) {
|
|
915
|
+
normalized.extra = c.extra;
|
|
916
|
+
}
|
|
917
|
+
if (c.ignoreSchemaChanges) {
|
|
918
|
+
normalized.ignoreSchemaChanges = c.ignoreSchemaChanges;
|
|
919
|
+
}
|
|
920
|
+
if (c.defaultConstraint) {
|
|
921
|
+
normalized.defaultConstraint = c.defaultConstraint;
|
|
922
|
+
}
|
|
923
|
+
o[col] = normalized;
|
|
849
924
|
return o;
|
|
850
925
|
}, {});
|
|
851
|
-
return {
|
|
926
|
+
return {
|
|
927
|
+
name: this.name,
|
|
928
|
+
schema: this.schema,
|
|
929
|
+
columns: columnsMapped,
|
|
930
|
+
indexes: this.#indexes,
|
|
931
|
+
checks: this.#checks,
|
|
932
|
+
foreignKeys: this.#foreignKeys,
|
|
933
|
+
nativeEnums: this.nativeEnums,
|
|
934
|
+
comment: this.comment,
|
|
935
|
+
};
|
|
852
936
|
}
|
|
853
937
|
}
|
|
@@ -7,9 +7,7 @@ import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
|
|
|
7
7
|
* Compares two Schemas and return an instance of SchemaDifference.
|
|
8
8
|
*/
|
|
9
9
|
export declare class SchemaComparator {
|
|
10
|
-
private
|
|
11
|
-
private readonly helper;
|
|
12
|
-
private readonly logger;
|
|
10
|
+
#private;
|
|
13
11
|
constructor(platform: AbstractSqlPlatform);
|
|
14
12
|
/**
|
|
15
13
|
* Returns a SchemaDifference object containing the differences between the schemas fromSchema and toSchema.
|