@mikro-orm/sql 7.1.0-dev.8 → 7.1.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 (56) hide show
  1. package/AbstractSqlConnection.d.ts +1 -1
  2. package/AbstractSqlConnection.js +27 -6
  3. package/AbstractSqlDriver.d.ts +15 -1
  4. package/AbstractSqlDriver.js +143 -26
  5. package/AbstractSqlPlatform.d.ts +15 -3
  6. package/AbstractSqlPlatform.js +25 -7
  7. package/PivotCollectionPersister.d.ts +2 -2
  8. package/PivotCollectionPersister.js +6 -1
  9. package/README.md +2 -1
  10. package/SqlEntityManager.d.ts +48 -5
  11. package/SqlEntityManager.js +77 -7
  12. package/SqlMikroORM.d.ts +23 -0
  13. package/SqlMikroORM.js +23 -0
  14. package/dialects/mysql/BaseMySqlPlatform.d.ts +3 -5
  15. package/dialects/mysql/BaseMySqlPlatform.js +6 -10
  16. package/dialects/mysql/MySqlSchemaHelper.d.ts +16 -3
  17. package/dialects/mysql/MySqlSchemaHelper.js +197 -49
  18. package/dialects/oracledb/OracleDialect.d.ts +1 -1
  19. package/dialects/oracledb/OracleDialect.js +2 -1
  20. package/dialects/postgresql/BasePostgreSqlEntityManager.d.ts +19 -0
  21. package/dialects/postgresql/BasePostgreSqlEntityManager.js +24 -0
  22. package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +11 -5
  23. package/dialects/postgresql/BasePostgreSqlPlatform.js +75 -17
  24. package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +31 -1
  25. package/dialects/postgresql/PostgreSqlSchemaHelper.js +269 -28
  26. package/dialects/postgresql/index.d.ts +2 -0
  27. package/dialects/postgresql/index.js +2 -0
  28. package/dialects/postgresql/typeOverrides.d.ts +14 -0
  29. package/dialects/postgresql/typeOverrides.js +12 -0
  30. package/dialects/sqlite/SqlitePlatform.d.ts +2 -1
  31. package/dialects/sqlite/SqlitePlatform.js +4 -0
  32. package/dialects/sqlite/SqliteSchemaHelper.d.ts +4 -1
  33. package/dialects/sqlite/SqliteSchemaHelper.js +49 -19
  34. package/index.d.ts +2 -0
  35. package/index.js +2 -0
  36. package/package.json +4 -4
  37. package/plugin/transformer.d.ts +11 -3
  38. package/plugin/transformer.js +138 -29
  39. package/query/CriteriaNode.d.ts +1 -1
  40. package/query/CriteriaNode.js +2 -2
  41. package/query/ObjectCriteriaNode.js +1 -1
  42. package/query/QueryBuilder.d.ts +42 -1
  43. package/query/QueryBuilder.js +78 -7
  44. package/schema/DatabaseSchema.d.ts +29 -2
  45. package/schema/DatabaseSchema.js +131 -4
  46. package/schema/DatabaseTable.d.ts +14 -1
  47. package/schema/DatabaseTable.js +165 -32
  48. package/schema/SchemaComparator.d.ts +18 -0
  49. package/schema/SchemaComparator.js +196 -1
  50. package/schema/SchemaHelper.d.ts +67 -1
  51. package/schema/SchemaHelper.js +255 -25
  52. package/schema/SqlSchemaGenerator.d.ts +2 -2
  53. package/schema/SqlSchemaGenerator.js +40 -10
  54. package/schema/partitioning.d.ts +13 -0
  55. package/schema/partitioning.js +326 -0
  56. package/typings.d.ts +59 -5
@@ -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 {
@@ -721,6 +722,12 @@ export class QueryBuilder {
721
722
  hasFlag(flag) {
722
723
  return this.#state.flags.has(flag);
723
724
  }
725
+ /** @internal */
726
+ setPartitionLimit(opts) {
727
+ this.ensureNotFinalized();
728
+ this.#state.partitionLimit = opts;
729
+ return this;
730
+ }
724
731
  cache(config = true) {
725
732
  this.ensureNotFinalized();
726
733
  this.#state.cache = config;
@@ -825,6 +832,9 @@ export class QueryBuilder {
825
832
  this.helper.getLockSQL(qb, this.#state.lockMode, this.#state.lockTables, this.#state.joins);
826
833
  }
827
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
+ }
828
838
  return (this.#query.qb = qb);
829
839
  }
830
840
  processReturningStatement(qb, meta, data, returning) {
@@ -1001,7 +1011,7 @@ export class QueryBuilder {
1001
1011
  if (cached?.data !== undefined) {
1002
1012
  return cached.data;
1003
1013
  }
1004
- const loggerContext = { id: this.em?.id, ...this.loggerContext };
1014
+ const loggerContext = { id: this.em?.id, ...this.loggerContext, ...this.#abortOptions };
1005
1015
  const res = await this.getConnection().execute(query.sql, query.params, method, this.context, loggerContext);
1006
1016
  const meta = this.mainAlias.meta;
1007
1017
  if (!options.mapResults || !meta) {
@@ -1055,9 +1065,10 @@ export class QueryBuilder {
1055
1065
  options ??= {};
1056
1066
  options.mergeResults ??= true;
1057
1067
  options.mapResults ??= true;
1068
+ const chunkSize = options.chunkSize ?? 100;
1058
1069
  const query = this.toQuery();
1059
- const loggerContext = { id: this.em?.id, ...this.loggerContext };
1060
- 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);
1061
1072
  const meta = this.mainAlias.meta;
1062
1073
  if (options.rawResults || !meta) {
1063
1074
  yield* res;
@@ -1280,6 +1291,7 @@ export class QueryBuilder {
1280
1291
  qb.#state.finalized = false;
1281
1292
  qb.#query = undefined;
1282
1293
  qb.#helper = qb.createQueryBuilderHelper();
1294
+ qb.#abortOptions = this.#abortOptions;
1283
1295
  return qb;
1284
1296
  }
1285
1297
  /**
@@ -1295,6 +1307,13 @@ export class QueryBuilder {
1295
1307
  this.loggerContext ??= {};
1296
1308
  return this.loggerContext;
1297
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
+ }
1298
1317
  fromVirtual(meta) {
1299
1318
  if (typeof meta.expression === 'string') {
1300
1319
  return `(${meta.expression}) as ${this.platform.quoteIdentifier(this.alias)}`;
@@ -1385,6 +1404,7 @@ export class QueryBuilder {
1385
1404
  aliased: [QueryType.SELECT, QueryType.COUNT].includes(this.type),
1386
1405
  });
1387
1406
  const criteriaNode = CriteriaNodeFactory.createNode(this.metadata, prop.targetMeta.class, cond);
1407
+ const joinCountBefore = Object.keys(this.#state.joins).length;
1388
1408
  cond = criteriaNode.process(this, { ignoreBranching: true, alias });
1389
1409
  let aliasedName = `${fromAlias}.${prop.name}#${alias}`;
1390
1410
  path ??= `${Object.values(this.#state.joins).find(j => j.alias === fromAlias)?.path ?? Utils.className(entityName)}.${prop.name}`;
@@ -1414,6 +1434,20 @@ export class QueryBuilder {
1414
1434
  this.#state.joins[aliasedName] = this.helper.joinManyToOneReference(prop, ownerAlias, alias, type, cond, schema);
1415
1435
  this.#state.joins[aliasedName].path ??= path;
1416
1436
  }
1437
+ // auto-joins added by cond processing that depend on the new alias would otherwise produce a
1438
+ // forward reference (the auto-join's ON refers to alias, while alias's ON refers back to it);
1439
+ // fold them into the new join so both aliases share scope in the outer ON clause (issue #7681)
1440
+ const condJoin = this.#state.joins[aliasedName];
1441
+ const joinKeys = Object.keys(this.#state.joins);
1442
+ for (let i = joinCountBefore; i < joinKeys.length; i++) {
1443
+ const j = this.#state.joins[joinKeys[i]];
1444
+ if (j === condJoin || j.ownerAlias !== alias) {
1445
+ continue;
1446
+ }
1447
+ const nested = (condJoin.nested ??= new Set());
1448
+ j.type = j.type === JoinType.innerJoin ? JoinType.nestedInnerJoin : JoinType.nestedLeftJoin;
1449
+ nested.add(j);
1450
+ }
1417
1451
  return { prop, key: aliasedName };
1418
1452
  }
1419
1453
  prepareFields(fields, type = 'where', schema) {
@@ -1743,13 +1777,13 @@ export class QueryBuilder {
1743
1777
  }
1744
1778
  const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
1745
1779
  const children = [];
1746
- const lookUpChildren = (ret, type) => {
1747
- const children = types.filter(meta2 => meta2.extends === type);
1748
- children.forEach(m => lookUpChildren(ret, m.class));
1780
+ const lookUpChildren = (ret, parent) => {
1781
+ const children = types.filter(meta2 => meta2.extends && this.metadata.find(meta2.extends) === parent);
1782
+ children.forEach(m => lookUpChildren(ret, m));
1749
1783
  ret.push(...children.filter(c => c.discriminatorValue));
1750
1784
  return children;
1751
1785
  };
1752
- lookUpChildren(children, meta.class);
1786
+ lookUpChildren(children, meta);
1753
1787
  this.andWhere({
1754
1788
  [meta.root.discriminatorColumn]: children.length > 0
1755
1789
  ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] }
@@ -1899,6 +1933,9 @@ export class QueryBuilder {
1899
1933
  (this.#state.limit > 0 || this.#state.offset > 0)) {
1900
1934
  this.wrapPaginateSubQuery(meta);
1901
1935
  }
1936
+ if (this.#state.partitionLimit) {
1937
+ this.preparePartitionLimit();
1938
+ }
1902
1939
  if (meta &&
1903
1940
  (this.#state.flags.has(QueryFlag.UPDATE_SUB_QUERY) || this.#state.flags.has(QueryFlag.DELETE_SUB_QUERY))) {
1904
1941
  this.wrapModifySubQuery(meta);
@@ -2118,6 +2155,40 @@ export class QueryBuilder {
2118
2155
  [Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: raw(sql, params) },
2119
2156
  });
2120
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
+ }
2121
2192
  /**
2122
2193
  * Computes the set of populate paths from the _populate hints.
2123
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;
@@ -237,6 +256,110 @@ export class DatabaseSchema {
237
256
  }
238
257
  return schema;
239
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
+ }
240
363
  static getViewDefinition(meta, em, platform) {
241
364
  if (typeof meta.expression === 'string') {
242
365
  return meta.expression;
@@ -337,12 +460,16 @@ export class DatabaseSchema {
337
460
  (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner));
338
461
  }
339
462
  toJSON() {
463
+ // locale-independent comparison so the snapshot is stable across machines
464
+ const byString = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
465
+ const tableKey = (t) => `${t.schema ?? ''}.${t.name}`;
466
+ const byTable = (a, b) => byString(tableKey(a), tableKey(b));
340
467
  return {
341
468
  name: this.name,
342
- namespaces: [...this.#namespaces],
343
- tables: this.#tables,
344
- views: this.#views,
345
- nativeEnums: this.#nativeEnums,
469
+ namespaces: [...this.#namespaces].sort(),
470
+ tables: [...this.#tables].sort(byTable),
471
+ views: [...this.#views].sort(byTable),
472
+ nativeEnums: Object.fromEntries(Object.entries(this.#nativeEnums).sort(([a], [b]) => byString(a, b))),
346
473
  };
347
474
  }
348
475
  prune(schema, wildcardSchemaTables) {
@@ -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, SqlTriggerDef } 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,6 +30,9 @@ 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;
25
36
  getTriggers(): SqlTriggerDef[];
26
37
  /** @internal */
27
38
  setIndexes(indexes: IndexDef[]): void;
@@ -67,6 +78,7 @@ export declare class DatabaseTable {
67
78
  name?: string;
68
79
  type?: string;
69
80
  expression?: string | IndexCallback<any>;
81
+ where?: string | Dictionary;
70
82
  deferMode?: DeferMode | `${DeferMode}`;
71
83
  options?: Dictionary;
72
84
  columns?: {
@@ -82,6 +94,7 @@ export declare class DatabaseTable {
82
94
  disabled?: boolean;
83
95
  clustered?: boolean;
84
96
  }, type: 'index' | 'unique' | 'primary'): void;
97
+ private processIndexWhere;
85
98
  addCheck(check: CheckDef): void;
86
99
  addTrigger(trigger: SqlTriggerDef): void;
87
100
  toJSON(): Dictionary;