@mikro-orm/knex 5.7.15-dev.9 → 5.8.1-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,5 @@
1
- import type { Knex } from 'knex';
2
- import type { AnyEntity, Configuration, ConnectionOptions, EntityData, IsolationLevel, QueryResult, Transaction, TransactionEventBroadcaster } from '@mikro-orm/core';
3
- import { Connection } from '@mikro-orm/core';
1
+ import { type Knex } from 'knex';
2
+ import { Connection, type AnyEntity, type Configuration, type ConnectionOptions, type EntityData, type IsolationLevel, type QueryResult, type Transaction, type TransactionEventBroadcaster } from '@mikro-orm/core';
4
3
  import type { AbstractSqlPlatform } from './AbstractSqlPlatform';
5
4
  export declare abstract class AbstractSqlConnection extends Connection {
6
5
  private static __patched;
@@ -12,11 +11,13 @@ export declare abstract class AbstractSqlConnection extends Connection {
12
11
  isConnected(): Promise<boolean>;
13
12
  transactional<T>(cb: (trx: Transaction<Knex.Transaction>) => Promise<T>, options?: {
14
13
  isolationLevel?: IsolationLevel;
14
+ readOnly?: boolean;
15
15
  ctx?: Knex.Transaction;
16
16
  eventBroadcaster?: TransactionEventBroadcaster;
17
17
  }): Promise<T>;
18
18
  begin(options?: {
19
19
  isolationLevel?: IsolationLevel;
20
+ readOnly?: boolean;
20
21
  ctx?: Knex.Transaction;
21
22
  eventBroadcaster?: TransactionEventBroadcaster;
22
23
  }): Promise<Knex.Transaction>;
@@ -46,7 +46,10 @@ class AbstractSqlConnection extends core_1.Connection {
46
46
  if (!options.ctx) {
47
47
  await options.eventBroadcaster?.dispatchEvent(core_1.EventType.beforeTransactionStart);
48
48
  }
49
- const trx = await (options.ctx || this.client).transaction(null, { isolationLevel: options.isolationLevel });
49
+ const trx = await (options.ctx || this.client).transaction(null, {
50
+ isolationLevel: options.isolationLevel,
51
+ readOnly: options.readOnly,
52
+ });
50
53
  if (!options.ctx) {
51
54
  await options.eventBroadcaster?.dispatchEvent(core_1.EventType.afterTransactionStart, trx);
52
55
  }
@@ -1,6 +1,5 @@
1
1
  import type { Knex } from 'knex';
2
- import type { AnyEntity, Collection, ConnectionType, Configuration, Constructor, CountOptions, DeleteOptions, Dictionary, DriverMethodOptions, EntityData, EntityDictionary, EntityField, EntityMetadata, EntityName, EntityProperty, FilterQuery, FindOneOptions, FindOptions, IDatabaseDriver, LockOptions, NativeInsertUpdateManyOptions, NativeInsertUpdateOptions, PopulateOptions, Primary, QueryOrderMap, QueryResult, Transaction } from '@mikro-orm/core';
3
- import { DatabaseDriver, EntityManagerType } from '@mikro-orm/core';
2
+ 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 IDatabaseDriver, type LockOptions, type NativeInsertUpdateManyOptions, type NativeInsertUpdateOptions, type PopulateOptions, type Primary, type QueryOrderMap, type QueryResult, type Transaction, type UpsertManyOptions, type UpsertOptions } from '@mikro-orm/core';
4
3
  import type { AbstractSqlConnection } from './AbstractSqlConnection';
5
4
  import type { AbstractSqlPlatform } from './AbstractSqlPlatform';
6
5
  import { QueryBuilder, QueryType } from './query';
@@ -27,8 +26,8 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
27
26
  count<T extends object>(entityName: string, where: any, options?: CountOptions<T>): Promise<number>;
28
27
  nativeInsert<T extends object>(entityName: string, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
29
28
  nativeInsertMany<T extends object>(entityName: string, data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>): Promise<QueryResult<T>>;
30
- nativeUpdate<T extends object>(entityName: string, where: FilterQuery<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
31
- nativeUpdateMany<T extends object>(entityName: string, where: FilterQuery<T>[], data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>): Promise<QueryResult<T>>;
29
+ nativeUpdate<T extends object>(entityName: string, where: FilterQuery<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T> & UpsertOptions<T>): Promise<QueryResult<T>>;
30
+ nativeUpdateMany<T extends object>(entityName: string, where: FilterQuery<T>[], data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T> & UpsertManyOptions<T>): Promise<QueryResult<T>>;
32
31
  nativeDelete<T extends object>(entityName: string, where: FilterQuery<T> | string | any, options?: DeleteOptions<T>): Promise<QueryResult<T>>;
33
32
  syncCollection<T extends object, O extends object>(coll: Collection<T, O>, options?: DriverMethodOptions): Promise<void>;
34
33
  loadFromPivotTable<T extends object, O extends object>(prop: EntityProperty, owners: Primary<O>[][], where?: FilterQuery<any>, orderBy?: QueryOrderMap<T>[], ctx?: Transaction, options?: FindOptions<T, any>): Promise<Dictionary<T[]>>;
@@ -41,7 +40,7 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
41
40
  /**
42
41
  * @internal
43
42
  */
44
- mergeJoinedResult<T extends object>(rawResults: EntityData<T>[], meta: EntityMetadata<T>): EntityData<T>[];
43
+ mergeJoinedResult<T extends object>(rawResults: EntityData<T>[], meta: EntityMetadata<T>, joinedProps: PopulateOptions<T>[]): EntityData<T>[];
45
44
  protected getFieldsForJoinedLoad<T extends object>(qb: QueryBuilder<T>, meta: EntityMetadata<T>, explicitFields?: Field<T>[], populate?: PopulateOptions<T>[], parentTableAlias?: string, parentJoinPath?: string): Field<T>[];
46
45
  /**
47
46
  * @internal
@@ -38,6 +38,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
38
38
  .orderBy([...core_1.Utils.asArray(options.orderBy), ...joinedPropsOrderBy])
39
39
  .groupBy(options.groupBy)
40
40
  .having(options.having)
41
+ .indexHint(options.indexHint)
42
+ .comment(options.comments)
43
+ .hintComment(options.hintComments)
41
44
  .withSchema(this.getSchemaName(meta, options));
42
45
  if (options.limit !== undefined) {
43
46
  qb.limit(options.limit, options.offset);
@@ -48,7 +51,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
48
51
  core_1.Utils.asArray(options.flags).forEach(flag => qb.setFlag(flag));
49
52
  const result = await this.rethrow(qb.execute('all'));
50
53
  if (joinedProps.length > 0) {
51
- return this.mergeJoinedResult(result, meta);
54
+ return this.mergeJoinedResult(result, meta, joinedProps);
52
55
  }
53
56
  return result;
54
57
  }
@@ -105,7 +108,10 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
105
108
  }
106
109
  async wrapVirtualExpressionInSubquery(meta, expression, where, options, type = query_1.QueryType.SELECT) {
107
110
  const qb = this.createQueryBuilder(meta.className, options?.ctx, options.connectionType, options.convertCustomTypes)
108
- .limit(options?.limit, options?.offset);
111
+ .limit(options?.limit, options?.offset)
112
+ .indexHint(options.indexHint)
113
+ .comment(options.comments)
114
+ .hintComment(options.hintComments);
109
115
  if (options.orderBy) {
110
116
  qb.orderBy(options.orderBy);
111
117
  }
@@ -207,6 +213,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
207
213
  return this.countVirtual(entityName, where, options);
208
214
  }
209
215
  const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false)
216
+ .indexHint(options.indexHint)
217
+ .comment(options.comments)
218
+ .hintComment(options.hintComments)
210
219
  .groupBy(options.groupBy)
211
220
  .having(options.having)
212
221
  .populate(options.populate ?? [])
@@ -332,12 +341,18 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
332
341
  .withSchema(this.getSchemaName(meta, options));
333
342
  if (options.upsert) {
334
343
  /* istanbul ignore next */
335
- const uniqueFields = core_1.Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys;
336
- /* istanbul ignore next */
344
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys);
345
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data, uniqueFields, options);
337
346
  qb.insert(data)
338
- .onConflict(uniqueFields.map(p => meta?.properties[p]?.fieldNames[0] ?? p))
339
- .merge(Object.keys(data).filter(f => !uniqueFields.includes(f)))
340
- .returning(meta?.comparableProps.filter(p => !p.lazy && !p.embeddable && !(p.name in data)).map(p => p.name) ?? '*');
347
+ .onConflict(uniqueFields)
348
+ .returning(returning);
349
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
350
+ const fields = (0, core_1.getOnConflictFields)(data, uniqueFields, options);
351
+ qb.merge(fields);
352
+ }
353
+ if (options.onConflictAction === 'ignore') {
354
+ qb.ignore();
355
+ }
341
356
  }
342
357
  else {
343
358
  qb.update(data).where(where);
@@ -354,12 +369,19 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
354
369
  options.convertCustomTypes ?? (options.convertCustomTypes = true);
355
370
  const meta = this.metadata.get(entityName);
356
371
  if (options.upsert) {
357
- const uniqueFields = core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys;
372
+ const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys);
358
373
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
374
+ const returning = (0, core_1.getOnConflictReturningFields)(meta, data[0], uniqueFields, options);
359
375
  qb.insert(data)
360
- .onConflict(uniqueFields.map(p => meta.properties[p]?.fieldNames[0] ?? p))
361
- .merge(Object.keys(data[0]).filter(f => !uniqueFields.includes(f)))
362
- .returning(meta.comparableProps.filter(p => !p.lazy && !p.embeddable && !(p.name in data[0])).map(p => p.name) ?? '*');
376
+ .onConflict(uniqueFields)
377
+ .returning(returning);
378
+ if (!options.onConflictAction || options.onConflictAction === 'merge') {
379
+ const fields = (0, core_1.getOnConflictFields)(data[0], uniqueFields, options);
380
+ qb.merge(fields);
381
+ }
382
+ if (options.onConflictAction === 'ignore') {
383
+ qb.ignore();
384
+ }
363
385
  return qb.execute('run', false);
364
386
  }
365
387
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
@@ -461,11 +483,23 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
461
483
  if (coll.property.reference === core_1.ReferenceType.ONE_TO_MANY) {
462
484
  const cols = coll.property.referencedColumnNames;
463
485
  const qb = this.createQueryBuilder(coll.property.type, ctx, 'write')
464
- .withSchema(this.getSchemaName(meta, options))
465
- .update({ [coll.property.mappedBy]: pks })
486
+ .withSchema(this.getSchemaName(meta, options));
487
+ if (coll.getSnapshot() === undefined) {
488
+ if (coll.property.orphanRemoval) {
489
+ const kqb = qb.delete({ [coll.property.mappedBy]: pks })
490
+ .getKnexQuery()
491
+ .whereNotIn(cols, insertDiff);
492
+ return this.rethrow(this.execute(kqb));
493
+ }
494
+ const kqb = qb.update({ [coll.property.mappedBy]: null })
495
+ .getKnexQuery()
496
+ .whereNotIn(cols, insertDiff);
497
+ return this.rethrow(this.execute(kqb));
498
+ }
499
+ const kqb = qb.update({ [coll.property.mappedBy]: pks })
466
500
  .getKnexQuery()
467
501
  .whereIn(cols, insertDiff);
468
- return this.rethrow(this.execute(qb));
502
+ return this.rethrow(this.execute(kqb));
469
503
  }
470
504
  /* istanbul ignore next */
471
505
  const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
@@ -489,17 +523,22 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
489
523
  else {
490
524
  where = { ...where, ...cond };
491
525
  }
526
+ /* istanbul ignore if */
527
+ options = { ...options };
492
528
  orderBy = this.getPivotOrderBy(prop, orderBy);
493
- const qb = this.createQueryBuilder(prop.type, ctx, options?.connectionType)
529
+ const qb = this.createQueryBuilder(prop.type, ctx, options.connectionType)
530
+ .indexHint(options.indexHint)
531
+ .comment(options.comments)
532
+ .hintComment(options.hintComments)
494
533
  .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
495
534
  .withSchema(this.getSchemaName(prop.targetMeta, options));
496
535
  const populate = this.autoJoinOneToOneOwner(prop.targetMeta, [{ field: prop.pivotEntity }]);
497
- const fields = this.buildFields(prop.targetMeta, (options?.populate ?? []), [], qb, options?.fields);
498
- qb.select(fields).populate(populate).where(where).orderBy(orderBy).setLockMode(options?.lockMode, options?.lockTableAliases);
499
- if (owners.length === 1 && (options?.offset != null || options?.limit != null)) {
536
+ const fields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, options.fields);
537
+ qb.select(fields).populate(populate).where(where).orderBy(orderBy).setLockMode(options.lockMode, options.lockTableAliases);
538
+ if (owners.length === 1 && (options.offset != null || options.limit != null)) {
500
539
  qb.limit(options.limit, options.offset);
501
540
  }
502
- if (prop.targetMeta.schema !== '*' && pivotMeta.schema === '*' && options?.schema) {
541
+ if (prop.targetMeta.schema !== '*' && pivotMeta.schema === '*' && options.schema) {
503
542
  // eslint-disable-next-line dot-notation
504
543
  qb['finalize']();
505
544
  // eslint-disable-next-line dot-notation
@@ -554,17 +593,21 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
554
593
  /**
555
594
  * @internal
556
595
  */
557
- mergeJoinedResult(rawResults, meta) {
596
+ mergeJoinedResult(rawResults, meta, joinedProps) {
558
597
  // group by the root entity primary key first
559
598
  const map = {};
560
599
  const res = [];
561
600
  rawResults.forEach(item => {
562
601
  const pk = core_1.Utils.getCompositeKeyHash(item, meta);
563
602
  if (map[pk]) {
564
- map[pk].push(item);
603
+ joinedProps.forEach(hint => {
604
+ if (Array.isArray(map[pk][hint.field]) && Array.isArray(item[hint.field])) {
605
+ map[pk][hint.field].push(...item[hint.field]);
606
+ }
607
+ });
565
608
  }
566
609
  else {
567
- map[pk] = [item];
610
+ map[pk] = item;
568
611
  res.push(item);
569
612
  }
570
613
  });
@@ -672,9 +715,10 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
672
715
  if (!deleteDiff) {
673
716
  deleteDiff = [];
674
717
  }
718
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
675
719
  if (deleteDiff === true || deleteDiff.length > 0) {
676
720
  const qb1 = this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
677
- .withSchema(this.getSchemaName(meta, options))
721
+ .withSchema(this.getSchemaName(pivotMeta, options))
678
722
  .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
679
723
  const knex = qb1.getKnex();
680
724
  if (Array.isArray(deleteDiff)) {
@@ -700,7 +744,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
700
744
  await core_1.Utils.runSerial(items, item => {
701
745
  return this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
702
746
  .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
703
- .withSchema(this.getSchemaName(meta, options))
747
+ .withSchema(this.getSchemaName(pivotMeta, options))
704
748
  .insert(item)
705
749
  .execute('run', false);
706
750
  });
@@ -754,7 +798,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
754
798
  }
755
799
  if (prop.reference === core_1.ReferenceType.EMBEDDED) {
756
800
  if (prop.object) {
757
- ret.push(prop.fieldNames[0]);
801
+ ret.push(prop.name);
758
802
  return;
759
803
  }
760
804
  const parts = field.split('.');
@@ -766,13 +810,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
766
810
  }
767
811
  return;
768
812
  }
769
- if (prop.formula) {
770
- ret.push(prop.name);
771
- return;
772
- }
773
- if (prop.fieldNames) {
774
- ret.push(...prop.fieldNames);
775
- }
813
+ ret.push(prop.name);
776
814
  }
777
815
  buildFields(meta, populate, joinedProps, qb, fields) {
778
816
  const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
@@ -1,7 +1,5 @@
1
- import type { Constructor, EntityManager, EntityRepository, IDatabaseDriver, MikroORM } from '@mikro-orm/core';
2
- import { Platform } from '@mikro-orm/core';
3
- import type { SchemaHelper } from './schema';
4
- import { SchemaGenerator } from './schema';
1
+ import { Platform, type Constructor, type EntityManager, type EntityRepository, type IDatabaseDriver, type MikroORM } from '@mikro-orm/core';
2
+ import { SchemaGenerator, type SchemaHelper } from './schema';
5
3
  export declare abstract class AbstractSqlPlatform extends Platform {
6
4
  protected readonly schemaHelper?: SchemaHelper;
7
5
  usesPivotTable(): boolean;
@@ -1,6 +1,5 @@
1
1
  import type { Knex } from 'knex';
2
- import type { AnyEntity, ConnectionType, EntityData, EntityName, EntityRepository, GetRepository, QueryResult } from '@mikro-orm/core';
3
- import { EntityManager } from '@mikro-orm/core';
2
+ import { EntityManager, type AnyEntity, type ConnectionType, type EntityData, type EntityName, type EntityRepository, type GetRepository, type QueryResult } from '@mikro-orm/core';
4
3
  import type { AbstractSqlDriver } from './AbstractSqlDriver';
5
4
  import { QueryBuilder } from './query';
6
5
  import type { SqlEntityRepository } from './SqlEntityRepository';
@@ -1,6 +1,5 @@
1
1
  import type { Knex } from 'knex';
2
- import type { ConnectionType, EntityName } from '@mikro-orm/core';
3
- import { EntityRepository } from '@mikro-orm/core';
2
+ import { EntityRepository, type ConnectionType, type EntityName } from '@mikro-orm/core';
4
3
  import type { SqlEntityManager } from './SqlEntityManager';
5
4
  import type { QueryBuilder } from './query';
6
5
  export declare class SqlEntityRepository<T extends object> extends EntityRepository<T> {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/knex",
3
- "version": "5.7.15-dev.9",
3
+ "version": "5.8.1-dev.0",
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
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -63,12 +63,12 @@
63
63
  "sqlstring": "2.3.3"
64
64
  },
65
65
  "devDependencies": {
66
- "@mikro-orm/core": "^5.7.14"
66
+ "@mikro-orm/core": "^5.8.0"
67
67
  },
68
68
  "peerDependencies": {
69
- "@mikro-orm/core": "~5.7.15-dev.9",
70
- "@mikro-orm/entity-generator": "~5.7.15-dev.9",
71
- "@mikro-orm/migrations": "~5.7.15-dev.9",
69
+ "@mikro-orm/core": "~5.8.1-dev.0",
70
+ "@mikro-orm/entity-generator": "~5.8.1-dev.0",
71
+ "@mikro-orm/migrations": "~5.8.1-dev.0",
72
72
  "better-sqlite3": "*",
73
73
  "mssql": "*",
74
74
  "mysql": "*",
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import { inspect } from 'util';
3
- import type { EntityProperty, MetadataStorage } from '@mikro-orm/core';
3
+ import { type EntityProperty, type MetadataStorage } from '@mikro-orm/core';
4
4
  import type { ICriteriaNode, IQueryBuilder } from '../typings';
5
5
  /**
6
6
  * Helper for working with deeply nested where/orderBy/having criteria. Uses composite pattern to build tree from the payload.
@@ -1,4 +1,4 @@
1
- import type { Dictionary, EntityMetadata, MetadataStorage } from '@mikro-orm/core';
1
+ import { type Dictionary, type EntityMetadata, type MetadataStorage } from '@mikro-orm/core';
2
2
  import type { ICriteriaNode } from '../typings';
3
3
  /**
4
4
  * @internal
@@ -1,12 +1,10 @@
1
1
  /// <reference types="node" />
2
2
  import { inspect } from 'util';
3
3
  import type { Knex } from 'knex';
4
- import type { AnyEntity, ConnectionType, Dictionary, EntityData, EntityName, EntityProperty, FlushMode, GroupOperator, MetadataStorage, ObjectQuery, PopulateOptions, QBFilterQuery, QBQueryOrderMap, QueryResult, RequiredEntityData } from '@mikro-orm/core';
5
- import { LockMode, PopulateHint, QueryFlag } from '@mikro-orm/core';
4
+ import { LockMode, PopulateHint, QueryFlag, type AnyEntity, type ConnectionType, type Dictionary, type EntityData, type EntityName, type EntityProperty, type FlushMode, type GroupOperator, type MetadataStorage, type ObjectQuery, type PopulateOptions, type QBFilterQuery, type QBQueryOrderMap, type QueryResult, type RequiredEntityData } from '@mikro-orm/core';
6
5
  import { QueryType } from './enums';
7
6
  import type { AbstractSqlDriver } from '../AbstractSqlDriver';
8
- import type { Alias } from './QueryBuilderHelper';
9
- import { QueryBuilderHelper } from './QueryBuilderHelper';
7
+ import { QueryBuilderHelper, type Alias } from './QueryBuilderHelper';
10
8
  import type { SqlEntityManager } from '../SqlEntityManager';
11
9
  import type { Field } from '../typings';
12
10
  /**
@@ -34,7 +32,7 @@ export declare class QueryBuilder<T extends object = AnyEntity> {
34
32
  private readonly context?;
35
33
  private connectionType?;
36
34
  private readonly em?;
37
- get mainAlias(): Alias;
35
+ get mainAlias(): Alias<T>;
38
36
  get alias(): string;
39
37
  get helper(): QueryBuilderHelper;
40
38
  /** @internal */
@@ -66,6 +64,8 @@ export declare class QueryBuilder<T extends object = AnyEntity> {
66
64
  private _joinedProps;
67
65
  private _cache?;
68
66
  private _indexHint?;
67
+ private _comments;
68
+ private _hintComments;
69
69
  private flushMode?;
70
70
  private lockMode?;
71
71
  private lockTables?;
@@ -132,6 +132,16 @@ export declare class QueryBuilder<T extends object = AnyEntity> {
132
132
  * Adds index hint to the FROM clause.
133
133
  */
134
134
  indexHint(sql: string): this;
135
+ /**
136
+ * Prepend comment to the sql query using the syntax `/* ... *&#8205;/`. Some characters are forbidden such as `/*, *&#8205;/` and `?`.
137
+ */
138
+ comment(comment: string | string[]): this;
139
+ /**
140
+ * Add hints to the query using comment-like syntax `/*+ ... *&#8205;/`. MySQL and Oracle use this syntax for optimizer hints.
141
+ * Also various DB proxies and routers use this syntax to pass hints to alter their behavior. In other dialects the hints
142
+ * are ignored as simple comments.
143
+ */
144
+ hintComment(comment: string | string[]): this;
135
145
  /**
136
146
  * Specifies FROM which entity's table select/update/delete will be executed, removing all previously set FROM-s.
137
147
  * Allows setting a main string alias of the selection data.
@@ -204,6 +214,7 @@ export declare class QueryBuilder<T extends object = AnyEntity> {
204
214
  private init;
205
215
  private getQueryBase;
206
216
  private finalize;
217
+ private processPopulateWhere;
207
218
  private hasToManyJoins;
208
219
  private wrapPaginateSubQuery;
209
220
  private wrapModifySubQuery;
@@ -60,6 +60,8 @@ class QueryBuilder {
60
60
  this._groupBy = [];
61
61
  this._having = {};
62
62
  this._joinedProps = new Map();
63
+ this._comments = [];
64
+ this._hintComments = [];
63
65
  this.subQueries = {};
64
66
  this._aliases = {};
65
67
  this.platform = this.driver.getPlatform();
@@ -244,9 +246,16 @@ class QueryBuilder {
244
246
  return this;
245
247
  }
246
248
  onConflict(fields = []) {
249
+ const meta = this.mainAlias.metadata;
247
250
  this.ensureNotFinalized();
248
- this._onConflict = this._onConflict || [];
249
- this._onConflict.push({ fields: core_1.Utils.asArray(fields).map(f => f.toString()) });
251
+ this._onConflict ?? (this._onConflict = []);
252
+ this._onConflict.push({
253
+ fields: core_1.Utils.asArray(fields).flatMap(f => {
254
+ const key = f.toString();
255
+ /* istanbul ignore next */
256
+ return meta.properties[key]?.fieldNames ?? [key];
257
+ }),
258
+ });
250
259
  return this;
251
260
  }
252
261
  ignore() {
@@ -345,6 +354,24 @@ class QueryBuilder {
345
354
  this._indexHint = sql;
346
355
  return this;
347
356
  }
357
+ /**
358
+ * Prepend comment to the sql query using the syntax `/* ... *&#8205;/`. Some characters are forbidden such as `/*, *&#8205;/` and `?`.
359
+ */
360
+ comment(comment) {
361
+ this.ensureNotFinalized();
362
+ this._comments.push(...core_1.Utils.asArray(comment));
363
+ return this;
364
+ }
365
+ /**
366
+ * Add hints to the query using comment-like syntax `/*+ ... *&#8205;/`. MySQL and Oracle use this syntax for optimizer hints.
367
+ * Also various DB proxies and routers use this syntax to pass hints to alter their behavior. In other dialects the hints
368
+ * are ignored as simple comments.
369
+ */
370
+ hintComment(comment) {
371
+ this.ensureNotFinalized();
372
+ this._hintComments.push(...core_1.Utils.asArray(comment));
373
+ return this;
374
+ }
348
375
  from(target, aliasName) {
349
376
  this.ensureNotFinalized();
350
377
  if (target instanceof QueryBuilder) {
@@ -374,6 +401,8 @@ class QueryBuilder {
374
401
  }, this._orderBy);
375
402
  core_1.Utils.runIfNotEmpty(() => qb.limit(this._limit), this._limit != null);
376
403
  core_1.Utils.runIfNotEmpty(() => qb.offset(this._offset), this._offset);
404
+ core_1.Utils.runIfNotEmpty(() => this._comments.forEach(comment => qb.comment(comment)), this._comments);
405
+ core_1.Utils.runIfNotEmpty(() => this._hintComments.forEach(comment => qb.hintComment(comment)), this._hintComments);
377
406
  core_1.Utils.runIfNotEmpty(() => this.helper.appendOnConflictClause(this.type ?? enums_1.QueryType.SELECT, this._onConflict, qb), this._onConflict);
378
407
  if (this.type === enums_1.QueryType.TRUNCATE && this.platform.usesCascadeStatement()) {
379
408
  return this.knex.raw(qb.toSQL().toNative().sql + ' cascade');
@@ -471,7 +500,7 @@ class QueryBuilder {
471
500
  await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.flushMode });
472
501
  let res = await this.execute('all', true);
473
502
  if (this._joinedProps.size > 0) {
474
- res = this.driver.mergeJoinedResult(res, this.mainAlias.metadata);
503
+ res = this.driver.mergeJoinedResult(res, this.mainAlias.metadata, [...this._joinedProps.values()]);
475
504
  }
476
505
  const entities = [];
477
506
  function propagatePopulateHint(entity, hint) {
@@ -570,6 +599,7 @@ class QueryBuilder {
570
599
  const properties = [
571
600
  'flags', '_populate', '_populateWhere', '_populateMap', '_joins', '_joinedProps', '_cond', '_data', '_orderBy',
572
601
  '_schema', '_indexHint', '_cache', 'subQueries', 'lockMode', 'lockTables', '_groupBy', '_having', '_returning',
602
+ '_comments', '_hintComments',
573
603
  ];
574
604
  properties.forEach(prop => qb[prop] = core_1.Utils.copy(this[prop]));
575
605
  /* istanbul ignore else */
@@ -686,6 +716,10 @@ class QueryBuilder {
686
716
  }
687
717
  return;
688
718
  }
719
+ if (prop && prop.fieldNames.length > 1) {
720
+ ret.push(...prop.fieldNames.map(f => this.helper.mapper(f, this.type)));
721
+ return;
722
+ }
689
723
  ret.push(this.helper.mapper(field, this.type));
690
724
  });
691
725
  const meta = this.mainAlias.metadata;
@@ -816,6 +850,7 @@ class QueryBuilder {
816
850
  .filter(field => !this._fields.includes(field))
817
851
  .forEach(field => this.addSelect(field));
818
852
  }
853
+ this.processPopulateWhere();
819
854
  core_1.QueryHelper.processObjectParams(this._data);
820
855
  core_1.QueryHelper.processObjectParams(this._cond);
821
856
  core_1.QueryHelper.processObjectParams(this._having);
@@ -831,6 +866,40 @@ class QueryBuilder {
831
866
  }
832
867
  this.finalized = true;
833
868
  }
869
+ processPopulateWhere() {
870
+ if (this._populateWhere == null || this._populateWhere === core_1.PopulateHint.ALL) {
871
+ return;
872
+ }
873
+ const joins = Object.values(this._joins);
874
+ joins.forEach(join => {
875
+ join.cond_ = join.cond;
876
+ join.cond = {};
877
+ });
878
+ const replaceOnConditions = (cond) => {
879
+ Object.keys(cond).forEach(k => {
880
+ if (core_1.Utils.isOperator(k)) {
881
+ if (Array.isArray(cond[k])) {
882
+ return cond[k].forEach((c) => replaceOnConditions(c));
883
+ }
884
+ return replaceOnConditions(cond[k]);
885
+ }
886
+ const [a] = this.helper.splitField(k);
887
+ const join = joins.find(j => j.alias === a);
888
+ if (join) {
889
+ join.cond = { ...join.cond, [k]: cond[k] };
890
+ }
891
+ });
892
+ };
893
+ if (this._populateWhere === core_1.PopulateHint.INFER) {
894
+ replaceOnConditions(this._cond);
895
+ }
896
+ else if (typeof this._populateWhere === 'object') {
897
+ const cond = CriteriaNodeFactory_1.CriteriaNodeFactory
898
+ .createNode(this.metadata, this.mainAlias.entityName, this._populateWhere)
899
+ .process(this);
900
+ replaceOnConditions(cond);
901
+ }
902
+ }
834
903
  hasToManyJoins() {
835
904
  return Object.values(this._joins).some(join => {
836
905
  return [core_1.ReferenceType.ONE_TO_MANY, core_1.ReferenceType.MANY_TO_MANY].includes(join.prop.reference);
@@ -839,6 +908,8 @@ class QueryBuilder {
839
908
  wrapPaginateSubQuery(meta) {
840
909
  const pks = this.prepareFields(meta.primaryKeys, 'sub-query');
841
910
  const subQuery = this.clone().select(pks).groupBy(pks).limit(this._limit);
911
+ // revert the on conditions added via populateWhere, we want to apply those only once
912
+ Object.values(subQuery._joins).forEach(join => join.cond = join.cond_ ?? {});
842
913
  if (this._offset) {
843
914
  subQuery.offset(this._offset);
844
915
  }
@@ -881,14 +952,7 @@ class QueryBuilder {
881
952
  subSubQuery.__raw = true; // tag it as there is now way to check via `instanceof`
882
953
  this._limit = undefined;
883
954
  this._offset = undefined;
884
- const cond = this._cond;
885
955
  this.select(this._fields).where({ [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: { $in: subSubQuery } });
886
- if (this._populateWhere === core_1.PopulateHint.INFER) {
887
- this.andWhere(cond);
888
- }
889
- else if (typeof this._populateWhere === 'object') {
890
- this.andWhere(this._populateWhere);
891
- }
892
956
  }
893
957
  wrapModifySubQuery(meta) {
894
958
  const subQuery = this.clone();
@@ -1,6 +1,5 @@
1
1
  import type { Knex } from 'knex';
2
- import type { Dictionary, EntityData, EntityMetadata, EntityProperty, FlatQueryOrderMap, QBFilterQuery } from '@mikro-orm/core';
3
- import { LockMode } from '@mikro-orm/core';
2
+ import { LockMode, type Dictionary, type EntityData, type EntityMetadata, type EntityProperty, type FlatQueryOrderMap, type QBFilterQuery } from '@mikro-orm/core';
4
3
  import { QueryType } from './enums';
5
4
  import type { Field, JoinOptions } from '../typings';
6
5
  import type { AbstractSqlDriver } from '../AbstractSqlDriver';
@@ -16,7 +15,7 @@ export declare class QueryBuilderHelper {
16
15
  private readonly driver;
17
16
  private readonly platform;
18
17
  private readonly metadata;
19
- constructor(entityName: string, alias: string, aliasMap: Dictionary<Alias>, subQueries: Dictionary<string>, knex: Knex, driver: AbstractSqlDriver);
18
+ constructor(entityName: string, alias: string, aliasMap: Dictionary<Alias<any>>, subQueries: Dictionary<string>, knex: Knex, driver: AbstractSqlDriver);
20
19
  mapper(field: string | Knex.Raw, type?: QueryType): string;
21
20
  mapper(field: string | Knex.Raw, type?: QueryType, value?: any, alias?: string | null): string;
22
21
  processData(data: Dictionary, convertCustomTypes: boolean, multi?: boolean): any;
@@ -61,9 +60,9 @@ export declare class QueryBuilderHelper {
61
60
  isTableNameAliasRequired(type?: QueryType): boolean;
62
61
  private mapData;
63
62
  }
64
- export interface Alias {
63
+ export interface Alias<T> {
65
64
  aliasName: string;
66
65
  entityName: string;
67
- metadata?: EntityMetadata;
66
+ metadata?: EntityMetadata<T>;
68
67
  subQuery?: Knex.QueryBuilder;
69
68
  }
@@ -128,12 +128,13 @@ class QueryBuilderHelper {
128
128
  const pivotMeta = this.metadata.find(prop.pivotEntity);
129
129
  const ret = {
130
130
  [`${ownerAlias}.${prop.name}#${pivotAlias}`]: {
131
- prop, type, cond, ownerAlias,
131
+ prop, type, ownerAlias,
132
132
  alias: pivotAlias,
133
133
  inverseAlias: alias,
134
134
  joinColumns: prop.joinColumns,
135
135
  inverseJoinColumns: prop.inverseJoinColumns,
136
136
  primaryKeys: prop.referencedColumnNames,
137
+ cond: {},
137
138
  table: pivotMeta.tableName,
138
139
  schema: this.driver.getSchemaName(pivotMeta),
139
140
  path: path.endsWith('[pivot]') ? path : `${path}[pivot]`,
@@ -143,7 +144,7 @@ class QueryBuilderHelper {
143
144
  return ret;
144
145
  }
145
146
  const prop2 = prop.owner ? pivotMeta.relations[1] : pivotMeta.relations[0];
146
- ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type);
147
+ ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type, cond);
147
148
  ret[`${pivotAlias}.${prop2.name}#${alias}`].path = path;
148
149
  return ret;
149
150
  }
@@ -1,4 +1,4 @@
1
- import type { Configuration, Dictionary, EntityMetadata } from '@mikro-orm/core';
1
+ import { type Configuration, type Dictionary, type EntityMetadata } from '@mikro-orm/core';
2
2
  import { DatabaseTable } from './DatabaseTable';
3
3
  import type { AbstractSqlConnection } from '../AbstractSqlConnection';
4
4
  import type { AbstractSqlPlatform } from '../AbstractSqlPlatform';
@@ -1,4 +1,4 @@
1
- import type { Dictionary, EntityMetadata, EntityProperty, NamingStrategy } from '@mikro-orm/core';
1
+ import { type Dictionary, type EntityMetadata, type EntityProperty, type NamingStrategy } from '@mikro-orm/core';
2
2
  import type { SchemaHelper } from './SchemaHelper';
3
3
  import type { Check, Column, ForeignKey, Index } from '../typings';
4
4
  import type { AbstractSqlPlatform } from '../AbstractSqlPlatform';
@@ -17,6 +17,7 @@ export declare class DatabaseTable {
17
17
  constructor(platform: AbstractSqlPlatform, name: string, schema?: string | undefined);
18
18
  getColumns(): Column[];
19
19
  getColumn(name: string): Column | undefined;
20
+ removeColumn(name: string): void;
20
21
  getIndexes(): Index[];
21
22
  getChecks(): Check[];
22
23
  init(cols: Column[], indexes: Index[] | undefined, checks: Check<unknown>[] | undefined, pks: string[], fks?: Dictionary<ForeignKey>, enums?: Dictionary<string[]>): void;
@@ -24,6 +24,9 @@ class DatabaseTable {
24
24
  getColumn(name) {
25
25
  return this.columns[name];
26
26
  }
27
+ removeColumn(name) {
28
+ delete this.columns[name];
29
+ }
27
30
  getIndexes() {
28
31
  return this.indexes;
29
32
  }
@@ -227,6 +230,8 @@ class DatabaseTable {
227
230
  primary: column.primary,
228
231
  fieldName: column.name,
229
232
  length: column.length,
233
+ precision: column.precision,
234
+ scale: column.scale,
230
235
  index: index ? index.keyName : undefined,
231
236
  unique: unique ? unique.keyName : undefined,
232
237
  enum: !!column.enumItems?.length,
@@ -1,5 +1,4 @@
1
- import type { MikroORM } from '@mikro-orm/core';
2
- import { AbstractSchemaGenerator } from '@mikro-orm/core';
1
+ import { AbstractSchemaGenerator, type MikroORM } from '@mikro-orm/core';
3
2
  import type { SchemaDifference } from '../typings';
4
3
  import { DatabaseSchema } from './DatabaseSchema';
5
4
  import type { AbstractSqlDriver } from '../AbstractSqlDriver';
@@ -1,5 +1,5 @@
1
1
  import type { Knex } from 'knex';
2
- import type { Connection, Dictionary } from '@mikro-orm/core';
2
+ import { type Connection, type Dictionary } from '@mikro-orm/core';
3
3
  import type { AbstractSqlConnection } from '../AbstractSqlConnection';
4
4
  import type { AbstractSqlPlatform } from '../AbstractSqlPlatform';
5
5
  import type { Check, Column, Index, Table, TableDifference } from '../typings';
package/typings.d.ts CHANGED
@@ -25,6 +25,7 @@ export interface JoinOptions {
25
25
  path?: string;
26
26
  prop: EntityProperty;
27
27
  cond: Dictionary;
28
+ cond_?: Dictionary;
28
29
  }
29
30
  export interface Column {
30
31
  name: string;