@mikro-orm/knex 6.0.0-dev.30 → 6.0.0-dev.300

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 +47 -17
  3. package/AbstractSqlDriver.d.ts +30 -24
  4. package/AbstractSqlDriver.js +590 -264
  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 +58 -26
  27. package/query/QueryBuilder.js +435 -139
  28. package/query/QueryBuilderHelper.d.ts +13 -18
  29. package/query/QueryBuilderHelper.js +196 -169
  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 +369 -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 +3 -4
  43. package/schema/SqlSchemaGenerator.js +66 -23
  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);
@@ -263,9 +298,9 @@ class QueryBuilderHelper {
263
298
  ...join.inverseJoinColumns.map(col => this.mapper(`${join.alias}.${col}`, type, undefined, `fk__${col}`)),
264
299
  ];
265
300
  }
266
- isOneToOneInverse(field) {
267
- const meta = this.metadata.find(this.entityName);
268
- const prop = meta.properties[field];
301
+ isOneToOneInverse(field, meta) {
302
+ meta ??= this.metadata.find(this.entityName);
303
+ const prop = meta.properties[field.replace(/:ref$/, '')];
269
304
  return prop && prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner;
270
305
  }
271
306
  getTableName(entityName) {
@@ -305,7 +340,7 @@ class QueryBuilderHelper {
305
340
  }
306
341
  appendOnConflictClause(type, onConflict, qb) {
307
342
  onConflict.forEach(item => {
308
- const sub = qb.onConflict(item.fields);
343
+ const sub = item.fields.length > 0 ? qb.onConflict(item.fields) : qb.onConflict();
309
344
  core_1.Utils.runIfNotEmpty(() => sub.ignore(), item.ignore);
310
345
  core_1.Utils.runIfNotEmpty(() => {
311
346
  let mergeParam = item.merge;
@@ -342,38 +377,37 @@ class QueryBuilderHelper {
342
377
  }
343
378
  appendQuerySubCondition(qb, type, method, cond, key, operator) {
344
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
+ }
345
383
  if (this.isSimpleRegExp(cond[key])) {
346
384
  return void qb[m](this.mapper(key, type), 'like', this.getRegExpParam(cond[key]));
347
385
  }
348
386
  if (core_1.Utils.isPlainObject(cond[key]) || cond[key] instanceof RegExp) {
349
387
  return this.processObjectSubCondition(cond, key, qb, method, m, type);
350
388
  }
351
- if (QueryBuilderHelper.isCustomExpression(key)) {
352
- return this.processCustomExpression(qb, m, key, cond, type);
353
- }
354
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
+ }
355
398
  if (this.subQueries[key]) {
356
399
  return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), op, cond[key]);
357
400
  }
358
401
  qb[m](this.mapper(key, type, cond[key], null), op, cond[key]);
359
402
  }
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
403
  processObjectSubCondition(cond, key, qb, method, m, type) {
374
404
  // grouped condition for one field
375
405
  let value = cond[key];
376
- 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) {
377
411
  const subCondition = Object.entries(value).map(([subKey, subValue]) => ({ [key]: { [subKey]: subValue } }));
378
412
  return subCondition.forEach(sub => this.appendQueryCondition(type, sub, qb, '$and', method));
379
413
  }
@@ -382,13 +416,15 @@ class QueryBuilderHelper {
382
416
  }
383
417
  // operators
384
418
  const op = Object.keys(core_1.QueryOperator).find(op => op in value);
419
+ /* istanbul ignore next */
385
420
  if (!op) {
386
421
  throw new Error(`Invalid query condition: ${(0, util_1.inspect)(cond)}`);
387
422
  }
388
423
  const replacement = this.getOperatorReplacement(op, value);
389
424
  const fields = core_1.Utils.splitPrimaryKeys(key);
390
425
  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]);
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);
392
428
  }
393
429
  if (this.subQueries[key]) {
394
430
  return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), replacement, value[op]);
@@ -426,60 +462,88 @@ class QueryBuilderHelper {
426
462
  }
427
463
  getQueryOrder(type, orderBy, populate) {
428
464
  if (Array.isArray(orderBy)) {
429
- return orderBy
430
- .map(o => this.getQueryOrder(type, o, populate))
431
- .filter(o => o)
432
- .join(', ');
465
+ return orderBy.flatMap(o => this.getQueryOrder(type, o, populate));
433
466
  }
434
467
  return this.getQueryOrderFromObject(type, orderBy, populate);
435
468
  }
436
469
  getQueryOrderFromObject(type, orderBy, populate) {
437
470
  const ret = [];
438
- Object.keys(orderBy).forEach(k => {
439
- const direction = orderBy[k];
471
+ for (const key of Object.keys(orderBy)) {
472
+ const direction = orderBy[key];
440
473
  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;
474
+ const raw = core_1.RawQueryFragment.getKnownFragment(key);
475
+ if (raw) {
476
+ ret.push(`${this.platform.formatQuery(raw.sql, raw.params)} ${order.toLowerCase()}`);
477
+ continue;
444
478
  }
445
- core_1.Utils.splitPrimaryKeys(k).forEach(f => {
479
+ for (const f of core_1.Utils.splitPrimaryKeys(key)) {
446
480
  // eslint-disable-next-line prefer-const
447
481
  let [alias, field] = this.splitField(f, true);
448
482
  alias = populate[alias] || alias;
449
483
  const prop = this.getProperty(field, alias);
450
- const noPrefix = (prop && prop.persist === false && !prop.formula) || QueryBuilderHelper.isCustomExpression(f);
484
+ const noPrefix = (prop && prop.persist === false && !prop.formula) || core_1.RawQueryFragment.isKnownFragment(f);
451
485
  const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
452
486
  /* istanbul ignore next */
453
487
  const rawColumn = core_1.Utils.isString(column) ? column.split('.').map(e => this.knex.ref(e)).join('.') : column;
454
488
  const customOrder = prop?.customOrder;
455
- const colPart = customOrder
489
+ let colPart = customOrder
456
490
  ? this.platform.generateCustomOrder(rawColumn, customOrder)
457
491
  : rawColumn;
458
- ret.push(`${colPart} ${order.toLowerCase()}`);
459
- });
460
- });
461
- return ret.join(', ');
492
+ if (core_1.Utils.isRawSql(colPart)) {
493
+ colPart = this.platform.formatQuery(colPart.sql, colPart.params);
494
+ }
495
+ if (Array.isArray(order)) {
496
+ order.forEach(part => ret.push(...this.getQueryOrderFromObject(type, part, populate)));
497
+ }
498
+ else {
499
+ ret.push(...this.platform.getOrderByExpression(colPart, order));
500
+ }
501
+ }
502
+ }
503
+ return ret;
462
504
  }
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)));
505
+ finalize(type, qb, meta, data, returning) {
506
+ if (!meta || !data || !this.platform.usesReturningStatement()) {
507
+ return;
508
+ }
509
+ // always respect explicit returning hint
510
+ if (returning && returning.length > 0) {
511
+ qb.returning(returning.map(field => this.mapper(field, type)));
512
+ return;
513
+ }
514
+ if (type === enums_1.QueryType.INSERT) {
515
+ const returningProps = meta.hydrateProps
516
+ .filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
517
+ .filter(prop => !(prop.name in data));
518
+ if (returningProps.length > 0) {
519
+ qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
520
+ }
521
+ return;
522
+ }
523
+ if (type === enums_1.QueryType.UPDATE) {
524
+ const returningProps = meta.hydrateProps.filter(prop => core_1.Utils.isRawSql(data[prop.name]));
525
+ if (returningProps.length > 0) {
526
+ qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
527
+ }
468
528
  }
469
529
  }
470
530
  splitField(field, greedyAlias = false) {
471
531
  const parts = field.split('.');
532
+ const ref = parts[parts.length - 1].split(':')[1];
533
+ if (ref) {
534
+ parts[parts.length - 1] = parts[parts.length - 1].substring(0, parts[parts.length - 1].indexOf(':'));
535
+ }
472
536
  if (parts.length === 1) {
473
- return [this.alias, parts[0]];
537
+ return [this.alias, parts[0], ref];
474
538
  }
475
539
  if (greedyAlias) {
476
540
  const fromField = parts.pop();
477
541
  const fromAlias = parts.join('.');
478
- return [fromAlias, fromField];
542
+ return [fromAlias, fromField, ref];
479
543
  }
480
544
  const fromAlias = parts.shift();
481
545
  const fromField = parts.join('.');
482
- return [fromAlias, fromField];
546
+ return [fromAlias, fromField, ref];
483
547
  }
484
548
  getLockSQL(qb, lockMode, lockTables = []) {
485
549
  const meta = this.metadata.find(this.entityName);
@@ -502,30 +566,25 @@ class QueryBuilderHelper {
502
566
  }
503
567
  const versionProperty = meta.properties[meta.versionProperty];
504
568
  let sql = this.platform.quoteIdentifier(versionProperty.fieldNames[0]) + ' + 1';
505
- if (versionProperty.type.toLowerCase() === 'date') {
569
+ if (versionProperty.runtimeType === 'Date') {
506
570
  sql = this.platform.getCurrentTimestampSQL(versionProperty.length);
507
571
  }
508
572
  qb.update(versionProperty.fieldNames[0], this.knex.raw(sql));
509
573
  }
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) {
574
+ prefix(field, always = false, quote = false, idx) {
516
575
  let ret;
517
576
  if (!this.isPrefixed(field)) {
518
577
  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;
578
+ const fieldName = this.fieldName(field, this.alias, always, idx);
579
+ if (fieldName instanceof core_1.RawQueryFragment) {
580
+ return fieldName.sql;
522
581
  }
523
582
  ret = alias + fieldName;
524
583
  }
525
584
  else {
526
585
  const [a, ...rest] = field.split('.');
527
586
  const f = rest.join('.');
528
- ret = a + '.' + this.fieldName(f, a);
587
+ ret = a + '.' + this.fieldName(f, a, always, idx);
529
588
  }
530
589
  if (quote) {
531
590
  return this.platform.quoteIdentifier(ret);
@@ -551,25 +610,25 @@ class QueryBuilderHelper {
551
610
  isPrefixed(field) {
552
611
  return !!field.match(/[\w`"[\]]+\./);
553
612
  }
554
- fieldName(field, alias, always) {
613
+ fieldName(field, alias, always, idx = 0) {
555
614
  const prop = this.getProperty(field, alias);
556
615
  if (!prop) {
557
616
  return field;
558
617
  }
559
618
  if (prop.fieldNameRaw) {
560
619
  if (!always) {
561
- return prop.fieldNameRaw
562
- .replace(/\[::alias::]\.?/g, '')
563
- .replace(this.platform.quoteIdentifier('') + '.', '');
620
+ return (0, core_1.raw)(prop.fieldNameRaw
621
+ .replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE + '\\.?', 'g'), '')
622
+ .replace(this.platform.quoteIdentifier('') + '.', ''));
564
623
  }
565
624
  if (alias) {
566
- return prop.fieldNameRaw.replace(/\[::alias::]/g, alias);
625
+ return (0, core_1.raw)(prop.fieldNameRaw.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE, 'g'), alias));
567
626
  }
568
627
  /* istanbul ignore next */
569
- return prop.fieldNameRaw;
628
+ return (0, core_1.raw)(prop.fieldNameRaw);
570
629
  }
571
630
  /* istanbul ignore next */
572
- return prop.fieldNames?.[0] ?? field;
631
+ return prop.fieldNames?.[idx] ?? field;
573
632
  }
574
633
  getProperty(field, alias) {
575
634
  const entityName = this.aliasMap[alias]?.entityName || this.entityName;
@@ -587,48 +646,16 @@ class QueryBuilderHelper {
587
646
  return nest(prop);
588
647
  }
589
648
  }
590
- return meta?.properties[field];
649
+ if (meta) {
650
+ if (meta.properties[field]) {
651
+ return meta.properties[field];
652
+ }
653
+ return meta.relations.find(prop => prop.fieldNames?.some(name => field === name));
654
+ }
655
+ return undefined;
591
656
  }
592
657
  isTableNameAliasRequired(type) {
593
658
  return [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(type ?? enums_1.QueryType.SELECT);
594
659
  }
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
660
  }
634
661
  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
  }