@mikro-orm/knex 6.0.0-dev.1 → 6.0.0-dev.11

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 (54) hide show
  1. package/{dist/AbstractSqlConnection.d.ts → AbstractSqlConnection.d.ts} +0 -0
  2. package/{dist/AbstractSqlConnection.js → AbstractSqlConnection.js} +5 -0
  3. package/AbstractSqlDriver.d.ts +66 -0
  4. package/{dist/AbstractSqlDriver.js → AbstractSqlDriver.js} +226 -70
  5. package/{dist/AbstractSqlPlatform.d.ts → AbstractSqlPlatform.d.ts} +7 -7
  6. package/{dist/AbstractSqlPlatform.js → AbstractSqlPlatform.js} +12 -14
  7. package/{dist/MonkeyPatchable.d.ts → MonkeyPatchable.d.ts} +0 -0
  8. package/{dist/MonkeyPatchable.js → MonkeyPatchable.js} +0 -0
  9. package/{dist/README.md → README.md} +2 -2
  10. package/{dist/SqlEntityManager.d.ts → SqlEntityManager.d.ts} +5 -5
  11. package/{dist/SqlEntityManager.js → SqlEntityManager.js} +0 -1
  12. package/{dist/SqlEntityRepository.d.ts → SqlEntityRepository.d.ts} +3 -3
  13. package/{dist/SqlEntityRepository.js → SqlEntityRepository.js} +1 -1
  14. package/{dist/index.d.ts → index.d.ts} +1 -0
  15. package/{dist/index.js → index.js} +6 -1
  16. package/index.mjs +192 -0
  17. package/package.json +18 -47
  18. package/{dist/query → query}/ArrayCriteriaNode.d.ts +0 -0
  19. package/{dist/query → query}/ArrayCriteriaNode.js +0 -0
  20. package/{dist/query → query}/CriteriaNode.d.ts +0 -0
  21. package/{dist/query → query}/CriteriaNode.js +7 -4
  22. package/{dist/query → query}/CriteriaNodeFactory.d.ts +0 -0
  23. package/{dist/query → query}/CriteriaNodeFactory.js +0 -0
  24. package/{dist/query → query}/ObjectCriteriaNode.d.ts +0 -0
  25. package/{dist/query → query}/ObjectCriteriaNode.js +14 -1
  26. package/{dist/query → query}/QueryBuilder.d.ts +61 -24
  27. package/{dist/query → query}/QueryBuilder.js +308 -84
  28. package/{dist/query → query}/QueryBuilderHelper.d.ts +19 -11
  29. package/{dist/query → query}/QueryBuilderHelper.js +206 -108
  30. package/{dist/query → query}/ScalarCriteriaNode.d.ts +0 -0
  31. package/{dist/query → query}/ScalarCriteriaNode.js +0 -0
  32. package/{dist/query → query}/enums.d.ts +0 -0
  33. package/{dist/query → query}/enums.js +0 -0
  34. package/{dist/query → query}/index.d.ts +0 -0
  35. package/{dist/query → query}/index.js +5 -1
  36. package/{dist/schema → schema}/DatabaseSchema.d.ts +1 -1
  37. package/{dist/schema → schema}/DatabaseSchema.js +8 -16
  38. package/{dist/schema → schema}/DatabaseTable.d.ts +8 -8
  39. package/{dist/schema → schema}/DatabaseTable.js +2 -1
  40. package/{dist/schema → schema}/SchemaComparator.d.ts +7 -5
  41. package/{dist/schema → schema}/SchemaComparator.js +58 -10
  42. package/{dist/schema → schema}/SchemaHelper.d.ts +15 -10
  43. package/{dist/schema → schema}/SchemaHelper.js +46 -8
  44. package/{dist/schema/SchemaGenerator.d.ts → schema/SqlSchemaGenerator.d.ts} +12 -12
  45. package/{dist/schema/SchemaGenerator.js → schema/SqlSchemaGenerator.js} +85 -34
  46. package/{dist/schema → schema}/index.d.ts +1 -1
  47. package/{dist/schema → schema}/index.js +6 -2
  48. package/{dist/typings.d.ts → typings.d.ts} +16 -12
  49. package/{dist/typings.js → typings.js} +0 -0
  50. package/CHANGELOG.md +0 -661
  51. package/dist/AbstractSqlDriver.d.ts +0 -55
  52. package/dist/LICENSE +0 -21
  53. package/dist/index.mjs +0 -26
  54. package/dist/package.json +0 -99
@@ -102,6 +102,11 @@ class AbstractSqlConnection extends core_1.Connection {
102
102
  await this.client.raw(buf.toString());
103
103
  }
104
104
  createKnexClient(type) {
105
+ const driverOptions = this.config.get('driverOptions');
106
+ if (driverOptions.context?.client instanceof knex_1.knex.Client) {
107
+ this.logger.log('info', 'Reusing knex client provided via `driverOptions`');
108
+ return driverOptions;
109
+ }
105
110
  return (0, knex_1.knex)(this.getKnexOptions(type))
106
111
  .on('query', data => {
107
112
  if (!data.__knexQueryUid) {
@@ -0,0 +1,66 @@
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';
4
+ import type { AbstractSqlConnection } from './AbstractSqlConnection';
5
+ import type { AbstractSqlPlatform } from './AbstractSqlPlatform';
6
+ import { QueryBuilder, QueryType } from './query';
7
+ import { SqlEntityManager } from './SqlEntityManager';
8
+ import type { Field } from './typings';
9
+ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlConnection = AbstractSqlConnection, Platform extends AbstractSqlPlatform = AbstractSqlPlatform> extends DatabaseDriver<Connection> {
10
+ [EntityManagerType]: SqlEntityManager<this>;
11
+ protected readonly connection: Connection;
12
+ protected readonly replicas: Connection[];
13
+ protected readonly platform: Platform;
14
+ protected constructor(config: Configuration, platform: Platform, connection: Constructor<Connection>, connector: string[]);
15
+ getPlatform(): Platform;
16
+ createEntityManager<D extends IDatabaseDriver = IDatabaseDriver>(useContext?: boolean): D[typeof EntityManagerType];
17
+ find<T extends object, P extends string = never>(entityName: string, where: FilterQuery<T>, options?: FindOptions<T, P>): Promise<EntityData<T>[]>;
18
+ findOne<T extends object, P extends string = never>(entityName: string, where: FilterQuery<T>, options?: FindOneOptions<T, P>): Promise<EntityData<T> | null>;
19
+ findVirtual<T extends object>(entityName: string, where: FilterQuery<T>, options: FindOptions<T, any>): Promise<EntityData<T>[]>;
20
+ countVirtual<T extends object>(entityName: string, where: FilterQuery<T>, options: CountOptions<T>): Promise<number>;
21
+ protected wrapVirtualExpressionInSubquery<T extends object>(meta: EntityMetadata<T>, expression: string, where: FilterQuery<T>, options: FindOptions<T, any>, type: QueryType.COUNT): Promise<number>;
22
+ protected wrapVirtualExpressionInSubquery<T extends object>(meta: EntityMetadata<T>, expression: string, where: FilterQuery<T>, options: FindOptions<T, any>, type: QueryType.SELECT): Promise<T[]>;
23
+ protected wrapVirtualExpressionInSubquery<T extends object>(meta: EntityMetadata<T>, expression: string, where: FilterQuery<T>, options: FindOptions<T, any>): Promise<T[]>;
24
+ mapResult<T extends object>(result: EntityData<T>, meta: EntityMetadata<T>, populate?: PopulateOptions<T>[], qb?: QueryBuilder<T>, map?: Dictionary): EntityData<T> | null;
25
+ private mapJoinedProps;
26
+ private appendToCollection;
27
+ count<T extends object>(entityName: string, where: any, options?: CountOptions<T>): Promise<number>;
28
+ nativeInsert<T extends object>(entityName: string, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
29
+ 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>>;
32
+ nativeDelete<T extends object>(entityName: string, where: FilterQuery<T> | string | any, options?: DeleteOptions<T>): Promise<QueryResult<T>>;
33
+ syncCollection<T extends object, O extends object>(coll: Collection<T, O>, options?: DriverMethodOptions): Promise<void>;
34
+ 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[]>>;
35
+ execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(queryOrKnex: string | Knex.QueryBuilder | Knex.Raw, params?: any[], method?: 'all' | 'get' | 'run', ctx?: Transaction): Promise<T>;
36
+ /**
37
+ * 1:1 owner side needs to be marked for population so QB auto-joins the owner id
38
+ */
39
+ protected autoJoinOneToOneOwner<T extends object, P extends string = never>(meta: EntityMetadata, populate: PopulateOptions<T>[], fields?: readonly EntityField<T, P>[]): PopulateOptions<T>[];
40
+ protected joinedProps<T>(meta: EntityMetadata, populate: PopulateOptions<T>[]): PopulateOptions<T>[];
41
+ /**
42
+ * @internal
43
+ */
44
+ mergeJoinedResult<T extends object>(rawResults: EntityData<T>[], meta: EntityMetadata<T>): EntityData<T>[];
45
+ protected getFieldsForJoinedLoad<T extends object>(qb: QueryBuilder<T>, meta: EntityMetadata<T>, explicitFields?: Field<T>[], populate?: PopulateOptions<T>[], parentTableAlias?: string, parentJoinPath?: string): Field<T>[];
46
+ /**
47
+ * @internal
48
+ */
49
+ mapPropToFieldNames<T extends object>(qb: QueryBuilder<T>, prop: EntityProperty<T>, tableAlias?: string): Field<T>[];
50
+ /** @internal */
51
+ createQueryBuilder<T extends object>(entityName: EntityName<T> | QueryBuilder<T>, ctx?: Transaction<Knex.Transaction>, preferredConnectionType?: ConnectionType, convertCustomTypes?: boolean): QueryBuilder<T>;
52
+ protected resolveConnectionType(args: {
53
+ ctx?: Transaction<Knex.Transaction>;
54
+ connectionType?: ConnectionType;
55
+ }): ConnectionType;
56
+ protected extractManyToMany<T>(entityName: string, data: EntityDictionary<T>): EntityData<T>;
57
+ protected processManyToMany<T extends object>(meta: EntityMetadata<T> | undefined, pks: Primary<T>[], collections: EntityData<T>, clear: boolean, options?: DriverMethodOptions): Promise<void>;
58
+ protected updateCollectionDiff<T extends object, O extends object>(meta: EntityMetadata<O>, prop: EntityProperty<T>, pks: Primary<O>[], deleteDiff: Primary<T>[][] | boolean, insertDiff: Primary<T>[][], options?: DriverMethodOptions & {
59
+ ownerSchema?: string;
60
+ }): Promise<void>;
61
+ lockPessimistic<T>(entity: T, options: LockOptions): Promise<void>;
62
+ protected buildJoinedPropsOrderBy<T extends object>(entityName: string, qb: QueryBuilder<T>, meta: EntityMetadata<T>, populate: PopulateOptions<T>[], parentPath?: string): QueryOrderMap<T>[];
63
+ protected normalizeFields<T extends object>(fields: Field<T>[], prefix?: string): string[];
64
+ protected processField<T extends object>(meta: EntityMetadata<T>, prop: EntityProperty<T> | undefined, field: string, ret: Field<T>[], populate: PopulateOptions<T>[], joinedProps: PopulateOptions<T>[], qb: QueryBuilder<T>): void;
65
+ protected buildFields<T extends object>(meta: EntityMetadata<T>, populate: PopulateOptions<T>[], joinedProps: PopulateOptions<T>[], qb: QueryBuilder<T>, fields?: Field<T>[]): Field<T>[];
66
+ }
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AbstractSqlDriver = void 0;
4
4
  const core_1 = require("@mikro-orm/core");
5
- const QueryBuilder_1 = require("./query/QueryBuilder");
5
+ const query_1 = require("./query");
6
6
  const SqlEntityManager_1 = require("./SqlEntityManager");
7
7
  class AbstractSqlDriver extends core_1.DatabaseDriver {
8
8
  constructor(config, platform, connection, connector) {
@@ -21,16 +21,19 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
21
21
  async find(entityName, where, options = {}) {
22
22
  options = { populate: [], orderBy: [], ...options };
23
23
  const meta = this.metadata.find(entityName);
24
+ if (meta?.virtual) {
25
+ return this.findVirtual(entityName, where, options);
26
+ }
24
27
  const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
25
28
  const joinedProps = this.joinedProps(meta, populate);
26
- const qb = this.createQueryBuilder(entityName, options.ctx, !!options.ctx, false);
29
+ const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false);
27
30
  const fields = this.buildFields(meta, populate, joinedProps, qb, options.fields);
28
31
  const joinedPropsOrderBy = this.buildJoinedPropsOrderBy(entityName, qb, meta, joinedProps);
29
32
  if (core_1.Utils.isPrimaryKey(where, meta.compositePK)) {
30
33
  where = { [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: where };
31
34
  }
32
35
  qb.select(fields)
33
- .populate(populate)
36
+ .populate(populate, joinedProps.length > 0 ? options.populateWhere : undefined)
34
37
  .where(where)
35
38
  .orderBy([...core_1.Utils.asArray(options.orderBy), ...joinedPropsOrderBy])
36
39
  .groupBy(options.groupBy)
@@ -57,9 +60,70 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
57
60
  if (joinedProps.length === 0) {
58
61
  opts.limit = 1;
59
62
  }
63
+ if (opts.limit > 0 && !opts.flags?.includes(core_1.QueryFlag.DISABLE_PAGINATE)) {
64
+ opts.flags ?? (opts.flags = []);
65
+ opts.flags.push(core_1.QueryFlag.DISABLE_PAGINATE);
66
+ }
60
67
  const res = await this.find(entityName, where, opts);
61
68
  return res[0] || null;
62
69
  }
70
+ async findVirtual(entityName, where, options) {
71
+ const meta = this.metadata.get(entityName);
72
+ /* istanbul ignore next */
73
+ if (!meta.expression) {
74
+ return [];
75
+ }
76
+ if (typeof meta.expression === 'string') {
77
+ return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options);
78
+ }
79
+ const em = this.createEntityManager(false);
80
+ em.setTransactionContext(options.ctx);
81
+ const res = meta.expression(em, where, options);
82
+ if (res instanceof (query_1.QueryBuilder)) {
83
+ return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options);
84
+ }
85
+ /* istanbul ignore next */
86
+ return res;
87
+ }
88
+ async countVirtual(entityName, where, options) {
89
+ const meta = this.metadata.get(entityName);
90
+ /* istanbul ignore next */
91
+ if (!meta.expression) {
92
+ return 0;
93
+ }
94
+ if (typeof meta.expression === 'string') {
95
+ return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options, query_1.QueryType.COUNT);
96
+ }
97
+ const em = this.createEntityManager(false);
98
+ em.setTransactionContext(options.ctx);
99
+ const res = meta.expression(em, where, options);
100
+ if (res instanceof (query_1.QueryBuilder)) {
101
+ return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options, query_1.QueryType.COUNT);
102
+ }
103
+ /* istanbul ignore next */
104
+ return res;
105
+ }
106
+ async wrapVirtualExpressionInSubquery(meta, expression, where, options, type = query_1.QueryType.SELECT) {
107
+ const qb = this.createQueryBuilder(meta.className, options?.ctx, options.connectionType, options.convertCustomTypes)
108
+ .limit(options?.limit, options?.offset);
109
+ if (options.orderBy) {
110
+ qb.orderBy(options.orderBy);
111
+ }
112
+ qb.where(where);
113
+ const kqb = qb.getKnexQuery().clear('select');
114
+ if (type === query_1.QueryType.COUNT) {
115
+ kqb.select(qb.raw('count(*) as count'));
116
+ }
117
+ else { // select
118
+ kqb.select('*');
119
+ }
120
+ kqb.fromRaw(`(${expression}) as ${this.platform.quoteIdentifier(qb.alias)}`);
121
+ const res = await this.execute(kqb);
122
+ if (type === query_1.QueryType.COUNT) {
123
+ return res[0].count;
124
+ }
125
+ return res.map(row => this.mapResult(row, meta));
126
+ }
63
127
  mapResult(result, meta, populate = [], qb, map = {}) {
64
128
  const ret = super.mapResult(result, meta);
65
129
  /* istanbul ignore if */
@@ -137,23 +201,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
137
201
  collection.push(relationPojo);
138
202
  }
139
203
  }
140
- async count(entityName, where, options = {}, ctx) {
204
+ async count(entityName, where, options = {}) {
141
205
  const meta = this.metadata.find(entityName);
142
- const pks = meta.primaryKeys;
143
- const qb = this.createQueryBuilder(entityName, ctx, !!ctx, false)
206
+ if (meta?.virtual) {
207
+ return this.countVirtual(entityName, where, options);
208
+ }
209
+ const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false)
144
210
  .groupBy(options.groupBy)
145
211
  .having(options.having)
146
212
  .populate(options.populate ?? [])
147
213
  .withSchema(this.getSchemaName(meta, options))
148
214
  .where(where);
149
- return this.rethrow(qb.getCount(pks, true));
215
+ return this.rethrow(qb.getCount());
150
216
  }
151
217
  async nativeInsert(entityName, data, options = {}) {
152
218
  options.convertCustomTypes ?? (options.convertCustomTypes = true);
153
219
  const meta = this.metadata.find(entityName);
154
220
  const collections = this.extractManyToMany(entityName, data);
155
- const pks = this.getPrimaryKeyFields(entityName);
156
- const qb = this.createQueryBuilder(entityName, options.ctx, true, options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
221
+ const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
222
+ const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
157
223
  const res = await this.rethrow(qb.insert(data).execute('run', false));
158
224
  res.row = res.row || {};
159
225
  let pk;
@@ -161,7 +227,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
161
227
  pk = core_1.Utils.getPrimaryKeyCond(data, pks);
162
228
  }
163
229
  else {
164
- res.insertId = data[pks[0]] || res.insertId || res.row[pks[0]];
230
+ /* istanbul ignore next */
231
+ res.insertId = data[pks[0]] ?? res.insertId ?? res.row[pks[0]];
165
232
  pk = [res.insertId];
166
233
  }
167
234
  await this.processManyToMany(meta, pk, collections, false, options);
@@ -170,29 +237,30 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
170
237
  async nativeInsertMany(entityName, data, options = {}) {
171
238
  options.processCollections ?? (options.processCollections = true);
172
239
  options.convertCustomTypes ?? (options.convertCustomTypes = true);
173
- const meta = this.metadata.get(entityName);
240
+ const meta = this.metadata.find(entityName);
174
241
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
175
242
  const pks = this.getPrimaryKeyFields(entityName);
176
243
  const set = new Set();
177
244
  data.forEach(row => Object.keys(row).forEach(k => set.add(k)));
178
- const props = [...set].map(name => meta.properties[name] ?? { name, fieldNames: [name] });
245
+ const props = [...set].map(name => meta?.properties[name] ?? { name, fieldNames: [name] });
179
246
  const fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
180
- let res;
181
- if (fields.length === 0) {
182
- const qb = this.createQueryBuilder(entityName, options.ctx, true, options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
183
- res = await this.rethrow(qb.insert(data).execute('run', false));
247
+ const params = [];
248
+ /* istanbul ignore next */
249
+ const tableName = meta ? this.getTableName(meta, options) : this.platform.quoteIdentifier(entityName);
250
+ let sql = `insert into ${tableName} `;
251
+ sql += fields.length > 0 ? '(' + fields.map(k => this.platform.quoteIdentifier(k)).join(', ') + ')' : `(${this.platform.quoteIdentifier(pks[0])})`;
252
+ if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
253
+ sql += ' values ';
184
254
  }
185
255
  else {
186
- let sql = `insert into ${this.getTableName(meta, options)} `;
187
- /* istanbul ignore next */
188
- sql += fields.length > 0 ? '(' + fields.map(k => this.platform.quoteIdentifier(k)).join(', ') + ')' : 'default';
189
- sql += ` values `;
190
- const params = [];
256
+ sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
257
+ }
258
+ if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
191
259
  sql += data.map(row => {
192
260
  const keys = [];
193
261
  props.forEach(prop => {
194
262
  if (prop.fieldNames.length > 1) {
195
- params.push(...row[prop.name]);
263
+ params.push(...(row[prop.name] ?? prop.fieldNames.map(() => null)));
196
264
  keys.push(...prop.fieldNames.map(_ => '?'));
197
265
  }
198
266
  else if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(row[prop.name])) {
@@ -204,17 +272,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
204
272
  keys.push('?');
205
273
  }
206
274
  });
207
- return '(' + keys.join(', ') + ')';
275
+ return '(' + (keys.join(', ') || 'default') + ')';
208
276
  }).join(', ');
209
- if (this.platform.usesReturningStatement()) {
210
- /* istanbul ignore next */
211
- const returningProps = meta.props.filter(prop => prop.primary || prop.defaultRaw);
212
- const returningFields = core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames));
213
- /* istanbul ignore next */
214
- sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
215
- }
216
- res = await this.execute(sql, params, 'run', options.ctx);
217
277
  }
278
+ if (this.platform.usesReturningStatement()) {
279
+ /* istanbul ignore next */
280
+ const returningProps = meta.hydrateProps.filter(prop => prop.persist !== false && (prop.primary || prop.defaultRaw));
281
+ const returningFields = core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames));
282
+ /* istanbul ignore next */
283
+ sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
284
+ }
285
+ const res = await this.execute(sql, params, 'run', options.ctx);
218
286
  let pk;
219
287
  /* istanbul ignore next */
220
288
  if (pks.length > 1) { // owner has composite pk
@@ -238,13 +306,23 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
238
306
  const collections = this.extractManyToMany(entityName, data);
239
307
  let res = { affectedRows: 0, insertId: 0, row: {} };
240
308
  if (core_1.Utils.isPrimaryKey(where) && pks.length === 1) {
241
- where = { [pks[0]]: where };
309
+ /* istanbul ignore next */
310
+ where = { [meta?.primaryKeys[0] ?? pks[0]]: where };
242
311
  }
243
312
  if (core_1.Utils.hasObjectKeys(data)) {
244
- const qb = this.createQueryBuilder(entityName, options.ctx, true, options.convertCustomTypes)
245
- .update(data)
246
- .withSchema(this.getSchemaName(meta, options))
247
- .where(where);
313
+ const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes)
314
+ .withSchema(this.getSchemaName(meta, options));
315
+ if (options.upsert) {
316
+ /* istanbul ignore next */
317
+ const uniqueFields = core_1.Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys;
318
+ /* istanbul ignore next */
319
+ qb.insert(data)
320
+ .onConflict(uniqueFields.map(p => meta?.properties[p]?.fieldNames[0] ?? p))
321
+ .merge(Object.keys(data).filter(f => !uniqueFields.includes(f)));
322
+ }
323
+ else {
324
+ qb.update(data).where(where);
325
+ }
248
326
  res = await this.rethrow(qb.execute('run', false));
249
327
  }
250
328
  /* istanbul ignore next */
@@ -256,6 +334,14 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
256
334
  options.processCollections ?? (options.processCollections = true);
257
335
  options.convertCustomTypes ?? (options.convertCustomTypes = true);
258
336
  const meta = this.metadata.get(entityName);
337
+ if (options.upsert) {
338
+ const uniqueFields = core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys;
339
+ const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
340
+ qb.insert(data)
341
+ .onConflict(uniqueFields.map(p => meta?.properties[p]?.fieldNames[0] ?? p))
342
+ .merge(Object.keys(data[0]).filter(f => !uniqueFields.includes(f)));
343
+ return qb.execute('run', false);
344
+ }
259
345
  const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
260
346
  const keys = new Set();
261
347
  data.forEach(row => Object.keys(row).forEach(k => keys.add(k)));
@@ -276,7 +362,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
276
362
  else {
277
363
  sql += '?';
278
364
  }
279
- params.push(...pks, prop.fieldNames.length > 1 ? data[idx][key][fieldNameIdx] : data[idx][key]);
365
+ params.push(...pks, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
280
366
  }
281
367
  });
282
368
  sql += ` else ${this.platform.quoteIdentifier(fieldName)} end, `;
@@ -300,15 +386,24 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
300
386
  const pks = core_1.Utils.flatten(pkProps.map(pk => meta.properties[pk].fieldNames));
301
387
  sql += pks.length > 1 ? `(${pks.map(pk => `${this.platform.quoteIdentifier(pk)}`).join(', ')})` : this.platform.quoteIdentifier(pks[0]);
302
388
  const conds = where.map(cond => {
389
+ if (core_1.Utils.isPlainObject(cond) && core_1.Utils.getObjectKeysSize(cond) === 1) {
390
+ cond = Object.values(cond)[0];
391
+ }
303
392
  if (pks.length > 1) {
304
- pkProps.forEach(pk => params.push(cond[pk]));
393
+ pkProps.forEach(pk => {
394
+ if (Array.isArray(cond[pk])) {
395
+ params.push(...core_1.Utils.flatten(cond[pk]));
396
+ }
397
+ else {
398
+ params.push(cond[pk]);
399
+ }
400
+ });
305
401
  return `(${new Array(pks.length).fill('?').join(', ')})`;
306
402
  }
307
403
  params.push(cond);
308
404
  return '?';
309
405
  });
310
- const values = pks.length > 1 && this.platform.requiresValuesKeyword() ? 'values ' : '';
311
- sql += ` in (${values}${conds.join(', ')})`;
406
+ sql += ` in (${conds.join(', ')})`;
312
407
  const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
313
408
  for (let i = 0; i < collections.length; i++) {
314
409
  await this.processManyToMany(meta, where[i], collections[i], false, options);
@@ -321,17 +416,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
321
416
  if (core_1.Utils.isPrimaryKey(where) && pks.length === 1) {
322
417
  where = { [pks[0]]: where };
323
418
  }
324
- const qb = this.createQueryBuilder(entityName, options.ctx, true, false).delete(where).withSchema(this.getSchemaName(meta, options));
419
+ const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false).delete(where).withSchema(this.getSchemaName(meta, options));
325
420
  return this.rethrow(qb.execute('run', false));
326
421
  }
327
422
  async syncCollection(coll, options) {
328
- const wrapped = coll.owner.__helper;
423
+ const wrapped = (0, core_1.helper)(coll.owner);
329
424
  const meta = wrapped.__meta;
330
425
  const pks = wrapped.getPrimaryKeys(true);
331
426
  const snap = coll.getSnapshot();
332
427
  const includes = (arr, item) => !!arr.find(i => core_1.Utils.equals(i, item));
333
- const snapshot = snap ? snap.map(item => item.__helper.getPrimaryKeys(true)) : [];
334
- const current = coll.getItems(false).map(item => item.__helper.getPrimaryKeys(true));
428
+ const snapshot = snap ? snap.map(item => (0, core_1.helper)(item).getPrimaryKeys(true)) : [];
429
+ const current = coll.getItems(false).map(item => (0, core_1.helper)(item).getPrimaryKeys(true));
335
430
  const deleteDiff = snap ? snapshot.filter(item => !includes(current, item)) : true;
336
431
  const insertDiff = current.filter(item => !includes(snapshot, item));
337
432
  const target = snapshot.filter(item => includes(current, item)).concat(...insertDiff);
@@ -345,7 +440,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
345
440
  }
346
441
  if (coll.property.reference === core_1.ReferenceType.ONE_TO_MANY) {
347
442
  const cols = coll.property.referencedColumnNames;
348
- const qb = this.createQueryBuilder(coll.property.type, ctx, true)
443
+ const qb = this.createQueryBuilder(coll.property.type, ctx, 'write')
349
444
  .withSchema(this.getSchemaName(meta, options))
350
445
  .update({ [coll.property.mappedBy]: pks })
351
446
  .getKnexQuery()
@@ -354,7 +449,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
354
449
  }
355
450
  /* istanbul ignore next */
356
451
  const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
357
- const pivotMeta = this.metadata.find(coll.property.pivotTable);
452
+ const pivotMeta = this.metadata.find(coll.property.pivotEntity);
358
453
  if (pivotMeta.schema === '*') {
359
454
  /* istanbul ignore next */
360
455
  options ?? (options = {});
@@ -365,7 +460,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
365
460
  async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options) {
366
461
  const pivotProp2 = this.getPivotInverseProperty(prop);
367
462
  const ownerMeta = this.metadata.find(pivotProp2.type);
368
- const cond = { [`${prop.pivotTable}.${pivotProp2.name}`]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) } };
463
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
464
+ const cond = { [`${prop.pivotEntity}.${pivotProp2.name}`]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) } };
369
465
  /* istanbul ignore if */
370
466
  if (!core_1.Utils.isEmpty(where) && Object.keys(where).every(k => core_1.Utils.isOperator(k, false))) {
371
467
  where = cond;
@@ -374,15 +470,23 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
374
470
  where = { ...where, ...cond };
375
471
  }
376
472
  orderBy = this.getPivotOrderBy(prop, orderBy);
377
- const qb = this.createQueryBuilder(prop.type, ctx, !!ctx)
473
+ const qb = this.createQueryBuilder(prop.type, ctx, options?.connectionType)
378
474
  .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
379
475
  .withSchema(this.getSchemaName(prop.targetMeta, options));
380
- const populate = this.autoJoinOneToOneOwner(prop.targetMeta, [{ field: prop.pivotTable }]);
476
+ const populate = this.autoJoinOneToOneOwner(prop.targetMeta, [{ field: prop.pivotEntity }]);
381
477
  const fields = this.buildFields(prop.targetMeta, (options?.populate ?? []), [], qb, options?.fields);
382
478
  qb.select(fields).populate(populate).where(where).orderBy(orderBy).setLockMode(options?.lockMode, options?.lockTableAliases);
383
479
  if (owners.length === 1 && (options?.offset != null || options?.limit != null)) {
384
480
  qb.limit(options.limit, options.offset);
385
481
  }
482
+ if (prop.targetMeta.schema !== '*' && pivotMeta.schema === '*' && options?.schema) {
483
+ // eslint-disable-next-line dot-notation
484
+ qb['finalize']();
485
+ // eslint-disable-next-line dot-notation
486
+ Object.values(qb['_joins']).forEach(join => {
487
+ join.schema = options.schema;
488
+ });
489
+ }
386
490
  const items = owners.length ? await this.rethrow(qb.execute('all')) : [];
387
491
  const map = {};
388
492
  const pkProps = ownerMeta.getPrimaryProps();
@@ -407,26 +511,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
407
511
  async execute(queryOrKnex, params = [], method = 'all', ctx) {
408
512
  return this.rethrow(this.connection.execute(queryOrKnex, params, method, ctx));
409
513
  }
410
- getTableName(meta, options) {
411
- let tableName = this.platform.quoteIdentifier(meta.collection);
412
- if (options.schema === '*') {
413
- return this.platform.quoteIdentifier(this.config.get('schema')) + '.' + tableName;
414
- }
415
- if (meta.schema || options.schema) {
416
- tableName = this.platform.quoteIdentifier(options.schema ?? meta.schema) + '.' + tableName;
417
- }
418
- return tableName;
419
- }
420
514
  /**
421
515
  * 1:1 owner side needs to be marked for population so QB auto-joins the owner id
422
516
  */
423
517
  autoJoinOneToOneOwner(meta, populate, fields = []) {
424
- if (!this.config.get('autoJoinOneToOneOwner') || fields.length > 0) {
518
+ if (!this.config.get('autoJoinOneToOneOwner')) {
425
519
  return populate;
426
520
  }
427
521
  const relationsToPopulate = populate.map(({ field }) => field);
428
522
  const toPopulate = meta.relations
429
523
  .filter(prop => prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
524
+ .filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
430
525
  .map(prop => ({ field: prop.name, strategy: prop.strategy }));
431
526
  return [...populate, ...toPopulate];
432
527
  }
@@ -506,13 +601,27 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
506
601
  }
507
602
  return prop.fieldNames;
508
603
  }
509
- createQueryBuilder(entityName, ctx, write, convertCustomTypes) {
510
- const qb = new QueryBuilder_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, write ? 'write' : 'read');
604
+ /** @internal */
605
+ createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes) {
606
+ const connectionType = this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
607
+ const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType);
511
608
  if (!convertCustomTypes) {
512
609
  qb.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
513
610
  }
514
611
  return qb;
515
612
  }
613
+ resolveConnectionType(args) {
614
+ if (args.ctx) {
615
+ return 'write';
616
+ }
617
+ else if (args.connectionType) {
618
+ return args.connectionType;
619
+ }
620
+ else if (this.config.get('preferReadReplicas') === true) {
621
+ return 'read';
622
+ }
623
+ return 'write';
624
+ }
516
625
  extractManyToMany(entityName, data) {
517
626
  if (!this.metadata.has(entityName)) {
518
627
  return {};
@@ -541,7 +650,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
541
650
  deleteDiff = [];
542
651
  }
543
652
  if (deleteDiff === true || deleteDiff.length > 0) {
544
- const qb1 = this.createQueryBuilder(prop.pivotTable, options?.ctx, true)
653
+ const qb1 = this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
545
654
  .withSchema(this.getSchemaName(meta, options))
546
655
  .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
547
656
  const knex = qb1.getKnex();
@@ -562,11 +671,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
562
671
  });
563
672
  /* istanbul ignore else */
564
673
  if (this.platform.allowsMultiInsert()) {
565
- await this.nativeInsertMany(prop.pivotTable, items, { ...options, convertCustomTypes: false, processCollections: false });
674
+ await this.nativeInsertMany(prop.pivotEntity, items, { ...options, convertCustomTypes: false, processCollections: false });
566
675
  }
567
676
  else {
568
677
  await core_1.Utils.runSerial(items, item => {
569
- return this.createQueryBuilder(prop.pivotTable, options?.ctx, true)
678
+ return this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
570
679
  .unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
571
680
  .withSchema(this.getSchemaName(meta, options))
572
681
  .insert(item)
@@ -575,7 +684,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
575
684
  }
576
685
  }
577
686
  async lockPessimistic(entity, options) {
578
- const meta = entity.__helper.__meta;
687
+ const meta = (0, core_1.helper)(entity).__meta;
579
688
  const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES).withSchema(options.schema ?? meta.schema);
580
689
  const cond = core_1.Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
581
690
  qb.select('1').where(cond).setLockMode(options.lockMode, options.lockTableAliases);
@@ -601,6 +710,43 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
601
710
  });
602
711
  return orderBy;
603
712
  }
713
+ normalizeFields(fields, prefix = '') {
714
+ const ret = [];
715
+ for (const field of fields) {
716
+ if (typeof field === 'string') {
717
+ ret.push(prefix + field);
718
+ continue;
719
+ }
720
+ if (core_1.Utils.isPlainObject(field)) {
721
+ for (const key of Object.keys(field)) {
722
+ ret.push(...this.normalizeFields(field[key], key + '.'));
723
+ }
724
+ }
725
+ }
726
+ return ret;
727
+ }
728
+ processField(meta, prop, field, ret, populate, joinedProps, qb) {
729
+ if (!prop || (prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner)) {
730
+ return;
731
+ }
732
+ if (prop.reference === core_1.ReferenceType.EMBEDDED) {
733
+ if (prop.object) {
734
+ ret.push(prop.fieldNames[0]);
735
+ return;
736
+ }
737
+ const parts = field.split('.');
738
+ const top = parts.shift();
739
+ for (const key of Object.keys(prop.embeddedProps)) {
740
+ if (!top || key === top) {
741
+ this.processField(meta, prop.embeddedProps[key], parts.join('.'), ret, populate, joinedProps, qb);
742
+ }
743
+ }
744
+ return;
745
+ }
746
+ if (prop.fieldNames) {
747
+ ret.push(prop.fieldNames[0]);
748
+ }
749
+ }
604
750
  buildFields(meta, populate, joinedProps, qb, fields) {
605
751
  const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
606
752
  const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
@@ -611,11 +757,21 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
611
757
  ret.push(...this.getFieldsForJoinedLoad(qb, meta, fields, populate));
612
758
  }
613
759
  else if (fields) {
614
- for (const field of [...fields]) {
615
- if (core_1.Utils.isPlainObject(field) || field.toString().includes('.')) {
760
+ for (const field of this.normalizeFields(fields)) {
761
+ if (field === '*') {
762
+ ret.push('*');
616
763
  continue;
617
764
  }
618
- ret.push(field);
765
+ const parts = field.split('.');
766
+ const rootPropName = parts.shift(); // first one is the `prop`
767
+ const prop = core_1.QueryHelper.findProperty(rootPropName, {
768
+ metadata: this.metadata,
769
+ platform: this.platform,
770
+ entityName: meta.className,
771
+ where: {},
772
+ aliasMap: qb.getAliasMap(),
773
+ });
774
+ this.processField(meta, prop, parts.join('.'), ret, populate, joinedProps, qb);
619
775
  }
620
776
  ret.unshift(...meta.primaryKeys.filter(pk => !fields.includes(pk)));
621
777
  }
@@ -638,7 +794,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
638
794
  .filter(prop => prop.customType?.convertToDatabaseValueSQL || prop.customType?.convertToJSValueSQL)
639
795
  .forEach(prop => ret.push(prop.name));
640
796
  }
641
- return ret.length > 0 ? ret : ['*'];
797
+ return ret.length > 0 ? core_1.Utils.unique(ret) : ['*'];
642
798
  }
643
799
  }
644
800
  exports.AbstractSqlDriver = AbstractSqlDriver;
@@ -1,19 +1,19 @@
1
- import type { Constructor, EntityManager, EntityRepository, IDatabaseDriver } from '@mikro-orm/core';
1
+ import type { Constructor, EntityManager, EntityRepository, IDatabaseDriver, MikroORM } from '@mikro-orm/core';
2
2
  import { Platform } from '@mikro-orm/core';
3
3
  import type { SchemaHelper } from './schema';
4
- import { SchemaGenerator } from './schema';
4
+ import { SqlSchemaGenerator } from './schema';
5
5
  export declare abstract class AbstractSqlPlatform extends Platform {
6
6
  protected readonly schemaHelper?: SchemaHelper;
7
7
  usesPivotTable(): boolean;
8
8
  indexForeignKeys(): boolean;
9
- getRepositoryClass<T>(): Constructor<EntityRepository<T>>;
9
+ getRepositoryClass<T extends object>(): Constructor<EntityRepository<T>>;
10
10
  getSchemaHelper(): SchemaHelper | undefined;
11
- getSchemaGenerator(driver: IDatabaseDriver): SchemaGenerator;
12
- getEntityGenerator(em: EntityManager): any;
13
- getMigrator(em: EntityManager): any;
11
+ /** @inheritDoc */
12
+ lookupExtensions(orm: MikroORM): void;
13
+ getSchemaGenerator(driver: IDatabaseDriver, em?: EntityManager): SqlSchemaGenerator;
14
14
  quoteValue(value: any): string;
15
15
  formatQuery(sql: string, params: readonly any[]): string;
16
- getSearchJsonPropertySQL(path: string, type: string): string;
16
+ getSearchJsonPropertySQL(path: string, type: string, aliased: boolean): string;
17
17
  isRaw(value: any): boolean;
18
18
  supportsSchemas(): boolean;
19
19
  /** @inheritDoc */