@mikro-orm/core 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 (51) hide show
  1. package/EntityManager.d.ts +63 -11
  2. package/EntityManager.js +180 -21
  3. package/drivers/DatabaseDriver.d.ts +1 -0
  4. package/drivers/DatabaseDriver.js +3 -0
  5. package/drivers/IDatabaseDriver.d.ts +44 -1
  6. package/entity/BaseEntity.d.ts +68 -1
  7. package/entity/BaseEntity.js +18 -0
  8. package/entity/Collection.d.ts +6 -3
  9. package/entity/Collection.js +15 -4
  10. package/entity/EntityFactory.js +20 -1
  11. package/entity/EntityLoader.d.ts +3 -1
  12. package/entity/EntityLoader.js +39 -16
  13. package/entity/EntityRepository.d.ts +27 -9
  14. package/entity/EntityRepository.js +12 -0
  15. package/entity/Reference.d.ts +42 -1
  16. package/entity/Reference.js +9 -0
  17. package/entity/defineEntity.d.ts +84 -15
  18. package/entity/defineEntity.js +10 -6
  19. package/entity/utils.js +4 -5
  20. package/errors.d.ts +2 -0
  21. package/errors.js +4 -0
  22. package/index.d.ts +2 -2
  23. package/index.js +1 -1
  24. package/metadata/EntitySchema.js +3 -0
  25. package/metadata/MetadataDiscovery.d.ts +12 -0
  26. package/metadata/MetadataDiscovery.js +155 -10
  27. package/metadata/MetadataValidator.d.ts +24 -0
  28. package/metadata/MetadataValidator.js +201 -0
  29. package/metadata/types.d.ts +71 -3
  30. package/naming-strategy/AbstractNamingStrategy.d.ts +1 -1
  31. package/naming-strategy/NamingStrategy.d.ts +1 -1
  32. package/package.json +1 -1
  33. package/platforms/Platform.d.ts +7 -0
  34. package/platforms/Platform.js +15 -1
  35. package/typings.d.ts +139 -10
  36. package/typings.js +21 -4
  37. package/utils/AbstractMigrator.d.ts +11 -1
  38. package/utils/AbstractMigrator.js +203 -0
  39. package/utils/Configuration.d.ts +7 -0
  40. package/utils/Configuration.js +1 -0
  41. package/utils/DataloaderUtils.d.ts +10 -1
  42. package/utils/DataloaderUtils.js +78 -0
  43. package/utils/QueryHelper.d.ts +16 -0
  44. package/utils/QueryHelper.js +15 -0
  45. package/utils/Utils.js +1 -1
  46. package/utils/fs-utils.d.ts +2 -0
  47. package/utils/fs-utils.js +7 -1
  48. package/utils/index.d.ts +1 -0
  49. package/utils/index.js +1 -0
  50. package/utils/partition-utils.d.ts +17 -0
  51. package/utils/partition-utils.js +79 -0
@@ -7,6 +7,30 @@ import type { MetadataStorage } from './MetadataStorage.js';
7
7
  export declare class MetadataValidator {
8
8
  validateEntityDefinition<T>(metadata: MetadataStorage, name: EntityName<T>, options: MetadataDiscoveryOptions): void;
9
9
  validateDiscovered(discovered: EntityMetadata[], options: MetadataDiscoveryOptions): void;
10
+ private validatePartitioning;
11
+ /**
12
+ * Find the first partition name whose normalized form (case-folded for unquoted segments,
13
+ * quoted segments preserved) has already been seen. Returns the offending name in its
14
+ * original form for the error message.
15
+ */
16
+ private findDuplicatePartitionName;
17
+ /**
18
+ * Partition names may be bare (`child`), schema-qualified (`schema.child`), or use quoted
19
+ * identifiers (`"my.schema"."child"`). Reject anything with more than one unquoted `.`.
20
+ */
21
+ private hasValidPartitionName;
22
+ private hasPartitionExpression;
23
+ private validatePartitionKeyConstraints;
24
+ /**
25
+ * Returns the list of physical field names that a partition expression references, or
26
+ * `undefined` when the expression is opaque (callback, or raw SQL like `date_trunc('day', x)`
27
+ * that we cannot statically parse). Opaque expressions intentionally bypass the primary-key /
28
+ * unique-constraint coverage checks — users are trusted to ensure the referenced columns are
29
+ * part of the partition key, since PostgreSQL will surface the violation at DDL execution.
30
+ */
31
+ private getPartitionKeyFields;
32
+ private resolvePartitionKeyField;
33
+ private getConstraintFields;
10
34
  private validateReference;
11
35
  private validateTargetKey;
12
36
  /**
@@ -1,4 +1,5 @@
1
1
  import { Utils } from '../utils/Utils.js';
2
+ import { normalizePartitionNameForComparison, splitCommaSeparatedIdentifiers } from '../utils/partition-utils.js';
2
3
  import { MetadataError } from '../errors.js';
3
4
  import { ReferenceKind } from '../enums.js';
4
5
  /**
@@ -28,6 +29,9 @@ export class MetadataValidator {
28
29
  // Virtual entities (expression without view flag) have restrictions - no PKs, limited relation types
29
30
  // Note: meta.virtual is set later in sync(), so we check for expression && !view here
30
31
  if (meta.virtual || (meta.expression && !meta.view)) {
32
+ if (meta.partitionBy) {
33
+ throw new MetadataError(`Virtual entity ${meta.className} cannot define partitionBy`);
34
+ }
31
35
  for (const prop of Utils.values(meta.properties)) {
32
36
  if (![ReferenceKind.SCALAR, ReferenceKind.EMBEDDED, ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
33
37
  throw new MetadataError(`Only scalars, embedded properties and to-many relations are allowed inside virtual entity. Found '${prop.kind}' in ${meta.className}.${prop.name}`);
@@ -46,6 +50,7 @@ export class MetadataValidator {
46
50
  this.validateDuplicateFieldNames(meta, options);
47
51
  this.validateIndexes(meta, meta.indexes ?? [], 'index');
48
52
  this.validateIndexes(meta, meta.uniques ?? [], 'unique');
53
+ this.validatePartitioning(meta);
49
54
  this.validatePropertyNames(meta);
50
55
  for (const prop of Utils.values(meta.properties)) {
51
56
  if (prop.kind !== ReferenceKind.SCALAR) {
@@ -111,6 +116,189 @@ export class MetadataValidator {
111
116
  }
112
117
  });
113
118
  }
119
+ validatePartitioning(meta) {
120
+ if (!meta.partitionBy) {
121
+ return;
122
+ }
123
+ if (!this.hasPartitionExpression(meta.partitionBy.expression)) {
124
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: missing expression`);
125
+ }
126
+ // Inheritance (STI/TPT) and partitioning both drive table layout, so combining them would
127
+ // require non-trivial DDL coordination that the schema generator does not produce today.
128
+ const hasInheritance = !!meta.root.discriminatorColumn || meta.root.inheritanceType === 'tpt' || meta.root.inheritance === 'tpt';
129
+ if (hasInheritance) {
130
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: combining partitioning with inheritance is not supported`);
131
+ }
132
+ this.validatePartitionKeyConstraints(meta);
133
+ if (meta.partitionBy.type === 'hash') {
134
+ const { partitions } = meta.partitionBy;
135
+ if (Array.isArray(partitions)) {
136
+ if (partitions.length === 0) {
137
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: hash partition name list must not be empty`);
138
+ }
139
+ const blank = partitions.find(name => typeof name !== 'string' || !name.trim());
140
+ if (blank !== undefined) {
141
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: hash partition names must be non-empty strings`);
142
+ }
143
+ const ambiguous = partitions.find(name => !this.hasValidPartitionName(name));
144
+ if (ambiguous) {
145
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: partition name '${ambiguous}' contains more than one '.' — use at most one '.' to separate schema from table`);
146
+ }
147
+ const duplicate = this.findDuplicatePartitionName(partitions);
148
+ if (duplicate !== undefined) {
149
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: duplicate hash partition name '${duplicate}'`);
150
+ }
151
+ return;
152
+ }
153
+ if (typeof partitions !== 'number' || !Number.isInteger(partitions) || partitions < 1) {
154
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: hash partition count must be a positive integer`);
155
+ }
156
+ return;
157
+ }
158
+ if (!Array.isArray(meta.partitionBy.partitions) || meta.partitionBy.partitions.length === 0) {
159
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: list/range partitions must be a non-empty array`);
160
+ }
161
+ if (meta.partitionBy.partitions.some(partition => !partition.values?.trim())) {
162
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: every partition must define values`);
163
+ }
164
+ const ambiguousName = meta.partitionBy.partitions.find(partition => partition.name != null && !this.hasValidPartitionName(partition.name));
165
+ if (ambiguousName) {
166
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: partition name '${ambiguousName.name}' contains more than one '.' — use at most one '.' to separate schema from table`);
167
+ }
168
+ // Include auto-generated default names (`${tableName}_${index}`, matching
169
+ // `createExplicitPartitions` in the sql package) so an explicit name that collides with
170
+ // an unnamed peer's default is caught here rather than at DDL execution time.
171
+ const resolvedNames = meta.partitionBy.partitions.map((partition, index) => partition.name ?? `${meta.tableName}_${index}`);
172
+ const duplicate = this.findDuplicatePartitionName(resolvedNames);
173
+ if (duplicate !== undefined) {
174
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: duplicate partition name '${duplicate}'`);
175
+ }
176
+ }
177
+ /**
178
+ * Find the first partition name whose normalized form (case-folded for unquoted segments,
179
+ * quoted segments preserved) has already been seen. Returns the offending name in its
180
+ * original form for the error message.
181
+ */
182
+ findDuplicatePartitionName(names) {
183
+ const seen = new Set();
184
+ for (const name of names) {
185
+ const normalized = normalizePartitionNameForComparison(name);
186
+ if (seen.has(normalized)) {
187
+ return name;
188
+ }
189
+ seen.add(normalized);
190
+ }
191
+ return undefined;
192
+ }
193
+ /**
194
+ * Partition names may be bare (`child`), schema-qualified (`schema.child`), or use quoted
195
+ * identifiers (`"my.schema"."child"`). Reject anything with more than one unquoted `.`.
196
+ */
197
+ hasValidPartitionName(name) {
198
+ let depth = 0;
199
+ let dots = 0;
200
+ for (let i = 0; i < name.length; i++) {
201
+ const ch = name[i];
202
+ if (ch === '"') {
203
+ if (name[i + 1] === '"') {
204
+ i++;
205
+ continue;
206
+ }
207
+ depth = depth === 0 ? 1 : 0;
208
+ continue;
209
+ }
210
+ if (ch === '.' && depth === 0) {
211
+ dots++;
212
+ }
213
+ }
214
+ return dots <= 1;
215
+ }
216
+ hasPartitionExpression(expression) {
217
+ if (expression == null) {
218
+ return false;
219
+ }
220
+ if (typeof expression === 'function') {
221
+ return true;
222
+ }
223
+ if (Array.isArray(expression)) {
224
+ return expression.length > 0 && expression.every(key => typeof key === 'string' && key.trim().length > 0);
225
+ }
226
+ return String(expression).trim().length > 0;
227
+ }
228
+ validatePartitionKeyConstraints(meta) {
229
+ const partitionFields = this.getPartitionKeyFields(meta);
230
+ if (!partitionFields?.length) {
231
+ return;
232
+ }
233
+ const primaryKeyFields = meta.root.getPrimaryProps().flatMap(prop => prop.fieldNames);
234
+ if (partitionFields.some(field => !primaryKeyFields.includes(field))) {
235
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: primary key must include partition key columns '${partitionFields.join("', '")}'`);
236
+ }
237
+ for (const prop of Object.values(meta.root.properties)) {
238
+ if (!prop.unique || !prop.fieldNames?.length) {
239
+ continue;
240
+ }
241
+ if (partitionFields.some(field => !prop.fieldNames.includes(field))) {
242
+ throw new MetadataError(`Entity ${meta.root.className} has invalid partitionBy option: unique property ${meta.root.className}.${prop.name} must include partition key columns '${partitionFields.join("', '")}'`);
243
+ }
244
+ }
245
+ for (const unique of meta.root.uniques ?? []) {
246
+ const fields = this.getConstraintFields(meta.root, unique.properties);
247
+ if (!fields?.length) {
248
+ continue;
249
+ }
250
+ if (partitionFields.some(field => !fields.includes(field))) {
251
+ const constraint = unique.name ? `unique constraint '${unique.name}'` : 'unique constraint';
252
+ throw new MetadataError(`Entity ${meta.root.className} has invalid partitionBy option: ${constraint} must include partition key columns '${partitionFields.join("', '")}'`);
253
+ }
254
+ }
255
+ }
256
+ /**
257
+ * Returns the list of physical field names that a partition expression references, or
258
+ * `undefined` when the expression is opaque (callback, or raw SQL like `date_trunc('day', x)`
259
+ * that we cannot statically parse). Opaque expressions intentionally bypass the primary-key /
260
+ * unique-constraint coverage checks — users are trusted to ensure the referenced columns are
261
+ * part of the partition key, since PostgreSQL will surface the violation at DDL execution.
262
+ */
263
+ getPartitionKeyFields(meta) {
264
+ const expression = meta.partitionBy?.expression;
265
+ if (!expression || typeof expression === 'function') {
266
+ return undefined;
267
+ }
268
+ if (Array.isArray(expression)) {
269
+ return expression.map(key => this.resolvePartitionKeyField(meta, key));
270
+ }
271
+ const keys = splitCommaSeparatedIdentifiers(String(expression).trim());
272
+ if (!keys) {
273
+ return undefined;
274
+ }
275
+ return keys.map(key => this.resolvePartitionKeyField(meta, key));
276
+ }
277
+ resolvePartitionKeyField(meta, key) {
278
+ const trimmed = key.trim().replaceAll('"', '');
279
+ if (!trimmed) {
280
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: empty partition key`);
281
+ }
282
+ const prop = meta.root.properties[trimmed] ??
283
+ Object.values(meta.root.properties).find(candidate => candidate.fieldNames?.length === 1 && candidate.fieldNames[0] === trimmed);
284
+ if (!prop) {
285
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: unknown partition key '${key.trim()}'`);
286
+ }
287
+ if (prop.fieldNames?.length !== 1) {
288
+ throw new MetadataError(`Entity ${meta.className} has invalid partitionBy option: partition key '${key.trim()}' maps to multiple columns ('${prop.fieldNames?.join("', '")}'); list them explicitly as partition keys`);
289
+ }
290
+ return prop.fieldNames[0];
291
+ }
292
+ getConstraintFields(meta, properties) {
293
+ if (!properties) {
294
+ return undefined;
295
+ }
296
+ const fields = Utils.asArray(properties).flatMap(propName => {
297
+ const prop = meta.root.properties[propName];
298
+ return prop?.fieldNames ?? [];
299
+ });
300
+ return fields.length > 0 ? fields : undefined;
301
+ }
114
302
  validateReference(meta, prop, options) {
115
303
  // references do have types
116
304
  if (!prop.type) {
@@ -171,6 +359,15 @@ export class MetadataValidator {
171
359
  }
172
360
  validatePolymorphicTargets(meta, prop) {
173
361
  const targets = prop.polymorphTargets;
362
+ // Union-target M:N stores one scalar target FK per pivot row, so composite-PK targets
363
+ // can't round-trip through this schema.
364
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && targets.length > 1) {
365
+ for (const target of targets) {
366
+ if (target.compositePK) {
367
+ throw MetadataError.incompatiblePolymorphicTargets(meta, prop, targets[0], target, `${target.className} has a composite primary key; union-target polymorphic M:N does not support composite-PK targets.`);
368
+ }
369
+ }
370
+ }
174
371
  // Validate targetKey exists and is compatible across all targets
175
372
  if (prop.targetKey) {
176
373
  for (const target of targets) {
@@ -349,6 +546,10 @@ export class MetadataValidator {
349
546
  if (!meta.expression) {
350
547
  throw MetadataError.viewEntityWithoutExpression(meta);
351
548
  }
549
+ // Views are not partitionable - reject explicitly instead of silently ignoring
550
+ if (meta.partitionBy) {
551
+ throw new MetadataError(`View entity ${meta.className} cannot define partitionBy`);
552
+ }
352
553
  // Validate indexes if present
353
554
  this.validateIndexes(meta, meta.indexes ?? [], 'index');
354
555
  this.validateIndexes(meta, meta.uniques ?? [], 'unique');
@@ -1,9 +1,52 @@
1
- import type { AnyEntity, Constructor, EntityName, AnyString, CheckCallback, GeneratedColumnCallback, FormulaCallback, FilterQuery, Dictionary, AutoPath, EntityClass, IndexCallback, ObjectQuery, Raw } from '../typings.js';
1
+ import type { AnyEntity, Constructor, EntityName, AnyString, CheckCallback, GeneratedColumnCallback, FormulaCallback, FilterQuery, Dictionary, AutoPath, EntityClass, IndexCallback, ObjectQuery, Raw, SchemaColumns, TriggerDef } from '../typings.js';
2
2
  import type { Cascade, LoadStrategy, DeferMode, QueryOrderMap, EmbeddedPrefixMode } from '../enums.js';
3
3
  import type { Type, types } from '../types/index.js';
4
4
  import type { EntityManager } from '../EntityManager.js';
5
5
  import type { FilterOptions, FindOptions } from '../drivers/IDatabaseDriver.js';
6
6
  import type { SerializeOptions } from '../serialization/EntitySerializer.js';
7
+ /** Supported PostgreSQL partitioning strategies. */
8
+ export type EntityPartitionType = 'hash' | 'list' | 'range';
9
+ /**
10
+ * Partition key expression for PostgreSQL partitioned tables.
11
+ *
12
+ * You can use:
13
+ * - a property name, e.g. `'type'`
14
+ * - a list of property names for composite expressions, e.g. `['tenant', 'createdAt']`
15
+ * - a raw SQL expression string, e.g. `"date_trunc('month', created_at)"`
16
+ * - a callback that receives schema column mappings and returns SQL
17
+ */
18
+ export type EntityPartitionExpression<E = AnyEntity> = (keyof E & string) | readonly (keyof E & string)[] | AnyString | ((columns: SchemaColumns<E>) => string);
19
+ /** Explicit child partition definition for PostgreSQL `list` and `range` partitioning. */
20
+ export interface EntityPartition<E = AnyEntity> {
21
+ /**
22
+ * Optional child table name. Defaults to `<tableName>_<index>`. A single `.` is treated as a
23
+ * `schema.table` separator; names with more than one `.` are rejected to avoid ambiguity.
24
+ */
25
+ name?: string;
26
+ /** Partition bound clause, either as `for values ...` or the trailing part such as `in (...)`, `from ... to ...`, or `default`. */
27
+ values: string;
28
+ }
29
+ /**
30
+ * PostgreSQL table partitioning definition.
31
+ *
32
+ * - `hash` partitions generate child tables automatically from the partition count
33
+ * - `list` and `range` partitions require explicit child partition definitions
34
+ */
35
+ export type EntityPartitionBy<E = AnyEntity> = {
36
+ type: Extract<EntityPartitionType, 'hash'>;
37
+ expression: EntityPartitionExpression<E>;
38
+ /**
39
+ * Hash partition fan-out: either a number (auto-named as `${tableName}_N`) or an array of
40
+ * explicit partition names in remainder order. Names may be `schema.name` to create the
41
+ * child in a non-default schema.
42
+ */
43
+ partitions: number | readonly string[];
44
+ } | {
45
+ type: Exclude<EntityPartitionType, 'hash'>;
46
+ expression: EntityPartitionExpression<E>;
47
+ /** Explicit child partitions created in the order provided. */
48
+ partitions: EntityPartition<E>[];
49
+ };
7
50
  export type EntityOptions<T, E = T extends EntityClass<infer P> ? P : T> = {
8
51
  /** Override default collection/table name. Alias for `collection`. */
9
52
  tableName?: string;
@@ -57,6 +100,15 @@ export type EntityOptions<T, E = T extends EntityClass<infer P> ? P : T> = {
57
100
  };
58
101
  /** Used to make ORM aware of externally defined triggers. This is needed for MS SQL Server multi inserts, ignored in other dialects. */
59
102
  hasTriggers?: boolean;
103
+ /** Database triggers to create for this entity's table. (SQL drivers only) */
104
+ triggers?: TriggerDef<E>[];
105
+ /**
106
+ * PostgreSQL partitioning definition for this table.
107
+ *
108
+ * Partitioned tables are tracked by the schema generator and introspected back from PostgreSQL.
109
+ * Changing an existing partition layout still requires a manual migration.
110
+ */
111
+ partitionBy?: EntityPartitionBy<E>;
60
112
  /** SQL query that maps to a {@doclink virtual-entities | virtual entity}, or for view entities, the view definition. */
61
113
  expression?: string | ((em: any, where: ObjectQuery<E>, options: FindOptions<E, any, any, any>, stream?: boolean) => object);
62
114
  /** Set {@doclink repositories#custom-repository | custom repository class}. */
@@ -202,6 +254,12 @@ export interface PropertyOptions<Owner> {
202
254
  * @see https://mikro-orm.io/docs/defining-entities#lazy-scalar-properties
203
255
  */
204
256
  lazy?: boolean;
257
+ /**
258
+ * Marks this property as an array. When used with a custom type, the type is automatically
259
+ * wrapped in an `ArrayType` that delegates element conversion to the inner type.
260
+ * The column type is inferred as `innerType[]` (e.g. `int[]`, `time[]`).
261
+ */
262
+ array?: boolean;
205
263
  /**
206
264
  * Set true to define entity's unique primary key identifier.
207
265
  * Alias for `@PrimaryKey()` decorator
@@ -334,7 +392,7 @@ export interface PropertyOptions<Owner> {
334
392
  }
335
393
  export interface ReferenceOptions<Owner, Target> extends PropertyOptions<Owner> {
336
394
  /** Set target entity type. For polymorphic relations, pass an array of entity types. */
337
- entity?: () => EntityName<Target> | EntityName<Target>[];
395
+ entity?: () => EntityName<Target> | EntityName[];
338
396
  /** Set what actions on owning entity should be cascaded to the relationship. Defaults to [Cascade.PERSIST, Cascade.MERGE] (see {@doclink cascading}). */
339
397
  cascade?: Cascade[];
340
398
  /** Always load the relationship. Discouraged for use with to-many relations for performance reasons. */
@@ -499,7 +557,7 @@ export interface EmbeddableOptions<Owner> {
499
557
  abstract?: boolean;
500
558
  }
501
559
  export interface EnumOptions<T> extends PropertyOptions<T> {
502
- items?: readonly (number | string)[] | (() => Dictionary);
560
+ items?: readonly (number | string)[] | Dictionary | (() => Dictionary);
503
561
  array?: boolean;
504
562
  /** for postgres, by default it uses text column with check constraint */
505
563
  nativeEnumName?: string;
@@ -543,6 +601,16 @@ interface BaseOptions<T, H extends string> {
543
601
  include?: T extends EntityClass<infer P> ? Properties<P, H> : Properties<T, H>;
544
602
  /** Fill factor for the index as a percentage 0-100 (PostgreSQL, MSSQL). */
545
603
  fillFactor?: number;
604
+ /**
605
+ * Predicate for partial indexes. Object form is a `FilterQuery` and is portable across
606
+ * SQL drivers and MongoDB; string form is a raw SQL fragment (SQL drivers only).
607
+ *
608
+ * Native support: PostgreSQL, SQLite, MSSQL (`WHERE`), MongoDB (`partialFilterExpression`).
609
+ * MySQL 8.0.13+ / Oracle: emulated via `CASE WHEN` functional index (uniqueness only enforced
610
+ * where the predicate holds). MariaDB is not supported — it has no inline expression indexes;
611
+ * define a virtual generated column and index that instead.
612
+ */
613
+ where?: string | FilterQuery<NoInfer<T> extends EntityClass<infer P> ? P : NoInfer<T>>;
546
614
  }
547
615
  export interface UniqueOptions<T, H extends string = string> extends BaseOptions<T, H> {
548
616
  deferMode?: DeferMode | `${DeferMode}`;
@@ -4,7 +4,7 @@ import { type ReferenceKind } from '../enums.js';
4
4
  export declare abstract class AbstractNamingStrategy implements NamingStrategy {
5
5
  getClassName(file: string, separator?: string): string;
6
6
  classToMigrationName(timestamp: string, customMigrationName?: string): string;
7
- indexName(tableName: string, columns: string[], type: 'primary' | 'foreign' | 'unique' | 'index' | 'sequence' | 'check' | 'default'): string;
7
+ indexName(tableName: string, columns: string[], type: 'primary' | 'foreign' | 'unique' | 'index' | 'sequence' | 'check' | 'default' | 'trigger'): string;
8
8
  /**
9
9
  * @inheritDoc
10
10
  */
@@ -75,7 +75,7 @@ export interface NamingStrategy {
75
75
  /**
76
76
  * Returns key/constraint name for the given type. Some drivers might not support all the types (e.g. mysql and sqlite enforce the PK name).
77
77
  */
78
- indexName(tableName: string, columns: string[], type: 'primary' | 'foreign' | 'unique' | 'index' | 'sequence' | 'check' | 'default'): string;
78
+ indexName(tableName: string, columns: string[], type: 'primary' | 'foreign' | 'unique' | 'index' | 'sequence' | 'check' | 'default' | 'trigger'): string;
79
79
  /**
80
80
  * Returns alias name for given entity. The alias needs to be unique across the query, which is by default
81
81
  * ensured via appended index parameter. It is optional to use it as long as you ensure it will be unique.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.1.0-dev.2",
3
+ "version": "7.1.0-dev.20",
4
4
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
5
5
  "keywords": [
6
6
  "data-mapper",
@@ -43,6 +43,8 @@ export declare abstract class Platform {
43
43
  getEnumArrayCheckConstraintExpression(column: string, items: string[]): string | null;
44
44
  /** Whether this platform supports materialized views. */
45
45
  supportsMaterializedViews(): boolean;
46
+ /** Whether this platform supports declarative table partitioning in schema generation. */
47
+ supportsPartitionedTables(): boolean;
46
48
  /** Returns the schema helper instance for this platform, or undefined if not supported. */
47
49
  getSchemaHelper(): unknown;
48
50
  /** Whether the platform automatically creates indexes on foreign key columns. */
@@ -211,6 +213,11 @@ export declare abstract class Platform {
211
213
  getFullTextWhereClause(prop: EntityProperty): string;
212
214
  supportsCreatingFullTextIndex(): boolean;
213
215
  getFullTextIndexExpression(indexName: string, schemaName: string | undefined, tableName: string, columns: SimpleColumnMeta[]): string;
216
+ /**
217
+ * Generates the SQL index hint clause for the given index names.
218
+ * Returns `undefined` when the platform does not support index hints (e.g. PostgreSQL, SQLite).
219
+ */
220
+ formatIndexHint(indexNames: string[]): string | undefined;
214
221
  /** Whether the driver automatically parses JSON columns into JS objects. */
215
222
  convertsJsonAutomatically(): boolean;
216
223
  /** Converts a JS value to its JSON database representation (typically JSON.stringify). */
@@ -2,6 +2,7 @@ import { clone } from '../utils/clone.js';
2
2
  import { EntityRepository } from '../entity/EntityRepository.js';
3
3
  import { UnderscoreNamingStrategy } from '../naming-strategy/UnderscoreNamingStrategy.js';
4
4
  import { ExceptionConverter } from './ExceptionConverter.js';
5
+ import { MetadataError } from '../errors.js';
5
6
  import { ArrayType, BigIntType, BlobType, BooleanType, CharacterType, DateTimeType, DateType, DecimalType, DoubleType, EnumType, FloatType, IntegerType, IntervalType, JsonType, MediumIntType, SmallIntType, StringType, TextType, TimeType, TinyIntType, Type, Uint8ArrayType, UnknownType, UuidType, } from '../types/index.js';
6
7
  import { parseJsonSafe, Utils } from '../utils/Utils.js';
7
8
  import { ReferenceKind } from '../enums.js';
@@ -62,6 +63,10 @@ export class Platform {
62
63
  supportsMaterializedViews() {
63
64
  return false;
64
65
  }
66
+ /** Whether this platform supports declarative table partitioning in schema generation. */
67
+ supportsPartitionedTables() {
68
+ return false;
69
+ }
65
70
  /** Returns the schema helper instance for this platform, or undefined if not supported. */
66
71
  getSchemaHelper() {
67
72
  return undefined;
@@ -415,6 +420,13 @@ export class Platform {
415
420
  getFullTextIndexExpression(indexName, schemaName, tableName, columns) {
416
421
  throw new Error('Full text searching is not supported by this driver.');
417
422
  }
423
+ /**
424
+ * Generates the SQL index hint clause for the given index names.
425
+ * Returns `undefined` when the platform does not support index hints (e.g. PostgreSQL, SQLite).
426
+ */
427
+ formatIndexHint(indexNames) {
428
+ return undefined;
429
+ }
418
430
  /** Whether the driver automatically parses JSON columns into JS objects. */
419
431
  convertsJsonAutomatically() {
420
432
  return true;
@@ -651,7 +663,9 @@ export class Platform {
651
663
  }
652
664
  /** Platform-specific validation of entity metadata. */
653
665
  validateMetadata(meta) {
654
- return;
666
+ if (meta.partitionBy && !this.supportsPartitionedTables()) {
667
+ throw new MetadataError(`Entity ${meta.className} uses partitionBy, but ${this.constructor.name} does not support partitioned tables`);
668
+ }
655
669
  }
656
670
  /**
657
671
  * Generates a custom order by statement given a set of in order values, eg.