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

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 (39) hide show
  1. package/AbstractSqlConnection.d.ts +8 -4
  2. package/AbstractSqlConnection.js +30 -18
  3. package/AbstractSqlDriver.d.ts +16 -17
  4. package/AbstractSqlDriver.js +283 -118
  5. package/AbstractSqlPlatform.d.ts +5 -4
  6. package/AbstractSqlPlatform.js +34 -5
  7. package/MonkeyPatchable.d.ts +1 -0
  8. package/MonkeyPatchable.js +3 -0
  9. package/README.md +64 -107
  10. package/SqlEntityManager.d.ts +2 -6
  11. package/SqlEntityManager.js +5 -9
  12. package/SqlEntityRepository.d.ts +7 -6
  13. package/SqlEntityRepository.js +11 -8
  14. package/index.mjs +22 -6
  15. package/package.json +8 -8
  16. package/query/ArrayCriteriaNode.d.ts +3 -3
  17. package/query/CriteriaNode.d.ts +8 -8
  18. package/query/CriteriaNode.js +16 -9
  19. package/query/CriteriaNodeFactory.d.ts +6 -6
  20. package/query/CriteriaNodeFactory.js +10 -13
  21. package/query/ObjectCriteriaNode.d.ts +3 -3
  22. package/query/ObjectCriteriaNode.js +12 -8
  23. package/query/QueryBuilder.d.ts +39 -19
  24. package/query/QueryBuilder.js +331 -111
  25. package/query/QueryBuilderHelper.d.ts +9 -13
  26. package/query/QueryBuilderHelper.js +153 -107
  27. package/query/ScalarCriteriaNode.d.ts +3 -2
  28. package/query/ScalarCriteriaNode.js +9 -6
  29. package/query/enums.js +1 -1
  30. package/schema/DatabaseSchema.d.ts +5 -2
  31. package/schema/DatabaseSchema.js +26 -8
  32. package/schema/DatabaseTable.d.ts +4 -2
  33. package/schema/DatabaseTable.js +55 -30
  34. package/schema/SchemaComparator.js +12 -2
  35. package/schema/SchemaHelper.d.ts +6 -4
  36. package/schema/SchemaHelper.js +11 -4
  37. package/schema/SqlSchemaGenerator.d.ts +3 -6
  38. package/schema/SqlSchemaGenerator.js +61 -28
  39. package/typings.d.ts +13 -7
@@ -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 { type Dictionary, type EntityData, type EntityKey, type EntityMetadata, type EntityProperty, type FlatQueryOrderMap, LockMode, 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,14 +15,13 @@ 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;
23
- joinOneToReference(prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary): JoinOptions;
24
- joinManyToOneReference(prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary): JoinOptions;
25
- joinManyToManyReference(prop: EntityProperty, ownerAlias: string, alias: string, pivotAlias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond: Dictionary, path: string): Dictionary<JoinOptions>;
26
- joinPivotTable(field: string, prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary): JoinOptions;
22
+ joinOneToReference(prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary, schema?: string): JoinOptions;
23
+ joinManyToOneReference(prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary, schema?: string): JoinOptions;
24
+ joinManyToManyReference(prop: EntityProperty, ownerAlias: string, alias: string, pivotAlias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond: Dictionary, path: string, schema?: string): Dictionary<JoinOptions>;
27
25
  processJoins(qb: Knex.QueryBuilder, joins: Dictionary<JoinOptions>, schema?: string): void;
28
26
  private processJoinClause;
29
27
  private wrapQueryGroup;
@@ -43,16 +41,14 @@ export declare class QueryBuilderHelper {
43
41
  }[], qb: Knex.QueryBuilder): void;
44
42
  appendQueryCondition(type: QueryType, cond: any, qb: Knex.QueryBuilder, operator?: '$and' | '$or', method?: 'where' | 'having'): void;
45
43
  private appendQuerySubCondition;
46
- private processCustomExpression;
47
44
  private processObjectSubCondition;
48
45
  private getOperatorReplacement;
49
46
  getQueryOrder(type: QueryType, orderBy: FlatQueryOrderMap | FlatQueryOrderMap[], populate: Dictionary<string>): string;
50
47
  getQueryOrderFromObject(type: QueryType, orderBy: FlatQueryOrderMap, populate: Dictionary<string>): string;
51
- finalize(type: QueryType, qb: Knex.QueryBuilder, meta?: EntityMetadata): void;
52
- splitField(field: string, greedyAlias?: boolean): [string, string];
48
+ finalize(type: QueryType, qb: Knex.QueryBuilder, meta?: EntityMetadata, data?: Dictionary, returning?: Field<any>[]): void;
49
+ splitField<T>(field: EntityKey<T>, greedyAlias?: boolean): [string, EntityKey<T>];
53
50
  getLockSQL(qb: Knex.QueryBuilder, lockMode: LockMode, lockTables?: string[]): void;
54
51
  updateVersionProperty(qb: Knex.QueryBuilder, data: Dictionary): void;
55
- static isCustomExpression(field: string, hasAlias?: boolean): boolean;
56
52
  private prefix;
57
53
  private appendGroupCondition;
58
54
  private isPrefixed;
@@ -61,9 +57,9 @@ export declare class QueryBuilderHelper {
61
57
  isTableNameAliasRequired(type?: QueryType): boolean;
62
58
  private mapData;
63
59
  }
64
- export interface Alias {
60
+ export interface Alias<T> {
65
61
  aliasName: string;
66
62
  entityName: string;
67
- metadata?: EntityMetadata;
63
+ metadata?: EntityMetadata<T>;
68
64
  subQuery?: Knex.QueryBuilder;
69
65
  }
@@ -8,6 +8,14 @@ const enums_1 = require("./enums");
8
8
  * @internal
9
9
  */
10
10
  class QueryBuilderHelper {
11
+ entityName;
12
+ alias;
13
+ aliasMap;
14
+ subQueries;
15
+ knex;
16
+ driver;
17
+ platform;
18
+ metadata;
11
19
  constructor(entityName, alias, aliasMap, subQueries, knex, driver) {
12
20
  this.entityName = entityName;
13
21
  this.alias = alias;
@@ -19,6 +27,10 @@ class QueryBuilderHelper {
19
27
  this.metadata = this.driver.getMetadata();
20
28
  }
21
29
  mapper(field, type = enums_1.QueryType.SELECT, value, alias) {
30
+ if (core_1.Utils.isRawSql(field)) {
31
+ return this.knex.raw(field.sql, field.params);
32
+ }
33
+ /* istanbul ignore next */
22
34
  if (typeof field !== 'string') {
23
35
  return field;
24
36
  }
@@ -36,12 +48,26 @@ class QueryBuilderHelper {
36
48
  parts.push(this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias));
37
49
  }
38
50
  }
51
+ // flatten the value if we see we are expanding nested composite key
52
+ // hackish, but cleaner solution would require quite a lot of refactoring
53
+ if (fields.length !== parts.length && Array.isArray(value)) {
54
+ value.forEach(row => {
55
+ if (Array.isArray(row)) {
56
+ const tmp = core_1.Utils.flatten(row);
57
+ row.length = 0;
58
+ row.push(...tmp);
59
+ }
60
+ });
61
+ }
39
62
  return this.knex.raw('(' + parts.map(part => this.knex.ref(part)).join(', ') + ')');
40
63
  }
41
- let ret = field;
42
- const customExpression = QueryBuilderHelper.isCustomExpression(field, !!alias);
64
+ const rawField = core_1.RawQueryFragment.getKnownFragment(field);
65
+ if (rawField) {
66
+ return this.knex.raw(rawField.sql, rawField.params);
67
+ }
43
68
  const [a, f] = this.splitField(field);
44
69
  const prop = this.getProperty(f, a);
70
+ let ret = field;
45
71
  // embeddable nested path instead of a regular property with table alias, reset alias
46
72
  if (prop?.name === a && prop.embeddedProps[f]) {
47
73
  return this.alias + '.' + prop.fieldNames[0];
@@ -56,7 +82,7 @@ class QueryBuilderHelper {
56
82
  const as = alias === null ? '' : ` as ${aliased}`;
57
83
  return this.knex.raw(`${prop.formula(alias2)}${as}`);
58
84
  }
59
- if (prop?.customType?.convertToJSValueSQL) {
85
+ if (prop?.hasConvertToJSValueSQL) {
60
86
  const prefixed = this.prefix(field, isTableNameAliasRequired, true);
61
87
  const valueSQL = prop.customType.convertToJSValueSQL(prefixed, this.platform);
62
88
  if (alias === null) {
@@ -65,15 +91,12 @@ class QueryBuilderHelper {
65
91
  return this.knex.raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[0])}`);
66
92
  }
67
93
  // do not wrap custom expressions
68
- if (!customExpression) {
94
+ if (!rawField) {
69
95
  ret = this.prefix(field);
70
96
  }
71
97
  if (alias) {
72
98
  ret += ' as ' + alias;
73
99
  }
74
- if (customExpression) {
75
- return this.knex.raw(ret, value);
76
- }
77
100
  if (!isTableNameAliasRequired || this.isPrefixed(ret) || noPrefix) {
78
101
  return ret;
79
102
  }
@@ -91,39 +114,40 @@ class QueryBuilderHelper {
91
114
  }
92
115
  return data;
93
116
  }
94
- joinOneToReference(prop, ownerAlias, alias, type, cond = {}) {
117
+ joinOneToReference(prop, ownerAlias, alias, type, cond = {}, schema) {
95
118
  const prop2 = prop.targetMeta.properties[prop.mappedBy || prop.inversedBy];
96
119
  const table = this.getTableName(prop.type);
97
- const schema = this.driver.getSchemaName(prop.targetMeta);
98
120
  const joinColumns = prop.owner ? prop.referencedColumnNames : prop2.joinColumns;
99
121
  const inverseJoinColumns = prop.referencedColumnNames;
100
122
  const primaryKeys = prop.owner ? prop.joinColumns : prop2.referencedColumnNames;
123
+ schema ??= prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta);
101
124
  return {
102
125
  prop, type, cond, ownerAlias, alias, table, schema,
103
126
  joinColumns, inverseJoinColumns, primaryKeys,
104
127
  };
105
128
  }
106
- joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}) {
129
+ joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}, schema) {
107
130
  return {
108
131
  prop, type, cond, ownerAlias, alias,
109
132
  table: this.getTableName(prop.type),
110
- schema: this.driver.getSchemaName(prop.targetMeta),
133
+ schema: this.driver.getSchemaName(prop.targetMeta, { schema }),
111
134
  joinColumns: prop.referencedColumnNames,
112
135
  primaryKeys: prop.fieldNames,
113
136
  };
114
137
  }
115
- joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path) {
138
+ joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path, schema) {
116
139
  const pivotMeta = this.metadata.find(prop.pivotEntity);
117
140
  const ret = {
118
141
  [`${ownerAlias}.${prop.name}#${pivotAlias}`]: {
119
- prop, type, cond, ownerAlias,
142
+ prop, type, ownerAlias,
120
143
  alias: pivotAlias,
121
144
  inverseAlias: alias,
122
145
  joinColumns: prop.joinColumns,
123
146
  inverseJoinColumns: prop.inverseJoinColumns,
124
147
  primaryKeys: prop.referencedColumnNames,
148
+ cond: {},
125
149
  table: pivotMeta.tableName,
126
- schema: this.driver.getSchemaName(pivotMeta),
150
+ schema: this.driver.getSchemaName(pivotMeta, { schema }),
127
151
  path: path.endsWith('[pivot]') ? path : `${path}[pivot]`,
128
152
  },
129
153
  };
@@ -131,41 +155,54 @@ class QueryBuilderHelper {
131
155
  return ret;
132
156
  }
133
157
  const prop2 = prop.owner ? pivotMeta.relations[1] : pivotMeta.relations[0];
134
- ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type);
158
+ ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type, cond, schema);
135
159
  ret[`${pivotAlias}.${prop2.name}#${alias}`].path = path;
136
160
  return ret;
137
161
  }
138
- joinPivotTable(field, prop, ownerAlias, alias, type, cond = {}) {
139
- const pivotMeta = this.metadata.find(field);
140
- const prop2 = pivotMeta.relations[0] === prop ? pivotMeta.relations[1] : pivotMeta.relations[0];
141
- return {
142
- prop, type, cond, ownerAlias, alias,
143
- table: pivotMeta.collection,
144
- schema: pivotMeta.schema,
145
- joinColumns: prop.joinColumns,
146
- inverseJoinColumns: prop2.joinColumns,
147
- primaryKeys: prop.referencedColumnNames,
148
- };
149
- }
150
162
  processJoins(qb, joins, schema) {
151
163
  Object.values(joins).forEach(join => {
152
- let table = `${join.table} as ${join.alias}`;
164
+ let table = join.table;
153
165
  const method = join.type === 'innerJoin' ? 'inner join' : 'left join';
166
+ const conditions = [];
167
+ const params = [];
154
168
  schema = join.schema && join.schema !== '*' ? join.schema : schema;
155
169
  if (schema) {
156
170
  table = `${schema}.${table}`;
157
171
  }
158
- const conditions = [];
159
- const params = [];
160
- join.primaryKeys.forEach((primaryKey, idx) => {
161
- const left = `${join.ownerAlias}.${primaryKey}`;
162
- const right = `${join.alias}.${join.joinColumns[idx]}`;
163
- conditions.push(`${this.knex.ref(left)} = ${this.knex.ref(right)}`);
164
- });
172
+ if (!join.subquery) {
173
+ join.primaryKeys.forEach((primaryKey, idx) => {
174
+ const right = `${join.alias}.${join.joinColumns[idx]}`;
175
+ if (join.prop.formula) {
176
+ const left = join.prop.formula(join.ownerAlias);
177
+ conditions.push(`${left} = ${this.knex.ref(right)}`);
178
+ return;
179
+ }
180
+ const left = join.prop.kind === core_1.ReferenceKind.SCALAR && join.prop.persist === false ? primaryKey : `${join.ownerAlias}.${primaryKey}`;
181
+ conditions.push(`${this.knex.ref(left)} = ${this.knex.ref(right)}`);
182
+ });
183
+ }
184
+ if (join.prop.targetMeta?.discriminatorValue && !join.path?.endsWith('[pivot]')) {
185
+ const typeProperty = join.prop.targetMeta.root.discriminatorColumn;
186
+ const alias = join.inverseAlias ?? join.alias;
187
+ join.cond[`${alias}.${typeProperty}`] = join.prop.targetMeta.discriminatorValue;
188
+ }
165
189
  Object.keys(join.cond).forEach(key => {
166
- conditions.push(this.processJoinClause(key, join.cond[key], params));
190
+ const needsPrefix = key.includes('.') || core_1.Utils.isOperator(key) || core_1.RawQueryFragment.isKnownFragment(key);
191
+ const newKey = needsPrefix ? key : `${join.alias}.${key}`;
192
+ conditions.push(this.processJoinClause(newKey, join.cond[key], params));
167
193
  });
168
- return qb.joinRaw(`${method} ${this.knex.ref(table)} on ${conditions.join(' and ')}`, params);
194
+ let sql = method + ' ';
195
+ if (join.subquery) {
196
+ sql += `(${join.subquery})`;
197
+ }
198
+ else {
199
+ sql += this.knex.ref(table);
200
+ }
201
+ sql += ` as ${this.knex.ref(join.alias)}`;
202
+ if (conditions.length > 0) {
203
+ sql += ` on ${conditions.join(' and ')}`;
204
+ }
205
+ return qb.joinRaw(sql, params);
169
206
  });
170
207
  }
171
208
  processJoinClause(key, value, params, operator = '$eq') {
@@ -212,20 +249,14 @@ class QueryBuilderHelper {
212
249
  if (operator === '$exists') {
213
250
  value = null;
214
251
  }
215
- if (QueryBuilderHelper.isCustomExpression(key)) {
216
- // unwind parameters when ? found in field name
217
- const count = key.concat('?').match(/\?/g).length - 1;
218
- const values = core_1.Utils.asArray(value);
219
- const params1 = values.slice(0, count).map((c) => core_1.Utils.isObject(c) ? JSON.stringify(c) : c);
220
- const params2 = values.slice(count);
221
- const k = this.mapper(key, enums_1.QueryType.SELECT, params1);
222
- const { sql, bindings } = k.toSQL().toNative();
223
- if (params2.length > 0) {
224
- params.push(...bindings);
225
- params.push(...params2);
226
- return sql + ' = ?';
252
+ const rawField = core_1.RawQueryFragment.getKnownFragment(key);
253
+ if (rawField) {
254
+ let sql = rawField.sql;
255
+ params.push(...rawField.params);
256
+ params.push(...core_1.Utils.asArray(value));
257
+ if (core_1.Utils.asArray(value).length > 0) {
258
+ sql += ' = ?';
227
259
  }
228
- params.push(...bindings);
229
260
  return sql;
230
261
  }
231
262
  const sql = this.mapper(key);
@@ -241,7 +272,7 @@ class QueryBuilderHelper {
241
272
  return `(${parts.join(` ${core_1.GroupOperator[operator]} `)})`;
242
273
  }
243
274
  mapJoinColumns(type, join) {
244
- if (join.prop && join.prop.reference === core_1.ReferenceType.ONE_TO_ONE && !join.prop.owner) {
275
+ if (join.prop && join.prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !join.prop.owner) {
245
276
  return join.prop.fieldNames.map((fieldName, idx) => {
246
277
  return this.mapper(`${join.alias}.${join.inverseJoinColumns[idx]}`, type, undefined, fieldName);
247
278
  });
@@ -254,7 +285,7 @@ class QueryBuilderHelper {
254
285
  isOneToOneInverse(field) {
255
286
  const meta = this.metadata.find(this.entityName);
256
287
  const prop = meta.properties[field];
257
- return prop && prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner;
288
+ return prop && prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner;
258
289
  }
259
290
  getTableName(entityName) {
260
291
  const meta = this.metadata.find(entityName);
@@ -293,13 +324,13 @@ class QueryBuilderHelper {
293
324
  }
294
325
  appendOnConflictClause(type, onConflict, qb) {
295
326
  onConflict.forEach(item => {
296
- const sub = qb.onConflict(item.fields);
327
+ const sub = item.fields.length > 0 ? qb.onConflict(item.fields) : qb.onConflict();
297
328
  core_1.Utils.runIfNotEmpty(() => sub.ignore(), item.ignore);
298
329
  core_1.Utils.runIfNotEmpty(() => {
299
330
  let mergeParam = item.merge;
300
331
  if (core_1.Utils.isObject(item.merge)) {
301
332
  mergeParam = {};
302
- Object.keys(item.merge).forEach(key => {
333
+ core_1.Utils.keys(item.merge).forEach(key => {
303
334
  const k = this.mapper(key, type);
304
335
  mergeParam[k] = item.merge[key];
305
336
  });
@@ -330,34 +361,29 @@ class QueryBuilderHelper {
330
361
  }
331
362
  appendQuerySubCondition(qb, type, method, cond, key, operator) {
332
363
  const m = operator === '$or' ? 'orWhere' : method;
364
+ if (cond[key] instanceof core_1.RawQueryFragment) {
365
+ cond[key] = this.knex.raw(cond[key].sql, cond[key].params);
366
+ }
333
367
  if (this.isSimpleRegExp(cond[key])) {
334
368
  return void qb[m](this.mapper(key, type), 'like', this.getRegExpParam(cond[key]));
335
369
  }
336
370
  if (core_1.Utils.isPlainObject(cond[key]) || cond[key] instanceof RegExp) {
337
371
  return this.processObjectSubCondition(cond, key, qb, method, m, type);
338
372
  }
339
- if (QueryBuilderHelper.isCustomExpression(key)) {
340
- return this.processCustomExpression(qb, m, key, cond, type);
341
- }
342
373
  const op = cond[key] === null ? 'is' : '=';
374
+ const raw = core_1.RawQueryFragment.getKnownFragment(key);
375
+ if (raw) {
376
+ const value = core_1.Utils.asArray(cond[key]);
377
+ if (value.length > 0) {
378
+ return void qb[m](this.knex.raw(raw.sql, raw.params), op, value[0]);
379
+ }
380
+ return void qb[m](this.knex.raw(raw.sql, raw.params));
381
+ }
343
382
  if (this.subQueries[key]) {
344
383
  return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), op, cond[key]);
345
384
  }
346
385
  qb[m](this.mapper(key, type, cond[key], null), op, cond[key]);
347
386
  }
348
- processCustomExpression(clause, m, key, cond, type) {
349
- // unwind parameters when ? found in field name
350
- const count = key.concat('?').match(/\?/g).length - 1;
351
- const value = core_1.Utils.asArray(cond[key]);
352
- const params1 = value.slice(0, count).map((c) => core_1.Utils.isObject(c) ? JSON.stringify(c) : c);
353
- const params2 = value.slice(count);
354
- const k = this.mapper(key, type, params1);
355
- if (params2.length > 0) {
356
- const val = params2.length === 1 && params2[0] === null ? null : this.knex.raw('?', params2);
357
- return void clause[m](k, val);
358
- }
359
- clause[m](k);
360
- }
361
387
  processObjectSubCondition(cond, key, qb, method, m, type) {
362
388
  // grouped condition for one field
363
389
  let value = cond[key];
@@ -376,7 +402,8 @@ class QueryBuilderHelper {
376
402
  const replacement = this.getOperatorReplacement(op, value);
377
403
  const fields = core_1.Utils.splitPrimaryKeys(key);
378
404
  if (fields.length > 1 && Array.isArray(value[op]) && !value[op].every((v) => Array.isArray(v))) {
379
- value[op] = this.knex.raw(`(${fields.map(() => '?').join(', ')})`, value[op]);
405
+ const tmp = value[op].length === 1 && core_1.Utils.isPlainObject(value[op][0]) ? fields.map(f => value[op][0][f]) : value[op];
406
+ value[op] = this.knex.raw(`(${fields.map(() => '?').join(', ')})`, tmp);
380
407
  }
381
408
  if (this.subQueries[key]) {
382
409
  return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), replacement, value[op]);
@@ -394,7 +421,8 @@ class QueryBuilderHelper {
394
421
  }));
395
422
  }
396
423
  else {
397
- qb[m](this.mapper(key, type, undefined, null), replacement, value[op]);
424
+ const mappedKey = this.mapper(key, type, value[op], null);
425
+ qb[m](mappedKey, replacement, value[op]);
398
426
  }
399
427
  }
400
428
  getOperatorReplacement(op, value) {
@@ -422,36 +450,58 @@ class QueryBuilderHelper {
422
450
  }
423
451
  getQueryOrderFromObject(type, orderBy, populate) {
424
452
  const ret = [];
425
- Object.keys(orderBy).forEach(k => {
426
- const direction = orderBy[k];
453
+ Object.keys(orderBy).forEach(key => {
454
+ const direction = orderBy[key];
427
455
  const order = core_1.Utils.isNumber(direction) ? core_1.QueryOrderNumeric[direction] : direction;
428
- if (QueryBuilderHelper.isCustomExpression(k)) {
429
- ret.push(`${k} ${order.toLowerCase()}`);
456
+ const raw = core_1.RawQueryFragment.getKnownFragment(key);
457
+ if (raw) {
458
+ ret.push(`${this.platform.formatQuery(raw.sql, raw.params)} ${order.toLowerCase()}`);
430
459
  return;
431
460
  }
432
- // eslint-disable-next-line prefer-const
433
- let [alias, field] = this.splitField(k, true);
434
- alias = populate[alias] || alias;
435
- core_1.Utils.splitPrimaryKeys(field).forEach(f => {
436
- const prop = this.getProperty(f, alias);
437
- const noPrefix = (prop && prop.persist === false && !prop.formula) || QueryBuilderHelper.isCustomExpression(f);
438
- const column = this.mapper(noPrefix ? f : `${alias}.${f}`, type, undefined, null);
461
+ core_1.Utils.splitPrimaryKeys(key).forEach(f => {
462
+ // eslint-disable-next-line prefer-const
463
+ let [alias, field] = this.splitField(f, true);
464
+ alias = populate[alias] || alias;
465
+ const prop = this.getProperty(field, alias);
466
+ const noPrefix = (prop && prop.persist === false && !prop.formula) || core_1.RawQueryFragment.isKnownFragment(f);
467
+ const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
439
468
  /* istanbul ignore next */
440
469
  const rawColumn = core_1.Utils.isString(column) ? column.split('.').map(e => this.knex.ref(e)).join('.') : column;
441
470
  const customOrder = prop?.customOrder;
442
- const colPart = customOrder
471
+ let colPart = customOrder
443
472
  ? this.platform.generateCustomOrder(rawColumn, customOrder)
444
473
  : rawColumn;
474
+ if (core_1.Utils.isRawSql(colPart)) {
475
+ colPart = this.platform.formatQuery(colPart.sql, colPart.params);
476
+ }
445
477
  ret.push(`${colPart} ${order.toLowerCase()}`);
446
478
  });
447
479
  });
448
480
  return ret.join(', ');
449
481
  }
450
- finalize(type, qb, meta) {
451
- const useReturningStatement = type === enums_1.QueryType.INSERT && this.platform.usesReturningStatement() && meta && !meta.compositePK;
452
- if (useReturningStatement) {
453
- const returningProps = meta.hydrateProps.filter(prop => prop.persist !== false && (prop.primary || prop.defaultRaw));
454
- qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
482
+ finalize(type, qb, meta, data, returning) {
483
+ if (!meta || !data || !this.platform.usesReturningStatement()) {
484
+ return;
485
+ }
486
+ // always respect explicit returning hint
487
+ if (returning && returning.length > 0) {
488
+ qb.returning(returning.map(field => this.mapper(field, type)));
489
+ return;
490
+ }
491
+ if (type === enums_1.QueryType.INSERT) {
492
+ const returningProps = meta.hydrateProps
493
+ .filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
494
+ .filter(prop => !(prop.name in data));
495
+ if (returningProps.length > 0) {
496
+ qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
497
+ }
498
+ return;
499
+ }
500
+ if (type === enums_1.QueryType.UPDATE) {
501
+ const returningProps = meta.hydrateProps.filter(prop => core_1.Utils.isRawSql(data[prop.name]));
502
+ if (returningProps.length > 0) {
503
+ qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
504
+ }
455
505
  }
456
506
  }
457
507
  splitField(field, greedyAlias = false) {
@@ -489,23 +539,18 @@ class QueryBuilderHelper {
489
539
  }
490
540
  const versionProperty = meta.properties[meta.versionProperty];
491
541
  let sql = this.platform.quoteIdentifier(versionProperty.fieldNames[0]) + ' + 1';
492
- if (versionProperty.type.toLowerCase() === 'date') {
542
+ if (versionProperty.runtimeType === 'Date') {
493
543
  sql = this.platform.getCurrentTimestampSQL(versionProperty.length);
494
544
  }
495
545
  qb.update(versionProperty.fieldNames[0], this.knex.raw(sql));
496
546
  }
497
- static isCustomExpression(field, hasAlias = false) {
498
- // if we do not have alias, we don't consider spaces as custom expressions
499
- const re = hasAlias ? /[ ?<>=()'"`:]|^\d/ : /[?<>=()'"`:]|^\d/;
500
- return !!field.match(re);
501
- }
502
547
  prefix(field, always = false, quote = false) {
503
548
  let ret;
504
549
  if (!this.isPrefixed(field)) {
505
550
  const alias = always ? (quote ? this.alias : this.platform.quoteIdentifier(this.alias)) + '.' : '';
506
551
  const fieldName = this.fieldName(field, this.alias, always);
507
- if (QueryBuilderHelper.isCustomExpression(fieldName)) {
508
- return fieldName;
552
+ if (fieldName instanceof core_1.RawQueryFragment) {
553
+ return fieldName.sql;
509
554
  }
510
555
  ret = alias + fieldName;
511
556
  }
@@ -545,15 +590,15 @@ class QueryBuilderHelper {
545
590
  }
546
591
  if (prop.fieldNameRaw) {
547
592
  if (!always) {
548
- return prop.fieldNameRaw
549
- .replace(/\[::alias::]\.?/g, '')
550
- .replace(this.platform.quoteIdentifier('') + '.', '');
593
+ return (0, core_1.raw)(prop.fieldNameRaw
594
+ .replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE + '\\.?', 'g'), '')
595
+ .replace(this.platform.quoteIdentifier('') + '.', ''));
551
596
  }
552
597
  if (alias) {
553
- return prop.fieldNameRaw.replace(/\[::alias::]/g, alias);
598
+ return (0, core_1.raw)(prop.fieldNameRaw.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE, 'g'), alias));
554
599
  }
555
600
  /* istanbul ignore next */
556
- return prop.fieldNameRaw;
601
+ return (0, core_1.raw)(prop.fieldNameRaw);
557
602
  }
558
603
  /* istanbul ignore next */
559
604
  return prop.fieldNames?.[0] ?? field;
@@ -564,7 +609,7 @@ class QueryBuilderHelper {
564
609
  // check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
565
610
  if (alias && meta) {
566
611
  const prop = meta.properties[alias];
567
- if (prop?.reference === core_1.ReferenceType.EMBEDDED) {
612
+ if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
568
613
  // we want to select the full object property so hydration works as expected
569
614
  if (prop.object) {
570
615
  return prop;
@@ -596,17 +641,18 @@ class QueryBuilderHelper {
596
641
  return;
597
642
  }
598
643
  if (prop.joinColumns && Array.isArray(data[k])) {
599
- const copy = data[k];
644
+ const copy = core_1.Utils.flatten(data[k]);
600
645
  delete data[k];
601
646
  prop.joinColumns.forEach((joinColumn, idx) => data[joinColumn] = copy[idx]);
602
647
  return;
603
648
  }
604
649
  if (prop.customType && convertCustomTypes && !this.platform.isRaw(data[k])) {
605
- data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, { fromQuery: true, key: k, mode: 'query' });
650
+ data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, { fromQuery: true, key: k, mode: 'query-data' });
606
651
  }
607
- if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(data[k])) {
652
+ if (prop.hasConvertToDatabaseValueSQL && !this.platform.isRaw(data[k])) {
608
653
  const quoted = this.platform.quoteValue(data[k]);
609
- data[k] = this.knex.raw(prop.customType.convertToDatabaseValueSQL(quoted, this.platform).replace(/\?/, '\\?'));
654
+ const sql = prop.customType.convertToDatabaseValueSQL(quoted, this.platform);
655
+ data[k] = this.knex.raw(sql.replace(/\?/g, '\\?'));
610
656
  }
611
657
  if (!prop.customType && (Array.isArray(data[k]) || core_1.Utils.isPlainObject(data[k]))) {
612
658
  data[k] = JSON.stringify(data[k]);
@@ -3,7 +3,8 @@ import type { IQueryBuilder } from '../typings';
3
3
  /**
4
4
  * @internal
5
5
  */
6
- export declare class ScalarCriteriaNode extends CriteriaNode {
7
- process<T>(qb: IQueryBuilder<T>, alias?: string): any;
6
+ export declare class ScalarCriteriaNode<T extends object> extends CriteriaNode<T> {
7
+ process(qb: IQueryBuilder<T>, alias?: string): any;
8
+ willAutoJoin<T>(qb: IQueryBuilder<T>, alias?: string): boolean;
8
9
  shouldJoin(): boolean;
9
10
  }
@@ -13,23 +13,26 @@ class ScalarCriteriaNode extends CriteriaNode_1.CriteriaNode {
13
13
  const parentPath = this.parent.getPath(); // the parent is always there, otherwise `shouldJoin` would return `false`
14
14
  const nestedAlias = qb.getAliasForJoinPath(path) || qb.getNextAlias(this.prop?.pivotTable ?? this.entityName);
15
15
  const field = `${alias}.${this.prop.name}`;
16
- const type = this.prop.reference === core_1.ReferenceType.MANY_TO_MANY ? 'pivotJoin' : 'leftJoin';
16
+ const type = this.prop.kind === core_1.ReferenceKind.MANY_TO_MANY ? 'pivotJoin' : 'leftJoin';
17
17
  qb.join(field, nestedAlias, undefined, type, path);
18
18
  // select the owner as virtual property when joining from 1:1 inverse side, but only if the parent is root entity
19
- if (this.prop.reference === core_1.ReferenceType.ONE_TO_ONE && !parentPath.includes('.')) {
19
+ if (this.prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !parentPath.includes('.')) {
20
20
  qb.addSelect(field);
21
21
  }
22
22
  }
23
23
  return this.payload;
24
24
  }
25
+ willAutoJoin(qb, alias) {
26
+ return this.shouldJoin();
27
+ }
25
28
  shouldJoin() {
26
29
  if (!this.parent || !this.prop) {
27
30
  return false;
28
31
  }
29
- switch (this.prop.reference) {
30
- case core_1.ReferenceType.ONE_TO_MANY: return true;
31
- case core_1.ReferenceType.MANY_TO_MANY: return true;
32
- case core_1.ReferenceType.ONE_TO_ONE: return !this.prop.owner;
32
+ switch (this.prop.kind) {
33
+ case core_1.ReferenceKind.ONE_TO_MANY: return true;
34
+ case core_1.ReferenceKind.MANY_TO_MANY: return true;
35
+ case core_1.ReferenceKind.ONE_TO_ONE: return !this.prop.owner;
33
36
  default: return false; // SCALAR, MANY_TO_ONE
34
37
  }
35
38
  }
package/query/enums.js CHANGED
@@ -9,4 +9,4 @@ var QueryType;
9
9
  QueryType["INSERT"] = "INSERT";
10
10
  QueryType["UPDATE"] = "UPDATE";
11
11
  QueryType["DELETE"] = "DELETE";
12
- })(QueryType = exports.QueryType || (exports.QueryType = {}));
12
+ })(QueryType || (exports.QueryType = QueryType = {}));
@@ -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';
@@ -10,14 +10,17 @@ export declare class DatabaseSchema {
10
10
  readonly name: string;
11
11
  private tables;
12
12
  private namespaces;
13
+ private nativeEnums;
13
14
  constructor(platform: AbstractSqlPlatform, name: string);
14
15
  addTable(name: string, schema: string | undefined | null, comment?: string): DatabaseTable;
15
16
  getTables(): DatabaseTable[];
16
17
  getTable(name: string): DatabaseTable | undefined;
17
18
  hasTable(name: string): boolean;
19
+ setNativeEnums(nativeEnums: Dictionary<unknown[]>): void;
20
+ getNativeEnums(): Dictionary<unknown[]>;
18
21
  hasNamespace(namespace: string): boolean;
19
22
  getNamespaces(): string[];
20
- static create(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, config: Configuration, schemaName?: string): Promise<DatabaseSchema>;
23
+ static create(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, schemas?: string[]): Promise<DatabaseSchema>;
21
24
  static fromMetadata(metadata: EntityMetadata[], platform: AbstractSqlPlatform, config: Configuration, schemaName?: string): DatabaseSchema;
22
25
  private static getSchemaName;
23
26
  private static shouldHaveColumn;