@mikro-orm/sql 7.1.0-dev.3 → 7.1.0-dev.30

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.
Files changed (47) hide show
  1. package/AbstractSqlConnection.d.ts +1 -1
  2. package/AbstractSqlConnection.js +2 -2
  3. package/AbstractSqlDriver.d.ts +19 -1
  4. package/AbstractSqlDriver.js +215 -16
  5. package/AbstractSqlPlatform.d.ts +15 -3
  6. package/AbstractSqlPlatform.js +25 -7
  7. package/PivotCollectionPersister.js +13 -2
  8. package/SqlEntityManager.d.ts +5 -1
  9. package/SqlEntityManager.js +36 -1
  10. package/SqlMikroORM.d.ts +23 -0
  11. package/SqlMikroORM.js +23 -0
  12. package/dialects/mysql/BaseMySqlPlatform.d.ts +1 -0
  13. package/dialects/mysql/BaseMySqlPlatform.js +3 -0
  14. package/dialects/mysql/MySqlSchemaHelper.d.ts +13 -3
  15. package/dialects/mysql/MySqlSchemaHelper.js +145 -21
  16. package/dialects/oracledb/OracleDialect.d.ts +1 -1
  17. package/dialects/oracledb/OracleDialect.js +2 -1
  18. package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +3 -0
  19. package/dialects/postgresql/BasePostgreSqlPlatform.js +28 -6
  20. package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +31 -1
  21. package/dialects/postgresql/PostgreSqlSchemaHelper.js +230 -5
  22. package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
  23. package/dialects/sqlite/SqlitePlatform.js +4 -0
  24. package/dialects/sqlite/SqliteSchemaHelper.d.ts +9 -2
  25. package/dialects/sqlite/SqliteSchemaHelper.js +148 -19
  26. package/index.d.ts +2 -0
  27. package/index.js +2 -0
  28. package/package.json +4 -4
  29. package/plugin/transformer.d.ts +11 -3
  30. package/plugin/transformer.js +138 -29
  31. package/query/CriteriaNode.d.ts +1 -1
  32. package/query/CriteriaNode.js +2 -2
  33. package/query/ObjectCriteriaNode.js +1 -1
  34. package/query/QueryBuilder.d.ts +36 -0
  35. package/query/QueryBuilder.js +63 -1
  36. package/schema/DatabaseSchema.js +26 -4
  37. package/schema/DatabaseTable.d.ts +20 -1
  38. package/schema/DatabaseTable.js +182 -31
  39. package/schema/SchemaComparator.d.ts +10 -0
  40. package/schema/SchemaComparator.js +104 -1
  41. package/schema/SchemaHelper.d.ts +63 -1
  42. package/schema/SchemaHelper.js +235 -6
  43. package/schema/SqlSchemaGenerator.d.ts +2 -2
  44. package/schema/SqlSchemaGenerator.js +16 -9
  45. package/schema/partitioning.d.ts +13 -0
  46. package/schema/partitioning.js +326 -0
  47. package/typings.d.ts +34 -2
@@ -27,6 +27,32 @@ export class SchemaHelper {
27
27
  }
28
28
  return '';
29
29
  }
30
+ /** Sets the current schema for the session (e.g. `SET search_path`). */
31
+ getSetSchemaSQL(_schema) {
32
+ return '';
33
+ }
34
+ /** Whether the driver supports setting a runtime schema per migration run. */
35
+ supportsMigrationSchema() {
36
+ return false;
37
+ }
38
+ /** Restores the session's schema to the connection's default after a migration. */
39
+ getResetSchemaSQL(_defaultSchema) {
40
+ return '';
41
+ }
42
+ /** Returns `undefined` for schemaless drivers, throws for drivers that have schemas but no session switch. */
43
+ resolveMigrationSchema(schema) {
44
+ if (!schema) {
45
+ return undefined;
46
+ }
47
+ if (this.supportsMigrationSchema()) {
48
+ return schema;
49
+ }
50
+ if (!this.platform.supportsSchemas()) {
51
+ return undefined;
52
+ }
53
+ const driverName = this.platform.constructor.name.replace(/Platform$/, 'Driver');
54
+ throw new Error(`Runtime schema for migrations is not supported by the ${driverName}`);
55
+ }
30
56
  finalizeTable(table, charset, collate) {
31
57
  return '';
32
58
  }
@@ -42,7 +68,7 @@ export class SchemaHelper {
42
68
  }
43
69
  inferLengthFromColumnType(type) {
44
70
  const match = /^\w+\s*(?:\(\s*(\d+)\s*\)|$)/.exec(type);
45
- if (!match) {
71
+ if (match?.[1] == null) {
46
72
  return;
47
73
  }
48
74
  return +match[1];
@@ -75,6 +101,13 @@ export class SchemaHelper {
75
101
  async getAllTables(connection, schemas, ctx) {
76
102
  return connection.execute(this.getListTablesSQL(), [], 'all', ctx);
77
103
  }
104
+ /** Checks whether a specific table exists in a given schema (not the connection's current schema). */
105
+ async tableExists(connection, tableName, schemaName, ctx) {
106
+ const qv = (v) => this.platform.quoteValue(v ?? '');
107
+ const resolved = schemaName ?? this.platform.getDefaultSchemaName();
108
+ const rows = await connection.execute(`select 1 from information_schema.tables where table_schema = ${qv(resolved)} and table_name = ${qv(tableName)}`, [], 'all', ctx);
109
+ return rows.length > 0;
110
+ }
78
111
  getListViewsSQL() {
79
112
  throw new Error('Not supported by given driver');
80
113
  }
@@ -110,7 +143,7 @@ export class SchemaHelper {
110
143
  // JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them
111
144
  sql = `create ${index.unique ? 'unique ' : ''}index ${keyName} on ${tableName}`;
112
145
  const columns = this.platform.getJsonIndexDefinition(index);
113
- return `${sql} (${columns.join(', ')})${this.getCreateIndexSuffix(index)}${defer}`;
146
+ return `${sql} (${columns.join(', ')})${this.getCreateIndexSuffix(index)}${this.getIndexWhereClause(index)}${defer}`;
114
147
  }
115
148
  // Build column list with advanced options
116
149
  const columns = this.getIndexColumns(index);
@@ -119,7 +152,7 @@ export class SchemaHelper {
119
152
  if (index.include?.length) {
120
153
  sql += ` include (${index.include.map(c => this.quote(c)).join(', ')})`;
121
154
  }
122
- return sql + this.getCreateIndexSuffix(index) + defer;
155
+ return sql + this.getCreateIndexSuffix(index) + this.getIndexWhereClause(index) + defer;
123
156
  }
124
157
  /**
125
158
  * Hook for adding driver-specific index options (e.g., fill factor for PostgreSQL).
@@ -127,6 +160,153 @@ export class SchemaHelper {
127
160
  getCreateIndexSuffix(_index) {
128
161
  return '';
129
162
  }
163
+ /**
164
+ * Default emits ` where <predicate>` for partial indexes. Only Oracle overrides this to
165
+ * return `''` (it emulates partials via CASE-WHEN columns). MySQL sidesteps the whole path
166
+ * with its own `getCreateIndexSQL` that never calls this, and MariaDB refuses the feature
167
+ * entirely via an override on `getIndexColumns`.
168
+ */
169
+ getIndexWhereClause(index) {
170
+ return index.where ? ` where ${index.where}` : '';
171
+ }
172
+ /**
173
+ * Wraps each indexed column in `(CASE WHEN <predicate> THEN <col> END)` for dialects that
174
+ * emulate partial indexes via functional indexes (MySQL/MariaDB/Oracle). Combined with NULL
175
+ * being treated as distinct in unique indexes, this enforces uniqueness only where the
176
+ * predicate holds. Throws if combined with the advanced `columns` option.
177
+ */
178
+ emulatePartialIndexColumns(index) {
179
+ if (index.columns?.length) {
180
+ throw new Error(`Index '${index.keyName}': combining \`where\` with advanced \`columns\` options is not supported when emulating a partial index via functional expressions; use plain \`properties\` (or \`columnNames\`).`);
181
+ }
182
+ const predicate = index.where;
183
+ return index.columnNames.map(c => `(case when ${predicate} then ${this.quote(c)} end)`).join(', ');
184
+ }
185
+ /**
186
+ * Strips `<col> IS NOT NULL` clauses (with the dialect's identifier quoting) from an
187
+ * introspected partial-index predicate when the column matches one of the index's own
188
+ * columns. MikroORM auto-emits this guard for unique indexes on nullable columns
189
+ * (MSSQL, Oracle) — it's an internal artifact, not user intent.
190
+ *
191
+ * Strips at most one guard per column (the tail-most occurrence), matching how MikroORM
192
+ * appends a single guard per index column. This preserves user intent when they redundantly
193
+ * include the same `<col> IS NOT NULL` in their predicate — the guard we added is removed,
194
+ * their copy survives.
195
+ */
196
+ stripAutoNotNullFilter(filterDef, columnNames, identifierPattern) {
197
+ // Peel off any number of balanced wrapping paren layers. Introspection sources differ
198
+ // (MSSQL `filter_definition` wraps once, Oracle `INDEX_EXPRESSIONS` typically not at all),
199
+ // and a user `where` round-tripped through a dialect that double-wraps would otherwise slip
200
+ // past the auto-NOT-NULL recognizer below.
201
+ let inner = filterDef.trim();
202
+ while (inner.startsWith('(') && inner.endsWith(')') && this.isBalancedWrap(inner)) {
203
+ inner = inner.slice(1, -1).trim();
204
+ }
205
+ const clauses = this.splitTopLevelAnd(inner);
206
+ const autoCol = (clause) => {
207
+ let trimmed = clause.trim();
208
+ while (trimmed.startsWith('(') && trimmed.endsWith(')') && this.isBalancedWrap(trimmed)) {
209
+ trimmed = trimmed.slice(1, -1).trim();
210
+ }
211
+ const match = identifierPattern.exec(trimmed);
212
+ return match && columnNames.includes(match[1]) ? match[1] : null;
213
+ };
214
+ const seen = new Set();
215
+ const kept = [];
216
+ for (let i = clauses.length - 1; i >= 0; i--) {
217
+ const col = autoCol(clauses[i]);
218
+ if (col && !seen.has(col)) {
219
+ seen.add(col);
220
+ continue;
221
+ }
222
+ kept.unshift(clauses[i]);
223
+ }
224
+ return kept.join(' and ').trim();
225
+ }
226
+ /**
227
+ * Whether `[…]` is a quoted identifier (MSSQL convention). Other dialects either reuse
228
+ * `[` for array literals/constructors or never produce it in introspected predicates,
229
+ * so the default is `false` and the MSSQL helper opts in.
230
+ */
231
+ get bracketQuotedIdentifiers() {
232
+ return false;
233
+ }
234
+ /**
235
+ * Splits on top-level ` AND ` (case-insensitive), ignoring matches that sit inside string
236
+ * literals, quoted identifiers, or parenthesized groups — so a predicate like
237
+ * `'foo AND bar' = col` or `(a AND b) OR c` is not mis-split.
238
+ */
239
+ splitTopLevelAnd(s) {
240
+ const parts = [];
241
+ let depth = 0;
242
+ let quote = null;
243
+ let start = 0;
244
+ let i = 0;
245
+ while (i < s.length) {
246
+ const c = s[i];
247
+ if (quote) {
248
+ // Handle SQL's doubled-delimiter escape inside quoted strings/identifiers:
249
+ // `'` → `''`, `"` → `""`, `` ` `` → ```` `` ````, MSSQL `]` → `]]`.
250
+ if (c === quote && s[i + 1] === quote) {
251
+ i += 2;
252
+ continue;
253
+ }
254
+ if (c === quote) {
255
+ quote = null;
256
+ }
257
+ i++;
258
+ continue;
259
+ }
260
+ if (c === "'" || c === '"' || c === '`') {
261
+ quote = c;
262
+ i++;
263
+ continue;
264
+ }
265
+ if (c === '[' && this.bracketQuotedIdentifiers) {
266
+ quote = ']';
267
+ i++;
268
+ continue;
269
+ }
270
+ if (c === '(') {
271
+ depth++;
272
+ i++;
273
+ continue;
274
+ }
275
+ if (c === ')') {
276
+ depth--;
277
+ i++;
278
+ continue;
279
+ }
280
+ if (depth === 0 && /\s/.test(c)) {
281
+ const m = /^\s+and\s+/i.exec(s.slice(i));
282
+ if (m) {
283
+ parts.push(s.slice(start, i).trim());
284
+ i += m[0].length;
285
+ start = i;
286
+ continue;
287
+ }
288
+ }
289
+ i++;
290
+ }
291
+ parts.push(s.slice(start).trim());
292
+ return parts.filter(p => p.length > 0);
293
+ }
294
+ /** Returns true iff the leading `(` matches the trailing `)` (i.e. they wrap the whole string). */
295
+ isBalancedWrap(s) {
296
+ let depth = 0;
297
+ for (let i = 0; i < s.length; i++) {
298
+ if (s[i] === '(') {
299
+ depth++;
300
+ }
301
+ else if (s[i] === ')') {
302
+ depth--;
303
+ if (depth === 0 && i < s.length - 1) {
304
+ return false;
305
+ }
306
+ }
307
+ }
308
+ return depth === 0;
309
+ }
130
310
  /**
131
311
  * Build the column list for an index, supporting advanced options like sort order, nulls ordering, and collation.
132
312
  * Note: Prefix length is only supported by MySQL/MariaDB which override this method.
@@ -204,6 +384,12 @@ export class SchemaHelper {
204
384
  for (const check of Object.values(diff.changedChecks)) {
205
385
  ret.push(this.dropConstraint(diff.name, check.name));
206
386
  }
387
+ for (const trigger of Object.values(diff.removedTriggers)) {
388
+ ret.push(this.dropTrigger(diff.toTable, trigger));
389
+ }
390
+ for (const trigger of Object.values(diff.changedTriggers)) {
391
+ ret.push(this.dropTrigger(diff.toTable, trigger));
392
+ }
207
393
  /* v8 ignore next */
208
394
  if (!safe && Object.values(diff.removedColumns).length > 0) {
209
395
  ret.push(this.getDropColumnsSQL(tableName, Object.values(diff.removedColumns), schemaName));
@@ -228,7 +414,7 @@ export class SchemaHelper {
228
414
  this.append(ret, this.alterTableColumn(column, diff.fromTable, changedProperties));
229
415
  }
230
416
  for (const { column, changedProperties } of Object.values(diff.changedColumns).filter(diff => diff.changedProperties.has('comment'))) {
231
- if (['type', 'nullable', 'autoincrement', 'unsigned', 'default', 'enumItems'].some(t => changedProperties.has(t))) {
417
+ if (['type', 'nullable', 'autoincrement', 'unsigned', 'default', 'enumItems', 'collation'].some(t => changedProperties.has(t))) {
232
418
  continue; // will be handled via column update
233
419
  }
234
420
  ret.push(this.getChangeColumnCommentSQL(tableName, column, schemaName));
@@ -263,6 +449,12 @@ export class SchemaHelper {
263
449
  for (const check of Object.values(diff.changedChecks)) {
264
450
  ret.push(this.createCheck(diff.toTable, check));
265
451
  }
452
+ for (const trigger of Object.values(diff.addedTriggers)) {
453
+ ret.push(this.createTrigger(diff.toTable, trigger));
454
+ }
455
+ for (const trigger of Object.values(diff.changedTriggers)) {
456
+ ret.push(this.createTrigger(diff.toTable, trigger));
457
+ }
266
458
  if ('changedComment' in diff) {
267
459
  ret.push(this.alterTableComment(diff.toTable, diff.changedComment));
268
460
  }
@@ -299,7 +491,7 @@ export class SchemaHelper {
299
491
  if (changedProperties.has('default') && column.default == null) {
300
492
  sql.push(`alter table ${table.getQuotedName()} alter column ${this.quote(column.name)} drop default`);
301
493
  }
302
- if (changedProperties.has('type')) {
494
+ if (changedProperties.has('type') || changedProperties.has('collation')) {
303
495
  let type = column.type + (column.generated ? ` generated always as ${column.generated}` : '');
304
496
  if (column.nativeEnumName) {
305
497
  const parts = type.split('.');
@@ -311,7 +503,8 @@ export class SchemaHelper {
311
503
  }
312
504
  type = this.quote(type);
313
505
  }
314
- sql.push(`alter table ${table.getQuotedName()} alter column ${this.quote(column.name)} type ${type + this.castColumn(column.name, type)}`);
506
+ const collateClause = column.collation ? ` ${this.getCollateSQL(column.collation)}` : '';
507
+ sql.push(`alter table ${table.getQuotedName()} alter column ${this.quote(column.name)} type ${type + collateClause + this.castColumn(column.name, type)}`);
315
508
  }
316
509
  if (changedProperties.has('default') && column.default != null) {
317
510
  sql.push(`alter table ${table.getQuotedName()} alter column ${this.quote(column.name)} set default ${column.default}`);
@@ -322,6 +515,11 @@ export class SchemaHelper {
322
515
  }
323
516
  return sql;
324
517
  }
518
+ /** Returns the bare `collate <name>` clause for column DDL. Overridden by PostgreSQL to quote the identifier. */
519
+ getCollateSQL(collation) {
520
+ this.platform.validateCollationName(collation);
521
+ return `collate ${collation}`;
522
+ }
325
523
  createTableColumn(column, table, changedProperties) {
326
524
  const compositePK = table.getPrimaryKey()?.composite;
327
525
  const primaryKey = !changedProperties && !this.hasNonDefaultPrimaryKeyName(table);
@@ -329,6 +527,7 @@ export class SchemaHelper {
329
527
  const useDefault = column.default != null && column.default !== 'null' && !column.autoincrement;
330
528
  const col = [this.quote(column.name), columnType];
331
529
  Utils.runIfNotEmpty(() => col.push('unsigned'), column.unsigned && this.platform.supportsUnsigned());
530
+ Utils.runIfNotEmpty(() => col.push(this.getCollateSQL(column.collation)), column.collation);
332
531
  Utils.runIfNotEmpty(() => col.push('null'), column.nullable);
333
532
  Utils.runIfNotEmpty(() => col.push('not null'), !column.nullable && !column.generated);
334
533
  Utils.runIfNotEmpty(() => col.push('auto_increment'), column.autoincrement);
@@ -521,6 +720,9 @@ export class SchemaHelper {
521
720
  for (const check of table.getChecks()) {
522
721
  this.append(ret, this.createCheck(table, check));
523
722
  }
723
+ for (const trigger of table.getTriggers()) {
724
+ this.append(ret, this.createTrigger(table, trigger));
725
+ }
524
726
  }
525
727
  return ret;
526
728
  }
@@ -600,6 +802,33 @@ export class SchemaHelper {
600
802
  createCheck(table, check) {
601
803
  return `alter table ${table.getQuotedName()} add constraint ${this.quote(check.name)} check (${check.expression})`;
602
804
  }
805
+ /**
806
+ * Generates SQL to create a database trigger on a table.
807
+ * Override in driver-specific helpers for custom DDL (e.g., PostgreSQL function wrapping).
808
+ */
809
+ /* v8 ignore next 10 */
810
+ createTrigger(table, trigger) {
811
+ if (trigger.expression) {
812
+ return trigger.expression;
813
+ }
814
+ const timing = trigger.timing.toUpperCase();
815
+ const events = trigger.events.map(e => e.toUpperCase()).join(' OR ');
816
+ const forEach = trigger.forEach === 'statement' ? 'STATEMENT' : 'ROW';
817
+ const when = trigger.when ? ` when (${trigger.when})` : '';
818
+ return `create trigger ${this.quote(trigger.name)} ${timing} ${events} on ${table.getQuotedName()} for each ${forEach}${when} begin ${trigger.body}; end`;
819
+ }
820
+ /**
821
+ * Generates SQL to drop a database trigger from a table.
822
+ * Override in driver-specific helpers for custom DDL.
823
+ */
824
+ dropTrigger(table, trigger) {
825
+ if (trigger.events.length > 1) {
826
+ return trigger.events
827
+ .map(event => `drop trigger if exists ${this.quote(`${trigger.name}_${event}`)}`)
828
+ .join(';\n');
829
+ }
830
+ return `drop trigger if exists ${this.quote(trigger.name)}`;
831
+ }
603
832
  /** @internal */
604
833
  getTableName(table, schema) {
605
834
  if (schema && schema !== this.platform.getDefaultSchemaName()) {
@@ -15,8 +15,8 @@ export declare class SqlSchemaGenerator extends AbstractSchemaGenerator<Abstract
15
15
  * Returns true if the database was created.
16
16
  */
17
17
  ensureDatabase(options?: EnsureDatabaseOptions): Promise<boolean>;
18
- getTargetSchema(schema?: string): DatabaseSchema;
19
- protected getOrderedMetadata(schema?: string): EntityMetadata[];
18
+ getTargetSchema(schema?: string, includeWildcardSchema?: boolean): DatabaseSchema;
19
+ protected getOrderedMetadata(schema?: string, includeWildcardSchema?: boolean): EntityMetadata[];
20
20
  getCreateSchemaSQL(options?: CreateSchemaOptions): Promise<string>;
21
21
  drop(options?: DropSchemaOptions): Promise<void>;
22
22
  createNamespace(name: string): Promise<void>;
@@ -44,13 +44,13 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
44
44
  }
45
45
  return false;
46
46
  }
47
- getTargetSchema(schema) {
48
- const metadata = this.getOrderedMetadata(schema);
47
+ getTargetSchema(schema, includeWildcardSchema = false) {
48
+ const metadata = this.getOrderedMetadata(schema, includeWildcardSchema);
49
49
  const schemaName = schema ?? this.config.get('schema') ?? this.platform.getDefaultSchemaName();
50
50
  return DatabaseSchema.fromMetadata(metadata, this.platform, this.config, schemaName, this.em);
51
51
  }
52
- getOrderedMetadata(schema) {
53
- const metadata = super.getOrderedMetadata(schema);
52
+ getOrderedMetadata(schema, includeWildcardSchema = false) {
53
+ const metadata = super.getOrderedMetadata(schema, includeWildcardSchema);
54
54
  // Filter out skipped tables
55
55
  return metadata.filter(meta => {
56
56
  const tableName = meta.tableName;
@@ -59,7 +59,7 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
59
59
  });
60
60
  }
61
61
  async getCreateSchemaSQL(options = {}) {
62
- const toSchema = this.getTargetSchema(options.schema);
62
+ const toSchema = this.getTargetSchema(options.schema, options.includeWildcardSchema);
63
63
  const ret = [];
64
64
  for (const namespace of toSchema.getNamespaces()) {
65
65
  if (namespace === this.platform.getDefaultSchemaName()) {
@@ -225,13 +225,13 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
225
225
  async prepareSchemaForComparison(options) {
226
226
  options.safe ??= false;
227
227
  options.dropTables ??= true;
228
- const toSchema = this.getTargetSchema(options.schema);
228
+ const toSchema = this.getTargetSchema(options.schema, options.includeWildcardSchema);
229
229
  const schemas = toSchema.getNamespaces();
230
230
  const fromSchema = options.fromSchema ??
231
231
  (await DatabaseSchema.create(this.connection, this.platform, this.config, options.schema, schemas, undefined, this.options.skipTables, this.options.skipViews));
232
- const wildcardSchemaTables = [...this.metadata.getAll().values()]
233
- .filter(meta => meta.schema === '*')
234
- .map(meta => meta.tableName);
232
+ const wildcardSchemaTables = options.includeWildcardSchema
233
+ ? []
234
+ : [...this.metadata.getAll().values()].filter(meta => meta.schema === '*').map(meta => meta.tableName);
235
235
  fromSchema.prune(options.schema, wildcardSchemaTables);
236
236
  toSchema.prune(options.schema, wildcardSchemaTables);
237
237
  return { fromSchema, toSchema };
@@ -300,11 +300,18 @@ export class SqlSchemaGenerator extends AbstractSchemaGenerator {
300
300
  for (const check of newTable.getChecks()) {
301
301
  this.append(sql, this.helper.createCheck(newTable, check));
302
302
  }
303
+ for (const trigger of newTable.getTriggers()) {
304
+ this.append(sql, this.helper.createTrigger(newTable, trigger));
305
+ }
303
306
  this.append(ret, sql, true);
304
307
  }
305
308
  }
306
309
  if (options.dropTables && !options.safe) {
307
310
  for (const table of Object.values(schemaDiff.removedTables)) {
311
+ // Drop triggers before the table so driver-specific cleanup runs (e.g. PostgreSQL function removal)
312
+ for (const trigger of table.getTriggers()) {
313
+ this.append(ret, this.helper.dropTrigger(table, trigger));
314
+ }
308
315
  this.append(ret, this.helper.dropTableIfExists(table.name, table.schema));
309
316
  }
310
317
  if (Utils.hasObjectKeys(schemaDiff.removedTables)) {
@@ -0,0 +1,13 @@
1
+ import { splitCommaSeparatedIdentifiers, type EntityMetadata, type EntityPartitionBy } from '@mikro-orm/core';
2
+ import type { TablePartitioning } from '../typings.js';
3
+ export { splitCommaSeparatedIdentifiers };
4
+ /** @internal */
5
+ export declare function normalizePartitionDefinition(value: string): string;
6
+ /** @internal */
7
+ export declare function normalizePartitionBound(value: string): string;
8
+ /** @internal */
9
+ export declare const getTablePartitioning: (meta: EntityMetadata, tableSchema: string | undefined, quoteIdentifier?: (id: string) => string) => TablePartitioning | undefined;
10
+ /** @internal */
11
+ export declare const diffPartitioning: (from: TablePartitioning | undefined, to: TablePartitioning | undefined, defaultSchema: string | undefined) => boolean;
12
+ /** @internal */
13
+ export declare const toEntityPartitionBy: (partitioning: TablePartitioning | undefined, parentTableName?: string, parentSchema?: string) => EntityPartitionBy | undefined;