@mikro-orm/sql 7.1.0-dev.2 → 7.1.0-dev.20

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 (40) hide show
  1. package/AbstractSqlConnection.d.ts +1 -1
  2. package/AbstractSqlConnection.js +2 -2
  3. package/AbstractSqlDriver.d.ts +25 -1
  4. package/AbstractSqlDriver.js +315 -15
  5. package/PivotCollectionPersister.js +13 -2
  6. package/SqlEntityManager.d.ts +5 -1
  7. package/SqlEntityManager.js +36 -1
  8. package/dialects/mssql/MsSqlNativeQueryBuilder.js +4 -0
  9. package/dialects/mysql/BaseMySqlPlatform.d.ts +1 -0
  10. package/dialects/mysql/BaseMySqlPlatform.js +3 -0
  11. package/dialects/mysql/MySqlNativeQueryBuilder.js +11 -0
  12. package/dialects/mysql/MySqlSchemaHelper.d.ts +9 -3
  13. package/dialects/mysql/MySqlSchemaHelper.js +102 -4
  14. package/dialects/oracledb/OracleDialect.d.ts +1 -1
  15. package/dialects/oracledb/OracleDialect.js +2 -1
  16. package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +1 -0
  17. package/dialects/postgresql/BasePostgreSqlPlatform.js +3 -0
  18. package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +21 -1
  19. package/dialects/postgresql/PostgreSqlSchemaHelper.js +200 -4
  20. package/dialects/sqlite/SqliteSchemaHelper.d.ts +6 -1
  21. package/dialects/sqlite/SqliteSchemaHelper.js +114 -2
  22. package/package.json +3 -3
  23. package/query/CriteriaNode.d.ts +1 -1
  24. package/query/CriteriaNode.js +2 -2
  25. package/query/NativeQueryBuilder.d.ts +6 -0
  26. package/query/NativeQueryBuilder.js +16 -1
  27. package/query/ObjectCriteriaNode.js +1 -1
  28. package/query/QueryBuilder.d.ts +77 -0
  29. package/query/QueryBuilder.js +170 -6
  30. package/schema/DatabaseSchema.js +18 -0
  31. package/schema/DatabaseTable.d.ts +13 -1
  32. package/schema/DatabaseTable.js +50 -3
  33. package/schema/SchemaComparator.d.ts +1 -0
  34. package/schema/SchemaComparator.js +86 -1
  35. package/schema/SchemaHelper.d.ts +51 -1
  36. package/schema/SchemaHelper.js +191 -2
  37. package/schema/SqlSchemaGenerator.js +7 -0
  38. package/schema/partitioning.d.ts +13 -0
  39. package/schema/partitioning.js +326 -0
  40. package/typings.d.ts +32 -1
@@ -1,5 +1,6 @@
1
1
  import { ReferenceKind, isRaw, } from '@mikro-orm/core';
2
2
  import { DatabaseTable } from './DatabaseTable.js';
3
+ import { getTablePartitioning } from './partitioning.js';
3
4
  /**
4
5
  * @internal
5
6
  */
@@ -173,6 +174,9 @@ export class DatabaseSchema {
173
174
  }
174
175
  const table = schema.addTable(meta.collection, this.getSchemaName(meta, config, schemaName));
175
176
  table.comment = meta.comment;
177
+ if (meta.partitionBy) {
178
+ table.setPartitioning(getTablePartitioning(meta, this.getSchemaName(meta, config, schemaName), id => platform.quoteIdentifier(id)));
179
+ }
176
180
  // For TPT child entities, only use ownProps (properties defined in this entity only)
177
181
  // For all other entities (including TPT root), use all props
178
182
  const propsToProcess = meta.inheritanceType === 'tpt' && meta.tptParent && meta.ownProps ? meta.ownProps : meta.props;
@@ -220,6 +224,20 @@ export class DatabaseSchema {
220
224
  columnName,
221
225
  });
222
226
  }
227
+ for (const trigger of meta.triggers) {
228
+ const body = isRaw(trigger.body)
229
+ ? platform.formatQuery(trigger.body.sql, trigger.body.params)
230
+ : trigger.body;
231
+ table.addTrigger({
232
+ name: trigger.name,
233
+ timing: trigger.timing,
234
+ events: trigger.events,
235
+ forEach: trigger.forEach ?? 'row',
236
+ body: body ?? '',
237
+ when: trigger.when,
238
+ expression: trigger.expression,
239
+ });
240
+ }
223
241
  }
224
242
  return schema;
225
243
  }
@@ -1,6 +1,6 @@
1
1
  import { type Configuration, type DeferMode, type Dictionary, type EntityMetadata, type EntityProperty, type IndexCallback, type NamingStrategy } from '@mikro-orm/core';
2
2
  import type { SchemaHelper } from './SchemaHelper.js';
3
- import type { CheckDef, Column, ForeignKey, IndexDef } from '../typings.js';
3
+ import type { CheckDef, Column, ForeignKey, IndexDef, TablePartitioning, SqlTriggerDef } from '../typings.js';
4
4
  import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
5
5
  /**
6
6
  * @internal
@@ -15,6 +15,7 @@ export declare class DatabaseTable {
15
15
  items: string[];
16
16
  }>;
17
17
  comment?: string;
18
+ partitioning?: TablePartitioning;
18
19
  constructor(platform: AbstractSqlPlatform, name: string, schema?: string | undefined);
19
20
  getQuotedName(): string;
20
21
  getColumns(): Column[];
@@ -22,11 +23,17 @@ export declare class DatabaseTable {
22
23
  removeColumn(name: string): void;
23
24
  getIndexes(): IndexDef[];
24
25
  getChecks(): CheckDef[];
26
+ getPartitioning(): TablePartitioning | undefined;
27
+ /** @internal */
28
+ setPartitioning(partitioning?: TablePartitioning): void;
29
+ getTriggers(): SqlTriggerDef[];
25
30
  /** @internal */
26
31
  setIndexes(indexes: IndexDef[]): void;
27
32
  /** @internal */
28
33
  setChecks(checks: CheckDef[]): void;
29
34
  /** @internal */
35
+ setTriggers(triggers: SqlTriggerDef[]): void;
36
+ /** @internal */
30
37
  setForeignKeys(fks: Dictionary<ForeignKey>): void;
31
38
  init(cols: Column[], indexes: IndexDef[] | undefined, checks: CheckDef[] | undefined, pks: string[], fks?: Dictionary<ForeignKey>, enums?: Dictionary<string[]>): void;
32
39
  addColumn(column: Column): void;
@@ -47,6 +54,8 @@ export declare class DatabaseTable {
47
54
  hasIndex(indexName: string): boolean;
48
55
  getCheck(checkName: string): CheckDef | undefined;
49
56
  hasCheck(checkName: string): boolean;
57
+ getTrigger(triggerName: string): SqlTriggerDef | undefined;
58
+ hasTrigger(triggerName: string): boolean;
50
59
  getPrimaryKey(): IndexDef | undefined;
51
60
  hasPrimaryKey(): boolean;
52
61
  private getForeignKeyDeclaration;
@@ -62,6 +71,7 @@ export declare class DatabaseTable {
62
71
  name?: string;
63
72
  type?: string;
64
73
  expression?: string | IndexCallback<any>;
74
+ where?: string | Dictionary;
65
75
  deferMode?: DeferMode | `${DeferMode}`;
66
76
  options?: Dictionary;
67
77
  columns?: {
@@ -77,6 +87,8 @@ export declare class DatabaseTable {
77
87
  disabled?: boolean;
78
88
  clustered?: boolean;
79
89
  }, type: 'index' | 'unique' | 'primary'): void;
90
+ private processIndexWhere;
80
91
  addCheck(check: CheckDef): void;
92
+ addTrigger(trigger: SqlTriggerDef): void;
81
93
  toJSON(): Dictionary;
82
94
  }
@@ -1,4 +1,5 @@
1
1
  import { DecimalType, EntitySchema, isRaw, ReferenceKind, t, Type, UnknownType, Utils, } from '@mikro-orm/core';
2
+ import { toEntityPartitionBy } from './partitioning.js';
2
3
  /**
3
4
  * @internal
4
5
  */
@@ -8,10 +9,12 @@ export class DatabaseTable {
8
9
  #columns = {};
9
10
  #indexes = [];
10
11
  #checks = [];
12
+ #triggers = [];
11
13
  #foreignKeys = {};
12
14
  #platform;
13
15
  nativeEnums = {}; // for postgres
14
16
  comment;
17
+ partitioning;
15
18
  constructor(platform, name, schema) {
16
19
  this.name = name;
17
20
  this.schema = schema;
@@ -35,6 +38,16 @@ export class DatabaseTable {
35
38
  getChecks() {
36
39
  return this.#checks;
37
40
  }
41
+ getPartitioning() {
42
+ return this.partitioning;
43
+ }
44
+ /** @internal */
45
+ setPartitioning(partitioning) {
46
+ this.partitioning = partitioning;
47
+ }
48
+ getTriggers() {
49
+ return this.#triggers;
50
+ }
38
51
  /** @internal */
39
52
  setIndexes(indexes) {
40
53
  this.#indexes = indexes;
@@ -44,6 +57,10 @@ export class DatabaseTable {
44
57
  this.#checks = checks;
45
58
  }
46
59
  /** @internal */
60
+ setTriggers(triggers) {
61
+ this.#triggers = triggers;
62
+ }
63
+ /** @internal */
47
64
  setForeignKeys(fks) {
48
65
  this.#foreignKeys = fks;
49
66
  }
@@ -177,6 +194,7 @@ export class DatabaseTable {
177
194
  const { fksOnColumnProps, fksOnStandaloneProps, columnFks, fkIndexes, nullableForeignKeys, skippedColumnNames } = this.foreignKeysToProps(namingStrategy, scalarPropertiesForRelations);
178
195
  const name = namingStrategy.getEntityName(this.name, this.schema);
179
196
  const schema = new EntitySchema({ name, collection: this.name, schema: this.schema, comment: this.comment });
197
+ schema.meta.partitionBy = toEntityPartitionBy(this.partitioning, this.name, this.schema);
180
198
  const compositeFkIndexes = {};
181
199
  const compositeFkUniques = {};
182
200
  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.
@@ -196,6 +214,7 @@ export class DatabaseTable {
196
214
  name: index.keyName,
197
215
  deferMode: index.deferMode,
198
216
  expression: index.expression,
217
+ where: index.where,
199
218
  // Advanced index options - convert column names to property names
200
219
  columns: index.columns?.map(col => ({
201
220
  ...col,
@@ -226,7 +245,7 @@ export class DatabaseTable {
226
245
  index.invisible ||
227
246
  index.disabled ||
228
247
  index.clustered;
229
- const isTrivial = !index.deferMode && !index.expression && !hasAdvancedOptions;
248
+ const isTrivial = !index.deferMode && !index.expression && !index.where && !hasAdvancedOptions;
230
249
  if (isTrivial) {
231
250
  // Index is for FK. Map to the FK prop and move on.
232
251
  const fkForIndex = fkIndexes.get(index);
@@ -598,6 +617,12 @@ export class DatabaseTable {
598
617
  hasCheck(checkName) {
599
618
  return !!this.getCheck(checkName);
600
619
  }
620
+ getTrigger(triggerName) {
621
+ return this.#triggers.find(t => t.name === triggerName);
622
+ }
623
+ hasTrigger(triggerName) {
624
+ return !!this.getTrigger(triggerName);
625
+ }
601
626
  getPrimaryKey() {
602
627
  return this.#indexes.find(i => i.primary);
603
628
  }
@@ -862,16 +887,25 @@ export class DatabaseTable {
862
887
  if (index.fillFactor != null && (index.fillFactor < 0 || index.fillFactor > 100)) {
863
888
  throw new Error(`fillFactor must be between 0 and 100, got ${index.fillFactor} for index '${name}' on entity '${meta.className}'`);
864
889
  }
890
+ // The `expression` escape hatch takes the full index definition as raw SQL; combining it
891
+ // with `where` is dialect-dependent (PG/Oracle/MySQL drop `where`, MSSQL appends it) so we
892
+ // reject the combination up-front and ask users to inline the predicate into `expression`.
893
+ if (index.expression && index.where != null) {
894
+ throw new Error(`Index '${name}' on entity '${meta.className}': cannot combine \`expression\` with \`where\` — inline the WHERE clause into the \`expression\` escape hatch, or drop \`expression\` and use structured \`properties\` + \`where\`.`);
895
+ }
896
+ const where = this.processIndexWhere(index.where, meta);
865
897
  this.#indexes.push({
866
898
  keyName: name,
867
899
  columnNames: properties,
868
900
  composite: properties.length > 1,
869
- // JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them
870
- constraint: type !== 'index' && !properties.some((d) => d.includes('.')),
901
+ // JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them.
902
+ // Partial indexes (`where`) must use CREATE [UNIQUE] INDEX form — constraints can't carry predicates.
903
+ constraint: type !== 'index' && !properties.some((d) => d.includes('.')) && !where,
871
904
  primary: type === 'primary',
872
905
  unique: type !== 'index',
873
906
  type: index.type,
874
907
  expression: this.processIndexExpression(name, index.expression, meta),
908
+ where,
875
909
  options: index.options,
876
910
  deferMode: index.deferMode,
877
911
  columns,
@@ -882,9 +916,21 @@ export class DatabaseTable {
882
916
  clustered: index.clustered,
883
917
  });
884
918
  }
919
+ processIndexWhere(where, meta) {
920
+ if (where == null) {
921
+ return undefined;
922
+ }
923
+ // The driver is always an `AbstractSqlDriver` here — `DatabaseTable` is only instantiated
924
+ // by SQL-side schema code, so the platform's config-bound driver is guaranteed to be one.
925
+ const driver = this.#platform.getConfig().getDriver();
926
+ return driver.renderPartialIndexWhere(meta.class, where);
927
+ }
885
928
  addCheck(check) {
886
929
  this.#checks.push(check);
887
930
  }
931
+ addTrigger(trigger) {
932
+ this.#triggers.push(trigger);
933
+ }
888
934
  toJSON() {
889
935
  const columns = this.#columns;
890
936
  const columnsMapped = Utils.keys(columns).reduce((o, col) => {
@@ -929,6 +975,7 @@ export class DatabaseTable {
929
975
  columns: columnsMapped,
930
976
  indexes: this.#indexes,
931
977
  checks: this.#checks,
978
+ triggers: this.#triggers,
932
979
  foreignKeys: this.#foreignKeys,
933
980
  nativeEnums: this.nativeEnums,
934
981
  comment: this.comment,
@@ -69,6 +69,7 @@ export declare class SchemaComparator {
69
69
  * @see https://github.com/mikro-orm/mikro-orm/issues/7308
70
70
  */
71
71
  private diffViewExpression;
72
+ private diffTrigger;
72
73
  parseJsonDefault(defaultValue?: string | null): Dictionary | string | null;
73
74
  hasSameDefaultValue(from: Column, to: Column): boolean;
74
75
  private mapColumnToProperty;
@@ -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
  */
@@ -176,14 +177,17 @@ export class SchemaComparator {
176
177
  addedForeignKeys: {},
177
178
  addedIndexes: {},
178
179
  addedChecks: {},
180
+ addedTriggers: {},
179
181
  changedColumns: {},
180
182
  changedForeignKeys: {},
181
183
  changedIndexes: {},
182
184
  changedChecks: {},
185
+ changedTriggers: {},
183
186
  removedColumns: {},
184
187
  removedForeignKeys: {},
185
188
  removedIndexes: {},
186
189
  removedChecks: {},
190
+ removedTriggers: {},
187
191
  renamedColumns: {},
188
192
  renamedIndexes: {},
189
193
  fromTable,
@@ -197,6 +201,17 @@ export class SchemaComparator {
197
201
  });
198
202
  changes++;
199
203
  }
204
+ if (diffPartitioning(fromTable.getPartitioning(), toTable.getPartitioning(), this.#platform.getDefaultSchemaName())) {
205
+ tableDifferences.changedPartitioning = {
206
+ from: fromTable.getPartitioning(),
207
+ to: toTable.getPartitioning(),
208
+ };
209
+ this.log(`table partitioning changed for ${tableDifferences.name}`, {
210
+ fromPartitioning: fromTable.getPartitioning(),
211
+ toPartitioning: toTable.getPartitioning(),
212
+ });
213
+ changes++;
214
+ }
200
215
  const fromTableColumns = fromTable.getColumns();
201
216
  const toTableColumns = toTable.getColumns();
202
217
  // See if all the columns in "from" table exist in "to" table
@@ -263,6 +278,19 @@ export class SchemaComparator {
263
278
  if (!this.diffIndex(index, toTableIndex)) {
264
279
  continue;
265
280
  }
281
+ // Constraint-vs-index form mismatch needs drop+create with the OLD form's drop SQL,
282
+ // which `changedIndexes` (uses new form only) can't do. Primary keys stay on the
283
+ // changed path which emits `add primary key`.
284
+ if (!index.primary && !!index.constraint !== !!toTableIndex.constraint) {
285
+ tableDifferences.removedIndexes[index.keyName] = index;
286
+ tableDifferences.addedIndexes[index.keyName] = toTableIndex;
287
+ this.log(`index ${index.keyName} changed form in table ${tableDifferences.name}`, {
288
+ fromTableIndex: index,
289
+ toTableIndex,
290
+ });
291
+ changes += 2;
292
+ continue;
293
+ }
266
294
  tableDifferences.changedIndexes[index.keyName] = toTableIndex;
267
295
  this.log(`index ${index.keyName} changed in table ${tableDifferences.name}`, {
268
296
  fromTableIndex: index,
@@ -309,6 +337,33 @@ export class SchemaComparator {
309
337
  tableDifferences.changedChecks[check.name] = toTableCheck;
310
338
  changes++;
311
339
  }
340
+ const fromTableTriggers = fromTable.getTriggers();
341
+ const toTableTriggers = toTable.getTriggers();
342
+ for (const trigger of toTableTriggers) {
343
+ if (fromTable.hasTrigger(trigger.name)) {
344
+ continue;
345
+ }
346
+ tableDifferences.addedTriggers[trigger.name] = trigger;
347
+ this.log(`trigger ${trigger.name} added to table ${tableDifferences.name}`, { trigger });
348
+ changes++;
349
+ }
350
+ for (const trigger of fromTableTriggers) {
351
+ if (!toTable.hasTrigger(trigger.name)) {
352
+ tableDifferences.removedTriggers[trigger.name] = trigger;
353
+ this.log(`trigger ${trigger.name} removed from table ${tableDifferences.name}`);
354
+ changes++;
355
+ continue;
356
+ }
357
+ const toTableTrigger = toTable.getTrigger(trigger.name);
358
+ if (this.diffTrigger(trigger, toTableTrigger)) {
359
+ this.log(`trigger ${trigger.name} changed in table ${tableDifferences.name}`, {
360
+ fromTableTrigger: trigger,
361
+ toTableTrigger,
362
+ });
363
+ tableDifferences.changedTriggers[trigger.name] = toTableTrigger;
364
+ changes++;
365
+ }
366
+ }
312
367
  const fromForeignKeys = { ...fromTable.getForeignKeys() };
313
368
  const toForeignKeys = { ...toTable.getForeignKeys() };
314
369
  for (const fromConstraint of Object.values(fromForeignKeys)) {
@@ -542,7 +597,8 @@ export class SchemaComparator {
542
597
  * Compares index1 with index2 and returns index2 if there are any differences or false in case there are no differences.
543
598
  */
544
599
  diffIndex(index1, index2) {
545
- // if one of them is a custom expression or full text index, compare only by name
600
+ // Opaque raw expressions (`expression` escape hatch) and full-text indexes can't be
601
+ // compared structurally — fall back to name-only matching.
546
602
  if (index1.expression || index2.expression || index1.type === 'fulltext' || index2.type === 'fulltext') {
547
603
  return index1.keyName !== index2.keyName;
548
604
  }
@@ -593,6 +649,11 @@ export class SchemaComparator {
593
649
  if (!!index1.clustered !== !!index2.clustered) {
594
650
  return false;
595
651
  }
652
+ // Compare WHERE predicate of partial indexes structurally (whitespace/quoting/casing
653
+ // are normalized via the same helper used for check constraints).
654
+ if (this.diffExpression(index1.where ?? '', index2.where ?? '')) {
655
+ return false;
656
+ }
596
657
  if (!index1.unique && !index1.primary) {
597
658
  // this is a special case: If the current key is neither primary or unique, any unique or
598
659
  // primary key will always have the same effect for the index and there cannot be any constraint
@@ -714,6 +775,30 @@ export class SchemaComparator {
714
775
  }
715
776
  return true;
716
777
  }
778
+ diffTrigger(from, to) {
779
+ // Raw DDL expression cannot be meaningfully compared to introspected
780
+ // trigger metadata, so skip diffing when the metadata side uses it.
781
+ if (to.expression) {
782
+ // Both sides have expression — compare the raw DDL directly
783
+ if (from.expression) {
784
+ return this.diffExpression(from.expression, to.expression);
785
+ }
786
+ // Only metadata side has expression — the raw DDL cannot be compared to
787
+ // introspected metadata. Changes to the expression value won't be detected;
788
+ // drop and recreate the trigger manually to apply expression changes.
789
+ return false;
790
+ }
791
+ if (from.timing !== to.timing || from.forEach !== to.forEach) {
792
+ return true;
793
+ }
794
+ if ([...from.events].sort().join(',') !== [...to.events].sort().join(',')) {
795
+ return true;
796
+ }
797
+ if ((from.when ?? '') !== (to.when ?? '')) {
798
+ return true;
799
+ }
800
+ return this.diffExpression(from.body, to.body);
801
+ }
717
802
  parseJsonDefault(defaultValue) {
718
803
  /* v8 ignore next */
719
804
  if (!defaultValue) {
@@ -1,7 +1,7 @@
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 } from '../typings.js';
5
5
  import type { DatabaseSchema } from './DatabaseSchema.js';
6
6
  import type { DatabaseTable } from './DatabaseTable.js';
7
7
  /** Base class for database-specific schema helpers. Provides SQL generation for DDL operations. */
@@ -41,6 +41,46 @@ export declare abstract class SchemaHelper {
41
41
  * Hook for adding driver-specific index options (e.g., fill factor for PostgreSQL).
42
42
  */
43
43
  protected getCreateIndexSuffix(_index: IndexDef): string;
44
+ /**
45
+ * Default emits ` where <predicate>` for partial indexes. Only Oracle overrides this to
46
+ * return `''` (it emulates partials via CASE-WHEN columns). MySQL sidesteps the whole path
47
+ * with its own `getCreateIndexSQL` that never calls this, and MariaDB refuses the feature
48
+ * entirely via an override on `getIndexColumns`.
49
+ */
50
+ protected getIndexWhereClause(index: IndexDef): string;
51
+ /**
52
+ * Wraps each indexed column in `(CASE WHEN <predicate> THEN <col> END)` for dialects that
53
+ * emulate partial indexes via functional indexes (MySQL/MariaDB/Oracle). Combined with NULL
54
+ * being treated as distinct in unique indexes, this enforces uniqueness only where the
55
+ * predicate holds. Throws if combined with the advanced `columns` option.
56
+ */
57
+ protected emulatePartialIndexColumns(index: IndexDef): string;
58
+ /**
59
+ * Strips `<col> IS NOT NULL` clauses (with the dialect's identifier quoting) from an
60
+ * introspected partial-index predicate when the column matches one of the index's own
61
+ * columns. MikroORM auto-emits this guard for unique indexes on nullable columns
62
+ * (MSSQL, Oracle) — it's an internal artifact, not user intent.
63
+ *
64
+ * Strips at most one guard per column (the tail-most occurrence), matching how MikroORM
65
+ * appends a single guard per index column. This preserves user intent when they redundantly
66
+ * include the same `<col> IS NOT NULL` in their predicate — the guard we added is removed,
67
+ * their copy survives.
68
+ */
69
+ protected stripAutoNotNullFilter(filterDef: string, columnNames: string[], identifierPattern: RegExp): string;
70
+ /**
71
+ * Whether `[…]` is a quoted identifier (MSSQL convention). Other dialects either reuse
72
+ * `[` for array literals/constructors or never produce it in introspected predicates,
73
+ * so the default is `false` and the MSSQL helper opts in.
74
+ */
75
+ protected get bracketQuotedIdentifiers(): boolean;
76
+ /**
77
+ * Splits on top-level ` AND ` (case-insensitive), ignoring matches that sit inside string
78
+ * literals, quoted identifiers, or parenthesized groups — so a predicate like
79
+ * `'foo AND bar' = col` or `(a AND b) OR c` is not mis-split.
80
+ */
81
+ protected splitTopLevelAnd(s: string): string[];
82
+ /** Returns true iff the leading `(` matches the trailing `)` (i.e. they wrap the whole string). */
83
+ protected isBalancedWrap(s: string): boolean;
44
84
  /**
45
85
  * Build the column list for an index, supporting advanced options like sort order, nulls ordering, and collation.
46
86
  * Note: Prefix length is only supported by MySQL/MariaDB which override this method.
@@ -84,6 +124,16 @@ export declare abstract class SchemaHelper {
84
124
  getReferencedTableName(referencedTableName: string, schema?: string): string;
85
125
  createIndex(index: IndexDef, table: DatabaseTable, createPrimary?: boolean): string;
86
126
  createCheck(table: DatabaseTable, check: CheckDef): string;
127
+ /**
128
+ * Generates SQL to create a database trigger on a table.
129
+ * Override in driver-specific helpers for custom DDL (e.g., PostgreSQL function wrapping).
130
+ */
131
+ createTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
132
+ /**
133
+ * Generates SQL to drop a database trigger from a table.
134
+ * Override in driver-specific helpers for custom DDL.
135
+ */
136
+ dropTrigger(table: DatabaseTable, trigger: SqlTriggerDef): string;
87
137
  /** @internal */
88
138
  getTableName(table: string, schema?: string): string;
89
139
  getTablesGroupedBySchemas(tables: Table[]): Map<string | undefined, Table[]>;