@mikro-orm/sql 7.0.0-rc.2 → 7.0.0-rc.3

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 (38) hide show
  1. package/AbstractSqlConnection.js +2 -1
  2. package/AbstractSqlDriver.d.ts +18 -12
  3. package/AbstractSqlDriver.js +187 -38
  4. package/AbstractSqlPlatform.d.ts +1 -0
  5. package/AbstractSqlPlatform.js +5 -3
  6. package/PivotCollectionPersister.js +2 -2
  7. package/SqlEntityManager.d.ts +2 -2
  8. package/SqlEntityManager.js +5 -5
  9. package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
  10. package/dialects/mssql/MsSqlNativeQueryBuilder.js +8 -4
  11. package/dialects/mysql/BaseMySqlPlatform.js +1 -2
  12. package/dialects/mysql/MySqlSchemaHelper.js +21 -10
  13. package/dialects/postgresql/BasePostgreSqlPlatform.js +38 -30
  14. package/dialects/postgresql/PostgreSqlSchemaHelper.js +63 -47
  15. package/dialects/sqlite/BaseSqliteConnection.js +2 -2
  16. package/dialects/sqlite/NodeSqliteDialect.js +3 -1
  17. package/dialects/sqlite/SqlitePlatform.js +4 -1
  18. package/dialects/sqlite/SqliteSchemaHelper.js +11 -9
  19. package/package.json +30 -30
  20. package/plugin/transformer.js +17 -16
  21. package/query/CriteriaNode.js +28 -10
  22. package/query/CriteriaNodeFactory.js +5 -1
  23. package/query/NativeQueryBuilder.d.ts +25 -0
  24. package/query/NativeQueryBuilder.js +61 -1
  25. package/query/ObjectCriteriaNode.js +71 -27
  26. package/query/QueryBuilder.d.ts +151 -48
  27. package/query/QueryBuilder.js +233 -54
  28. package/query/QueryBuilderHelper.d.ts +4 -3
  29. package/query/QueryBuilderHelper.js +47 -17
  30. package/query/ScalarCriteriaNode.js +14 -7
  31. package/query/raw.js +1 -1
  32. package/schema/DatabaseSchema.js +21 -15
  33. package/schema/DatabaseTable.js +114 -54
  34. package/schema/SchemaComparator.js +56 -32
  35. package/schema/SchemaHelper.js +28 -8
  36. package/schema/SqlSchemaGenerator.js +13 -7
  37. package/tsconfig.build.tsbuildinfo +1 -1
  38. package/typings.d.ts +6 -4
@@ -181,7 +181,8 @@ export class AbstractSqlConnection extends Connection {
181
181
  try {
182
182
  const res = (ctx ?? this.getClient()).getExecutor().stream(compiled, 1);
183
183
  this.logQuery(sql, {
184
- sql, params,
184
+ sql,
185
+ params,
185
186
  ...loggerContext,
186
187
  affected: Utils.isPlainObject(res) ? res.affectedRows : undefined,
187
188
  });
@@ -1,7 +1,7 @@
1
1
  import { type AnyEntity, type Collection, type Configuration, type ConnectionType, type Constructor, type CountOptions, DatabaseDriver, type DeleteOptions, type Dictionary, type DriverMethodOptions, type EntityData, type EntityDictionary, type EntityField, EntityManagerType, type EntityMetadata, type EntityName, type EntityProperty, type FilterQuery, type FindOneOptions, type FindOptions, type FormulaTable, type LockOptions, type LoggingOptions, type NativeInsertUpdateManyOptions, type NativeInsertUpdateOptions, type ObjectQuery, type Options, type OrderDefinition, type PopulateOptions, type PopulatePath, type Primary, type QueryOrderMap, type QueryResult, type Raw, RawQueryFragment, type StreamOptions, type Transaction, type UpsertManyOptions, type UpsertOptions } from '@mikro-orm/core';
2
2
  import type { AbstractSqlConnection } from './AbstractSqlConnection.js';
3
3
  import type { AbstractSqlPlatform } from './AbstractSqlPlatform.js';
4
- import { QueryBuilder } from './query/QueryBuilder.js';
4
+ import { type AnyQueryBuilder } from './query/QueryBuilder.js';
5
5
  import { type NativeQueryBuilder } from './query/NativeQueryBuilder.js';
6
6
  import { QueryType } from './query/enums.js';
7
7
  import { SqlEntityManager } from './SqlEntityManager.js';
@@ -36,7 +36,7 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
36
36
  * Force balanced strategy to load to-many relations via separate queries.
37
37
  */
38
38
  private forceBalancedStrategy;
39
- mapResult<T extends object>(result: EntityData<T>, meta: EntityMetadata<T>, populate?: PopulateOptions<T>[], qb?: QueryBuilder<T, any, any, any>, map?: Dictionary): EntityData<T> | null;
39
+ mapResult<T extends object>(result: EntityData<T>, meta: EntityMetadata<T>, populate?: PopulateOptions<T>[], qb?: AnyQueryBuilder<T>, map?: Dictionary): EntityData<T> | null;
40
40
  /**
41
41
  * Maps aliased columns from TPT parent tables back to their original field names.
42
42
  * TPT parent columns are selected with aliases like `parent_alias__column_name`,
@@ -99,17 +99,17 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
99
99
  */
100
100
  mergeJoinedResult<T extends object>(rawResults: EntityData<T>[], meta: EntityMetadata<T>, joinedProps: PopulateOptions<T>[]): EntityData<T>[];
101
101
  protected shouldHaveColumn<T, U>(meta: EntityMetadata<T>, prop: EntityProperty<U>, populate: readonly PopulateOptions<U>[], fields?: readonly InternalField<U>[], exclude?: readonly InternalField<U>[]): boolean;
102
- protected getFieldsForJoinedLoad<T extends object>(qb: QueryBuilder<T, any, any, any>, meta: EntityMetadata<T>, options: FieldsForJoinedLoadOptions<T>): InternalField<T>[];
102
+ protected getFieldsForJoinedLoad<T extends object>(qb: AnyQueryBuilder<T>, meta: EntityMetadata<T>, options: FieldsForJoinedLoadOptions<T>): InternalField<T>[];
103
103
  /**
104
104
  * Adds LEFT JOINs and fields for TPT polymorphic loading when populating a relation to a TPT base class.
105
105
  * @internal
106
106
  */
107
- protected addTPTPolymorphicJoinsForRelation<T extends object>(qb: QueryBuilder<T, any, any, any>, meta: EntityMetadata<T>, baseAlias: string, fields: InternalField<T>[]): void;
107
+ protected addTPTPolymorphicJoinsForRelation<T extends object>(qb: AnyQueryBuilder<T>, meta: EntityMetadata<T>, baseAlias: string, fields: InternalField<T>[]): void;
108
108
  /**
109
109
  * Find the alias for a TPT child table in the query builder.
110
110
  * @internal
111
111
  */
112
- protected findTPTChildAlias<T extends object>(qb: QueryBuilder<T, any, any, any>, childMeta: EntityMetadata): string | undefined;
112
+ protected findTPTChildAlias<T extends object>(qb: AnyQueryBuilder<T>, childMeta: EntityMetadata): string | undefined;
113
113
  /**
114
114
  * Builds a CASE WHEN expression for TPT discriminator.
115
115
  * Determines concrete entity type based on which child table has a non-null PK.
@@ -122,13 +122,13 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
122
122
  * This method reads the discriminator to determine the concrete type and maps child-specific fields.
123
123
  * @internal
124
124
  */
125
- protected mapTPTChildFields<T extends object>(relationPojo: EntityData<T>, meta: EntityMetadata<T>, relationAlias: string, qb: QueryBuilder<T, any, any, any>, root: EntityData<T>): void;
125
+ protected mapTPTChildFields<T extends object>(relationPojo: EntityData<T>, meta: EntityMetadata<T>, relationAlias: string, qb: AnyQueryBuilder<T>, root: EntityData<T>): void;
126
126
  /**
127
127
  * @internal
128
128
  */
129
- mapPropToFieldNames<T extends object>(qb: QueryBuilder<T, any, any, any, any, any>, prop: EntityProperty<T>, tableAlias: string, meta: EntityMetadata<T>, schema?: string, explicitFields?: readonly InternalField<T>[]): InternalField<T>[];
129
+ mapPropToFieldNames<T extends object>(qb: AnyQueryBuilder<T>, prop: EntityProperty<T>, tableAlias: string, meta: EntityMetadata<T>, schema?: string, explicitFields?: readonly InternalField<T>[]): InternalField<T>[];
130
130
  /** @internal */
131
- createQueryBuilder<T extends object>(entityName: EntityName<T> | QueryBuilder<T, any, any, any>, ctx?: Transaction, preferredConnectionType?: ConnectionType, convertCustomTypes?: boolean, loggerContext?: LoggingOptions, alias?: string, em?: SqlEntityManager): QueryBuilder<T, any, any, any>;
131
+ createQueryBuilder<T extends object>(entityName: EntityName<T> | AnyQueryBuilder<T>, ctx?: Transaction, preferredConnectionType?: ConnectionType, convertCustomTypes?: boolean, loggerContext?: LoggingOptions, alias?: string, em?: SqlEntityManager): AnyQueryBuilder<T>;
132
132
  protected resolveConnectionType(args: {
133
133
  ctx?: Transaction;
134
134
  connectionType?: ConnectionType;
@@ -137,13 +137,19 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
137
137
  protected processManyToMany<T extends object>(meta: EntityMetadata<T>, pks: Primary<T>[], collections: EntityData<T>, clear: boolean, options?: DriverMethodOptions): Promise<void>;
138
138
  lockPessimistic<T extends object>(entity: T, options: LockOptions): Promise<void>;
139
139
  protected buildPopulateWhere<T extends object>(meta: EntityMetadata<T>, joinedProps: PopulateOptions<T>[], options: Pick<FindOptions<any>, 'populateWhere'>): ObjectQuery<T>;
140
- protected buildOrderBy<T extends object>(qb: QueryBuilder<T, any, any, any>, meta: EntityMetadata<T>, populate: PopulateOptions<T>[], options: Pick<FindOptions<any>, 'strategy' | 'orderBy' | 'populateOrderBy'>): QueryOrderMap<T>[];
141
- protected buildPopulateOrderBy<T extends object>(qb: QueryBuilder<T, any, any, any>, meta: EntityMetadata<T>, populateOrderBy: QueryOrderMap<T>[], parentPath: string, explicit: boolean, parentAlias?: string): QueryOrderMap<T>[];
142
- protected buildJoinedPropsOrderBy<T extends object>(qb: QueryBuilder<T, any, any, any>, meta: EntityMetadata<T>, populate: PopulateOptions<T>[], options?: Pick<FindOptions<any>, 'strategy' | 'orderBy' | 'populateOrderBy'>, parentPath?: string): QueryOrderMap<T>[];
140
+ /**
141
+ * Builds a UNION ALL (or UNION) subquery from `unionWhere` branches and merges it
142
+ * into the main WHERE as `pk IN (branch_1 UNION ALL branch_2 ...)`.
143
+ * Each branch is planned independently by the database, enabling per-table index usage.
144
+ */
145
+ protected applyUnionWhere<T extends object>(meta: EntityMetadata<T>, where: ObjectQuery<T>, options: FindOptions<T, any, any, any> | CountOptions<T> | NativeInsertUpdateOptions<T> | DeleteOptions<T>, forDml?: boolean): Promise<ObjectQuery<T>>;
146
+ protected buildOrderBy<T extends object>(qb: AnyQueryBuilder<T>, meta: EntityMetadata<T>, populate: PopulateOptions<T>[], options: Pick<FindOptions<any>, 'strategy' | 'orderBy' | 'populateOrderBy'>): QueryOrderMap<T>[];
147
+ protected buildPopulateOrderBy<T extends object>(qb: AnyQueryBuilder<T>, meta: EntityMetadata<T>, populateOrderBy: QueryOrderMap<T>[], parentPath: string, explicit: boolean, parentAlias?: string): QueryOrderMap<T>[];
148
+ protected buildJoinedPropsOrderBy<T extends object>(qb: AnyQueryBuilder<T>, meta: EntityMetadata<T>, populate: PopulateOptions<T>[], options?: Pick<FindOptions<any>, 'strategy' | 'orderBy' | 'populateOrderBy'>, parentPath?: string): QueryOrderMap<T>[];
143
149
  private buildToManyOrderBy;
144
150
  protected normalizeFields<T extends object>(fields: InternalField<T>[], prefix?: string): string[];
145
151
  protected processField<T extends object>(meta: EntityMetadata<T>, prop: EntityProperty<T> | undefined, field: string, ret: InternalField<T>[]): void;
146
- protected buildFields<T extends object>(meta: EntityMetadata<T>, populate: PopulateOptions<T>[], joinedProps: PopulateOptions<T>[], qb: QueryBuilder<T, any, any, any>, alias: string, options: Pick<FindOptions<T, any, any, any>, 'strategy' | 'fields' | 'exclude'>, schema?: string): InternalField<T>[];
152
+ protected buildFields<T extends object>(meta: EntityMetadata<T>, populate: PopulateOptions<T>[], joinedProps: PopulateOptions<T>[], qb: AnyQueryBuilder<T>, alias: string, options: Pick<FindOptions<T, any, any, any>, 'strategy' | 'fields' | 'exclude'>, schema?: string): InternalField<T>[];
147
153
  }
148
154
  interface FieldsForJoinedLoadOptions<T extends object> {
149
155
  explicitFields?: readonly InternalField<T>[];
@@ -37,7 +37,7 @@ export class AbstractSqlDriver extends DatabaseDriver {
37
37
  throw new Error('Collation option for SQL drivers must be a string (collation name). Use a CollationOptions object only with MongoDB.');
38
38
  }
39
39
  if (options.indexHint != null && typeof options.indexHint !== 'string') {
40
- throw new Error('indexHint for SQL drivers must be a string (e.g. \'force index(my_index)\'). Use an object only with MongoDB.');
40
+ throw new Error("indexHint for SQL drivers must be a string (e.g. 'force index(my_index)'). Use an object only with MongoDB.");
41
41
  }
42
42
  }
43
43
  createEntityManager(useContext) {
@@ -95,6 +95,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
95
95
  if (meta.virtual) {
96
96
  return this.findVirtual(entityName, where, options);
97
97
  }
98
+ if (options.unionWhere?.length) {
99
+ where = await this.applyUnionWhere(meta, where, options);
100
+ }
98
101
  const qb = await this.createQueryBuilderFromOptions(meta, where, options);
99
102
  const result = await this.rethrow(qb.execute('all'));
100
103
  if (options.last && !options.first) {
@@ -297,7 +300,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
297
300
  }
298
301
  // Polymorphic to-one: iterate targets, find the matching one, build entity from its columns.
299
302
  // Skip :ref hints — no JOINs were created, so the FK reference is already set by the result mapper.
300
- if (prop.polymorphic && prop.polymorphTargets?.length && !ref && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
303
+ if (prop.polymorphic &&
304
+ prop.polymorphTargets?.length &&
305
+ !ref &&
306
+ [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
301
307
  const basePath = parentJoinPath ? `${parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`;
302
308
  const pathPrefix = !parentJoinPath ? '[populate]' : '';
303
309
  let matched = false;
@@ -306,16 +312,22 @@ export class AbstractSqlDriver extends DatabaseDriver {
306
312
  const relationAlias = qb.getAliasForJoinPath(targetPath, { matchPopulateJoins: true });
307
313
  const meta2 = targetMeta;
308
314
  const targetProps = meta2.props.filter(p => this.platform.shouldHaveColumn(p, hint.children || []));
309
- const hasPK = meta2.getPrimaryProps().every(pk => pk.fieldNames.every(name => root[`${relationAlias}__${name}`] != null));
315
+ const hasPK = meta2
316
+ .getPrimaryProps()
317
+ .every(pk => pk.fieldNames.every(name => root[`${relationAlias}__${name}`] != null));
310
318
  if (hasPK && !matched) {
311
319
  matched = true;
312
- let relationPojo = {};
320
+ const relationPojo = {};
313
321
  const tz = this.platform.getTimezone();
314
322
  for (const p of targetProps) {
315
323
  this.mapJoinedProp(relationPojo, p, relationAlias, root, tz, meta2);
316
324
  }
317
325
  // Inject the entity class constructor so that the factory creates the correct type
318
- Object.defineProperty(relationPojo, 'constructor', { value: meta2.class, enumerable: false, configurable: true });
326
+ Object.defineProperty(relationPojo, 'constructor', {
327
+ value: meta2.class,
328
+ enumerable: false,
329
+ configurable: true,
330
+ });
319
331
  result[prop.name] = relationPojo;
320
332
  const populateChildren = hint.children || [];
321
333
  this.mapJoinedProps(relationPojo, meta2, populateChildren, qb, root, map, targetPath);
@@ -366,7 +378,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
366
378
  return;
367
379
  }
368
380
  const mapToPk = !hint.dataOnly && !!(ref || prop.mapToPk);
369
- const targetProps = mapToPk ? meta2.getPrimaryProps() : meta2.props.filter(prop => this.platform.shouldHaveColumn(prop, hint.children || []));
381
+ const targetProps = mapToPk
382
+ ? meta2.getPrimaryProps()
383
+ : meta2.props.filter(prop => this.platform.shouldHaveColumn(prop, hint.children || []));
370
384
  // If the primary key value for the relation is null, we know we haven't joined to anything
371
385
  // and therefore we don't return any record (since all values would be null)
372
386
  const hasPK = meta2.getPrimaryProps().every(pk => pk.fieldNames.every(name => {
@@ -459,7 +473,12 @@ export class AbstractSqlDriver extends DatabaseDriver {
459
473
  else if (prop.runtimeType === 'Date') {
460
474
  const alias = `${relationAlias}__${prop.fieldNames[0]}`;
461
475
  const value = root[alias];
462
- if (tz && tz !== 'local' && typeof value === 'string' && !value.includes('+') && value.lastIndexOf('-') < 11 && !value.endsWith('Z')) {
476
+ if (tz &&
477
+ tz !== 'local' &&
478
+ typeof value === 'string' &&
479
+ !value.includes('+') &&
480
+ value.lastIndexOf('-') < 11 &&
481
+ !value.endsWith('Z')) {
463
482
  relationPojo[prop.name] = this.platform.parseDate(value + tz);
464
483
  }
465
484
  else if (['string', 'number'].includes(typeof value)) {
@@ -475,10 +494,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
475
494
  if (prop.kind === ReferenceKind.EMBEDDED && (prop.object || meta.embeddable)) {
476
495
  const item = parseJsonSafe(relationPojo[prop.name]);
477
496
  if (Array.isArray(item)) {
478
- relationPojo[prop.name] = item.map(row => (row == null ? row : this.comparator.mapResult(prop.targetMeta, row)));
497
+ relationPojo[prop.name] = item.map(row => row == null ? row : this.comparator.mapResult(prop.targetMeta, row));
479
498
  }
480
499
  else {
481
- relationPojo[prop.name] = item == null ? item : this.comparator.mapResult(prop.targetMeta, item);
500
+ relationPojo[prop.name] =
501
+ item == null ? item : this.comparator.mapResult(prop.targetMeta, item);
482
502
  }
483
503
  }
484
504
  }
@@ -493,6 +513,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
493
513
  if (meta.virtual) {
494
514
  return this.countVirtual(entityName, where, options);
495
515
  }
516
+ if (options.unionWhere?.length) {
517
+ where = await this.applyUnionWhere(meta, where, options);
518
+ }
496
519
  options = { populate: [], ...options };
497
520
  const populate = options.populate;
498
521
  const joinedProps = this.joinedProps(meta, populate, options);
@@ -557,13 +580,19 @@ export class AbstractSqlDriver extends DatabaseDriver {
557
580
  }
558
581
  const tableName = this.getTableName(meta, options);
559
582
  let sql = `insert into ${tableName} `;
560
- sql += fields.length > 0 ? '(' + fields.map(k => this.platform.quoteIdentifier(k)).join(', ') + ')' : `(${this.platform.quoteIdentifier(pks[0])})`;
583
+ sql +=
584
+ fields.length > 0
585
+ ? '(' + fields.map(k => this.platform.quoteIdentifier(k)).join(', ') + ')'
586
+ : `(${this.platform.quoteIdentifier(pks[0])})`;
561
587
  if (this.platform.usesOutputStatement()) {
562
588
  const returningProps = this.getTableProps(meta)
563
589
  .filter(prop => (prop.persist !== false && prop.defaultRaw) || prop.autoincrement || prop.generated)
564
590
  .filter(prop => !(prop.name in data[0]) || isRaw(data[0][prop.name]));
565
591
  const returningFields = Utils.flatten(returningProps.map(prop => prop.fieldNames));
566
- sql += returningFields.length > 0 ? ` output ${returningFields.map(field => 'inserted.' + this.platform.quoteIdentifier(field)).join(', ')}` : '';
592
+ sql +=
593
+ returningFields.length > 0
594
+ ? ` output ${returningFields.map(field => 'inserted.' + this.platform.quoteIdentifier(field)).join(', ')}`
595
+ : '';
567
596
  }
568
597
  if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
569
598
  sql += ' values ';
@@ -650,7 +679,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
650
679
  else {
651
680
  const field = prop.fieldNames[0];
652
681
  if (!duplicates.includes(field) || !usedDups.includes(field)) {
653
- if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && row[prop.name] != null && !isRaw(row[prop.name])) {
682
+ if (prop.customType &&
683
+ !prop.object &&
684
+ 'convertToDatabaseValueSQL' in prop.customType &&
685
+ row[prop.name] != null &&
686
+ !isRaw(row[prop.name])) {
654
687
  keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
655
688
  }
656
689
  else {
@@ -671,7 +704,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
671
704
  .filter(prop => !(prop.name in data[0]) || isRaw(data[0][prop.name]));
672
705
  const returningFields = Utils.flatten(returningProps.map(prop => prop.fieldNames));
673
706
  /* v8 ignore next */
674
- sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
707
+ sql +=
708
+ returningFields.length > 0
709
+ ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}`
710
+ : '';
675
711
  }
676
712
  if (transform) {
677
713
  sql = transform(sql);
@@ -704,11 +740,15 @@ export class AbstractSqlDriver extends DatabaseDriver {
704
740
  /* v8 ignore next */
705
741
  where = { [meta.primaryKeys[0] ?? pks[0]]: where };
706
742
  }
743
+ if (!options.upsert && options.unionWhere?.length) {
744
+ where = (await this.applyUnionWhere(meta, where, options, true));
745
+ }
707
746
  if (Utils.hasObjectKeys(data)) {
708
747
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
709
748
  if (options.upsert) {
710
749
  /* v8 ignore next */
711
- const uniqueFields = options.onConflictFields ?? (Utils.isPlainObject(where) ? Utils.keys(where) : meta.primaryKeys);
750
+ const uniqueFields = options.onConflictFields ??
751
+ (Utils.isPlainObject(where) ? Utils.keys(where) : meta.primaryKeys);
712
752
  const returning = getOnConflictReturningFields(meta, data, uniqueFields, options);
713
753
  qb.insert(data)
714
754
  .onConflict(uniqueFields)
@@ -728,7 +768,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
728
768
  qb.update(data).where(where);
729
769
  // reload generated columns and version fields
730
770
  const returning = [];
731
- meta.props.filter(prop => (prop.generated && !prop.primary) || prop.version).forEach(prop => returning.push(prop.name));
771
+ meta.props
772
+ .filter(prop => (prop.generated && !prop.primary) || prop.version)
773
+ .forEach(prop => returning.push(prop.name));
732
774
  qb.returning(returning);
733
775
  }
734
776
  res = await this.rethrow(qb.execute('run', false));
@@ -744,7 +786,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
744
786
  const meta = this.metadata.get(entityName);
745
787
  if (options.upsert) {
746
788
  const uniqueFields = options.onConflictFields ??
747
- (Utils.isPlainObject(where[0]) ? Object.keys(where[0]).flatMap(key => Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
789
+ (Utils.isPlainObject(where[0])
790
+ ? Object.keys(where[0]).flatMap(key => Utils.splitPrimaryKeys(key))
791
+ : meta.primaryKeys);
748
792
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes, options.loggerContext).withSchema(this.getSchemaName(meta, options));
749
793
  const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
750
794
  qb.insert(data)
@@ -815,7 +859,11 @@ export class AbstractSqlDriver extends DatabaseDriver {
815
859
  if (key in data[idx]) {
816
860
  const pks = Utils.getOrderedPrimaryKeys(cond, meta);
817
861
  sql += ` when (${pkCond}) then `;
818
- if (prop.customType && !prop.object && 'convertToDatabaseValueSQL' in prop.customType && data[idx][prop.name] != null && !isRaw(data[idx][key])) {
862
+ if (prop.customType &&
863
+ !prop.object &&
864
+ 'convertToDatabaseValueSQL' in prop.customType &&
865
+ data[idx][prop.name] != null &&
866
+ !isRaw(data[idx][key])) {
819
867
  sql += prop.customType.convertToDatabaseValueSQL('?', this.platform);
820
868
  }
821
869
  else {
@@ -844,7 +892,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
844
892
  sql = sql.substring(0, sql.length - 2) + ' where ';
845
893
  const pkProps = meta.primaryKeys.concat(...meta.concurrencyCheckKeys);
846
894
  const pks = Utils.flatten(pkProps.map(pk => meta.properties[pk].fieldNames));
847
- sql += pks.length > 1 ? `(${pks.map(pk => `${this.platform.quoteIdentifier(pk)}`).join(', ')})` : this.platform.quoteIdentifier(pks[0]);
895
+ sql +=
896
+ pks.length > 1
897
+ ? `(${pks.map(pk => `${this.platform.quoteIdentifier(pk)}`).join(', ')})`
898
+ : this.platform.quoteIdentifier(pks[0]);
848
899
  const conds = where.map(cond => {
849
900
  if (Utils.isPlainObject(cond) && Utils.getObjectKeysSize(cond) === 1) {
850
901
  cond = Object.values(cond)[0];
@@ -867,7 +918,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
867
918
  if (this.platform.usesReturningStatement() && returning.size > 0) {
868
919
  const returningFields = Utils.flatten([...returning].map(prop => meta.properties[prop].fieldNames));
869
920
  /* v8 ignore next */
870
- sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
921
+ sql +=
922
+ returningFields.length > 0
923
+ ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}`
924
+ : '';
871
925
  }
872
926
  const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx, options.loggerContext));
873
927
  for (let i = 0; i < collections.length; i++) {
@@ -881,6 +935,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
881
935
  if (Utils.isPrimaryKey(where) && pks.length === 1) {
882
936
  where = { [pks[0]]: where };
883
937
  }
938
+ if (options.unionWhere?.length) {
939
+ where = await this.applyUnionWhere(meta, where, options, true);
940
+ }
884
941
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false, options.loggerContext)
885
942
  .delete(where)
886
943
  .withSchema(this.getSchemaName(meta, options));
@@ -935,7 +992,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
935
992
  const qb = this.createQueryBuilder(coll.property.targetMeta.class, options?.ctx, 'write').withSchema(this.getSchemaName(meta, options));
936
993
  if (coll.getSnapshot() === undefined) {
937
994
  if (coll.property.orphanRemoval) {
938
- const query = qb.delete({ [coll.property.mappedBy]: pks }).andWhere({ [cols.join(Utils.PK_SEPARATOR)]: { $nin: insertDiff } });
995
+ const query = qb
996
+ .delete({ [coll.property.mappedBy]: pks })
997
+ .andWhere({ [cols.join(Utils.PK_SEPARATOR)]: { $nin: insertDiff } });
939
998
  await this.rethrow(query.execute());
940
999
  continue;
941
1000
  }
@@ -947,7 +1006,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
947
1006
  continue;
948
1007
  }
949
1008
  /* v8 ignore next */
950
- const query = qb.update({ [coll.property.mappedBy]: pks }).where({ [cols.join(Utils.PK_SEPARATOR)]: { $in: insertDiff } });
1009
+ const query = qb
1010
+ .update({ [coll.property.mappedBy]: pks })
1011
+ .where({ [cols.join(Utils.PK_SEPARATOR)]: { $in: insertDiff } });
951
1012
  await this.rethrow(query.execute());
952
1013
  continue;
953
1014
  }
@@ -960,7 +1021,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
960
1021
  else {
961
1022
  const targetMeta = coll.property.targetMeta;
962
1023
  const targetSchema = (coll[0] ?? snap?.[0]) && helper(coll[0] ?? snap?.[0]).getSchema();
963
- schema = targetMeta.schema === '*' ? (options?.schema ?? targetSchema ?? this.config.get('schema')) : targetMeta.schema;
1024
+ schema =
1025
+ targetMeta.schema === '*'
1026
+ ? (options?.schema ?? targetSchema ?? this.config.get('schema'))
1027
+ : targetMeta.schema;
964
1028
  }
965
1029
  }
966
1030
  else if (schema == null) {
@@ -997,7 +1061,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
997
1061
  const populate = this.autoJoinOneToOneOwner(prop.targetMeta, options?.populate ?? [], options?.fields);
998
1062
  const childFields = !Utils.isEmpty(options?.fields) ? options.fields.map(f => `${pivotProp1.name}.${f}`) : [];
999
1063
  const childExclude = !Utils.isEmpty(options?.exclude) ? options.exclude.map(f => `${pivotProp1.name}.${f}`) : [];
1000
- const fields = pivotJoin ? [pivotProp1.name, pivotProp2.name] : [pivotProp1.name, pivotProp2.name, ...childFields];
1064
+ const fields = pivotJoin
1065
+ ? [pivotProp1.name, pivotProp2.name]
1066
+ : [pivotProp1.name, pivotProp2.name, ...childFields];
1001
1067
  const res = await this.find(pivotMeta.class, where, {
1002
1068
  ctx,
1003
1069
  ...options,
@@ -1050,7 +1116,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
1050
1116
  const populateField = pivotJoin ? `${inverseProp.name}:ref` : inverseProp.name;
1051
1117
  const populate = this.autoJoinOneToOneOwner(targetMeta, options?.populate ?? [], options?.fields);
1052
1118
  const childFields = !Utils.isEmpty(options?.fields) ? options.fields.map(f => `${inverseProp.name}.${f}`) : [];
1053
- const childExclude = !Utils.isEmpty(options?.exclude) ? options.exclude.map(f => `${inverseProp.name}.${f}`) : [];
1119
+ const childExclude = !Utils.isEmpty(options?.exclude)
1120
+ ? options.exclude.map(f => `${inverseProp.name}.${f}`)
1121
+ : [];
1054
1122
  const fields = pivotJoin
1055
1123
  ? [inverseProp.name, prop.discriminator, prop.discriminatorColumn]
1056
1124
  : [inverseProp.name, prop.discriminator, prop.discriminatorColumn, ...childFields];
@@ -1060,7 +1128,15 @@ export class AbstractSqlDriver extends DatabaseDriver {
1060
1128
  fields,
1061
1129
  exclude: childExclude,
1062
1130
  orderBy: this.getPivotOrderBy(prop, inverseProp, orderBy, options?.orderBy),
1063
- populate: [{ field: populateField, strategy: LoadStrategy.JOINED, joinType: JoinType.innerJoin, children: populate, dataOnly: inverseProp.mapToPk && !pivotJoin }],
1131
+ populate: [
1132
+ {
1133
+ field: populateField,
1134
+ strategy: LoadStrategy.JOINED,
1135
+ joinType: JoinType.innerJoin,
1136
+ children: populate,
1137
+ dataOnly: inverseProp.mapToPk && !pivotJoin,
1138
+ },
1139
+ ],
1064
1140
  populateWhere: undefined,
1065
1141
  // @ts-ignore
1066
1142
  _populateWhere: 'infer',
@@ -1092,7 +1168,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
1092
1168
  const populateField = ownerRelationName;
1093
1169
  const populate = this.autoJoinOneToOneOwner(targetMeta, options?.populate ?? [], options?.fields);
1094
1170
  const childFields = !Utils.isEmpty(options?.fields) ? options.fields.map(f => `${ownerRelationName}.${f}`) : [];
1095
- const childExclude = !Utils.isEmpty(options?.exclude) ? options.exclude.map(f => `${ownerRelationName}.${f}`) : [];
1171
+ const childExclude = !Utils.isEmpty(options?.exclude)
1172
+ ? options.exclude.map(f => `${ownerRelationName}.${f}`)
1173
+ : [];
1096
1174
  const fields = [ownerRelationName, tagProp.name, prop.discriminatorColumn, ...childFields];
1097
1175
  const res = await this.find(pivotMeta.class, cond, {
1098
1176
  ctx,
@@ -1100,7 +1178,14 @@ export class AbstractSqlDriver extends DatabaseDriver {
1100
1178
  fields,
1101
1179
  exclude: childExclude,
1102
1180
  orderBy: this.getPivotOrderBy(prop, ownerProp, orderBy, options?.orderBy),
1103
- populate: [{ field: populateField, strategy: LoadStrategy.JOINED, joinType: JoinType.innerJoin, children: populate }],
1181
+ populate: [
1182
+ {
1183
+ field: populateField,
1184
+ strategy: LoadStrategy.JOINED,
1185
+ joinType: JoinType.innerJoin,
1186
+ children: populate,
1187
+ },
1188
+ ],
1104
1189
  populateWhere: undefined,
1105
1190
  // @ts-ignore
1106
1191
  _populateWhere: 'infer',
@@ -1179,7 +1264,10 @@ export class AbstractSqlDriver extends DatabaseDriver {
1179
1264
  }
1180
1265
  const relationsToPopulate = populate.map(({ field }) => field.split(':')[0]);
1181
1266
  const toPopulate = meta.relations
1182
- .filter(prop => prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !prop.lazy && !relationsToPopulate.includes(prop.name))
1267
+ .filter(prop => prop.kind === ReferenceKind.ONE_TO_ONE &&
1268
+ !prop.owner &&
1269
+ !prop.lazy &&
1270
+ !relationsToPopulate.includes(prop.name))
1183
1271
  .filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
1184
1272
  .map(prop => ({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED }));
1185
1273
  return [...populate, ...toPopulate];
@@ -1295,8 +1383,13 @@ export class AbstractSqlDriver extends DatabaseDriver {
1295
1383
  const prop = meta.properties[propName];
1296
1384
  // Polymorphic to-one: create a LEFT JOIN per target type
1297
1385
  // Skip :ref hints — polymorphic to-one already has FK + discriminator in the row
1298
- if (prop.polymorphic && prop.polymorphTargets?.length && !ref && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
1299
- const basePath = options.parentJoinPath ? `${options.parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`;
1386
+ if (prop.polymorphic &&
1387
+ prop.polymorphTargets?.length &&
1388
+ !ref &&
1389
+ [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
1390
+ const basePath = options.parentJoinPath
1391
+ ? `${options.parentJoinPath}.${prop.name}`
1392
+ : `${meta.name}.${prop.name}`;
1300
1393
  const pathPrefix = !options.parentJoinPath && populateWhereAll && !basePath.startsWith('[populate]') ? '[populate]' : '';
1301
1394
  for (const targetMeta of prop.polymorphTargets) {
1302
1395
  const tableAlias = qb.getNextAlias(targetMeta.className);
@@ -1314,7 +1407,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
1314
1407
  continue;
1315
1408
  }
1316
1409
  // ignore ref joins of known FKs unless it's a filter hint
1317
- if (ref && !hint.filter && (prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner))) {
1410
+ if (ref &&
1411
+ !hint.filter &&
1412
+ (prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner))) {
1318
1413
  continue;
1319
1414
  }
1320
1415
  const meta2 = prop.targetMeta;
@@ -1360,7 +1455,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
1360
1455
  childExplicitFields.push(f.substring(prop.name.length + 1));
1361
1456
  }
1362
1457
  });
1363
- const childExclude = options.exclude ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
1458
+ const childExclude = options.exclude
1459
+ ? Utils.extractChildElements(options.exclude, prop.name)
1460
+ : options.exclude;
1364
1461
  if (!ref && (!prop.mapToPk || hint.dataOnly)) {
1365
1462
  fields.push(...this.getFieldsForJoinedLoad(qb, meta2, {
1366
1463
  ...options,
@@ -1371,7 +1468,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
1371
1468
  parentJoinPath: path,
1372
1469
  }));
1373
1470
  }
1374
- else if (hint.filter || (prop.mapToPk && !hint.dataOnly) || (ref && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind))) {
1471
+ else if (hint.filter ||
1472
+ (prop.mapToPk && !hint.dataOnly) ||
1473
+ (ref && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind))) {
1375
1474
  fields.push(...prop.referencedColumnNames.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)));
1376
1475
  }
1377
1476
  }
@@ -1473,7 +1572,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
1473
1572
  if (childAlias) {
1474
1573
  // Map fields using same filtering as joined loading, plus skip PKs
1475
1574
  for (const prop of currentMeta.ownProps.filter(p => !p.primary && this.platform.shouldHaveColumn(p, []))) {
1476
- this.mapJoinedProp(relationPojo, prop, childAlias, root, tz, currentMeta, { deleteFromRoot: true });
1575
+ this.mapJoinedProp(relationPojo, prop, childAlias, root, tz, currentMeta, {
1576
+ deleteFromRoot: true,
1577
+ });
1477
1578
  }
1478
1579
  }
1479
1580
  currentMeta = currentMeta.tptParent;
@@ -1522,7 +1623,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
1522
1623
  /** @internal */
1523
1624
  createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes, loggerContext, alias, em) {
1524
1625
  // do not compute the connectionType if EM is provided as it will be computed from it in the QB later on
1525
- const connectionType = em ? preferredConnectionType : this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
1626
+ const connectionType = em
1627
+ ? preferredConnectionType
1628
+ : this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
1526
1629
  const qb = new QueryBuilder(entityName, this.metadata, this, ctx, alias, connectionType, em, loggerContext);
1527
1630
  if (!convertCustomTypes) {
1528
1631
  qb.unsetFlag(QueryFlag.CONVERT_CUSTOM_TYPES);
@@ -1598,6 +1701,47 @@ export class AbstractSqlDriver extends DatabaseDriver {
1598
1701
  /* v8 ignore next */
1599
1702
  return { $and: [options.populateWhere, where] };
1600
1703
  }
1704
+ /**
1705
+ * Builds a UNION ALL (or UNION) subquery from `unionWhere` branches and merges it
1706
+ * into the main WHERE as `pk IN (branch_1 UNION ALL branch_2 ...)`.
1707
+ * Each branch is planned independently by the database, enabling per-table index usage.
1708
+ */
1709
+ async applyUnionWhere(meta, where, options, forDml = false) {
1710
+ const unionWhere = options.unionWhere;
1711
+ const strategy = options.unionWhereStrategy ?? 'union-all';
1712
+ const schema = this.getSchemaName(meta, options);
1713
+ const connectionType = this.resolveConnectionType({
1714
+ ctx: options.ctx,
1715
+ connectionType: options.connectionType,
1716
+ });
1717
+ const branchQbs = [];
1718
+ for (const branch of unionWhere) {
1719
+ const qb = this.createQueryBuilder(meta.class, options.ctx, connectionType, false, options.logging).withSchema(schema);
1720
+ const pkFields = meta.primaryKeys.map(pk => {
1721
+ const prop = meta.properties[pk];
1722
+ return `${qb.alias}.${prop.fieldNames[0]}`;
1723
+ });
1724
+ qb.select(pkFields).where(branch);
1725
+ if (options.em) {
1726
+ await qb.applyJoinedFilters(options.em, options.filters);
1727
+ }
1728
+ branchQbs.push(qb);
1729
+ }
1730
+ const [first, ...rest] = branchQbs;
1731
+ const unionQb = strategy === 'union' ? first.union(...rest) : first.unionAll(...rest);
1732
+ const pkHash = Utils.getPrimaryKeyHash(meta.primaryKeys);
1733
+ // MySQL does not allow referencing the target table in a subquery
1734
+ // for UPDATE/DELETE, so we wrap the union in a derived table.
1735
+ if (forDml) {
1736
+ const { sql, params } = unionQb.toQuery();
1737
+ return {
1738
+ $and: [where, { [pkHash]: { $in: raw(`select * from (${sql}) as __u`, params) } }],
1739
+ };
1740
+ }
1741
+ return {
1742
+ $and: [where, { [pkHash]: { $in: unionQb.toRaw() } }],
1743
+ };
1744
+ }
1601
1745
  buildOrderBy(qb, meta, populate, options) {
1602
1746
  const joinedProps = this.joinedProps(meta, populate, options);
1603
1747
  // `options._populateWhere` is a copy of the value provided by user with a fallback to the global config option
@@ -1628,7 +1772,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
1628
1772
  let path = parentPath;
1629
1773
  const meta2 = prop.targetMeta;
1630
1774
  if (prop.kind !== ReferenceKind.SCALAR &&
1631
- (![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) || !prop.owner || Utils.isPlainObject(childOrder))) {
1775
+ (![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) ||
1776
+ !prop.owner ||
1777
+ Utils.isPlainObject(childOrder))) {
1632
1778
  path += `.${field}`;
1633
1779
  }
1634
1780
  if (prop.kind === ReferenceKind.MANY_TO_MANY && typeof childOrder !== 'object') {
@@ -1639,7 +1785,9 @@ export class AbstractSqlDriver extends DatabaseDriver {
1639
1785
  if (!join) {
1640
1786
  continue;
1641
1787
  }
1642
- if (join && ![ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
1788
+ if (join &&
1789
+ ![ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind) &&
1790
+ typeof childOrder === 'object') {
1643
1791
  const children = this.buildPopulateOrderBy(qb, meta2, Utils.asArray(childOrder), path, explicit, propAlias);
1644
1792
  orderBy.push(...children);
1645
1793
  continue;
@@ -1770,7 +1918,8 @@ export class AbstractSqlDriver extends DatabaseDriver {
1770
1918
  if (!options.fields.includes('*') && !options.fields.includes(`${qb.alias}.*`)) {
1771
1919
  ret.unshift(...meta.primaryKeys.filter(pk => !options.fields.includes(pk)));
1772
1920
  }
1773
- if (meta.root.inheritanceType === 'sti' && !options.fields.includes(`${qb.alias}.${meta.root.discriminatorColumn}`)) {
1921
+ if (meta.root.inheritanceType === 'sti' &&
1922
+ !options.fields.includes(`${qb.alias}.${meta.root.discriminatorColumn}`)) {
1774
1923
  ret.push(meta.root.discriminatorColumn);
1775
1924
  }
1776
1925
  }
@@ -27,6 +27,7 @@ export declare abstract class AbstractSqlPlatform extends Platform {
27
27
  getSearchJsonPropertySQL(path: string, type: string, aliased: boolean): string | RawQueryFragment;
28
28
  getSearchJsonPropertyKey(path: string[], type: string, aliased: boolean, value?: unknown): string | RawQueryFragment;
29
29
  getJsonIndexDefinition(index: IndexDef): string[];
30
+ supportsUnionWhere(): boolean;
30
31
  supportsSchemas(): boolean;
31
32
  /** @inheritDoc */
32
33
  generateCustomOrder(escapedColumn: string, values: unknown[]): string;
@@ -64,15 +64,14 @@ export class AbstractSqlPlatform extends Platform {
64
64
  }
65
65
  getSearchJsonPropertyKey(path, type, aliased, value) {
66
66
  const [a, ...b] = path;
67
- const quoteKey = (key) => key.match(/^[a-z]\w*$/i) ? key : `"${key}"`;
67
+ const quoteKey = (key) => (key.match(/^[a-z]\w*$/i) ? key : `"${key}"`);
68
68
  if (aliased) {
69
69
  return raw(alias => `json_extract(${this.quoteIdentifier(`${alias}.${a}`)}, '$.${b.map(quoteKey).join('.')}')`);
70
70
  }
71
71
  return raw(`json_extract(${this.quoteIdentifier(a)}, '$.${b.map(quoteKey).join('.')}')`);
72
72
  }
73
73
  getJsonIndexDefinition(index) {
74
- return index.columnNames
75
- .map(column => {
74
+ return index.columnNames.map(column => {
76
75
  if (!column.includes('.')) {
77
76
  return column;
78
77
  }
@@ -80,6 +79,9 @@ export class AbstractSqlPlatform extends Platform {
80
79
  return `(json_extract(${root}, '$.${path.join('.')}'))`;
81
80
  });
82
81
  }
82
+ supportsUnionWhere() {
83
+ return true;
84
+ }
83
85
  supportsSchemas() {
84
86
  return false;
85
87
  }