@mikro-orm/knex 6.0.0-dev.32 → 6.0.0-dev.321

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 (44) hide show
  1. package/AbstractSqlConnection.d.ts +22 -4
  2. package/AbstractSqlConnection.js +45 -20
  3. package/AbstractSqlDriver.d.ts +30 -24
  4. package/AbstractSqlDriver.js +606 -268
  5. package/AbstractSqlPlatform.d.ts +9 -5
  6. package/AbstractSqlPlatform.js +25 -29
  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 +4 -8
  13. package/SqlEntityManager.js +8 -12
  14. package/SqlEntityRepository.d.ts +7 -5
  15. package/SqlEntityRepository.js +11 -7
  16. package/index.mjs +24 -3
  17. package/package.json +8 -8
  18. package/query/ArrayCriteriaNode.d.ts +3 -2
  19. package/query/ArrayCriteriaNode.js +7 -2
  20. package/query/CriteriaNode.d.ts +6 -4
  21. package/query/CriteriaNode.js +22 -9
  22. package/query/CriteriaNodeFactory.d.ts +1 -1
  23. package/query/CriteriaNodeFactory.js +27 -11
  24. package/query/ObjectCriteriaNode.d.ts +3 -2
  25. package/query/ObjectCriteriaNode.js +72 -28
  26. package/query/QueryBuilder.d.ts +69 -36
  27. package/query/QueryBuilder.js +469 -146
  28. package/query/QueryBuilderHelper.d.ts +13 -18
  29. package/query/QueryBuilderHelper.js +199 -171
  30. package/query/ScalarCriteriaNode.d.ts +3 -2
  31. package/query/ScalarCriteriaNode.js +14 -4
  32. package/query/enums.d.ts +7 -0
  33. package/query/enums.js +10 -2
  34. package/schema/DatabaseSchema.d.ts +5 -2
  35. package/schema/DatabaseSchema.js +26 -8
  36. package/schema/DatabaseTable.d.ts +12 -4
  37. package/schema/DatabaseTable.js +370 -42
  38. package/schema/SchemaComparator.d.ts +7 -5
  39. package/schema/SchemaComparator.js +69 -35
  40. package/schema/SchemaHelper.d.ts +7 -5
  41. package/schema/SchemaHelper.js +30 -11
  42. package/schema/SqlSchemaGenerator.d.ts +11 -48
  43. package/schema/SqlSchemaGenerator.js +84 -30
  44. package/typings.d.ts +27 -8
@@ -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,7 +41,11 @@ 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 {
@@ -49,13 +65,18 @@ class QueryBuilderHelper {
49
65
  }
50
66
  return this.knex.raw('(' + parts.map(part => this.knex.ref(part)).join(', ') + ')');
51
67
  }
52
- let ret = field;
53
- 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
+ }
54
72
  const [a, f] = this.splitField(field);
55
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;
56
77
  // embeddable nested path instead of a regular property with table alias, reset alias
57
78
  if (prop?.name === a && prop.embeddedProps[f]) {
58
- return this.alias + '.' + prop.fieldNames[0];
79
+ return this.alias + '.' + prop.fieldNames[fkIdx];
59
80
  }
60
81
  const noPrefix = prop && prop.persist === false;
61
82
  if (prop?.fieldNameRaw) {
@@ -67,25 +88,29 @@ class QueryBuilderHelper {
67
88
  const as = alias === null ? '' : ` as ${aliased}`;
68
89
  return this.knex.raw(`${prop.formula(alias2)}${as}`);
69
90
  }
70
- if (prop?.customType?.convertToJSValueSQL) {
71
- const prefixed = this.prefix(field, isTableNameAliasRequired, true);
72
- 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
+ }
73
102
  if (alias === null) {
74
103
  return this.knex.raw(valueSQL);
75
104
  }
76
- 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])}`);
77
106
  }
78
107
  // do not wrap custom expressions
79
- if (!customExpression) {
80
- ret = this.prefix(field);
108
+ if (!rawField) {
109
+ ret = this.prefix(field, false, false, fkIdx);
81
110
  }
82
111
  if (alias) {
83
112
  ret += ' as ' + alias;
84
113
  }
85
- if (customExpression) {
86
- const bindings = core_1.Utils.asArray(value).slice(0, ret.match(/\?/g)?.length ?? 0);
87
- return this.knex.raw(ret, bindings);
88
- }
89
114
  if (!isTableNameAliasRequired || this.isPrefixed(ret) || noPrefix) {
90
115
  return ret;
91
116
  }
@@ -96,94 +121,110 @@ class QueryBuilderHelper {
96
121
  return data.map(d => this.processData(d, convertCustomTypes, true));
97
122
  }
98
123
  const meta = this.metadata.find(this.entityName);
99
- data = this.mapData(data, meta?.properties, convertCustomTypes);
124
+ data = this.driver.mapDataToFieldNames(data, true, meta?.properties, convertCustomTypes);
100
125
  if (!core_1.Utils.hasObjectKeys(data) && meta && multi) {
101
126
  /* istanbul ignore next */
102
127
  data[meta.primaryKeys[0]] = this.platform.usesDefaultKeyword() ? this.knex.raw('default') : undefined;
103
128
  }
104
129
  return data;
105
130
  }
106
- joinOneToReference(prop, ownerAlias, alias, type, cond = {}) {
131
+ joinOneToReference(prop, ownerAlias, alias, type, cond = {}, schema) {
107
132
  const prop2 = prop.targetMeta.properties[prop.mappedBy || prop.inversedBy];
108
133
  const table = this.getTableName(prop.type);
109
- const schema = this.driver.getSchemaName(prop.targetMeta);
110
134
  const joinColumns = prop.owner ? prop.referencedColumnNames : prop2.joinColumns;
111
135
  const inverseJoinColumns = prop.referencedColumnNames;
112
136
  const primaryKeys = prop.owner ? prop.joinColumns : prop2.referencedColumnNames;
137
+ schema ??= prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta);
113
138
  return {
114
139
  prop, type, cond, ownerAlias, alias, table, schema,
115
140
  joinColumns, inverseJoinColumns, primaryKeys,
116
141
  };
117
142
  }
118
- joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}) {
143
+ joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}, schema) {
119
144
  return {
120
145
  prop, type, cond, ownerAlias, alias,
121
146
  table: this.getTableName(prop.type),
122
- schema: this.driver.getSchemaName(prop.targetMeta),
147
+ schema: prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta, { schema }),
123
148
  joinColumns: prop.referencedColumnNames,
124
149
  primaryKeys: prop.fieldNames,
125
150
  };
126
151
  }
127
- joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path) {
152
+ joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path, schema) {
128
153
  const pivotMeta = this.metadata.find(prop.pivotEntity);
129
154
  const ret = {
130
155
  [`${ownerAlias}.${prop.name}#${pivotAlias}`]: {
131
- prop, type, cond, ownerAlias,
156
+ prop, type, ownerAlias,
132
157
  alias: pivotAlias,
133
158
  inverseAlias: alias,
134
159
  joinColumns: prop.joinColumns,
135
160
  inverseJoinColumns: prop.inverseJoinColumns,
136
161
  primaryKeys: prop.referencedColumnNames,
162
+ cond: {},
137
163
  table: pivotMeta.tableName,
138
- schema: this.driver.getSchemaName(pivotMeta),
164
+ schema: prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(pivotMeta, { schema }),
139
165
  path: path.endsWith('[pivot]') ? path : `${path}[pivot]`,
140
166
  },
141
167
  };
142
- if (type === 'pivotJoin') {
168
+ if (type === enums_1.JoinType.pivotJoin) {
143
169
  return ret;
144
170
  }
145
171
  const prop2 = prop.owner ? pivotMeta.relations[1] : pivotMeta.relations[0];
146
- 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);
147
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;
148
176
  return ret;
149
177
  }
150
- joinPivotTable(field, prop, ownerAlias, alias, type, cond = {}) {
151
- const pivotMeta = this.metadata.find(field);
152
- const prop2 = pivotMeta.relations[0] === prop ? pivotMeta.relations[1] : pivotMeta.relations[0];
153
- return {
154
- prop, type, cond, ownerAlias, alias,
155
- table: pivotMeta.collection,
156
- schema: pivotMeta.schema,
157
- joinColumns: prop.joinColumns,
158
- inverseJoinColumns: prop2.joinColumns,
159
- primaryKeys: prop.referencedColumnNames,
160
- };
161
- }
162
178
  processJoins(qb, joins, schema) {
163
179
  Object.values(joins).forEach(join => {
164
- let table = `${join.table} as ${join.alias}`;
165
- 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 = [];
166
184
  schema = join.schema && join.schema !== '*' ? join.schema : schema;
167
185
  if (schema) {
168
186
  table = `${schema}.${table}`;
169
187
  }
170
- const conditions = [];
171
- const params = [];
172
- join.primaryKeys.forEach((primaryKey, idx) => {
173
- const left = `${join.ownerAlias}.${primaryKey}`;
174
- const right = `${join.alias}.${join.joinColumns[idx]}`;
175
- conditions.push(`${this.knex.ref(left)} = ${this.knex.ref(right)}`);
176
- });
177
- Object.keys(join.cond).forEach(key => {
178
- conditions.push(this.processJoinClause(key, join.cond[key], params));
179
- });
180
- return qb.joinRaw(`${method} ${this.knex.ref(table)} on ${conditions.join(' and ')}`, params);
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
+ }
205
+ for (const key of Object.keys(join.cond)) {
206
+ const hasPrefix = key.includes('.') || core_1.Utils.isOperator(key) || core_1.RawQueryFragment.isKnownFragment(key);
207
+ const newKey = hasPrefix ? key : `${join.alias}.${key}`;
208
+ conditions.push(this.processJoinClause(newKey, join.cond[key], join.alias, params));
209
+ }
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);
181
222
  });
182
223
  }
183
- processJoinClause(key, value, params, operator = '$eq') {
224
+ processJoinClause(key, value, alias, params, operator = '$eq') {
184
225
  if (core_1.Utils.isGroupOperator(key) && Array.isArray(value)) {
185
226
  const parts = value.map(sub => {
186
- return this.wrapQueryGroup(Object.keys(sub).map(k => this.processJoinClause(k, sub[k], params)));
227
+ return this.wrapQueryGroup(Object.keys(sub).map(k => this.processJoinClause(k, sub[k], alias, params)));
187
228
  });
188
229
  return this.wrapQueryGroup(parts, key);
189
230
  }
@@ -195,11 +236,11 @@ class QueryBuilderHelper {
195
236
  value = this.platform.getRegExpValue(value);
196
237
  }
197
238
  if (core_1.Utils.isOperator(key, false) && core_1.Utils.isPlainObject(value)) {
198
- const parts = Object.keys(value).map(k => this.processJoinClause(k, value[k], params, key));
239
+ const parts = Object.keys(value).map(k => this.processJoinClause(k, value[k], alias, params, key));
199
240
  return key === '$not' ? `not ${this.wrapQueryGroup(parts)}` : this.wrapQueryGroup(parts);
200
241
  }
201
242
  if (core_1.Utils.isPlainObject(value) && Object.keys(value).every(k => core_1.Utils.isOperator(k, false))) {
202
- const parts = Object.keys(value).map(op => this.processJoinClause(key, value[op], params, op));
243
+ const parts = Object.keys(value).map(op => this.processJoinClause(key, value[op], alias, params, op));
203
244
  return this.wrapQueryGroup(parts);
204
245
  }
205
246
  operator = operator === '$not' ? '$eq' : operator;
@@ -224,20 +265,14 @@ class QueryBuilderHelper {
224
265
  if (operator === '$exists') {
225
266
  value = null;
226
267
  }
227
- if (QueryBuilderHelper.isCustomExpression(key)) {
228
- // unwind parameters when ? found in field name
229
- const count = key.concat('?').match(/\?/g).length - 1;
230
- const values = core_1.Utils.asArray(value);
231
- const params1 = values.slice(0, count).map((c) => core_1.Utils.isObject(c) ? JSON.stringify(c) : c);
232
- const params2 = values.slice(count);
233
- const k = this.mapper(key, enums_1.QueryType.SELECT, params1);
234
- const { sql, bindings } = k.toSQL().toNative();
235
- if (params2.length > 0) {
236
- params.push(...bindings);
237
- params.push(...params2);
238
- return sql + ' = ?';
268
+ const rawField = core_1.RawQueryFragment.getKnownFragment(key);
269
+ if (rawField) {
270
+ let sql = rawField.sql.replaceAll(core_1.ALIAS_REPLACEMENT, alias);
271
+ params.push(...rawField.params);
272
+ params.push(...core_1.Utils.asArray(value));
273
+ if (core_1.Utils.asArray(value).length > 0) {
274
+ sql += ' = ?';
239
275
  }
240
- params.push(...bindings);
241
276
  return sql;
242
277
  }
243
278
  const sql = this.mapper(key);
@@ -253,9 +288,10 @@ class QueryBuilderHelper {
253
288
  return `(${parts.join(` ${core_1.GroupOperator[operator]} `)})`;
254
289
  }
255
290
  mapJoinColumns(type, join) {
256
- if (join.prop && join.prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !join.prop.owner) {
291
+ if (join.prop && [core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(join.prop.kind)) {
257
292
  return join.prop.fieldNames.map((fieldName, idx) => {
258
- return this.mapper(`${join.alias}.${join.inverseJoinColumns[idx]}`, type, undefined, fieldName);
293
+ const columns = join.prop.owner ? join.joinColumns : join.inverseJoinColumns;
294
+ return this.mapper(`${join.alias}.${columns[idx]}`, type, undefined, fieldName);
259
295
  });
260
296
  }
261
297
  return [
@@ -263,9 +299,9 @@ class QueryBuilderHelper {
263
299
  ...join.inverseJoinColumns.map(col => this.mapper(`${join.alias}.${col}`, type, undefined, `fk__${col}`)),
264
300
  ];
265
301
  }
266
- isOneToOneInverse(field) {
267
- const meta = this.metadata.find(this.entityName);
268
- const prop = meta.properties[field];
302
+ isOneToOneInverse(field, meta) {
303
+ meta ??= this.metadata.find(this.entityName);
304
+ const prop = meta.properties[field.replace(/:ref$/, '')];
269
305
  return prop && prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner;
270
306
  }
271
307
  getTableName(entityName) {
@@ -305,7 +341,7 @@ class QueryBuilderHelper {
305
341
  }
306
342
  appendOnConflictClause(type, onConflict, qb) {
307
343
  onConflict.forEach(item => {
308
- const sub = qb.onConflict(item.fields);
344
+ const sub = item.fields.length > 0 ? qb.onConflict(item.fields) : qb.onConflict();
309
345
  core_1.Utils.runIfNotEmpty(() => sub.ignore(), item.ignore);
310
346
  core_1.Utils.runIfNotEmpty(() => {
311
347
  let mergeParam = item.merge;
@@ -342,38 +378,37 @@ class QueryBuilderHelper {
342
378
  }
343
379
  appendQuerySubCondition(qb, type, method, cond, key, operator) {
344
380
  const m = operator === '$or' ? 'orWhere' : method;
381
+ if (cond[key] instanceof core_1.RawQueryFragment) {
382
+ cond[key] = this.knex.raw(cond[key].sql, cond[key].params);
383
+ }
345
384
  if (this.isSimpleRegExp(cond[key])) {
346
385
  return void qb[m](this.mapper(key, type), 'like', this.getRegExpParam(cond[key]));
347
386
  }
348
387
  if (core_1.Utils.isPlainObject(cond[key]) || cond[key] instanceof RegExp) {
349
388
  return this.processObjectSubCondition(cond, key, qb, method, m, type);
350
389
  }
351
- if (QueryBuilderHelper.isCustomExpression(key)) {
352
- return this.processCustomExpression(qb, m, key, cond, type);
353
- }
354
390
  const op = cond[key] === null ? 'is' : '=';
391
+ const raw = core_1.RawQueryFragment.getKnownFragment(key);
392
+ if (raw) {
393
+ const value = core_1.Utils.asArray(cond[key]);
394
+ if (value.length > 0) {
395
+ return void qb[m](this.knex.raw(raw.sql, raw.params), op, value[0]);
396
+ }
397
+ return void qb[m](this.knex.raw(raw.sql, raw.params));
398
+ }
355
399
  if (this.subQueries[key]) {
356
400
  return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), op, cond[key]);
357
401
  }
358
402
  qb[m](this.mapper(key, type, cond[key], null), op, cond[key]);
359
403
  }
360
- processCustomExpression(clause, m, key, cond, type) {
361
- // unwind parameters when ? found in field name
362
- const count = key.concat('?').match(/\?/g).length - 1;
363
- const value = core_1.Utils.asArray(cond[key]);
364
- const params1 = value.slice(0, count).map((c) => core_1.Utils.isObject(c) ? JSON.stringify(c) : c);
365
- const params2 = value.slice(count);
366
- const k = this.mapper(key, type, params1);
367
- if (params2.length > 0) {
368
- const val = params2.length === 1 && params2[0] === null ? null : this.knex.raw('?', params2);
369
- return void clause[m](k, val);
370
- }
371
- clause[m](k);
372
- }
373
404
  processObjectSubCondition(cond, key, qb, method, m, type) {
374
405
  // grouped condition for one field
375
406
  let value = cond[key];
376
- if (core_1.Utils.getObjectKeysSize(value) > 1) {
407
+ const size = core_1.Utils.getObjectKeysSize(value);
408
+ if (core_1.Utils.isPlainObject(value) && size === 0) {
409
+ return;
410
+ }
411
+ if (size > 1) {
377
412
  const subCondition = Object.entries(value).map(([subKey, subValue]) => ({ [key]: { [subKey]: subValue } }));
378
413
  return subCondition.forEach(sub => this.appendQueryCondition(type, sub, qb, '$and', method));
379
414
  }
@@ -382,13 +417,15 @@ class QueryBuilderHelper {
382
417
  }
383
418
  // operators
384
419
  const op = Object.keys(core_1.QueryOperator).find(op => op in value);
420
+ /* istanbul ignore next */
385
421
  if (!op) {
386
422
  throw new Error(`Invalid query condition: ${(0, util_1.inspect)(cond)}`);
387
423
  }
388
424
  const replacement = this.getOperatorReplacement(op, value);
389
425
  const fields = core_1.Utils.splitPrimaryKeys(key);
390
426
  if (fields.length > 1 && Array.isArray(value[op]) && !value[op].every((v) => Array.isArray(v))) {
391
- value[op] = this.knex.raw(`(${fields.map(() => '?').join(', ')})`, value[op]);
427
+ const tmp = value[op].length === 1 && core_1.Utils.isPlainObject(value[op][0]) ? fields.map(f => value[op][0][f]) : value[op];
428
+ value[op] = this.knex.raw(`(${fields.map(() => '?').join(', ')})`, tmp);
392
429
  }
393
430
  if (this.subQueries[key]) {
394
431
  return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), replacement, value[op]);
@@ -426,60 +463,88 @@ class QueryBuilderHelper {
426
463
  }
427
464
  getQueryOrder(type, orderBy, populate) {
428
465
  if (Array.isArray(orderBy)) {
429
- return orderBy
430
- .map(o => this.getQueryOrder(type, o, populate))
431
- .filter(o => o)
432
- .join(', ');
466
+ return orderBy.flatMap(o => this.getQueryOrder(type, o, populate));
433
467
  }
434
468
  return this.getQueryOrderFromObject(type, orderBy, populate);
435
469
  }
436
470
  getQueryOrderFromObject(type, orderBy, populate) {
437
471
  const ret = [];
438
- Object.keys(orderBy).forEach(k => {
439
- const direction = orderBy[k];
472
+ for (const key of Object.keys(orderBy)) {
473
+ const direction = orderBy[key];
440
474
  const order = core_1.Utils.isNumber(direction) ? core_1.QueryOrderNumeric[direction] : direction;
441
- if (QueryBuilderHelper.isCustomExpression(k)) {
442
- ret.push(`${k} ${order.toLowerCase()}`);
443
- return;
475
+ const raw = core_1.RawQueryFragment.getKnownFragment(key);
476
+ if (raw) {
477
+ ret.push(`${this.platform.formatQuery(raw.sql, raw.params)} ${order.toLowerCase()}`);
478
+ continue;
444
479
  }
445
- core_1.Utils.splitPrimaryKeys(k).forEach(f => {
480
+ for (const f of core_1.Utils.splitPrimaryKeys(key)) {
446
481
  // eslint-disable-next-line prefer-const
447
482
  let [alias, field] = this.splitField(f, true);
448
483
  alias = populate[alias] || alias;
449
484
  const prop = this.getProperty(field, alias);
450
- const noPrefix = (prop && prop.persist === false && !prop.formula) || QueryBuilderHelper.isCustomExpression(f);
485
+ const noPrefix = (prop && prop.persist === false && !prop.formula) || core_1.RawQueryFragment.isKnownFragment(f);
451
486
  const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
452
487
  /* istanbul ignore next */
453
488
  const rawColumn = core_1.Utils.isString(column) ? column.split('.').map(e => this.knex.ref(e)).join('.') : column;
454
489
  const customOrder = prop?.customOrder;
455
- const colPart = customOrder
490
+ let colPart = customOrder
456
491
  ? this.platform.generateCustomOrder(rawColumn, customOrder)
457
492
  : rawColumn;
458
- ret.push(`${colPart} ${order.toLowerCase()}`);
459
- });
460
- });
461
- return ret.join(', ');
493
+ if (core_1.Utils.isRawSql(colPart)) {
494
+ colPart = this.platform.formatQuery(colPart.sql, colPart.params);
495
+ }
496
+ if (Array.isArray(order)) {
497
+ order.forEach(part => ret.push(...this.getQueryOrderFromObject(type, part, populate)));
498
+ }
499
+ else {
500
+ ret.push(...this.platform.getOrderByExpression(colPart, order));
501
+ }
502
+ }
503
+ }
504
+ return ret;
462
505
  }
463
- finalize(type, qb, meta) {
464
- const useReturningStatement = type === enums_1.QueryType.INSERT && this.platform.usesReturningStatement() && meta && !meta.compositePK;
465
- if (useReturningStatement) {
466
- const returningProps = meta.hydrateProps.filter(prop => prop.persist !== false && (prop.primary || prop.defaultRaw));
467
- qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
506
+ finalize(type, qb, meta, data, returning) {
507
+ if (!meta || !data || !this.platform.usesReturningStatement()) {
508
+ return;
509
+ }
510
+ // always respect explicit returning hint
511
+ if (returning && returning.length > 0) {
512
+ qb.returning(returning.map(field => this.mapper(field, type)));
513
+ return;
514
+ }
515
+ if (type === enums_1.QueryType.INSERT) {
516
+ const returningProps = meta.hydrateProps
517
+ .filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
518
+ .filter(prop => !(prop.name in data));
519
+ if (returningProps.length > 0) {
520
+ qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
521
+ }
522
+ return;
523
+ }
524
+ if (type === enums_1.QueryType.UPDATE) {
525
+ const returningProps = meta.hydrateProps.filter(prop => core_1.Utils.isRawSql(data[prop.name]));
526
+ if (returningProps.length > 0) {
527
+ qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
528
+ }
468
529
  }
469
530
  }
470
531
  splitField(field, greedyAlias = false) {
471
532
  const parts = field.split('.');
533
+ const ref = parts[parts.length - 1].split(':')[1];
534
+ if (ref) {
535
+ parts[parts.length - 1] = parts[parts.length - 1].substring(0, parts[parts.length - 1].indexOf(':'));
536
+ }
472
537
  if (parts.length === 1) {
473
- return [this.alias, parts[0]];
538
+ return [this.alias, parts[0], ref];
474
539
  }
475
540
  if (greedyAlias) {
476
541
  const fromField = parts.pop();
477
542
  const fromAlias = parts.join('.');
478
- return [fromAlias, fromField];
543
+ return [fromAlias, fromField, ref];
479
544
  }
480
545
  const fromAlias = parts.shift();
481
546
  const fromField = parts.join('.');
482
- return [fromAlias, fromField];
547
+ return [fromAlias, fromField, ref];
483
548
  }
484
549
  getLockSQL(qb, lockMode, lockTables = []) {
485
550
  const meta = this.metadata.find(this.entityName);
@@ -502,30 +567,25 @@ class QueryBuilderHelper {
502
567
  }
503
568
  const versionProperty = meta.properties[meta.versionProperty];
504
569
  let sql = this.platform.quoteIdentifier(versionProperty.fieldNames[0]) + ' + 1';
505
- if (versionProperty.type.toLowerCase() === 'date') {
570
+ if (versionProperty.runtimeType === 'Date') {
506
571
  sql = this.platform.getCurrentTimestampSQL(versionProperty.length);
507
572
  }
508
573
  qb.update(versionProperty.fieldNames[0], this.knex.raw(sql));
509
574
  }
510
- static isCustomExpression(field, hasAlias = false) {
511
- // if we do not have alias, we don't consider spaces as custom expressions
512
- const re = hasAlias ? /[ ?<>=()'"`:]|^\d/ : /[?<>=()'"`:]|^\d/;
513
- return !!field.match(re);
514
- }
515
- prefix(field, always = false, quote = false) {
575
+ prefix(field, always = false, quote = false, idx) {
516
576
  let ret;
517
577
  if (!this.isPrefixed(field)) {
518
578
  const alias = always ? (quote ? this.alias : this.platform.quoteIdentifier(this.alias)) + '.' : '';
519
- const fieldName = this.fieldName(field, this.alias, always);
520
- if (QueryBuilderHelper.isCustomExpression(fieldName)) {
521
- return fieldName;
579
+ const fieldName = this.fieldName(field, this.alias, always, idx);
580
+ if (fieldName instanceof core_1.RawQueryFragment) {
581
+ return fieldName.sql;
522
582
  }
523
583
  ret = alias + fieldName;
524
584
  }
525
585
  else {
526
586
  const [a, ...rest] = field.split('.');
527
587
  const f = rest.join('.');
528
- ret = a + '.' + this.fieldName(f, a);
588
+ ret = a + '.' + this.fieldName(f, a, always, idx);
529
589
  }
530
590
  if (quote) {
531
591
  return this.platform.quoteIdentifier(ret);
@@ -551,25 +611,25 @@ class QueryBuilderHelper {
551
611
  isPrefixed(field) {
552
612
  return !!field.match(/[\w`"[\]]+\./);
553
613
  }
554
- fieldName(field, alias, always) {
614
+ fieldName(field, alias, always, idx = 0) {
555
615
  const prop = this.getProperty(field, alias);
556
616
  if (!prop) {
557
617
  return field;
558
618
  }
559
619
  if (prop.fieldNameRaw) {
560
620
  if (!always) {
561
- return prop.fieldNameRaw
562
- .replace(/\[::alias::]\.?/g, '')
563
- .replace(this.platform.quoteIdentifier('') + '.', '');
621
+ return (0, core_1.raw)(prop.fieldNameRaw
622
+ .replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE + '\\.?', 'g'), '')
623
+ .replace(this.platform.quoteIdentifier('') + '.', ''));
564
624
  }
565
625
  if (alias) {
566
- return prop.fieldNameRaw.replace(/\[::alias::]/g, alias);
626
+ return (0, core_1.raw)(prop.fieldNameRaw.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE, 'g'), alias));
567
627
  }
568
628
  /* istanbul ignore next */
569
- return prop.fieldNameRaw;
629
+ return (0, core_1.raw)(prop.fieldNameRaw);
570
630
  }
571
631
  /* istanbul ignore next */
572
- return prop.fieldNames?.[0] ?? field;
632
+ return prop.fieldNames?.[idx] ?? field;
573
633
  }
574
634
  getProperty(field, alias) {
575
635
  const entityName = this.aliasMap[alias]?.entityName || this.entityName;
@@ -587,48 +647,16 @@ class QueryBuilderHelper {
587
647
  return nest(prop);
588
648
  }
589
649
  }
590
- return meta?.properties[field];
650
+ if (meta) {
651
+ if (meta.properties[field]) {
652
+ return meta.properties[field];
653
+ }
654
+ return meta.relations.find(prop => prop.fieldNames?.some(name => field === name));
655
+ }
656
+ return undefined;
591
657
  }
592
658
  isTableNameAliasRequired(type) {
593
659
  return [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(type ?? enums_1.QueryType.SELECT);
594
660
  }
595
- mapData(data, properties, convertCustomTypes) {
596
- if (!properties) {
597
- return data;
598
- }
599
- data = Object.assign({}, data); // copy first
600
- Object.keys(data).forEach(k => {
601
- const prop = properties[k];
602
- if (!prop) {
603
- return;
604
- }
605
- if (prop.embeddedProps && !prop.object) {
606
- const copy = data[k];
607
- delete data[k];
608
- Object.assign(data, this.mapData(copy, prop.embeddedProps, convertCustomTypes));
609
- return;
610
- }
611
- if (prop.joinColumns && Array.isArray(data[k])) {
612
- const copy = data[k];
613
- delete data[k];
614
- prop.joinColumns.forEach((joinColumn, idx) => data[joinColumn] = copy[idx]);
615
- return;
616
- }
617
- if (prop.customType && convertCustomTypes && !this.platform.isRaw(data[k])) {
618
- data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, { fromQuery: true, key: k, mode: 'query' });
619
- }
620
- if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(data[k])) {
621
- const quoted = this.platform.quoteValue(data[k]);
622
- data[k] = this.knex.raw(prop.customType.convertToDatabaseValueSQL(quoted, this.platform).replace(/\?/, '\\?'));
623
- }
624
- if (!prop.customType && (Array.isArray(data[k]) || core_1.Utils.isPlainObject(data[k]))) {
625
- data[k] = JSON.stringify(data[k]);
626
- }
627
- if (prop.fieldNames) {
628
- core_1.Utils.renameKey(data, k, prop.fieldNames[0]);
629
- }
630
- });
631
- return data;
632
- }
633
661
  }
634
662
  exports.QueryBuilderHelper = QueryBuilderHelper;
@@ -1,9 +1,10 @@
1
1
  import { CriteriaNode } from './CriteriaNode';
2
- import type { IQueryBuilder } from '../typings';
2
+ import type { IQueryBuilder, ICriteriaNodeProcessOptions } from '../typings';
3
3
  /**
4
4
  * @internal
5
5
  */
6
6
  export declare class ScalarCriteriaNode<T extends object> extends CriteriaNode<T> {
7
- process(qb: IQueryBuilder<T>, alias?: string): any;
7
+ process(qb: IQueryBuilder<T>, options?: ICriteriaNodeProcessOptions): any;
8
+ willAutoJoin<T>(qb: IQueryBuilder<T>, alias?: string): boolean;
8
9
  shouldJoin(): boolean;
9
10
  }