@mikro-orm/sql 7.0.17-dev.9 → 7.0.18-dev.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.
Files changed (50) hide show
  1. package/AbstractSqlConnection.d.ts +1 -1
  2. package/AbstractSqlConnection.js +27 -6
  3. package/AbstractSqlDriver.d.ts +25 -1
  4. package/AbstractSqlDriver.js +356 -20
  5. package/AbstractSqlPlatform.d.ts +13 -2
  6. package/AbstractSqlPlatform.js +16 -3
  7. package/PivotCollectionPersister.d.ts +2 -2
  8. package/PivotCollectionPersister.js +19 -3
  9. package/README.md +2 -1
  10. package/SqlEntityManager.d.ts +46 -3
  11. package/SqlEntityManager.js +77 -7
  12. package/SqlMikroORM.d.ts +4 -4
  13. package/dialects/mssql/MsSqlNativeQueryBuilder.js +4 -0
  14. package/dialects/mysql/BaseMySqlPlatform.d.ts +1 -0
  15. package/dialects/mysql/BaseMySqlPlatform.js +3 -0
  16. package/dialects/mysql/MySqlNativeQueryBuilder.js +11 -0
  17. package/dialects/mysql/MySqlSchemaHelper.d.ts +19 -3
  18. package/dialects/mysql/MySqlSchemaHelper.js +254 -21
  19. package/dialects/oracledb/OracleDialect.d.ts +1 -1
  20. package/dialects/oracledb/OracleDialect.js +2 -1
  21. package/dialects/postgresql/BasePostgreSqlEntityManager.d.ts +19 -0
  22. package/dialects/postgresql/BasePostgreSqlEntityManager.js +24 -0
  23. package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +8 -0
  24. package/dialects/postgresql/BasePostgreSqlPlatform.js +50 -0
  25. package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +38 -1
  26. package/dialects/postgresql/PostgreSqlSchemaHelper.js +341 -6
  27. package/dialects/postgresql/index.d.ts +2 -0
  28. package/dialects/postgresql/index.js +2 -0
  29. package/dialects/postgresql/typeOverrides.d.ts +14 -0
  30. package/dialects/postgresql/typeOverrides.js +12 -0
  31. package/dialects/sqlite/SqliteSchemaHelper.d.ts +7 -1
  32. package/dialects/sqlite/SqliteSchemaHelper.js +131 -2
  33. package/package.json +4 -4
  34. package/query/NativeQueryBuilder.d.ts +6 -0
  35. package/query/NativeQueryBuilder.js +16 -1
  36. package/query/QueryBuilder.d.ts +83 -1
  37. package/query/QueryBuilder.js +181 -8
  38. package/schema/DatabaseSchema.d.ts +29 -2
  39. package/schema/DatabaseSchema.js +137 -0
  40. package/schema/DatabaseTable.d.ts +20 -1
  41. package/schema/DatabaseTable.js +62 -3
  42. package/schema/SchemaComparator.d.ts +19 -0
  43. package/schema/SchemaComparator.js +250 -1
  44. package/schema/SchemaHelper.d.ts +77 -1
  45. package/schema/SchemaHelper.js +279 -5
  46. package/schema/SqlSchemaGenerator.d.ts +2 -2
  47. package/schema/SqlSchemaGenerator.js +47 -10
  48. package/schema/partitioning.d.ts +13 -0
  49. package/schema/partitioning.js +326 -0
  50. package/typings.d.ts +69 -2
@@ -35,6 +35,7 @@ export class QueryBuilder {
35
35
  #state = _a.createDefaultState();
36
36
  #helper;
37
37
  #query;
38
+ #abortOptions;
38
39
  /** @internal */
39
40
  static createDefaultState() {
40
41
  return {
@@ -159,6 +160,41 @@ export class QueryBuilder {
159
160
  insert(data) {
160
161
  return this.init(QueryType.INSERT, data);
161
162
  }
163
+ /**
164
+ * Creates an INSERT ... SELECT query that copies rows from the source query.
165
+ *
166
+ * Column resolution (3 tiers):
167
+ * 1. No explicit select on source, no explicit columns → all cloneable columns derived from entity metadata
168
+ * 2. Explicit select on source, no explicit columns → columns derived from selected field names
169
+ * 3. Explicit `columns` option → user-provided column list
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * // Clone all fields (columns auto-derived from metadata)
174
+ * const source = em.createQueryBuilder(User).where({ id: 1 });
175
+ * await em.createQueryBuilder(User).insertFrom(source).execute();
176
+ *
177
+ * // Clone with overrides via raw() aliases
178
+ * const source = em.createQueryBuilder(User)
179
+ * .select(['name', raw("'new@email.com'").as('email')])
180
+ * .where({ id: 1 });
181
+ * await em.createQueryBuilder(User).insertFrom(source).execute();
182
+ *
183
+ * // Explicit columns for full control
184
+ * await em.createQueryBuilder(User)
185
+ * .insertFrom(source, { columns: ['name', 'email'] })
186
+ * .execute();
187
+ * ```
188
+ */
189
+ insertFrom(subQuery, options) {
190
+ this.ensureNotFinalized();
191
+ this.#state.type = QueryType.INSERT;
192
+ this.#state.insertSubQuery = subQuery;
193
+ if (options?.columns) {
194
+ this.#state.insertColumns = Utils.asArray(options.columns);
195
+ }
196
+ return this;
197
+ }
162
198
  /**
163
199
  * Creates an UPDATE query with the given data.
164
200
  * Use `where()` to specify which rows to update.
@@ -686,6 +722,12 @@ export class QueryBuilder {
686
722
  hasFlag(flag) {
687
723
  return this.#state.flags.has(flag);
688
724
  }
725
+ /** @internal */
726
+ setPartitionLimit(opts) {
727
+ this.ensureNotFinalized();
728
+ this.#state.partitionLimit = opts;
729
+ return this;
730
+ }
689
731
  cache(config = true) {
690
732
  this.ensureNotFinalized();
691
733
  this.#state.cache = config;
@@ -789,12 +831,18 @@ export class QueryBuilder {
789
831
  if (this.#state.lockMode) {
790
832
  this.helper.getLockSQL(qb, this.#state.lockMode, this.#state.lockTables, this.#state.joins);
791
833
  }
792
- this.processReturningStatement(qb, this.mainAlias.meta, this.#state.data, this.#state.returning);
834
+ this.processReturningStatement(qb, this.mainAlias.meta, this.#state.insertSubQuery ? undefined : this.#state.data, this.#state.returning);
835
+ if (this.#state.partitionLimit) {
836
+ return (this.#query.qb = this.wrapPartitionLimitSubQuery(qb));
837
+ }
793
838
  return (this.#query.qb = qb);
794
839
  }
795
840
  processReturningStatement(qb, meta, data, returning) {
796
841
  const usesReturningStatement = this.platform.usesReturningStatement() || this.platform.usesOutputStatement();
797
- if (!meta || !data || !usesReturningStatement) {
842
+ if (!meta || !usesReturningStatement) {
843
+ return;
844
+ }
845
+ if (!data && !this.#state.insertSubQuery) {
798
846
  return;
799
847
  }
800
848
  // always respect explicit returning hint
@@ -805,13 +853,13 @@ export class QueryBuilder {
805
853
  if (this.type === QueryType.INSERT) {
806
854
  const returningProps = meta.hydrateProps
807
855
  .filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
808
- .filter(prop => !(prop.name in data));
856
+ .filter(prop => !data || !(prop.name in data));
809
857
  if (returningProps.length > 0) {
810
858
  qb.returning(Utils.flatten(returningProps.map(prop => prop.fieldNames)));
811
859
  }
812
860
  return;
813
861
  }
814
- if (this.type === QueryType.UPDATE) {
862
+ if (this.type === QueryType.UPDATE && data) {
815
863
  const returningProps = meta.hydrateProps.filter(prop => prop.fieldNames && isRaw(data[prop.fieldNames[0]]));
816
864
  if (returningProps.length > 0) {
817
865
  qb.returning(returningProps.flatMap((prop) => {
@@ -963,7 +1011,7 @@ export class QueryBuilder {
963
1011
  if (cached?.data !== undefined) {
964
1012
  return cached.data;
965
1013
  }
966
- const loggerContext = { id: this.em?.id, ...this.loggerContext };
1014
+ const loggerContext = { id: this.em?.id, ...this.loggerContext, ...this.#abortOptions };
967
1015
  const res = await this.getConnection().execute(query.sql, query.params, method, this.context, loggerContext);
968
1016
  const meta = this.mainAlias.meta;
969
1017
  if (!options.mapResults || !meta) {
@@ -1017,9 +1065,10 @@ export class QueryBuilder {
1017
1065
  options ??= {};
1018
1066
  options.mergeResults ??= true;
1019
1067
  options.mapResults ??= true;
1068
+ const chunkSize = options.chunkSize ?? 100;
1020
1069
  const query = this.toQuery();
1021
- const loggerContext = { id: this.em?.id, ...this.loggerContext };
1022
- const res = this.getConnection().stream(query.sql, query.params, this.context, loggerContext);
1070
+ const loggerContext = { id: this.em?.id, ...this.loggerContext, ...this.#abortOptions };
1071
+ const res = this.getConnection().stream(query.sql, query.params, this.context, loggerContext, chunkSize);
1023
1072
  const meta = this.mainAlias.meta;
1024
1073
  if (options.rawResults || !meta) {
1025
1074
  yield* res;
@@ -1242,6 +1291,7 @@ export class QueryBuilder {
1242
1291
  qb.#state.finalized = false;
1243
1292
  qb.#query = undefined;
1244
1293
  qb.#helper = qb.createQueryBuilderHelper();
1294
+ qb.#abortOptions = this.#abortOptions;
1245
1295
  return qb;
1246
1296
  }
1247
1297
  /**
@@ -1257,6 +1307,13 @@ export class QueryBuilder {
1257
1307
  this.loggerContext ??= {};
1258
1308
  return this.loggerContext;
1259
1309
  }
1310
+ /**
1311
+ * Configures cancellation behavior for this query builder. The signal is forwarded to the
1312
+ * underlying database client; see {@apilink AbortQueryOptions} for the available strategies.
1313
+ */
1314
+ setAbortOptions(options) {
1315
+ this.#abortOptions = options;
1316
+ }
1260
1317
  fromVirtual(meta) {
1261
1318
  if (typeof meta.expression === 'string') {
1262
1319
  return `(${meta.expression}) as ${this.platform.quoteIdentifier(this.alias)}`;
@@ -1618,7 +1675,14 @@ export class QueryBuilder {
1618
1675
  break;
1619
1676
  }
1620
1677
  case QueryType.INSERT:
1621
- qb.insert(this.#state.data);
1678
+ if (this.#state.insertSubQuery) {
1679
+ const columns = this.resolveInsertFromColumns();
1680
+ const compiled = this.#state.insertSubQuery.toQuery();
1681
+ qb.insertSelect(columns, raw(compiled.sql, compiled.params));
1682
+ }
1683
+ else {
1684
+ qb.insert(this.#state.data);
1685
+ }
1622
1686
  break;
1623
1687
  case QueryType.UPDATE:
1624
1688
  qb.update(this.#state.data);
@@ -1634,6 +1698,78 @@ export class QueryBuilder {
1634
1698
  }
1635
1699
  return qb;
1636
1700
  }
1701
+ /**
1702
+ * Resolves the INSERT column list for `insertFrom()`.
1703
+ *
1704
+ * Tier 1: Explicit `insertColumns` from `options.columns` → map property names to field names
1705
+ * Tier 2: Source QB has explicit select fields → derive from those
1706
+ * Tier 3: Derive from target entity metadata (all cloneable columns), auto-populate source select
1707
+ */
1708
+ resolveInsertFromColumns() {
1709
+ const meta = this.mainAlias.meta;
1710
+ const subQuery = this.#state.insertSubQuery;
1711
+ // Tier 1: explicit columns
1712
+ if (this.#state.insertColumns?.length) {
1713
+ return this.#state.insertColumns.flatMap(col => {
1714
+ const prop = meta?.properties[col];
1715
+ return prop?.fieldNames ?? [col];
1716
+ });
1717
+ }
1718
+ // Tier 2: source QB has explicit select fields
1719
+ const sourceFields = subQuery.state.fields;
1720
+ if (sourceFields && subQuery.state.type === QueryType.SELECT) {
1721
+ return sourceFields
1722
+ .filter((field) => typeof field === 'string' || isRaw(field))
1723
+ .flatMap((field) => {
1724
+ if (typeof field === 'string') {
1725
+ // Strip alias prefix like 'a0.'
1726
+ const bare = field.replace(/^\w+\./, '');
1727
+ const prop = meta?.properties[bare];
1728
+ return prop?.fieldNames ?? [bare];
1729
+ }
1730
+ // RawQueryFragment with alias: raw('...').as('name')
1731
+ const alias = String(field.params[field.params.length - 1]);
1732
+ const prop = meta?.properties[alias];
1733
+ return prop?.fieldNames ?? [alias];
1734
+ });
1735
+ }
1736
+ // Tier 3: derive from metadata — all cloneable columns
1737
+ const cloneableProps = this.getCloneableProps(meta);
1738
+ const selectFields = [];
1739
+ const columns = [];
1740
+ for (const prop of cloneableProps) {
1741
+ for (const fieldName of prop.fieldNames) {
1742
+ columns.push(fieldName);
1743
+ selectFields.push(fieldName);
1744
+ }
1745
+ }
1746
+ // Auto-populate source select with matching fields
1747
+ if (!sourceFields) {
1748
+ subQuery.select(selectFields);
1749
+ }
1750
+ return columns;
1751
+ }
1752
+ /** Returns properties that are safe to clone (persistable, non-PK, non-generated). */
1753
+ getCloneableProps(meta) {
1754
+ return meta.props.filter(prop => {
1755
+ if (prop.persist === false) {
1756
+ return false;
1757
+ }
1758
+ if (prop.primary) {
1759
+ return false;
1760
+ }
1761
+ if (!prop.fieldNames?.length) {
1762
+ return false;
1763
+ }
1764
+ if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
1765
+ return false;
1766
+ }
1767
+ if (prop.kind === ReferenceKind.EMBEDDED && !prop.object) {
1768
+ return false;
1769
+ }
1770
+ return true;
1771
+ });
1772
+ }
1637
1773
  applyDiscriminatorCondition() {
1638
1774
  const meta = this.mainAlias.meta;
1639
1775
  if (meta.root.inheritanceType !== 'sti' || !meta.discriminatorValue) {
@@ -1797,6 +1933,9 @@ export class QueryBuilder {
1797
1933
  (this.#state.limit > 0 || this.#state.offset > 0)) {
1798
1934
  this.wrapPaginateSubQuery(meta);
1799
1935
  }
1936
+ if (this.#state.partitionLimit) {
1937
+ this.preparePartitionLimit();
1938
+ }
1800
1939
  if (meta &&
1801
1940
  (this.#state.flags.has(QueryFlag.UPDATE_SUB_QUERY) || this.#state.flags.has(QueryFlag.DELETE_SUB_QUERY))) {
1802
1941
  this.wrapModifySubQuery(meta);
@@ -2016,6 +2155,40 @@ export class QueryBuilder {
2016
2155
  [Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) },
2017
2156
  });
2018
2157
  }
2158
+ /**
2159
+ * Wraps the inner query (which has ROW_NUMBER in SELECT) with an outer query
2160
+ * that filters by the __rn column to apply per-parent limiting.
2161
+ */
2162
+ wrapPartitionLimitSubQuery(innerQb) {
2163
+ const { limit, offset = 0 } = this.#state.partitionLimit;
2164
+ const rnCol = this.platform.quoteIdentifier('__rn');
2165
+ innerQb.as(this.mainAlias.aliasName);
2166
+ const outerQb = this.platform.createNativeQueryBuilder();
2167
+ outerQb.select('*').from(innerQb);
2168
+ outerQb.where(`${rnCol} > ? and ${rnCol} <= ?`, [offset, offset + limit]);
2169
+ outerQb.orderBy(rnCol);
2170
+ return outerQb;
2171
+ }
2172
+ /**
2173
+ * Adds ROW_NUMBER() OVER (PARTITION BY ...) to the SELECT list and prepares
2174
+ * the query state for per-parent limiting. The actual wrapping into a subquery
2175
+ * with __rn filtering happens in getNativeQuery().
2176
+ */
2177
+ preparePartitionLimit() {
2178
+ const { partitionBy } = this.#state.partitionLimit;
2179
+ // `partitionBy` is always a declared property name, so mapper returns a string here.
2180
+ const partitionCol = this.helper.mapper(partitionBy, this.type, undefined, null);
2181
+ const quotedPartition = partitionCol
2182
+ .split('.')
2183
+ .map(e => this.platform.quoteIdentifier(e))
2184
+ .join('.');
2185
+ const queryOrder = this.helper.getQueryOrder(this.type, this.#state.orderBy, this.#state.populateMap, this.#state.collation);
2186
+ const orderBySql = queryOrder.length > 0 ? Utils.unique(queryOrder).join(', ') : quotedPartition;
2187
+ const rnAlias = this.platform.quoteIdentifier('__rn');
2188
+ this.#state.fields.push(raw(`row_number() over (partition by ${quotedPartition} order by ${orderBySql}) as ${rnAlias}`));
2189
+ // Moved into the OVER clause; outer query re-applies via wrapPartitionLimitSubQuery
2190
+ this.#state.orderBy = [];
2191
+ }
2019
2192
  /**
2020
2193
  * Computes the set of populate paths from the _populate hints.
2021
2194
  */
@@ -1,7 +1,7 @@
1
- import { type Configuration, type Dictionary, type EntityMetadata, type Transaction } from '@mikro-orm/core';
1
+ import { type Configuration, type Dictionary, type EntityMetadata, type Routine, type Transaction, type Type } from '@mikro-orm/core';
2
2
  import { DatabaseTable } from './DatabaseTable.js';
3
3
  import type { AbstractSqlConnection } from '../AbstractSqlConnection.js';
4
- import type { DatabaseView } from '../typings.js';
4
+ import type { DatabaseView, SqlRoutineDef } from '../typings.js';
5
5
  import type { AbstractSqlPlatform } from '../AbstractSqlPlatform.js';
6
6
  /**
7
7
  * @internal
@@ -24,6 +24,8 @@ export declare class DatabaseSchema {
24
24
  setViews(views: DatabaseView[]): void;
25
25
  getView(name: string): DatabaseView | undefined;
26
26
  hasView(name: string): boolean;
27
+ addRoutine(routine: SqlRoutineDef): SqlRoutineDef;
28
+ getRoutines(): SqlRoutineDef[];
27
29
  setNativeEnums(nativeEnums: Dictionary<{
28
30
  name: string;
29
31
  schema?: string;
@@ -43,7 +45,32 @@ export declare class DatabaseSchema {
43
45
  hasNativeEnum(name: string): boolean;
44
46
  getNamespaces(): string[];
45
47
  static create(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, schemas?: string[], takeTables?: (string | RegExp)[], skipTables?: (string | RegExp)[], skipViews?: (string | RegExp)[], ctx?: Transaction): Promise<DatabaseSchema>;
48
+ /** Separate from `create()` so the comparator only pays for routine introspection when the user actually defined routines. SQLite/libSQL helpers return []. */
49
+ loadRoutines(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, schemas?: string[]): Promise<void>;
46
50
  static fromMetadata(metadata: EntityMetadata[], platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, em?: any): DatabaseSchema;
51
+ /** Separate from {@link fromMetadata} so the comparator only walks routines when the user defined any. */
52
+ addRoutinesFromMetadata(routines: readonly Routine[], platform: AbstractSqlPlatform, em?: any): void;
53
+ /**
54
+ * Normalises a routine's `returns` config to the `{ type, runtimeType, nullable }` shape the
55
+ * DDL side and introspection-comparator both consume. Supports both `{ type: SomeType }`
56
+ * (Type drives column + runtime types) and `{ runtimeType, columnType, ... }` (explicit).
57
+ *
58
+ * @internal
59
+ */
60
+ static normaliseRoutineReturns(returns: Routine['returns'], platform: AbstractSqlPlatform): {
61
+ type: string;
62
+ runtimeType: string;
63
+ nullable?: boolean;
64
+ } | undefined;
65
+ /**
66
+ * Maps a routine param's declared `type` to a dialect-specific SQL column type. A `Type`
67
+ * instance (when the user passed a `Type` class/instance at `type`) routes through
68
+ * `Type.getColumnType` so its dialect-aware mapping wins. String aliases (`'string'`,
69
+ * `'number'`, …) go through the platform's type registry; literal SQL types pass through.
70
+ *
71
+ * @internal
72
+ */
73
+ static resolveRoutineColumnType(type: string | Type<unknown>, platform: AbstractSqlPlatform): string;
47
74
  private static getViewDefinition;
48
75
  private static getSchemaName;
49
76
  /**
@@ -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
  */
@@ -7,6 +8,7 @@ export class DatabaseSchema {
7
8
  name;
8
9
  #tables = [];
9
10
  #views = [];
11
+ #routines = [];
10
12
  #namespaces = new Set();
11
13
  #nativeEnums = {}; // for postgres
12
14
  #platform;
@@ -64,6 +66,16 @@ export class DatabaseSchema {
64
66
  hasView(name) {
65
67
  return !!this.getView(name);
66
68
  }
69
+ addRoutine(routine) {
70
+ this.#routines.push(routine);
71
+ if (routine.schema != null) {
72
+ this.#namespaces.add(routine.schema);
73
+ }
74
+ return routine;
75
+ }
76
+ getRoutines() {
77
+ return this.#routines;
78
+ }
67
79
  setNativeEnums(nativeEnums) {
68
80
  this.#nativeEnums = nativeEnums;
69
81
  for (const nativeEnum of Object.values(nativeEnums)) {
@@ -110,6 +122,10 @@ export class DatabaseSchema {
110
122
  }
111
123
  return schema;
112
124
  }
125
+ /** Separate from `create()` so the comparator only pays for routine introspection when the user actually defined routines. SQLite/libSQL helpers return []. */
126
+ async loadRoutines(connection, platform, schemas = []) {
127
+ this.#routines = await platform.getSchemaHelper().getAllRoutines(connection, schemas);
128
+ }
113
129
  static fromMetadata(metadata, platform, config, schemaName, em) {
114
130
  const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema'));
115
131
  const nativeEnums = {};
@@ -173,6 +189,9 @@ export class DatabaseSchema {
173
189
  }
174
190
  const table = schema.addTable(meta.collection, this.getSchemaName(meta, config, schemaName));
175
191
  table.comment = meta.comment;
192
+ if (meta.partitionBy) {
193
+ table.setPartitioning(getTablePartitioning(meta, this.getSchemaName(meta, config, schemaName), id => platform.quoteIdentifier(id)));
194
+ }
176
195
  // For TPT child entities, only use ownProps (properties defined in this entity only)
177
196
  // For all other entities (including TPT root), use all props
178
197
  const propsToProcess = meta.inheritanceType === 'tpt' && meta.tptParent && meta.ownProps ? meta.ownProps : meta.props;
@@ -220,9 +239,127 @@ export class DatabaseSchema {
220
239
  columnName,
221
240
  });
222
241
  }
242
+ for (const trigger of meta.triggers) {
243
+ const body = isRaw(trigger.body)
244
+ ? platform.formatQuery(trigger.body.sql, trigger.body.params)
245
+ : trigger.body;
246
+ table.addTrigger({
247
+ name: trigger.name,
248
+ timing: trigger.timing,
249
+ events: trigger.events,
250
+ forEach: trigger.forEach ?? 'row',
251
+ body: body ?? '',
252
+ when: trigger.when,
253
+ expression: trigger.expression,
254
+ });
255
+ }
223
256
  }
224
257
  return schema;
225
258
  }
259
+ /** Separate from {@link fromMetadata} so the comparator only walks routines when the user defined any. */
260
+ addRoutinesFromMetadata(routines, platform, em) {
261
+ const resolveBody = (raw) => {
262
+ if (raw == null) {
263
+ return undefined;
264
+ }
265
+ if (typeof raw === 'string') {
266
+ return raw;
267
+ }
268
+ if (isRaw(raw)) {
269
+ return platform.formatQuery(raw.sql, raw.params);
270
+ }
271
+ return undefined;
272
+ };
273
+ const helper = platform.getSchemaHelper();
274
+ for (const routine of routines) {
275
+ const paramMap = routine.params.reduce((o, p) => {
276
+ o[p.name] = helper.routineParamReference(p.name);
277
+ return o;
278
+ }, {});
279
+ const evaluated = typeof routine.body === 'function' ? routine.body(paramMap, em) : routine.body;
280
+ const body = resolveBody(evaluated);
281
+ const returns = DatabaseSchema.normaliseRoutineReturns(routine.returns, platform);
282
+ this.addRoutine({
283
+ name: routine.name,
284
+ // MySQL has no schema namespace for routines, so leave undefined to align with the introspection side.
285
+ schema: routine.schema ?? (platform.getDefaultSchemaName() != null ? this.name : undefined),
286
+ type: routine.type,
287
+ language: routine.language,
288
+ comment: routine.comment,
289
+ security: routine.security,
290
+ definer: routine.definer,
291
+ deterministic: routine.deterministic,
292
+ dataAccess: routine.dataAccess,
293
+ body,
294
+ expression: routine.expression,
295
+ ignoreSchemaChanges: routine.ignoreSchemaChanges,
296
+ params: routine.params.map(p => ({
297
+ name: p.name,
298
+ type: DatabaseSchema.resolveRoutineColumnType(p.type, platform),
299
+ direction: helper.normaliseRoutineParamDirection(p.direction),
300
+ nullable: p.nullable,
301
+ defaultRaw: p.defaultRaw,
302
+ })),
303
+ returns,
304
+ });
305
+ }
306
+ }
307
+ /**
308
+ * Normalises a routine's `returns` config to the `{ type, runtimeType, nullable }` shape the
309
+ * DDL side and introspection-comparator both consume. Supports both `{ type: SomeType }`
310
+ * (Type drives column + runtime types) and `{ runtimeType, columnType, ... }` (explicit).
311
+ *
312
+ * @internal
313
+ */
314
+ static normaliseRoutineReturns(returns, platform) {
315
+ if (!returns || typeof returns !== 'object') {
316
+ return undefined;
317
+ }
318
+ if ('runtimeType' in returns) {
319
+ return {
320
+ type: (returns.columnType ?? returns.runtimeType),
321
+ runtimeType: returns.runtimeType,
322
+ nullable: returns.nullable,
323
+ };
324
+ }
325
+ if ('type' in returns && returns.type) {
326
+ const instance = typeof returns.type === 'function' ? new returns.type() : returns.type;
327
+ return {
328
+ type: DatabaseSchema.resolveRoutineColumnType(instance, platform),
329
+ runtimeType: instance.runtimeType,
330
+ nullable: returns.nullable,
331
+ };
332
+ }
333
+ return undefined;
334
+ }
335
+ /**
336
+ * Maps a routine param's declared `type` to a dialect-specific SQL column type. A `Type`
337
+ * instance (when the user passed a `Type` class/instance at `type`) routes through
338
+ * `Type.getColumnType` so its dialect-aware mapping wins. String aliases (`'string'`,
339
+ * `'number'`, …) go through the platform's type registry; literal SQL types pass through.
340
+ *
341
+ * @internal
342
+ */
343
+ static resolveRoutineColumnType(type, platform) {
344
+ if (typeof type !== 'string') {
345
+ return type.getColumnType({ columnTypes: [], runtimeType: 'any' }, platform);
346
+ }
347
+ const lower = type.toLowerCase();
348
+ const aliases = {
349
+ string: 'string',
350
+ number: 'integer',
351
+ bigint: 'bigint',
352
+ boolean: 'boolean',
353
+ date: 'datetime',
354
+ buffer: 'blob',
355
+ };
356
+ const mappedKey = aliases[lower];
357
+ if (!mappedKey) {
358
+ return type;
359
+ }
360
+ const t = platform.getMappedType(mappedKey);
361
+ return t.getColumnType({ type: mappedKey, length: undefined }, platform);
362
+ }
226
363
  static getViewDefinition(meta, em, platform) {
227
364
  if (typeof meta.expression === 'string') {
228
365
  return meta.expression;
@@ -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,14 @@ export declare class DatabaseTable {
15
15
  items: string[];
16
16
  }>;
17
17
  comment?: string;
18
+ partitioning?: TablePartitioning;
19
+ /**
20
+ * Effective collation the column defaults to when no explicit `COLLATE` is set on a column.
21
+ * For MySQL/MariaDB this is the table collation; for PostgreSQL and MSSQL this is the database default;
22
+ * SQLite has no configurable default. Used by `SchemaComparator.diffCollation` to avoid flapping
23
+ * when a property explicitly names the default collation.
24
+ */
25
+ collation?: string;
18
26
  constructor(platform: AbstractSqlPlatform, name: string, schema?: string | undefined);
19
27
  getQuotedName(): string;
20
28
  getColumns(): Column[];
@@ -22,11 +30,17 @@ export declare class DatabaseTable {
22
30
  removeColumn(name: string): void;
23
31
  getIndexes(): IndexDef[];
24
32
  getChecks(): CheckDef[];
33
+ getPartitioning(): TablePartitioning | undefined;
34
+ /** @internal */
35
+ setPartitioning(partitioning?: TablePartitioning): void;
36
+ getTriggers(): SqlTriggerDef[];
25
37
  /** @internal */
26
38
  setIndexes(indexes: IndexDef[]): void;
27
39
  /** @internal */
28
40
  setChecks(checks: CheckDef[]): void;
29
41
  /** @internal */
42
+ setTriggers(triggers: SqlTriggerDef[]): void;
43
+ /** @internal */
30
44
  setForeignKeys(fks: Dictionary<ForeignKey>): void;
31
45
  init(cols: Column[], indexes: IndexDef[] | undefined, checks: CheckDef[] | undefined, pks: string[], fks?: Dictionary<ForeignKey>, enums?: Dictionary<string[]>): void;
32
46
  addColumn(column: Column): void;
@@ -47,6 +61,8 @@ export declare class DatabaseTable {
47
61
  hasIndex(indexName: string): boolean;
48
62
  getCheck(checkName: string): CheckDef | undefined;
49
63
  hasCheck(checkName: string): boolean;
64
+ getTrigger(triggerName: string): SqlTriggerDef | undefined;
65
+ hasTrigger(triggerName: string): boolean;
50
66
  getPrimaryKey(): IndexDef | undefined;
51
67
  hasPrimaryKey(): boolean;
52
68
  private getForeignKeyDeclaration;
@@ -62,6 +78,7 @@ export declare class DatabaseTable {
62
78
  name?: string;
63
79
  type?: string;
64
80
  expression?: string | IndexCallback<any>;
81
+ where?: string | Dictionary;
65
82
  deferMode?: DeferMode | `${DeferMode}`;
66
83
  options?: Dictionary;
67
84
  columns?: {
@@ -77,6 +94,8 @@ export declare class DatabaseTable {
77
94
  disabled?: boolean;
78
95
  clustered?: boolean;
79
96
  }, type: 'index' | 'unique' | 'primary'): void;
97
+ private processIndexWhere;
80
98
  addCheck(check: CheckDef): void;
99
+ addTrigger(trigger: SqlTriggerDef): void;
81
100
  toJSON(): Dictionary;
82
101
  }