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

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 (60) hide show
  1. package/{dist/AbstractSqlConnection.d.ts → AbstractSqlConnection.d.ts} +8 -4
  2. package/{dist/AbstractSqlConnection.js → AbstractSqlConnection.js} +35 -18
  3. package/AbstractSqlDriver.d.ts +66 -0
  4. package/{dist/AbstractSqlDriver.js → AbstractSqlDriver.js} +422 -129
  5. package/AbstractSqlPlatform.d.ts +22 -0
  6. package/{dist/AbstractSqlPlatform.js → AbstractSqlPlatform.js} +46 -19
  7. package/{dist/MonkeyPatchable.d.ts → MonkeyPatchable.d.ts} +1 -0
  8. package/{dist/MonkeyPatchable.js → MonkeyPatchable.js} +3 -0
  9. package/{dist/README.md → README.md} +66 -109
  10. package/{dist/SqlEntityManager.d.ts → SqlEntityManager.d.ts} +6 -10
  11. package/{dist/SqlEntityManager.js → SqlEntityManager.js} +5 -10
  12. package/SqlEntityRepository.d.ts +24 -0
  13. package/{dist/SqlEntityRepository.js → SqlEntityRepository.js} +11 -8
  14. package/{dist/index.d.ts → index.d.ts} +1 -0
  15. package/{dist/index.js → index.js} +6 -1
  16. package/index.mjs +208 -0
  17. package/package.json +20 -49
  18. package/query/ArrayCriteriaNode.d.ts +9 -0
  19. package/{dist/query → query}/CriteriaNode.d.ts +8 -8
  20. package/{dist/query → query}/CriteriaNode.js +23 -13
  21. package/query/CriteriaNodeFactory.d.ts +12 -0
  22. package/{dist/query → query}/CriteriaNodeFactory.js +10 -13
  23. package/{dist/query → query}/ObjectCriteriaNode.d.ts +3 -3
  24. package/{dist/query → query}/ObjectCriteriaNode.js +26 -9
  25. package/{dist/query → query}/QueryBuilder.d.ts +90 -34
  26. package/query/QueryBuilder.js +1098 -0
  27. package/{dist/query → query}/QueryBuilderHelper.d.ts +23 -19
  28. package/{dist/query → query}/QueryBuilderHelper.js +300 -176
  29. package/query/ScalarCriteriaNode.d.ts +10 -0
  30. package/{dist/query → query}/ScalarCriteriaNode.js +9 -6
  31. package/{dist/query → query}/enums.js +1 -1
  32. package/{dist/query → query}/index.js +5 -1
  33. package/{dist/schema → schema}/DatabaseSchema.d.ts +6 -3
  34. package/{dist/schema → schema}/DatabaseSchema.js +33 -23
  35. package/{dist/schema → schema}/DatabaseTable.d.ts +12 -10
  36. package/{dist/schema → schema}/DatabaseTable.js +55 -29
  37. package/{dist/schema → schema}/SchemaComparator.d.ts +7 -5
  38. package/{dist/schema → schema}/SchemaComparator.js +68 -10
  39. package/{dist/schema → schema}/SchemaHelper.d.ts +20 -13
  40. package/{dist/schema → schema}/SchemaHelper.js +56 -11
  41. package/{dist/schema/SchemaGenerator.d.ts → schema/SqlSchemaGenerator.d.ts} +11 -14
  42. package/{dist/schema/SchemaGenerator.js → schema/SqlSchemaGenerator.js} +139 -55
  43. package/{dist/schema → schema}/index.d.ts +1 -1
  44. package/{dist/schema → schema}/index.js +6 -2
  45. package/{dist/typings.d.ts → typings.d.ts} +27 -18
  46. package/CHANGELOG.md +0 -661
  47. package/dist/AbstractSqlDriver.d.ts +0 -55
  48. package/dist/AbstractSqlPlatform.d.ts +0 -21
  49. package/dist/LICENSE +0 -21
  50. package/dist/SqlEntityRepository.d.ts +0 -23
  51. package/dist/index.mjs +0 -26
  52. package/dist/package.json +0 -99
  53. package/dist/query/ArrayCriteriaNode.d.ts +0 -9
  54. package/dist/query/CriteriaNodeFactory.d.ts +0 -12
  55. package/dist/query/QueryBuilder.js +0 -688
  56. package/dist/query/ScalarCriteriaNode.d.ts +0 -9
  57. /package/{dist/query → query}/ArrayCriteriaNode.js +0 -0
  58. /package/{dist/query → query}/enums.d.ts +0 -0
  59. /package/{dist/query → query}/index.d.ts +0 -0
  60. /package/{dist/typings.js → typings.js} +0 -0
@@ -8,16 +8,32 @@ const enums_1 = require("./enums");
8
8
  * @internal
9
9
  */
10
10
  class QueryBuilderHelper {
11
- constructor(entityName, alias, aliasMap, subQueries, metadata, knex, platform) {
11
+ entityName;
12
+ alias;
13
+ aliasMap;
14
+ subQueries;
15
+ knex;
16
+ driver;
17
+ platform;
18
+ metadata;
19
+ constructor(entityName, alias, aliasMap, subQueries, knex, driver) {
12
20
  this.entityName = entityName;
13
21
  this.alias = alias;
14
22
  this.aliasMap = aliasMap;
15
23
  this.subQueries = subQueries;
16
- this.metadata = metadata;
17
24
  this.knex = knex;
18
- this.platform = platform;
25
+ this.driver = driver;
26
+ this.platform = this.driver.getPlatform();
27
+ this.metadata = this.driver.getMetadata();
19
28
  }
20
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 */
34
+ if (typeof field !== 'string') {
35
+ return field;
36
+ }
21
37
  const isTableNameAliasRequired = this.isTableNameAliasRequired(type);
22
38
  const fields = core_1.Utils.splitPrimaryKeys(field);
23
39
  if (fields.length > 1) {
@@ -26,18 +42,36 @@ class QueryBuilderHelper {
26
42
  const [a, f] = this.splitField(p);
27
43
  const prop = this.getProperty(f, a);
28
44
  if (prop) {
29
- parts.push(...prop.fieldNames.map(f => this.mapper(f, type, value, alias)));
45
+ parts.push(...prop.fieldNames.map(f => this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias)));
30
46
  }
31
47
  else {
32
- parts.push(this.mapper(`${a}.${f}`, type, value, alias));
48
+ parts.push(this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias));
33
49
  }
34
50
  }
51
+ // flatten the value if we see we are expanding nested composite key
52
+ // hackish, but cleaner solution would require quite a lot of refactoring
53
+ if (fields.length !== parts.length && Array.isArray(value)) {
54
+ value.forEach(row => {
55
+ if (Array.isArray(row)) {
56
+ const tmp = core_1.Utils.flatten(row);
57
+ row.length = 0;
58
+ row.push(...tmp);
59
+ }
60
+ });
61
+ }
35
62
  return this.knex.raw('(' + parts.map(part => this.knex.ref(part)).join(', ') + ')');
36
63
  }
37
- let ret = field;
38
- const customExpression = QueryBuilderHelper.isCustomExpression(field, !!alias);
64
+ const rawField = core_1.RawQueryFragment.getKnownFragment(field);
65
+ if (rawField) {
66
+ return this.knex.raw(rawField.sql, rawField.params);
67
+ }
39
68
  const [a, f] = this.splitField(field);
40
69
  const prop = this.getProperty(f, a);
70
+ let ret = field;
71
+ // embeddable nested path instead of a regular property with table alias, reset alias
72
+ if (prop?.name === a && prop.embeddedProps[f]) {
73
+ return this.alias + '.' + prop.fieldNames[0];
74
+ }
41
75
  const noPrefix = prop && prop.persist === false;
42
76
  if (prop?.fieldNameRaw) {
43
77
  return this.knex.raw(this.prefix(field, isTableNameAliasRequired));
@@ -48,7 +82,7 @@ class QueryBuilderHelper {
48
82
  const as = alias === null ? '' : ` as ${aliased}`;
49
83
  return this.knex.raw(`${prop.formula(alias2)}${as}`);
50
84
  }
51
- if (prop?.customType?.convertToJSValueSQL) {
85
+ if (prop?.hasConvertToJSValueSQL) {
52
86
  const prefixed = this.prefix(field, isTableNameAliasRequired, true);
53
87
  const valueSQL = prop.customType.convertToJSValueSQL(prefixed, this.platform);
54
88
  if (alias === null) {
@@ -57,15 +91,12 @@ class QueryBuilderHelper {
57
91
  return this.knex.raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[0])}`);
58
92
  }
59
93
  // do not wrap custom expressions
60
- if (!customExpression) {
94
+ if (!rawField) {
61
95
  ret = this.prefix(field);
62
96
  }
63
97
  if (alias) {
64
98
  ret += ' as ' + alias;
65
99
  }
66
- if (customExpression) {
67
- return this.knex.raw(ret, value);
68
- }
69
100
  if (!isTableNameAliasRequired || this.isPrefixed(ret) || noPrefix) {
70
101
  return ret;
71
102
  }
@@ -75,111 +106,153 @@ class QueryBuilderHelper {
75
106
  if (Array.isArray(data)) {
76
107
  return data.map(d => this.processData(d, convertCustomTypes, true));
77
108
  }
78
- data = Object.assign({}, data); // copy first
79
109
  const meta = this.metadata.find(this.entityName);
80
- Object.keys(data).forEach(k => {
81
- if (!meta?.properties[k]) {
82
- return;
83
- }
84
- const prop = meta.properties[k];
85
- if (prop.joinColumns && Array.isArray(data[k])) {
86
- const copy = data[k];
87
- delete data[k];
88
- prop.joinColumns.forEach((joinColumn, idx) => data[joinColumn] = copy[idx]);
89
- return;
90
- }
91
- if (prop.customType && convertCustomTypes && !this.platform.isRaw(data[k])) {
92
- data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, true);
93
- }
94
- if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(data[k])) {
95
- const quoted = this.platform.quoteValue(data[k]);
96
- data[k] = this.knex.raw(prop.customType.convertToDatabaseValueSQL(quoted, this.platform));
97
- }
98
- if (!prop.customType && (Array.isArray(data[k]) || core_1.Utils.isPlainObject(data[k]))) {
99
- data[k] = JSON.stringify(data[k]);
100
- }
101
- if (prop.fieldNames) {
102
- core_1.Utils.renameKey(data, k, prop.fieldNames[0]);
103
- }
104
- });
110
+ data = this.mapData(data, meta?.properties, convertCustomTypes);
105
111
  if (!core_1.Utils.hasObjectKeys(data) && meta && multi) {
112
+ /* istanbul ignore next */
106
113
  data[meta.primaryKeys[0]] = this.platform.usesDefaultKeyword() ? this.knex.raw('default') : undefined;
107
114
  }
108
115
  return data;
109
116
  }
110
- joinOneToReference(prop, ownerAlias, alias, type, cond = {}) {
111
- const meta = this.metadata.find(prop.type);
112
- const prop2 = meta.properties[prop.mappedBy || prop.inversedBy];
117
+ joinOneToReference(prop, ownerAlias, alias, type, cond = {}, schema) {
118
+ const prop2 = prop.targetMeta.properties[prop.mappedBy || prop.inversedBy];
113
119
  const table = this.getTableName(prop.type);
114
120
  const joinColumns = prop.owner ? prop.referencedColumnNames : prop2.joinColumns;
115
121
  const inverseJoinColumns = prop.referencedColumnNames;
116
122
  const primaryKeys = prop.owner ? prop.joinColumns : prop2.referencedColumnNames;
123
+ schema ??= prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta);
117
124
  return {
118
- prop, type, cond, ownerAlias, alias, table,
125
+ prop, type, cond, ownerAlias, alias, table, schema,
119
126
  joinColumns, inverseJoinColumns, primaryKeys,
120
127
  };
121
128
  }
122
- joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}) {
129
+ joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}, schema) {
123
130
  return {
124
131
  prop, type, cond, ownerAlias, alias,
125
132
  table: this.getTableName(prop.type),
133
+ schema: this.driver.getSchemaName(prop.targetMeta, { schema }),
126
134
  joinColumns: prop.referencedColumnNames,
127
135
  primaryKeys: prop.fieldNames,
128
136
  };
129
137
  }
130
- joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path) {
138
+ joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path, schema) {
139
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
131
140
  const ret = {
132
141
  [`${ownerAlias}.${prop.name}#${pivotAlias}`]: {
133
- prop, type, cond, ownerAlias,
142
+ prop, type, ownerAlias,
134
143
  alias: pivotAlias,
135
144
  inverseAlias: alias,
136
145
  joinColumns: prop.joinColumns,
137
146
  inverseJoinColumns: prop.inverseJoinColumns,
138
147
  primaryKeys: prop.referencedColumnNames,
139
- table: prop.pivotTable,
148
+ cond: {},
149
+ table: pivotMeta.tableName,
150
+ schema: this.driver.getSchemaName(pivotMeta, { schema }),
140
151
  path: path.endsWith('[pivot]') ? path : `${path}[pivot]`,
141
152
  },
142
153
  };
143
154
  if (type === 'pivotJoin') {
144
155
  return ret;
145
156
  }
146
- const prop2 = this.metadata.find(prop.pivotTable).properties[prop.type + (prop.owner ? '_inverse' : '_owner')];
147
- ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type);
157
+ const prop2 = prop.owner ? pivotMeta.relations[1] : pivotMeta.relations[0];
158
+ ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type, cond, schema);
148
159
  ret[`${pivotAlias}.${prop2.name}#${alias}`].path = path;
149
160
  return ret;
150
161
  }
151
- joinPivotTable(field, prop, ownerAlias, alias, type, cond = {}) {
152
- const pivotMeta = this.metadata.find(field);
153
- const prop2 = pivotMeta.properties[prop.mappedBy || prop.inversedBy];
154
- return {
155
- prop, type, cond, ownerAlias, alias,
156
- table: pivotMeta.collection,
157
- schema: pivotMeta.schema,
158
- joinColumns: prop.joinColumns,
159
- inverseJoinColumns: prop2.joinColumns,
160
- primaryKeys: prop.referencedColumnNames,
161
- };
162
- }
163
162
  processJoins(qb, joins, schema) {
164
163
  Object.values(joins).forEach(join => {
165
164
  let table = `${join.table} as ${join.alias}`;
166
- const method = join.type === 'pivotJoin' ? 'leftJoin' : join.type;
165
+ const method = join.type === 'innerJoin' ? 'inner join' : 'left join';
167
166
  schema = join.schema && join.schema !== '*' ? join.schema : schema;
168
167
  if (schema) {
169
168
  table = `${schema}.${table}`;
170
169
  }
171
- return qb[method](this.knex.ref(table), inner => {
172
- join.primaryKeys.forEach((primaryKey, idx) => {
173
- const left = `${join.ownerAlias}.${primaryKey}`;
174
- const right = `${join.alias}.${join.joinColumns[idx]}`;
175
- inner.on(left, right);
176
- });
177
- this.appendJoinClause(inner, join.cond);
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)}`);
178
176
  });
177
+ if (join.prop.targetMeta.discriminatorValue && !join.path?.endsWith('[pivot]')) {
178
+ const typeProperty = join.prop.targetMeta.root.discriminatorColumn;
179
+ const alias = join.inverseAlias ?? join.alias;
180
+ join.cond[`${alias}.${typeProperty}`] = join.prop.targetMeta.discriminatorValue;
181
+ }
182
+ Object.keys(join.cond).forEach(key => {
183
+ conditions.push(this.processJoinClause(key, join.cond[key], params));
184
+ });
185
+ return qb.joinRaw(`${method} ${this.knex.ref(table)} on ${conditions.join(' and ')}`, params);
179
186
  });
180
187
  }
188
+ processJoinClause(key, value, params, operator = '$eq') {
189
+ if (core_1.Utils.isGroupOperator(key) && Array.isArray(value)) {
190
+ const parts = value.map(sub => {
191
+ return this.wrapQueryGroup(Object.keys(sub).map(k => this.processJoinClause(k, sub[k], params)));
192
+ });
193
+ return this.wrapQueryGroup(parts, key);
194
+ }
195
+ if (this.isSimpleRegExp(value)) {
196
+ params.push(this.getRegExpParam(value));
197
+ return `${this.knex.ref(this.mapper(key))} like ?`;
198
+ }
199
+ if (value instanceof RegExp) {
200
+ value = this.platform.getRegExpValue(value);
201
+ }
202
+ if (core_1.Utils.isOperator(key, false) && core_1.Utils.isPlainObject(value)) {
203
+ const parts = Object.keys(value).map(k => this.processJoinClause(k, value[k], params, key));
204
+ return key === '$not' ? `not ${this.wrapQueryGroup(parts)}` : this.wrapQueryGroup(parts);
205
+ }
206
+ if (core_1.Utils.isPlainObject(value) && Object.keys(value).every(k => core_1.Utils.isOperator(k, false))) {
207
+ const parts = Object.keys(value).map(op => this.processJoinClause(key, value[op], params, op));
208
+ return this.wrapQueryGroup(parts);
209
+ }
210
+ operator = operator === '$not' ? '$eq' : operator;
211
+ if (value === null) {
212
+ return `${this.knex.ref(this.mapper(key))} is ${operator === '$ne' ? 'not ' : ''}null`;
213
+ }
214
+ if (operator === '$fulltext') {
215
+ const [fromAlias, fromField] = this.splitField(key);
216
+ const property = this.getProperty(fromField, fromAlias);
217
+ const query = this.knex.raw(this.platform.getFullTextWhereClause(property), {
218
+ column: this.mapper(key),
219
+ query: this.knex.raw('?'),
220
+ }).toSQL().toNative();
221
+ params.push(value);
222
+ return query.sql;
223
+ }
224
+ const replacement = this.getOperatorReplacement(operator, { [operator]: value });
225
+ if (['$in', '$nin'].includes(operator) && Array.isArray(value)) {
226
+ params.push(...value);
227
+ return `${this.knex.ref(this.mapper(key))} ${replacement} (${value.map(() => '?').join(', ')})`;
228
+ }
229
+ if (operator === '$exists') {
230
+ value = null;
231
+ }
232
+ const rawField = core_1.RawQueryFragment.getKnownFragment(key);
233
+ if (rawField) {
234
+ let sql = rawField.sql;
235
+ params.push(...rawField.params);
236
+ params.push(...core_1.Utils.asArray(value));
237
+ if (core_1.Utils.asArray(value).length > 0) {
238
+ sql += ' = ?';
239
+ }
240
+ return sql;
241
+ }
242
+ const sql = this.mapper(key);
243
+ if (value !== null) {
244
+ params.push(value);
245
+ }
246
+ return `${this.knex.ref(sql)} ${replacement} ${value === null ? 'null' : '?'}`;
247
+ }
248
+ wrapQueryGroup(parts, operator = '$and') {
249
+ if (parts.length === 1) {
250
+ return parts[0];
251
+ }
252
+ return `(${parts.join(` ${core_1.GroupOperator[operator]} `)})`;
253
+ }
181
254
  mapJoinColumns(type, join) {
182
- if (join.prop && join.prop.reference === core_1.ReferenceType.ONE_TO_ONE && !join.prop.owner) {
255
+ if (join.prop && join.prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !join.prop.owner) {
183
256
  return join.prop.fieldNames.map((fieldName, idx) => {
184
257
  return this.mapper(`${join.alias}.${join.inverseJoinColumns[idx]}`, type, undefined, fieldName);
185
258
  });
@@ -192,7 +265,7 @@ class QueryBuilderHelper {
192
265
  isOneToOneInverse(field) {
193
266
  const meta = this.metadata.find(this.entityName);
194
267
  const prop = meta.properties[field];
195
- return prop && prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner;
268
+ return prop && prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner;
196
269
  }
197
270
  getTableName(entityName) {
198
271
  const meta = this.metadata.find(entityName);
@@ -205,6 +278,9 @@ class QueryBuilderHelper {
205
278
  if (!(re instanceof RegExp)) {
206
279
  return false;
207
280
  }
281
+ if (re.flags.includes('i')) {
282
+ return false;
283
+ }
208
284
  // when including the opening bracket/paren we consider it complex
209
285
  return !re.source.match(/[{[(]/);
210
286
  }
@@ -228,13 +304,13 @@ class QueryBuilderHelper {
228
304
  }
229
305
  appendOnConflictClause(type, onConflict, qb) {
230
306
  onConflict.forEach(item => {
231
- const sub = qb.onConflict(item.fields);
307
+ const sub = item.fields.length > 0 ? qb.onConflict(item.fields) : qb.onConflict();
232
308
  core_1.Utils.runIfNotEmpty(() => sub.ignore(), item.ignore);
233
309
  core_1.Utils.runIfNotEmpty(() => {
234
310
  let mergeParam = item.merge;
235
311
  if (core_1.Utils.isObject(item.merge)) {
236
312
  mergeParam = {};
237
- Object.keys(item.merge).forEach(key => {
313
+ core_1.Utils.keys(item.merge).forEach(key => {
238
314
  const k = this.mapper(key, type);
239
315
  mergeParam[k] = item.merge[key];
240
316
  });
@@ -265,34 +341,29 @@ class QueryBuilderHelper {
265
341
  }
266
342
  appendQuerySubCondition(qb, type, method, cond, key, operator) {
267
343
  const m = operator === '$or' ? 'orWhere' : method;
344
+ if (cond[key] instanceof core_1.RawQueryFragment) {
345
+ cond[key] = this.knex.raw(cond[key].sql, cond[key].params);
346
+ }
268
347
  if (this.isSimpleRegExp(cond[key])) {
269
348
  return void qb[m](this.mapper(key, type), 'like', this.getRegExpParam(cond[key]));
270
349
  }
271
350
  if (core_1.Utils.isPlainObject(cond[key]) || cond[key] instanceof RegExp) {
272
351
  return this.processObjectSubCondition(cond, key, qb, method, m, type);
273
352
  }
274
- if (QueryBuilderHelper.isCustomExpression(key)) {
275
- return this.processCustomExpression(qb, m, key, cond, type);
276
- }
277
353
  const op = cond[key] === null ? 'is' : '=';
354
+ const raw = core_1.RawQueryFragment.getKnownFragment(key);
355
+ if (raw) {
356
+ const value = core_1.Utils.asArray(cond[key]);
357
+ if (value.length > 0) {
358
+ return void qb[m](this.knex.raw(raw.sql, raw.params), op, value[0]);
359
+ }
360
+ return void qb[m](this.knex.raw(raw.sql, raw.params));
361
+ }
278
362
  if (this.subQueries[key]) {
279
363
  return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), op, cond[key]);
280
364
  }
281
365
  qb[m](this.mapper(key, type, cond[key], null), op, cond[key]);
282
366
  }
283
- processCustomExpression(clause, m, key, cond, type = enums_1.QueryType.SELECT) {
284
- // unwind parameters when ? found in field name
285
- const count = key.concat('?').match(/\?/g).length - 1;
286
- const value = core_1.Utils.asArray(cond[key]);
287
- const params1 = value.slice(0, count).map((c) => core_1.Utils.isObject(c) ? JSON.stringify(c) : c);
288
- const params2 = value.slice(count);
289
- const k = this.mapper(key, type, params1);
290
- if (params2.length > 0) {
291
- const val = params2.length === 1 && params2[0] === null ? null : this.knex.raw('?', params2);
292
- return void clause[m](k, val);
293
- }
294
- clause[m](k);
295
- }
296
367
  processObjectSubCondition(cond, key, qb, method, m, type) {
297
368
  // grouped condition for one field
298
369
  let value = cond[key];
@@ -301,7 +372,7 @@ class QueryBuilderHelper {
301
372
  return subCondition.forEach(sub => this.appendQueryCondition(type, sub, qb, '$and', method));
302
373
  }
303
374
  if (value instanceof RegExp) {
304
- value = { $re: value.source };
375
+ value = this.platform.getRegExpValue(value);
305
376
  }
306
377
  // operators
307
378
  const op = Object.keys(core_1.QueryOperator).find(op => op in value);
@@ -311,68 +382,43 @@ class QueryBuilderHelper {
311
382
  const replacement = this.getOperatorReplacement(op, value);
312
383
  const fields = core_1.Utils.splitPrimaryKeys(key);
313
384
  if (fields.length > 1 && Array.isArray(value[op]) && !value[op].every((v) => Array.isArray(v))) {
314
- const values = this.platform.requiresValuesKeyword() ? 'values ' : '';
315
- value[op] = this.knex.raw(`${values}(${fields.map(() => '?').join(', ')})`, value[op]);
385
+ const tmp = value[op].length === 1 && core_1.Utils.isPlainObject(value[op][0]) ? fields.map(f => value[op][0][f]) : value[op];
386
+ value[op] = this.knex.raw(`(${fields.map(() => '?').join(', ')})`, tmp);
316
387
  }
317
388
  if (this.subQueries[key]) {
318
389
  return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), replacement, value[op]);
319
390
  }
320
- qb[m](this.mapper(key, type, undefined, null), replacement, value[op]);
391
+ if (op === '$fulltext') {
392
+ const [a, f] = this.splitField(key);
393
+ const prop = this.getProperty(f, a);
394
+ /* istanbul ignore next */
395
+ if (!prop) {
396
+ throw new Error(`Cannot use $fulltext operator on ${key}, property not found`);
397
+ }
398
+ qb[m](this.knex.raw(this.platform.getFullTextWhereClause(prop), {
399
+ column: this.mapper(key, type, undefined, null),
400
+ query: value[op],
401
+ }));
402
+ }
403
+ else {
404
+ const mappedKey = this.mapper(key, type, value[op], null);
405
+ qb[m](mappedKey, replacement, value[op]);
406
+ }
321
407
  }
322
408
  getOperatorReplacement(op, value) {
323
409
  let replacement = core_1.QueryOperator[op];
410
+ if (op === '$exists') {
411
+ replacement = value[op] ? 'is not' : 'is';
412
+ value[op] = null;
413
+ }
324
414
  if (value[op] === null && ['$eq', '$ne'].includes(op)) {
325
415
  replacement = op === '$eq' ? 'is' : 'is not';
326
416
  }
327
417
  if (op === '$re') {
328
- replacement = this.platform.getRegExpOperator();
418
+ replacement = this.platform.getRegExpOperator(value[op], value.$flags);
329
419
  }
330
420
  return replacement;
331
421
  }
332
- appendJoinClause(clause, cond, operator) {
333
- Object.keys(cond).forEach(k => {
334
- if (k === '$and' || k === '$or') {
335
- const method = operator === '$or' ? 'orOn' : 'andOn';
336
- const m = k === '$or' ? 'orOn' : 'andOn';
337
- return clause[method](outer => cond[k].forEach((sub) => {
338
- if (core_1.Utils.getObjectKeysSize(sub) === 1) {
339
- return this.appendJoinClause(outer, sub, k);
340
- }
341
- outer[m](inner => this.appendJoinClause(inner, sub, '$and'));
342
- }));
343
- }
344
- this.appendJoinSubClause(clause, cond, k, operator);
345
- });
346
- }
347
- appendJoinSubClause(clause, cond, key, operator) {
348
- const m = operator === '$or' ? 'orOn' : 'andOn';
349
- if (cond[key] instanceof RegExp) {
350
- return void clause[m](this.mapper(key), 'like', this.knex.raw('?', this.getRegExpParam(cond[key])));
351
- }
352
- if (core_1.Utils.isPlainObject(cond[key])) {
353
- return this.processObjectSubClause(cond, key, clause, m);
354
- }
355
- if (QueryBuilderHelper.isCustomExpression(key)) {
356
- return this.processCustomExpression(clause, m, key, cond);
357
- }
358
- const op = cond[key] === null ? 'is' : '=';
359
- clause[m](this.knex.raw(`${this.knex.ref(this.mapper(key, enums_1.QueryType.SELECT, cond[key]))} ${op} ?`, cond[key]));
360
- }
361
- processObjectSubClause(cond, key, clause, m) {
362
- // grouped condition for one field
363
- if (core_1.Utils.getObjectKeysSize(cond[key]) > 1) {
364
- const subCondition = Object.entries(cond[key]).map(([subKey, subValue]) => ({ [key]: { [subKey]: subValue } }));
365
- return void clause[m](inner => subCondition.map(sub => this.appendJoinClause(inner, sub, '$and')));
366
- }
367
- // operators
368
- for (const [op, replacement] of Object.entries(core_1.QueryOperator)) {
369
- if (!(op in cond[key])) {
370
- continue;
371
- }
372
- clause[m](this.mapper(key), replacement, this.knex.raw('?', cond[key][op]));
373
- break;
374
- }
375
- }
376
422
  getQueryOrder(type, orderBy, populate) {
377
423
  if (Array.isArray(orderBy)) {
378
424
  return orderBy
@@ -384,42 +430,72 @@ class QueryBuilderHelper {
384
430
  }
385
431
  getQueryOrderFromObject(type, orderBy, populate) {
386
432
  const ret = [];
387
- Object.keys(orderBy).forEach(k => {
388
- const direction = orderBy[k];
433
+ Object.keys(orderBy).forEach(key => {
434
+ const direction = orderBy[key];
389
435
  const order = core_1.Utils.isNumber(direction) ? core_1.QueryOrderNumeric[direction] : direction;
390
- if (QueryBuilderHelper.isCustomExpression(k)) {
391
- ret.push(`${k} ${order.toLowerCase()}`);
436
+ const raw = core_1.RawQueryFragment.getKnownFragment(key);
437
+ if (raw) {
438
+ ret.push(`${this.platform.formatQuery(raw.sql, raw.params)} ${order.toLowerCase()}`);
392
439
  return;
393
440
  }
394
- // eslint-disable-next-line prefer-const
395
- let [alias, field] = this.splitField(k);
396
- alias = populate[alias] || alias;
397
- core_1.Utils.splitPrimaryKeys(field).forEach(f => {
398
- const prop = this.getProperty(f, alias);
399
- const noPrefix = (prop && prop.persist === false) || QueryBuilderHelper.isCustomExpression(f);
400
- const column = this.mapper(noPrefix ? f : `${alias}.${f}`, type);
441
+ core_1.Utils.splitPrimaryKeys(key).forEach(f => {
442
+ // eslint-disable-next-line prefer-const
443
+ let [alias, field] = this.splitField(f, true);
444
+ alias = populate[alias] || alias;
445
+ const prop = this.getProperty(field, alias);
446
+ const noPrefix = (prop && prop.persist === false && !prop.formula) || core_1.RawQueryFragment.isKnownFragment(f);
447
+ const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
401
448
  /* istanbul ignore next */
402
449
  const rawColumn = core_1.Utils.isString(column) ? column.split('.').map(e => this.knex.ref(e)).join('.') : column;
403
450
  const customOrder = prop?.customOrder;
404
- const colPart = customOrder
451
+ let colPart = customOrder
405
452
  ? this.platform.generateCustomOrder(rawColumn, customOrder)
406
453
  : rawColumn;
454
+ if (core_1.Utils.isRawSql(colPart)) {
455
+ colPart = this.platform.formatQuery(colPart.sql, colPart.params);
456
+ }
407
457
  ret.push(`${colPart} ${order.toLowerCase()}`);
408
458
  });
409
459
  });
410
460
  return ret.join(', ');
411
461
  }
412
- finalize(type, qb, meta) {
413
- const useReturningStatement = type === enums_1.QueryType.INSERT && this.platform.usesReturningStatement() && meta && !meta.compositePK;
414
- if (useReturningStatement) {
415
- const returningProps = meta.props.filter(prop => prop.primary || prop.defaultRaw);
416
- qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
462
+ finalize(type, qb, meta, data, returning) {
463
+ if (!meta || !data || !this.platform.usesReturningStatement()) {
464
+ return;
465
+ }
466
+ // always respect explicit returning hint
467
+ if (returning && returning.length > 0) {
468
+ qb.returning(returning.map(field => this.mapper(field, type)));
469
+ return;
470
+ }
471
+ if (type === enums_1.QueryType.INSERT) {
472
+ const returningProps = meta.hydrateProps
473
+ .filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
474
+ .filter(prop => !(prop.name in data));
475
+ if (returningProps.length > 0) {
476
+ qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
477
+ }
478
+ return;
479
+ }
480
+ if (type === enums_1.QueryType.UPDATE) {
481
+ const returningProps = meta.hydrateProps.filter(prop => core_1.Utils.isRawSql(data[prop.name]));
482
+ if (returningProps.length > 0) {
483
+ qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
484
+ }
417
485
  }
418
486
  }
419
- splitField(field) {
487
+ splitField(field, greedyAlias = false) {
420
488
  const parts = field.split('.');
421
- const fromField = parts.pop();
422
- const fromAlias = parts.length > 0 ? parts.join('.') : this.alias;
489
+ if (parts.length === 1) {
490
+ return [this.alias, parts[0]];
491
+ }
492
+ if (greedyAlias) {
493
+ const fromField = parts.pop();
494
+ const fromAlias = parts.join('.');
495
+ return [fromAlias, fromField];
496
+ }
497
+ const fromAlias = parts.shift();
498
+ const fromField = parts.join('.');
423
499
  return [fromAlias, fromField];
424
500
  }
425
501
  getLockSQL(qb, lockMode, lockTables = []) {
@@ -438,33 +514,29 @@ class QueryBuilderHelper {
438
514
  }
439
515
  updateVersionProperty(qb, data) {
440
516
  const meta = this.metadata.find(this.entityName);
441
- if (!meta || !meta.versionProperty || meta.versionProperty in data) {
517
+ if (!meta?.versionProperty || meta.versionProperty in data) {
442
518
  return;
443
519
  }
444
520
  const versionProperty = meta.properties[meta.versionProperty];
445
521
  let sql = this.platform.quoteIdentifier(versionProperty.fieldNames[0]) + ' + 1';
446
- if (versionProperty.type.toLowerCase() === 'date') {
522
+ if (versionProperty.runtimeType === 'Date') {
447
523
  sql = this.platform.getCurrentTimestampSQL(versionProperty.length);
448
524
  }
449
525
  qb.update(versionProperty.fieldNames[0], this.knex.raw(sql));
450
526
  }
451
- static isCustomExpression(field, hasAlias = false) {
452
- // if we do not have alias, we don't consider spaces as custom expressions
453
- const re = hasAlias ? /[ ?<>=()'"`:]|^\d/ : /[?<>=()'"`:]|^\d/;
454
- return !!field.match(re);
455
- }
456
527
  prefix(field, always = false, quote = false) {
457
528
  let ret;
458
529
  if (!this.isPrefixed(field)) {
459
530
  const alias = always ? (quote ? this.alias : this.platform.quoteIdentifier(this.alias)) + '.' : '';
460
531
  const fieldName = this.fieldName(field, this.alias, always);
461
- if (QueryBuilderHelper.isCustomExpression(fieldName)) {
462
- return fieldName;
532
+ if (fieldName instanceof core_1.RawQueryFragment) {
533
+ return fieldName.sql;
463
534
  }
464
535
  ret = alias + fieldName;
465
536
  }
466
537
  else {
467
- const [a, f] = field.split('.');
538
+ const [a, ...rest] = field.split('.');
539
+ const f = rest.join('.');
468
540
  ret = a + '.' + this.fieldName(f, a);
469
541
  }
470
542
  if (quote) {
@@ -498,26 +570,78 @@ class QueryBuilderHelper {
498
570
  }
499
571
  if (prop.fieldNameRaw) {
500
572
  if (!always) {
501
- return prop.fieldNameRaw
502
- .replace(/\[::alias::]\.?/g, '')
503
- .replace(this.platform.quoteIdentifier('') + '.', '');
573
+ return (0, core_1.raw)(prop.fieldNameRaw
574
+ .replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE + '\\.?', 'g'), '')
575
+ .replace(this.platform.quoteIdentifier('') + '.', ''));
504
576
  }
505
577
  if (alias) {
506
- return prop.fieldNameRaw.replace(/\[::alias::]/g, alias);
578
+ return (0, core_1.raw)(prop.fieldNameRaw.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE, 'g'), alias));
507
579
  }
508
580
  /* istanbul ignore next */
509
- return prop.fieldNameRaw;
581
+ return (0, core_1.raw)(prop.fieldNameRaw);
510
582
  }
511
583
  /* istanbul ignore next */
512
- return prop.fieldNames[0] ?? field;
584
+ return prop.fieldNames?.[0] ?? field;
513
585
  }
514
586
  getProperty(field, alias) {
515
- const entityName = this.aliasMap[alias] || this.entityName;
587
+ const entityName = this.aliasMap[alias]?.entityName || this.entityName;
516
588
  const meta = this.metadata.find(entityName);
517
- return meta ? meta.properties[field] : undefined;
589
+ // check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
590
+ if (alias && meta) {
591
+ const prop = meta.properties[alias];
592
+ if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
593
+ // we want to select the full object property so hydration works as expected
594
+ if (prop.object) {
595
+ return prop;
596
+ }
597
+ const parts = field.split('.');
598
+ const nest = (p) => parts.length > 0 ? nest(p.embeddedProps[parts.shift()]) : p;
599
+ return nest(prop);
600
+ }
601
+ }
602
+ return meta?.properties[field];
518
603
  }
519
604
  isTableNameAliasRequired(type) {
520
- return [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(type);
605
+ return [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(type ?? enums_1.QueryType.SELECT);
606
+ }
607
+ mapData(data, properties, convertCustomTypes) {
608
+ if (!properties) {
609
+ return data;
610
+ }
611
+ data = Object.assign({}, data); // copy first
612
+ Object.keys(data).forEach(k => {
613
+ const prop = properties[k];
614
+ if (!prop) {
615
+ return;
616
+ }
617
+ if (prop.embeddedProps && !prop.object) {
618
+ const copy = data[k];
619
+ delete data[k];
620
+ Object.assign(data, this.mapData(copy, prop.embeddedProps, convertCustomTypes));
621
+ return;
622
+ }
623
+ if (prop.joinColumns && Array.isArray(data[k])) {
624
+ const copy = core_1.Utils.flatten(data[k]);
625
+ delete data[k];
626
+ prop.joinColumns.forEach((joinColumn, idx) => data[joinColumn] = copy[idx]);
627
+ return;
628
+ }
629
+ if (prop.customType && convertCustomTypes && !this.platform.isRaw(data[k])) {
630
+ data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, { fromQuery: true, key: k, mode: 'query-data' });
631
+ }
632
+ if (prop.hasConvertToDatabaseValueSQL && !this.platform.isRaw(data[k])) {
633
+ const quoted = this.platform.quoteValue(data[k]);
634
+ const sql = prop.customType.convertToDatabaseValueSQL(quoted, this.platform);
635
+ data[k] = this.knex.raw(sql.replace(/\?/g, '\\?'));
636
+ }
637
+ if (!prop.customType && (Array.isArray(data[k]) || core_1.Utils.isPlainObject(data[k]))) {
638
+ data[k] = JSON.stringify(data[k]);
639
+ }
640
+ if (prop.fieldNames) {
641
+ core_1.Utils.renameKey(data, k, prop.fieldNames[0]);
642
+ }
643
+ });
644
+ return data;
521
645
  }
522
646
  }
523
647
  exports.QueryBuilderHelper = QueryBuilderHelper;