@mikro-orm/sql 7.1.0-dev.5 → 7.1.0-dev.50
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 +1 -1
- package/AbstractSqlConnection.js +27 -6
- package/AbstractSqlDriver.d.ts +26 -1
- package/AbstractSqlDriver.js +294 -37
- package/AbstractSqlPlatform.d.ts +15 -3
- package/AbstractSqlPlatform.js +25 -7
- package/PivotCollectionPersister.d.ts +2 -2
- package/PivotCollectionPersister.js +19 -3
- package/README.md +2 -1
- package/SqlEntityManager.d.ts +48 -5
- package/SqlEntityManager.js +77 -7
- package/SqlMikroORM.d.ts +23 -0
- package/SqlMikroORM.js +23 -0
- package/dialects/mysql/BaseMySqlPlatform.d.ts +4 -5
- package/dialects/mysql/BaseMySqlPlatform.js +9 -10
- package/dialects/mysql/MySqlSchemaHelper.d.ts +19 -3
- package/dialects/mysql/MySqlSchemaHelper.js +280 -49
- package/dialects/oracledb/OracleDialect.d.ts +1 -1
- package/dialects/oracledb/OracleDialect.js +2 -1
- package/dialects/postgresql/BasePostgreSqlEntityManager.d.ts +19 -0
- package/dialects/postgresql/BasePostgreSqlEntityManager.js +24 -0
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +11 -5
- package/dialects/postgresql/BasePostgreSqlPlatform.js +75 -17
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +38 -1
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +362 -28
- package/dialects/postgresql/index.d.ts +2 -0
- package/dialects/postgresql/index.js +2 -0
- package/dialects/postgresql/typeOverrides.d.ts +14 -0
- package/dialects/postgresql/typeOverrides.js +12 -0
- package/dialects/sqlite/SqlitePlatform.d.ts +2 -1
- package/dialects/sqlite/SqlitePlatform.js +4 -0
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +9 -2
- package/dialects/sqlite/SqliteSchemaHelper.js +148 -19
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +4 -4
- package/plugin/transformer.d.ts +11 -3
- package/plugin/transformer.js +138 -29
- package/query/CriteriaNode.d.ts +1 -1
- package/query/CriteriaNode.js +2 -2
- package/query/ObjectCriteriaNode.js +1 -1
- package/query/QueryBuilder.d.ts +42 -1
- package/query/QueryBuilder.js +78 -7
- package/schema/DatabaseSchema.d.ts +29 -2
- package/schema/DatabaseSchema.js +145 -4
- package/schema/DatabaseTable.d.ts +20 -1
- package/schema/DatabaseTable.js +182 -31
- package/schema/SchemaComparator.d.ts +19 -0
- package/schema/SchemaComparator.js +250 -1
- package/schema/SchemaHelper.d.ts +77 -1
- package/schema/SchemaHelper.js +297 -25
- package/schema/SqlSchemaGenerator.d.ts +2 -2
- package/schema/SqlSchemaGenerator.js +47 -10
- package/schema/partitioning.d.ts +13 -0
- package/schema/partitioning.js +326 -0
- package/typings.d.ts +72 -5
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ArrayType, BooleanType, DateTimeType, inspect, JsonType, parseJsonSafe, Utils, } from '@mikro-orm/core';
|
|
2
2
|
import { DatabaseTable } from './DatabaseTable.js';
|
|
3
|
+
import { diffPartitioning } from './partitioning.js';
|
|
3
4
|
/**
|
|
4
5
|
* Compares two Schemas and return an instance of SchemaDifference.
|
|
5
6
|
*/
|
|
@@ -27,6 +28,9 @@ export class SchemaComparator {
|
|
|
27
28
|
newViews: {},
|
|
28
29
|
changedViews: {},
|
|
29
30
|
removedViews: {},
|
|
31
|
+
newRoutines: {},
|
|
32
|
+
changedRoutines: {},
|
|
33
|
+
removedRoutines: {},
|
|
30
34
|
orphanedForeignKeys: [],
|
|
31
35
|
newNativeEnums: [],
|
|
32
36
|
removedNativeEnums: [],
|
|
@@ -162,8 +166,151 @@ export class SchemaComparator {
|
|
|
162
166
|
diff.changedTables[viewName] = tableDiff;
|
|
163
167
|
}
|
|
164
168
|
}
|
|
169
|
+
this.compareRoutines(fromSchema, toSchema, diff);
|
|
165
170
|
return diff;
|
|
166
171
|
}
|
|
172
|
+
compareRoutines(fromSchema, toSchema, diff) {
|
|
173
|
+
// Case-fold so user-written `'sql_hash'` matches Oracle's introspected `'SQL_HASH'`.
|
|
174
|
+
const routineKey = (r) => ((r.schema ? `${r.schema}.` : '') + r.name).toLowerCase();
|
|
175
|
+
const fromByKey = new Map(fromSchema.getRoutines().map(r => [routineKey(r), r]));
|
|
176
|
+
const toByKey = new Map(toSchema.getRoutines().map(r => [routineKey(r), r]));
|
|
177
|
+
for (const [key, toRoutine] of toByKey) {
|
|
178
|
+
const fromRoutine = fromByKey.get(key);
|
|
179
|
+
if (!fromRoutine) {
|
|
180
|
+
diff.newRoutines[key] = toRoutine;
|
|
181
|
+
this.log(`routine ${key} added`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (this.diffRoutine(fromRoutine, toRoutine)) {
|
|
185
|
+
diff.changedRoutines[key] = { from: fromRoutine, to: toRoutine };
|
|
186
|
+
this.log(`routine ${key} changed`, { fromRoutine, toRoutine });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
for (const [key, fromRoutine] of fromByKey) {
|
|
190
|
+
if (!toByKey.has(key)) {
|
|
191
|
+
diff.removedRoutines[key] = fromRoutine;
|
|
192
|
+
this.log(`routine ${key} removed`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
diffRoutine(from, to) {
|
|
197
|
+
const ignore = new Set(to.ignoreSchemaChanges ?? []);
|
|
198
|
+
if (from.type !== to.type) {
|
|
199
|
+
this.log(`routine ${from.name}: type ${from.type} -> ${to.type}`);
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
if (!ignore.has('body') && from.expression == null && to.expression == null) {
|
|
203
|
+
const a = this.normaliseBody(from.body);
|
|
204
|
+
const b = this.normaliseBody(to.body);
|
|
205
|
+
if (a !== b) {
|
|
206
|
+
this.log(`routine ${from.name}: body differs`, { from: a, to: b });
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (!ignore.has('comment') && (from.comment ?? '') !== (to.comment ?? '')) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
// For security/deterministic/definer, unset on the metadata side means "don't care": the
|
|
214
|
+
// DB always has some server default, comparing it would force a drop+create on every run.
|
|
215
|
+
if (!ignore.has('security') && to.security != null && from.security !== to.security) {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
if (!ignore.has('deterministic') &&
|
|
219
|
+
to.deterministic != null &&
|
|
220
|
+
(from.deterministic ?? false) !== to.deterministic) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
if (!ignore.has('definer') && to.definer != null && from.definer !== to.definer) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
// `language` (PG) / `dataAccess` (MySQL) follow the same to-side-wins policy: only diff when
|
|
227
|
+
// the metadata explicitly declares them, otherwise the create DDL's defaults will line up with
|
|
228
|
+
// whatever the engine introspected.
|
|
229
|
+
if (to.language != null && (from.language ?? '').toLowerCase() !== to.language.toLowerCase()) {
|
|
230
|
+
this.log(`routine ${from.name}: language ${from.language} -> ${to.language}`);
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
if (to.dataAccess != null && from.dataAccess !== to.dataAccess) {
|
|
234
|
+
this.log(`routine ${from.name}: dataAccess ${from.dataAccess} -> ${to.dataAccess}`);
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
if (this.diffRoutineParams(from.params, to.params)) {
|
|
238
|
+
this.log(`routine ${from.name}: params differ`, { from: from.params, to: to.params });
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
if (this.diffRoutineReturns(from.returns, to.returns)) {
|
|
242
|
+
this.log(`routine ${from.name}: returns differ`, { from: from.returns, to: to.returns });
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
/** Strips outer `BEGIN ... END` and trailing semicolons so the comparator doesn't churn on cosmetic round-trip differences. */
|
|
248
|
+
normaliseBody(body) {
|
|
249
|
+
let result = (body ?? '').replace(/\s+/g, ' ').trim();
|
|
250
|
+
const beginEnd = /^begin\s+([\s\S]*?)\s*end\s*;?\s*$/i.exec(result);
|
|
251
|
+
if (beginEnd) {
|
|
252
|
+
result = beginEnd[1].trim();
|
|
253
|
+
}
|
|
254
|
+
return result.replace(/;+\s*$/, '').trim();
|
|
255
|
+
}
|
|
256
|
+
diffRoutineParams(from, to) {
|
|
257
|
+
if (from.length !== to.length) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
for (let i = 0; i < from.length; i++) {
|
|
261
|
+
const a = from[i];
|
|
262
|
+
const b = to[i];
|
|
263
|
+
if (a.name.toLowerCase() !== b.name.toLowerCase() ||
|
|
264
|
+
this.normaliseParamType(a.type) !== this.normaliseParamType(b.type) ||
|
|
265
|
+
a.direction !== b.direction) {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
// Asymmetric: drivers don't currently introspect param nullability, so the from side is
|
|
269
|
+
// always undefined; comparing eagerly would churn metadata-declared `nullable: true`.
|
|
270
|
+
if (a.nullable != null && !!a.nullable !== !!b.nullable) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
/** Engines drop length/precision modifiers and use different aliases for the same logical type — canonicalise both sides. */
|
|
277
|
+
normaliseParamType(type) {
|
|
278
|
+
const lengthMatch = /^([^()]+)\(([^)]*)\)\s*$/.exec(type);
|
|
279
|
+
const base = (lengthMatch ? lengthMatch[1] : type).trim().toLowerCase();
|
|
280
|
+
const aliased = SchemaComparator.PARAM_TYPE_ALIASES[base] ?? base;
|
|
281
|
+
const length = lengthMatch ? Number.parseInt(lengthMatch[2].split(',')[0].trim(), 10) : NaN;
|
|
282
|
+
const options = Number.isFinite(length) ? { length } : {};
|
|
283
|
+
return this.#platform.normalizeColumnType(aliased, options).toLowerCase();
|
|
284
|
+
}
|
|
285
|
+
static PARAM_TYPE_ALIASES = {
|
|
286
|
+
int: 'integer',
|
|
287
|
+
int4: 'integer',
|
|
288
|
+
int8: 'bigint',
|
|
289
|
+
int2: 'smallint',
|
|
290
|
+
bool: 'boolean',
|
|
291
|
+
'character varying': 'varchar',
|
|
292
|
+
bpchar: 'char',
|
|
293
|
+
float8: 'double precision',
|
|
294
|
+
float4: 'real',
|
|
295
|
+
// Oracle's USER_ARGUMENTS reports `REF CURSOR`; users declare `sys_refcursor`.
|
|
296
|
+
'ref cursor': 'sys_refcursor',
|
|
297
|
+
};
|
|
298
|
+
diffRoutineReturns(from, to) {
|
|
299
|
+
if (from == null && to == null) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
if (from == null || to == null) {
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
if (this.normaliseParamType(from.type) !== this.normaliseParamType(to.type)) {
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
// Engines report function returns as nullable; only diff when metadata explicitly declares it.
|
|
309
|
+
if (to.nullable != null && (from.nullable ?? false) !== to.nullable) {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
167
314
|
/**
|
|
168
315
|
* Returns the difference between the tables fromTable and toTable.
|
|
169
316
|
* If there are no differences this method returns the boolean false.
|
|
@@ -176,14 +323,17 @@ export class SchemaComparator {
|
|
|
176
323
|
addedForeignKeys: {},
|
|
177
324
|
addedIndexes: {},
|
|
178
325
|
addedChecks: {},
|
|
326
|
+
addedTriggers: {},
|
|
179
327
|
changedColumns: {},
|
|
180
328
|
changedForeignKeys: {},
|
|
181
329
|
changedIndexes: {},
|
|
182
330
|
changedChecks: {},
|
|
331
|
+
changedTriggers: {},
|
|
183
332
|
removedColumns: {},
|
|
184
333
|
removedForeignKeys: {},
|
|
185
334
|
removedIndexes: {},
|
|
186
335
|
removedChecks: {},
|
|
336
|
+
removedTriggers: {},
|
|
187
337
|
renamedColumns: {},
|
|
188
338
|
renamedIndexes: {},
|
|
189
339
|
fromTable,
|
|
@@ -197,6 +347,17 @@ export class SchemaComparator {
|
|
|
197
347
|
});
|
|
198
348
|
changes++;
|
|
199
349
|
}
|
|
350
|
+
if (diffPartitioning(fromTable.getPartitioning(), toTable.getPartitioning(), this.#platform.getDefaultSchemaName())) {
|
|
351
|
+
tableDifferences.changedPartitioning = {
|
|
352
|
+
from: fromTable.getPartitioning(),
|
|
353
|
+
to: toTable.getPartitioning(),
|
|
354
|
+
};
|
|
355
|
+
this.log(`table partitioning changed for ${tableDifferences.name}`, {
|
|
356
|
+
fromPartitioning: fromTable.getPartitioning(),
|
|
357
|
+
toPartitioning: toTable.getPartitioning(),
|
|
358
|
+
});
|
|
359
|
+
changes++;
|
|
360
|
+
}
|
|
200
361
|
const fromTableColumns = fromTable.getColumns();
|
|
201
362
|
const toTableColumns = toTable.getColumns();
|
|
202
363
|
// See if all the columns in "from" table exist in "to" table
|
|
@@ -263,6 +424,19 @@ export class SchemaComparator {
|
|
|
263
424
|
if (!this.diffIndex(index, toTableIndex)) {
|
|
264
425
|
continue;
|
|
265
426
|
}
|
|
427
|
+
// Constraint-vs-index form mismatch needs drop+create with the OLD form's drop SQL,
|
|
428
|
+
// which `changedIndexes` (uses new form only) can't do. Primary keys stay on the
|
|
429
|
+
// changed path which emits `add primary key`.
|
|
430
|
+
if (!index.primary && !!index.constraint !== !!toTableIndex.constraint) {
|
|
431
|
+
tableDifferences.removedIndexes[index.keyName] = index;
|
|
432
|
+
tableDifferences.addedIndexes[index.keyName] = toTableIndex;
|
|
433
|
+
this.log(`index ${index.keyName} changed form in table ${tableDifferences.name}`, {
|
|
434
|
+
fromTableIndex: index,
|
|
435
|
+
toTableIndex,
|
|
436
|
+
});
|
|
437
|
+
changes += 2;
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
266
440
|
tableDifferences.changedIndexes[index.keyName] = toTableIndex;
|
|
267
441
|
this.log(`index ${index.keyName} changed in table ${tableDifferences.name}`, {
|
|
268
442
|
fromTableIndex: index,
|
|
@@ -309,6 +483,33 @@ export class SchemaComparator {
|
|
|
309
483
|
tableDifferences.changedChecks[check.name] = toTableCheck;
|
|
310
484
|
changes++;
|
|
311
485
|
}
|
|
486
|
+
const fromTableTriggers = fromTable.getTriggers();
|
|
487
|
+
const toTableTriggers = toTable.getTriggers();
|
|
488
|
+
for (const trigger of toTableTriggers) {
|
|
489
|
+
if (fromTable.hasTrigger(trigger.name)) {
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
tableDifferences.addedTriggers[trigger.name] = trigger;
|
|
493
|
+
this.log(`trigger ${trigger.name} added to table ${tableDifferences.name}`, { trigger });
|
|
494
|
+
changes++;
|
|
495
|
+
}
|
|
496
|
+
for (const trigger of fromTableTriggers) {
|
|
497
|
+
if (!toTable.hasTrigger(trigger.name)) {
|
|
498
|
+
tableDifferences.removedTriggers[trigger.name] = trigger;
|
|
499
|
+
this.log(`trigger ${trigger.name} removed from table ${tableDifferences.name}`);
|
|
500
|
+
changes++;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
const toTableTrigger = toTable.getTrigger(trigger.name);
|
|
504
|
+
if (this.diffTrigger(trigger, toTableTrigger)) {
|
|
505
|
+
this.log(`trigger ${trigger.name} changed in table ${tableDifferences.name}`, {
|
|
506
|
+
fromTableTrigger: trigger,
|
|
507
|
+
toTableTrigger,
|
|
508
|
+
});
|
|
509
|
+
tableDifferences.changedTriggers[trigger.name] = toTableTrigger;
|
|
510
|
+
changes++;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
312
513
|
const fromForeignKeys = { ...fromTable.getForeignKeys() };
|
|
313
514
|
const toForeignKeys = { ...toTable.getForeignKeys() };
|
|
314
515
|
for (const fromConstraint of Object.values(fromForeignKeys)) {
|
|
@@ -516,6 +717,11 @@ export class SchemaComparator {
|
|
|
516
717
|
log(`'comment' changed for column ${fromTable.name}.${fromColumn.name}`, { fromColumn, toColumn });
|
|
517
718
|
changedProperties.add('comment');
|
|
518
719
|
}
|
|
720
|
+
if (!(fromColumn.ignoreSchemaChanges?.includes('collation') || toColumn.ignoreSchemaChanges?.includes('collation')) &&
|
|
721
|
+
this.diffCollation(fromColumn.collation, toColumn.collation, fromTable.collation)) {
|
|
722
|
+
log(`'collation' changed for column ${fromTable.name}.${fromColumn.name}`, { fromColumn, toColumn });
|
|
723
|
+
changedProperties.add('collation');
|
|
724
|
+
}
|
|
519
725
|
const isNonNativeEnumArray = !(fromColumn.nativeEnumName || toColumn.nativeEnumName) &&
|
|
520
726
|
(fromColumn.mappedType instanceof ArrayType || toColumn.mappedType instanceof ArrayType);
|
|
521
727
|
if (!isNonNativeEnumArray && this.diffEnumItems(fromColumn.enumItems, toColumn.enumItems)) {
|
|
@@ -537,12 +743,26 @@ export class SchemaComparator {
|
|
|
537
743
|
// eslint-disable-next-line eqeqeq
|
|
538
744
|
return comment1 != comment2 && !(comment1 == null && comment2 === '') && !(comment2 == null && comment1 === '');
|
|
539
745
|
}
|
|
746
|
+
/**
|
|
747
|
+
* `from` is the introspected DB state, `to` is the target metadata. A column-level `COLLATE`
|
|
748
|
+
* clause naming the table/database default is just verbose syntax for inheriting that default,
|
|
749
|
+
* so both sides are normalized — anything matching `tableDefault` collapses to `undefined` and
|
|
750
|
+
* compares equal to "no explicit collation". Comparison is case-insensitive on dialects that
|
|
751
|
+
* treat collation identifiers as case-insensitive (MySQL/MSSQL/SQLite); PostgreSQL's
|
|
752
|
+
* `pg_collation.collname` is case-sensitive and is compared verbatim.
|
|
753
|
+
*/
|
|
754
|
+
diffCollation(fromCollation, toCollation, tableDefault) {
|
|
755
|
+
const fold = this.#platform.caseInsensitiveCollationNames() ? (s) => s.toLowerCase() : (s) => s;
|
|
756
|
+
const norm = (c) => c && tableDefault && fold(c) === fold(tableDefault) ? undefined : c == null ? undefined : fold(c);
|
|
757
|
+
return norm(fromCollation) !== norm(toCollation);
|
|
758
|
+
}
|
|
540
759
|
/**
|
|
541
760
|
* Finds the difference between the indexes index1 and index2.
|
|
542
761
|
* Compares index1 with index2 and returns index2 if there are any differences or false in case there are no differences.
|
|
543
762
|
*/
|
|
544
763
|
diffIndex(index1, index2) {
|
|
545
|
-
//
|
|
764
|
+
// Opaque raw expressions (`expression` escape hatch) and full-text indexes can't be
|
|
765
|
+
// compared structurally — fall back to name-only matching.
|
|
546
766
|
if (index1.expression || index2.expression || index1.type === 'fulltext' || index2.type === 'fulltext') {
|
|
547
767
|
return index1.keyName !== index2.keyName;
|
|
548
768
|
}
|
|
@@ -593,6 +813,11 @@ export class SchemaComparator {
|
|
|
593
813
|
if (!!index1.clustered !== !!index2.clustered) {
|
|
594
814
|
return false;
|
|
595
815
|
}
|
|
816
|
+
// Compare WHERE predicate of partial indexes structurally (whitespace/quoting/casing
|
|
817
|
+
// are normalized via the same helper used for check constraints).
|
|
818
|
+
if (this.diffExpression(index1.where ?? '', index2.where ?? '')) {
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
596
821
|
if (!index1.unique && !index1.primary) {
|
|
597
822
|
// this is a special case: If the current key is neither primary or unique, any unique or
|
|
598
823
|
// primary key will always have the same effect for the index and there cannot be any constraint
|
|
@@ -714,6 +939,30 @@ export class SchemaComparator {
|
|
|
714
939
|
}
|
|
715
940
|
return true;
|
|
716
941
|
}
|
|
942
|
+
diffTrigger(from, to) {
|
|
943
|
+
// Raw DDL expression cannot be meaningfully compared to introspected
|
|
944
|
+
// trigger metadata, so skip diffing when the metadata side uses it.
|
|
945
|
+
if (to.expression) {
|
|
946
|
+
// Both sides have expression — compare the raw DDL directly
|
|
947
|
+
if (from.expression) {
|
|
948
|
+
return this.diffExpression(from.expression, to.expression);
|
|
949
|
+
}
|
|
950
|
+
// Only metadata side has expression — the raw DDL cannot be compared to
|
|
951
|
+
// introspected metadata. Changes to the expression value won't be detected;
|
|
952
|
+
// drop and recreate the trigger manually to apply expression changes.
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
if (from.timing !== to.timing || from.forEach !== to.forEach) {
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
958
|
+
if ([...from.events].sort().join(',') !== [...to.events].sort().join(',')) {
|
|
959
|
+
return true;
|
|
960
|
+
}
|
|
961
|
+
if ((from.when ?? '') !== (to.when ?? '')) {
|
|
962
|
+
return true;
|
|
963
|
+
}
|
|
964
|
+
return this.diffExpression(from.body, to.body);
|
|
965
|
+
}
|
|
717
966
|
parseJsonDefault(defaultValue) {
|
|
718
967
|
/* v8 ignore next */
|
|
719
968
|
if (!defaultValue) {
|
package/schema/SchemaHelper.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { type Connection, type Dictionary, type Options, type Transaction, type RawQueryFragment } from '@mikro-orm/core';
|
|
2
2
|
import type { AbstractSqlConnection } from '../AbstractSqlConnection.js';
|
|
3
3
|
import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
|
|
4
|
-
import type { CheckDef, Column, ForeignKey, IndexDef, Table, TableDifference } from '../typings.js';
|
|
4
|
+
import type { CheckDef, Column, ForeignKey, IndexDef, Table, TableDifference, SqlTriggerDef, SqlRoutineDef } from '../typings.js';
|
|
5
5
|
import type { DatabaseSchema } from './DatabaseSchema.js';
|
|
6
6
|
import type { DatabaseTable } from './DatabaseTable.js';
|
|
7
|
+
/** Flattens `;\n` boundaries so the schema-generator's statement splitter doesn't break the routine DDL apart. Other whitespace is preserved. */
|
|
8
|
+
export declare function stripStatementNewlines(body: string): string;
|
|
7
9
|
/** Base class for database-specific schema helpers. Provides SQL generation for DDL operations. */
|
|
8
10
|
export declare abstract class SchemaHelper {
|
|
9
11
|
protected readonly platform: AbstractSqlPlatform;
|
|
@@ -16,6 +18,14 @@ export declare abstract class SchemaHelper {
|
|
|
16
18
|
enableForeignKeysSQL(): string;
|
|
17
19
|
/** Returns SQL to append to schema migration scripts (e.g., re-enabling FK checks). */
|
|
18
20
|
getSchemaEnd(disableForeignKeys?: boolean): string;
|
|
21
|
+
/** Sets the current schema for the session (e.g. `SET search_path`). */
|
|
22
|
+
getSetSchemaSQL(_schema: string): string;
|
|
23
|
+
/** Whether the driver supports setting a runtime schema per migration run. */
|
|
24
|
+
supportsMigrationSchema(): boolean;
|
|
25
|
+
/** Restores the session's schema to the connection's default after a migration. */
|
|
26
|
+
getResetSchemaSQL(_defaultSchema: string): string;
|
|
27
|
+
/** Returns `undefined` for schemaless drivers, throws for drivers that have schemas but no session switch. */
|
|
28
|
+
resolveMigrationSchema(schema: string | undefined): string | undefined;
|
|
19
29
|
finalizeTable(table: DatabaseTable, charset: string, collate?: string): string;
|
|
20
30
|
appendComments(table: DatabaseTable): string[];
|
|
21
31
|
supportsSchemaConstraints(): boolean;
|
|
@@ -31,6 +41,8 @@ export declare abstract class SchemaHelper {
|
|
|
31
41
|
getListTablesSQL(): string;
|
|
32
42
|
/** Retrieves all tables from the database. */
|
|
33
43
|
getAllTables(connection: AbstractSqlConnection, schemas?: string[], ctx?: Transaction): Promise<Table[]>;
|
|
44
|
+
/** Checks whether a specific table exists in a given schema (not the connection's current schema). */
|
|
45
|
+
tableExists(connection: AbstractSqlConnection, tableName: string, schemaName: string | undefined, ctx?: Transaction): Promise<boolean>;
|
|
34
46
|
getListViewsSQL(): string;
|
|
35
47
|
loadViews(schema: DatabaseSchema, connection: AbstractSqlConnection, schemaName?: string, ctx?: Transaction): Promise<void>;
|
|
36
48
|
/** Returns SQL to rename a column in a table. */
|
|
@@ -41,6 +53,46 @@ export declare abstract class SchemaHelper {
|
|
|
41
53
|
* Hook for adding driver-specific index options (e.g., fill factor for PostgreSQL).
|
|
42
54
|
*/
|
|
43
55
|
protected getCreateIndexSuffix(_index: IndexDef): string;
|
|
56
|
+
/**
|
|
57
|
+
* Default emits ` where <predicate>` for partial indexes. Only Oracle overrides this to
|
|
58
|
+
* return `''` (it emulates partials via CASE-WHEN columns). MySQL sidesteps the whole path
|
|
59
|
+
* with its own `getCreateIndexSQL` that never calls this, and MariaDB refuses the feature
|
|
60
|
+
* entirely via an override on `getIndexColumns`.
|
|
61
|
+
*/
|
|
62
|
+
protected getIndexWhereClause(index: IndexDef): string;
|
|
63
|
+
/**
|
|
64
|
+
* Wraps each indexed column in `(CASE WHEN <predicate> THEN <col> END)` for dialects that
|
|
65
|
+
* emulate partial indexes via functional indexes (MySQL/MariaDB/Oracle). Combined with NULL
|
|
66
|
+
* being treated as distinct in unique indexes, this enforces uniqueness only where the
|
|
67
|
+
* predicate holds. Throws if combined with the advanced `columns` option.
|
|
68
|
+
*/
|
|
69
|
+
protected emulatePartialIndexColumns(index: IndexDef): string;
|
|
70
|
+
/**
|
|
71
|
+
* Strips `<col> IS NOT NULL` clauses (with the dialect's identifier quoting) from an
|
|
72
|
+
* introspected partial-index predicate when the column matches one of the index's own
|
|
73
|
+
* columns. MikroORM auto-emits this guard for unique indexes on nullable columns
|
|
74
|
+
* (MSSQL, Oracle) — it's an internal artifact, not user intent.
|
|
75
|
+
*
|
|
76
|
+
* Strips at most one guard per column (the tail-most occurrence), matching how MikroORM
|
|
77
|
+
* appends a single guard per index column. This preserves user intent when they redundantly
|
|
78
|
+
* include the same `<col> IS NOT NULL` in their predicate — the guard we added is removed,
|
|
79
|
+
* their copy survives.
|
|
80
|
+
*/
|
|
81
|
+
protected stripAutoNotNullFilter(filterDef: string, columnNames: string[], identifierPattern: RegExp): string;
|
|
82
|
+
/**
|
|
83
|
+
* Whether `[…]` is a quoted identifier (MSSQL convention). Other dialects either reuse
|
|
84
|
+
* `[` for array literals/constructors or never produce it in introspected predicates,
|
|
85
|
+
* so the default is `false` and the MSSQL helper opts in.
|
|
86
|
+
*/
|
|
87
|
+
protected get bracketQuotedIdentifiers(): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Splits on top-level ` AND ` (case-insensitive), ignoring matches that sit inside string
|
|
90
|
+
* literals, quoted identifiers, or parenthesized groups — so a predicate like
|
|
91
|
+
* `'foo AND bar' = col` or `(a AND b) OR c` is not mis-split.
|
|
92
|
+
*/
|
|
93
|
+
protected splitTopLevelAnd(s: string): string[];
|
|
94
|
+
/** Returns true iff the leading `(` matches the trailing `)` (i.e. they wrap the whole string). */
|
|
95
|
+
protected isBalancedWrap(s: string): boolean;
|
|
44
96
|
/**
|
|
45
97
|
* Build the column list for an index, supporting advanced options like sort order, nulls ordering, and collation.
|
|
46
98
|
* Note: Prefix length is only supported by MySQL/MariaDB which override this method.
|
|
@@ -57,6 +109,8 @@ export declare abstract class SchemaHelper {
|
|
|
57
109
|
hasNonDefaultPrimaryKeyName(table: DatabaseTable): boolean;
|
|
58
110
|
castColumn(name: string, type: string): string;
|
|
59
111
|
alterTableColumn(column: Column, table: DatabaseTable, changedProperties: Set<string>): string[];
|
|
112
|
+
/** Returns the bare `collate <name>` clause for column DDL. Overridden by PostgreSQL to quote the identifier. */
|
|
113
|
+
protected getCollateSQL(collation: string): string;
|
|
60
114
|
createTableColumn(column: Column, table: DatabaseTable, changedProperties?: Set<string>): string | undefined;
|
|
61
115
|
getPreAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
|
62
116
|
getPostAlterTable(tableDiff: TableDifference, safe: boolean): string[];
|
|
@@ -84,6 +138,28 @@ export declare abstract class SchemaHelper {
|
|
|
84
138
|
getReferencedTableName(referencedTableName: string, schema?: string): string;
|
|
85
139
|
createIndex(index: IndexDef, table: DatabaseTable, createPrimary?: boolean): string;
|
|
86
140
|
createCheck(table: DatabaseTable, check: CheckDef): string;
|
|
141
|
+
/**
|
|
142
|
+
* Generates SQL to create a database trigger on a table.
|
|
143
|
+
* Override in driver-specific helpers for custom DDL (e.g., PostgreSQL function wrapping).
|
|
144
|
+
*/
|
|
145
|
+
createTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
|
|
146
|
+
/**
|
|
147
|
+
* Generates SQL to drop a database trigger from a table.
|
|
148
|
+
* Override in driver-specific helpers for custom DDL.
|
|
149
|
+
*/
|
|
150
|
+
dropTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
|
|
151
|
+
/** Default no-op so SQLite/libSQL silent-skip routine DDL; routine-capable dialects override. */
|
|
152
|
+
createRoutine(_routine: SqlRoutineDef): string;
|
|
153
|
+
dropRoutine(_routine: SqlRoutineDef): string;
|
|
154
|
+
getAllRoutines(_connection: AbstractSqlConnection, _schemas?: string[]): Promise<SqlRoutineDef[]>;
|
|
155
|
+
/** Wraps the body in `BEGIN ... END` if not already, and flattens internal `;\n` so the schema-generator's statement splitter doesn't tear the DDL. */
|
|
156
|
+
protected wrapRoutineBody(body: string): string;
|
|
157
|
+
protected stripRoutineBody(body: string): string;
|
|
158
|
+
/** T-SQL requires `@name` inside the body; PG/MySQL/Oracle use the bare name. */
|
|
159
|
+
routineParamReference(name: string): string;
|
|
160
|
+
/** T-SQL doesn't distinguish `OUT` from `INOUT` in the catalog — overrides fold `'out'` into `'inout'`. */
|
|
161
|
+
normaliseRoutineParamDirection(direction: 'in' | 'out' | 'inout'): 'in' | 'out' | 'inout';
|
|
162
|
+
protected qualifiedRoutineName(routine: SqlRoutineDef): string;
|
|
87
163
|
/** @internal */
|
|
88
164
|
getTableName(table: string, schema?: string): string;
|
|
89
165
|
getTablesGroupedBySchemas(tables: Table[]): Map<string | undefined, Table[]>;
|