@mikro-orm/knex 6.0.0-dev.13 → 6.0.0-dev.130

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 (43) hide show
  1. package/AbstractSqlConnection.d.ts +8 -4
  2. package/AbstractSqlConnection.js +30 -18
  3. package/AbstractSqlDriver.d.ts +21 -23
  4. package/AbstractSqlDriver.js +393 -224
  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/PivotCollectionPersister.d.ts +17 -0
  10. package/PivotCollectionPersister.js +128 -0
  11. package/README.md +64 -107
  12. package/SqlEntityManager.d.ts +2 -6
  13. package/SqlEntityManager.js +5 -9
  14. package/SqlEntityRepository.d.ts +7 -6
  15. package/SqlEntityRepository.js +11 -8
  16. package/index.mjs +24 -6
  17. package/package.json +8 -8
  18. package/query/ArrayCriteriaNode.d.ts +3 -3
  19. package/query/CriteriaNode.d.ts +8 -8
  20. package/query/CriteriaNode.js +16 -9
  21. package/query/CriteriaNodeFactory.d.ts +6 -6
  22. package/query/CriteriaNodeFactory.js +17 -13
  23. package/query/ObjectCriteriaNode.d.ts +3 -3
  24. package/query/ObjectCriteriaNode.js +14 -10
  25. package/query/QueryBuilder.d.ts +49 -21
  26. package/query/QueryBuilder.js +388 -132
  27. package/query/QueryBuilderHelper.d.ts +11 -16
  28. package/query/QueryBuilderHelper.js +194 -155
  29. package/query/ScalarCriteriaNode.d.ts +3 -2
  30. package/query/ScalarCriteriaNode.js +10 -6
  31. package/query/enums.d.ts +7 -0
  32. package/query/enums.js +10 -2
  33. package/schema/DatabaseSchema.d.ts +5 -2
  34. package/schema/DatabaseSchema.js +27 -9
  35. package/schema/DatabaseTable.d.ts +4 -2
  36. package/schema/DatabaseTable.js +67 -32
  37. package/schema/SchemaComparator.d.ts +3 -3
  38. package/schema/SchemaComparator.js +50 -30
  39. package/schema/SchemaHelper.d.ts +6 -4
  40. package/schema/SchemaHelper.js +18 -7
  41. package/schema/SqlSchemaGenerator.d.ts +3 -6
  42. package/schema/SqlSchemaGenerator.js +69 -34
  43. package/typings.d.ts +17 -10
@@ -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
  }
@@ -29,22 +41,42 @@ class QueryBuilderHelper {
29
41
  for (const p of fields) {
30
42
  const [a, f] = this.splitField(p);
31
43
  const prop = this.getProperty(f, a);
32
- if (prop) {
44
+ const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
45
+ if (fkIdx2 !== -1) {
46
+ parts.push(this.mapper(a !== this.alias ? `${a}.${prop.fieldNames[fkIdx2]}` : prop.fieldNames[fkIdx2], type, value, alias));
47
+ }
48
+ else if (prop) {
33
49
  parts.push(...prop.fieldNames.map(f => this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias)));
34
50
  }
35
51
  else {
36
52
  parts.push(this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias));
37
53
  }
38
54
  }
55
+ // flatten the value if we see we are expanding nested composite key
56
+ // hackish, but cleaner solution would require quite a lot of refactoring
57
+ if (fields.length !== parts.length && Array.isArray(value)) {
58
+ value.forEach(row => {
59
+ if (Array.isArray(row)) {
60
+ const tmp = core_1.Utils.flatten(row);
61
+ row.length = 0;
62
+ row.push(...tmp);
63
+ }
64
+ });
65
+ }
39
66
  return this.knex.raw('(' + parts.map(part => this.knex.ref(part)).join(', ') + ')');
40
67
  }
41
- let ret = field;
42
- const customExpression = QueryBuilderHelper.isCustomExpression(field, !!alias);
68
+ const rawField = core_1.RawQueryFragment.getKnownFragment(field);
69
+ if (rawField) {
70
+ return this.knex.raw(rawField.sql, rawField.params);
71
+ }
43
72
  const [a, f] = this.splitField(field);
44
73
  const prop = this.getProperty(f, a);
74
+ const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
75
+ const fkIdx = fkIdx2 === -1 ? 0 : fkIdx2;
76
+ let ret = field;
45
77
  // embeddable nested path instead of a regular property with table alias, reset alias
46
78
  if (prop?.name === a && prop.embeddedProps[f]) {
47
- return this.alias + '.' + prop.fieldNames[0];
79
+ return this.alias + '.' + prop.fieldNames[fkIdx];
48
80
  }
49
81
  const noPrefix = prop && prop.persist === false;
50
82
  if (prop?.fieldNameRaw) {
@@ -56,24 +88,29 @@ class QueryBuilderHelper {
56
88
  const as = alias === null ? '' : ` as ${aliased}`;
57
89
  return this.knex.raw(`${prop.formula(alias2)}${as}`);
58
90
  }
59
- if (prop?.customType?.convertToJSValueSQL) {
60
- const prefixed = this.prefix(field, isTableNameAliasRequired, true);
61
- const valueSQL = prop.customType.convertToJSValueSQL(prefixed, this.platform);
91
+ if (prop?.hasConvertToJSValueSQL) {
92
+ let valueSQL;
93
+ if (prop.fieldNames.length > 1 && fkIdx !== -1) {
94
+ const fk = prop.targetMeta.getPrimaryProps()[fkIdx];
95
+ const prefixed = this.prefix(field, isTableNameAliasRequired, true, fkIdx);
96
+ valueSQL = fk.customType.convertToJSValueSQL(prefixed, this.platform);
97
+ }
98
+ else {
99
+ const prefixed = this.prefix(field, isTableNameAliasRequired, true);
100
+ valueSQL = prop.customType.convertToJSValueSQL(prefixed, this.platform);
101
+ }
62
102
  if (alias === null) {
63
103
  return this.knex.raw(valueSQL);
64
104
  }
65
- return this.knex.raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[0])}`);
105
+ return this.knex.raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[fkIdx])}`);
66
106
  }
67
107
  // do not wrap custom expressions
68
- if (!customExpression) {
69
- ret = this.prefix(field);
108
+ if (!rawField) {
109
+ ret = this.prefix(field, false, false, fkIdx);
70
110
  }
71
111
  if (alias) {
72
112
  ret += ' as ' + alias;
73
113
  }
74
- if (customExpression) {
75
- return this.knex.raw(ret, value);
76
- }
77
114
  if (!isTableNameAliasRequired || this.isPrefixed(ret) || noPrefix) {
78
115
  return ret;
79
116
  }
@@ -84,88 +121,104 @@ class QueryBuilderHelper {
84
121
  return data.map(d => this.processData(d, convertCustomTypes, true));
85
122
  }
86
123
  const meta = this.metadata.find(this.entityName);
87
- data = this.mapData(data, meta?.properties, convertCustomTypes);
124
+ data = this.driver.mapDataToFieldNames(data, true, meta?.properties, convertCustomTypes);
88
125
  if (!core_1.Utils.hasObjectKeys(data) && meta && multi) {
89
126
  /* istanbul ignore next */
90
127
  data[meta.primaryKeys[0]] = this.platform.usesDefaultKeyword() ? this.knex.raw('default') : undefined;
91
128
  }
92
129
  return data;
93
130
  }
94
- joinOneToReference(prop, ownerAlias, alias, type, cond = {}) {
131
+ joinOneToReference(prop, ownerAlias, alias, type, cond = {}, schema) {
95
132
  const prop2 = prop.targetMeta.properties[prop.mappedBy || prop.inversedBy];
96
133
  const table = this.getTableName(prop.type);
97
- const schema = this.driver.getSchemaName(prop.targetMeta);
98
134
  const joinColumns = prop.owner ? prop.referencedColumnNames : prop2.joinColumns;
99
135
  const inverseJoinColumns = prop.referencedColumnNames;
100
136
  const primaryKeys = prop.owner ? prop.joinColumns : prop2.referencedColumnNames;
137
+ schema ??= prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta);
101
138
  return {
102
139
  prop, type, cond, ownerAlias, alias, table, schema,
103
140
  joinColumns, inverseJoinColumns, primaryKeys,
104
141
  };
105
142
  }
106
- joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}) {
143
+ joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}, schema) {
107
144
  return {
108
145
  prop, type, cond, ownerAlias, alias,
109
146
  table: this.getTableName(prop.type),
110
- schema: this.driver.getSchemaName(prop.targetMeta),
147
+ schema: prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta, { schema }),
111
148
  joinColumns: prop.referencedColumnNames,
112
149
  primaryKeys: prop.fieldNames,
113
150
  };
114
151
  }
115
- joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path) {
152
+ joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path, schema) {
116
153
  const pivotMeta = this.metadata.find(prop.pivotEntity);
117
154
  const ret = {
118
155
  [`${ownerAlias}.${prop.name}#${pivotAlias}`]: {
119
- prop, type, cond, ownerAlias,
156
+ prop, type, ownerAlias,
120
157
  alias: pivotAlias,
121
158
  inverseAlias: alias,
122
159
  joinColumns: prop.joinColumns,
123
160
  inverseJoinColumns: prop.inverseJoinColumns,
124
161
  primaryKeys: prop.referencedColumnNames,
162
+ cond: {},
125
163
  table: pivotMeta.tableName,
126
- schema: this.driver.getSchemaName(pivotMeta),
164
+ schema: prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(pivotMeta, { schema }),
127
165
  path: path.endsWith('[pivot]') ? path : `${path}[pivot]`,
128
166
  },
129
167
  };
130
- if (type === 'pivotJoin') {
168
+ if (type === enums_1.JoinType.pivotJoin) {
131
169
  return ret;
132
170
  }
133
171
  const prop2 = prop.owner ? pivotMeta.relations[1] : pivotMeta.relations[0];
134
- ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type);
172
+ ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type, cond, schema);
135
173
  ret[`${pivotAlias}.${prop2.name}#${alias}`].path = path;
174
+ const tmp = prop2.referencedTableName.split('.');
175
+ ret[`${pivotAlias}.${prop2.name}#${alias}`].schema ??= tmp.length > 1 ? tmp[0] : undefined;
136
176
  return ret;
137
177
  }
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
178
  processJoins(qb, joins, schema) {
151
179
  Object.values(joins).forEach(join => {
152
- let table = `${join.table} as ${join.alias}`;
153
- const method = join.type === 'innerJoin' ? 'inner join' : 'left join';
180
+ let table = join.table;
181
+ const method = join.type === enums_1.JoinType.pivotJoin ? 'left join' : join.type;
182
+ const conditions = [];
183
+ const params = [];
154
184
  schema = join.schema && join.schema !== '*' ? join.schema : schema;
155
185
  if (schema) {
156
186
  table = `${schema}.${table}`;
157
187
  }
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
- });
188
+ if (!join.subquery) {
189
+ join.primaryKeys.forEach((primaryKey, idx) => {
190
+ const right = `${join.alias}.${join.joinColumns[idx]}`;
191
+ if (join.prop.formula) {
192
+ const left = join.prop.formula(join.ownerAlias);
193
+ conditions.push(`${left} = ${this.knex.ref(right)}`);
194
+ return;
195
+ }
196
+ const left = `${join.ownerAlias}.${primaryKey}`;
197
+ conditions.push(`${this.knex.ref(left)} = ${this.knex.ref(right)}`);
198
+ });
199
+ }
200
+ if (join.prop.targetMeta?.discriminatorValue && !join.path?.endsWith('[pivot]')) {
201
+ const typeProperty = join.prop.targetMeta.root.discriminatorColumn;
202
+ const alias = join.inverseAlias ?? join.alias;
203
+ join.cond[`${alias}.${typeProperty}`] = join.prop.targetMeta.discriminatorValue;
204
+ }
165
205
  Object.keys(join.cond).forEach(key => {
166
- conditions.push(this.processJoinClause(key, join.cond[key], params));
206
+ const needsPrefix = key.includes('.') || core_1.Utils.isOperator(key) || core_1.RawQueryFragment.isKnownFragment(key);
207
+ const newKey = needsPrefix ? key : `${join.alias}.${key}`;
208
+ conditions.push(this.processJoinClause(newKey, join.cond[key], params));
167
209
  });
168
- return qb.joinRaw(`${method} ${this.knex.ref(table)} on ${conditions.join(' and ')}`, params);
210
+ let sql = method + ' ';
211
+ if (join.subquery) {
212
+ sql += `(${join.subquery})`;
213
+ }
214
+ else {
215
+ sql += this.knex.ref(table);
216
+ }
217
+ sql += ` as ${this.knex.ref(join.alias)}`;
218
+ if (conditions.length > 0) {
219
+ sql += ` on ${conditions.join(' and ')}`;
220
+ }
221
+ return qb.joinRaw(sql, params);
169
222
  });
170
223
  }
171
224
  processJoinClause(key, value, params, operator = '$eq') {
@@ -212,20 +265,14 @@ class QueryBuilderHelper {
212
265
  if (operator === '$exists') {
213
266
  value = null;
214
267
  }
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 + ' = ?';
268
+ const rawField = core_1.RawQueryFragment.getKnownFragment(key);
269
+ if (rawField) {
270
+ let sql = rawField.sql;
271
+ params.push(...rawField.params);
272
+ params.push(...core_1.Utils.asArray(value));
273
+ if (core_1.Utils.asArray(value).length > 0) {
274
+ sql += ' = ?';
227
275
  }
228
- params.push(...bindings);
229
276
  return sql;
230
277
  }
231
278
  const sql = this.mapper(key);
@@ -241,7 +288,7 @@ class QueryBuilderHelper {
241
288
  return `(${parts.join(` ${core_1.GroupOperator[operator]} `)})`;
242
289
  }
243
290
  mapJoinColumns(type, join) {
244
- if (join.prop && join.prop.reference === core_1.ReferenceType.ONE_TO_ONE && !join.prop.owner) {
291
+ if (join.prop && join.prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !join.prop.owner) {
245
292
  return join.prop.fieldNames.map((fieldName, idx) => {
246
293
  return this.mapper(`${join.alias}.${join.inverseJoinColumns[idx]}`, type, undefined, fieldName);
247
294
  });
@@ -251,10 +298,10 @@ class QueryBuilderHelper {
251
298
  ...join.inverseJoinColumns.map(col => this.mapper(`${join.alias}.${col}`, type, undefined, `fk__${col}`)),
252
299
  ];
253
300
  }
254
- isOneToOneInverse(field) {
255
- const meta = this.metadata.find(this.entityName);
301
+ isOneToOneInverse(field, meta) {
302
+ meta ??= this.metadata.find(this.entityName);
256
303
  const prop = meta.properties[field];
257
- return prop && prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner;
304
+ return prop && prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner;
258
305
  }
259
306
  getTableName(entityName) {
260
307
  const meta = this.metadata.find(entityName);
@@ -293,13 +340,13 @@ class QueryBuilderHelper {
293
340
  }
294
341
  appendOnConflictClause(type, onConflict, qb) {
295
342
  onConflict.forEach(item => {
296
- const sub = qb.onConflict(item.fields);
343
+ const sub = item.fields.length > 0 ? qb.onConflict(item.fields) : qb.onConflict();
297
344
  core_1.Utils.runIfNotEmpty(() => sub.ignore(), item.ignore);
298
345
  core_1.Utils.runIfNotEmpty(() => {
299
346
  let mergeParam = item.merge;
300
347
  if (core_1.Utils.isObject(item.merge)) {
301
348
  mergeParam = {};
302
- Object.keys(item.merge).forEach(key => {
349
+ core_1.Utils.keys(item.merge).forEach(key => {
303
350
  const k = this.mapper(key, type);
304
351
  mergeParam[k] = item.merge[key];
305
352
  });
@@ -330,38 +377,37 @@ class QueryBuilderHelper {
330
377
  }
331
378
  appendQuerySubCondition(qb, type, method, cond, key, operator) {
332
379
  const m = operator === '$or' ? 'orWhere' : method;
380
+ if (cond[key] instanceof core_1.RawQueryFragment) {
381
+ cond[key] = this.knex.raw(cond[key].sql, cond[key].params);
382
+ }
333
383
  if (this.isSimpleRegExp(cond[key])) {
334
384
  return void qb[m](this.mapper(key, type), 'like', this.getRegExpParam(cond[key]));
335
385
  }
336
386
  if (core_1.Utils.isPlainObject(cond[key]) || cond[key] instanceof RegExp) {
337
387
  return this.processObjectSubCondition(cond, key, qb, method, m, type);
338
388
  }
339
- if (QueryBuilderHelper.isCustomExpression(key)) {
340
- return this.processCustomExpression(qb, m, key, cond, type);
341
- }
342
389
  const op = cond[key] === null ? 'is' : '=';
390
+ const raw = core_1.RawQueryFragment.getKnownFragment(key);
391
+ if (raw) {
392
+ const value = core_1.Utils.asArray(cond[key]);
393
+ if (value.length > 0) {
394
+ return void qb[m](this.knex.raw(raw.sql, raw.params), op, value[0]);
395
+ }
396
+ return void qb[m](this.knex.raw(raw.sql, raw.params));
397
+ }
343
398
  if (this.subQueries[key]) {
344
399
  return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), op, cond[key]);
345
400
  }
346
401
  qb[m](this.mapper(key, type, cond[key], null), op, cond[key]);
347
402
  }
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
403
  processObjectSubCondition(cond, key, qb, method, m, type) {
362
404
  // grouped condition for one field
363
405
  let value = cond[key];
364
- if (core_1.Utils.getObjectKeysSize(value) > 1) {
406
+ const size = core_1.Utils.getObjectKeysSize(value);
407
+ if (core_1.Utils.isPlainObject(value) && size === 0) {
408
+ return;
409
+ }
410
+ if (size > 1) {
365
411
  const subCondition = Object.entries(value).map(([subKey, subValue]) => ({ [key]: { [subKey]: subValue } }));
366
412
  return subCondition.forEach(sub => this.appendQueryCondition(type, sub, qb, '$and', method));
367
413
  }
@@ -370,13 +416,15 @@ class QueryBuilderHelper {
370
416
  }
371
417
  // operators
372
418
  const op = Object.keys(core_1.QueryOperator).find(op => op in value);
419
+ /* istanbul ignore next */
373
420
  if (!op) {
374
421
  throw new Error(`Invalid query condition: ${(0, util_1.inspect)(cond)}`);
375
422
  }
376
423
  const replacement = this.getOperatorReplacement(op, value);
377
424
  const fields = core_1.Utils.splitPrimaryKeys(key);
378
425
  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]);
426
+ const tmp = value[op].length === 1 && core_1.Utils.isPlainObject(value[op][0]) ? fields.map(f => value[op][0][f]) : value[op];
427
+ value[op] = this.knex.raw(`(${fields.map(() => '?').join(', ')})`, tmp);
380
428
  }
381
429
  if (this.subQueries[key]) {
382
430
  return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), replacement, value[op]);
@@ -394,7 +442,8 @@ class QueryBuilderHelper {
394
442
  }));
395
443
  }
396
444
  else {
397
- qb[m](this.mapper(key, type, undefined, null), replacement, value[op]);
445
+ const mappedKey = this.mapper(key, type, value[op], null);
446
+ qb[m](mappedKey, replacement, value[op]);
398
447
  }
399
448
  }
400
449
  getOperatorReplacement(op, value) {
@@ -422,36 +471,63 @@ class QueryBuilderHelper {
422
471
  }
423
472
  getQueryOrderFromObject(type, orderBy, populate) {
424
473
  const ret = [];
425
- Object.keys(orderBy).forEach(k => {
426
- const direction = orderBy[k];
474
+ Object.keys(orderBy).forEach(key => {
475
+ const direction = orderBy[key];
427
476
  const order = core_1.Utils.isNumber(direction) ? core_1.QueryOrderNumeric[direction] : direction;
428
- if (QueryBuilderHelper.isCustomExpression(k)) {
429
- ret.push(`${k} ${order.toLowerCase()}`);
477
+ const raw = core_1.RawQueryFragment.getKnownFragment(key);
478
+ if (raw) {
479
+ ret.push(`${this.platform.formatQuery(raw.sql, raw.params)} ${order.toLowerCase()}`);
430
480
  return;
431
481
  }
432
- core_1.Utils.splitPrimaryKeys(k).forEach(f => {
482
+ core_1.Utils.splitPrimaryKeys(key).forEach(f => {
433
483
  // eslint-disable-next-line prefer-const
434
484
  let [alias, field] = this.splitField(f, true);
435
485
  alias = populate[alias] || alias;
436
486
  const prop = this.getProperty(field, alias);
437
- const noPrefix = (prop && prop.persist === false && !prop.formula) || QueryBuilderHelper.isCustomExpression(f);
487
+ const noPrefix = (prop && prop.persist === false && !prop.formula) || core_1.RawQueryFragment.isKnownFragment(f);
438
488
  const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
439
489
  /* istanbul ignore next */
440
490
  const rawColumn = core_1.Utils.isString(column) ? column.split('.').map(e => this.knex.ref(e)).join('.') : column;
441
491
  const customOrder = prop?.customOrder;
442
- const colPart = customOrder
492
+ let colPart = customOrder
443
493
  ? this.platform.generateCustomOrder(rawColumn, customOrder)
444
494
  : rawColumn;
445
- ret.push(`${colPart} ${order.toLowerCase()}`);
495
+ if (core_1.Utils.isRawSql(colPart)) {
496
+ colPart = this.platform.formatQuery(colPart.sql, colPart.params);
497
+ }
498
+ if (Array.isArray(order)) {
499
+ order.forEach(part => ret.push(this.getQueryOrderFromObject(type, part, populate)));
500
+ }
501
+ else {
502
+ ret.push(`${colPart} ${order.toLowerCase()}`);
503
+ }
446
504
  });
447
505
  });
448
506
  return ret.join(', ');
449
507
  }
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)));
508
+ finalize(type, qb, meta, data, returning) {
509
+ if (!meta || !data || !this.platform.usesReturningStatement()) {
510
+ return;
511
+ }
512
+ // always respect explicit returning hint
513
+ if (returning && returning.length > 0) {
514
+ qb.returning(returning.map(field => this.mapper(field, type)));
515
+ return;
516
+ }
517
+ if (type === enums_1.QueryType.INSERT) {
518
+ const returningProps = meta.hydrateProps
519
+ .filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
520
+ .filter(prop => !(prop.name in data));
521
+ if (returningProps.length > 0) {
522
+ qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
523
+ }
524
+ return;
525
+ }
526
+ if (type === enums_1.QueryType.UPDATE) {
527
+ const returningProps = meta.hydrateProps.filter(prop => core_1.Utils.isRawSql(data[prop.name]));
528
+ if (returningProps.length > 0) {
529
+ qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
530
+ }
455
531
  }
456
532
  }
457
533
  splitField(field, greedyAlias = false) {
@@ -489,30 +565,25 @@ class QueryBuilderHelper {
489
565
  }
490
566
  const versionProperty = meta.properties[meta.versionProperty];
491
567
  let sql = this.platform.quoteIdentifier(versionProperty.fieldNames[0]) + ' + 1';
492
- if (versionProperty.type.toLowerCase() === 'date') {
568
+ if (versionProperty.runtimeType === 'Date') {
493
569
  sql = this.platform.getCurrentTimestampSQL(versionProperty.length);
494
570
  }
495
571
  qb.update(versionProperty.fieldNames[0], this.knex.raw(sql));
496
572
  }
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
- prefix(field, always = false, quote = false) {
573
+ prefix(field, always = false, quote = false, idx) {
503
574
  let ret;
504
575
  if (!this.isPrefixed(field)) {
505
576
  const alias = always ? (quote ? this.alias : this.platform.quoteIdentifier(this.alias)) + '.' : '';
506
- const fieldName = this.fieldName(field, this.alias, always);
507
- if (QueryBuilderHelper.isCustomExpression(fieldName)) {
508
- return fieldName;
577
+ const fieldName = this.fieldName(field, this.alias, always, idx);
578
+ if (fieldName instanceof core_1.RawQueryFragment) {
579
+ return fieldName.sql;
509
580
  }
510
581
  ret = alias + fieldName;
511
582
  }
512
583
  else {
513
584
  const [a, ...rest] = field.split('.');
514
585
  const f = rest.join('.');
515
- ret = a + '.' + this.fieldName(f, a);
586
+ ret = a + '.' + this.fieldName(f, a, always, idx);
516
587
  }
517
588
  if (quote) {
518
589
  return this.platform.quoteIdentifier(ret);
@@ -538,25 +609,25 @@ class QueryBuilderHelper {
538
609
  isPrefixed(field) {
539
610
  return !!field.match(/[\w`"[\]]+\./);
540
611
  }
541
- fieldName(field, alias, always) {
612
+ fieldName(field, alias, always, idx = 0) {
542
613
  const prop = this.getProperty(field, alias);
543
614
  if (!prop) {
544
615
  return field;
545
616
  }
546
617
  if (prop.fieldNameRaw) {
547
618
  if (!always) {
548
- return prop.fieldNameRaw
549
- .replace(/\[::alias::]\.?/g, '')
550
- .replace(this.platform.quoteIdentifier('') + '.', '');
619
+ return (0, core_1.raw)(prop.fieldNameRaw
620
+ .replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE + '\\.?', 'g'), '')
621
+ .replace(this.platform.quoteIdentifier('') + '.', ''));
551
622
  }
552
623
  if (alias) {
553
- return prop.fieldNameRaw.replace(/\[::alias::]/g, alias);
624
+ return (0, core_1.raw)(prop.fieldNameRaw.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE, 'g'), alias));
554
625
  }
555
626
  /* istanbul ignore next */
556
- return prop.fieldNameRaw;
627
+ return (0, core_1.raw)(prop.fieldNameRaw);
557
628
  }
558
629
  /* istanbul ignore next */
559
- return prop.fieldNames?.[0] ?? field;
630
+ return prop.fieldNames?.[idx] ?? field;
560
631
  }
561
632
  getProperty(field, alias) {
562
633
  const entityName = this.aliasMap[alias]?.entityName || this.entityName;
@@ -564,7 +635,7 @@ class QueryBuilderHelper {
564
635
  // check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
565
636
  if (alias && meta) {
566
637
  const prop = meta.properties[alias];
567
- if (prop?.reference === core_1.ReferenceType.EMBEDDED) {
638
+ if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
568
639
  // we want to select the full object property so hydration works as expected
569
640
  if (prop.object) {
570
641
  return prop;
@@ -574,48 +645,16 @@ class QueryBuilderHelper {
574
645
  return nest(prop);
575
646
  }
576
647
  }
577
- return meta?.properties[field];
648
+ if (meta) {
649
+ if (meta.properties[field]) {
650
+ return meta.properties[field];
651
+ }
652
+ return meta.relations.find(prop => prop.fieldNames?.some(name => field === name));
653
+ }
654
+ return undefined;
578
655
  }
579
656
  isTableNameAliasRequired(type) {
580
657
  return [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(type ?? enums_1.QueryType.SELECT);
581
658
  }
582
- mapData(data, properties, convertCustomTypes) {
583
- if (!properties) {
584
- return data;
585
- }
586
- data = Object.assign({}, data); // copy first
587
- Object.keys(data).forEach(k => {
588
- const prop = properties[k];
589
- if (!prop) {
590
- return;
591
- }
592
- if (prop.embeddedProps && !prop.object) {
593
- const copy = data[k];
594
- delete data[k];
595
- Object.assign(data, this.mapData(copy, prop.embeddedProps, convertCustomTypes));
596
- return;
597
- }
598
- if (prop.joinColumns && Array.isArray(data[k])) {
599
- const copy = data[k];
600
- delete data[k];
601
- prop.joinColumns.forEach((joinColumn, idx) => data[joinColumn] = copy[idx]);
602
- return;
603
- }
604
- 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' });
606
- }
607
- if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(data[k])) {
608
- const quoted = this.platform.quoteValue(data[k]);
609
- data[k] = this.knex.raw(prop.customType.convertToDatabaseValueSQL(quoted, this.platform).replace(/\?/, '\\?'));
610
- }
611
- if (!prop.customType && (Array.isArray(data[k]) || core_1.Utils.isPlainObject(data[k]))) {
612
- data[k] = JSON.stringify(data[k]);
613
- }
614
- if (prop.fieldNames) {
615
- core_1.Utils.renameKey(data, k, prop.fieldNames[0]);
616
- }
617
- });
618
- return data;
619
- }
620
659
  }
621
660
  exports.QueryBuilderHelper = QueryBuilderHelper;
@@ -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
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ScalarCriteriaNode = void 0;
4
4
  const core_1 = require("@mikro-orm/core");
5
5
  const CriteriaNode_1 = require("./CriteriaNode");
6
+ const enums_1 = require("./enums");
6
7
  /**
7
8
  * @internal
8
9
  */
@@ -13,23 +14,26 @@ class ScalarCriteriaNode extends CriteriaNode_1.CriteriaNode {
13
14
  const parentPath = this.parent.getPath(); // the parent is always there, otherwise `shouldJoin` would return `false`
14
15
  const nestedAlias = qb.getAliasForJoinPath(path) || qb.getNextAlias(this.prop?.pivotTable ?? this.entityName);
15
16
  const field = `${alias}.${this.prop.name}`;
16
- const type = this.prop.reference === core_1.ReferenceType.MANY_TO_MANY ? 'pivotJoin' : 'leftJoin';
17
+ const type = this.prop.kind === core_1.ReferenceKind.MANY_TO_MANY ? enums_1.JoinType.pivotJoin : enums_1.JoinType.leftJoin;
17
18
  qb.join(field, nestedAlias, undefined, type, path);
18
19
  // 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('.')) {
20
+ if (this.prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !parentPath.includes('.')) {
20
21
  qb.addSelect(field);
21
22
  }
22
23
  }
23
24
  return this.payload;
24
25
  }
26
+ willAutoJoin(qb, alias) {
27
+ return this.shouldJoin();
28
+ }
25
29
  shouldJoin() {
26
30
  if (!this.parent || !this.prop) {
27
31
  return false;
28
32
  }
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;
33
+ switch (this.prop.kind) {
34
+ case core_1.ReferenceKind.ONE_TO_MANY: return true;
35
+ case core_1.ReferenceKind.MANY_TO_MANY: return true;
36
+ case core_1.ReferenceKind.ONE_TO_ONE: return !this.prop.owner;
33
37
  default: return false; // SCALAR, MANY_TO_ONE
34
38
  }
35
39
  }