@mikro-orm/sql 7.0.0-dev.97 → 7.0.0-dev.99

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 (91) hide show
  1. package/AbstractSqlConnection.d.ts +57 -0
  2. package/AbstractSqlConnection.js +239 -0
  3. package/AbstractSqlDriver.d.ts +94 -0
  4. package/AbstractSqlDriver.js +1387 -0
  5. package/AbstractSqlPlatform.d.ts +38 -0
  6. package/AbstractSqlPlatform.js +104 -0
  7. package/LICENSE +21 -0
  8. package/PivotCollectionPersister.d.ts +22 -0
  9. package/PivotCollectionPersister.js +159 -0
  10. package/README.md +390 -0
  11. package/SqlEntityManager.d.ts +33 -0
  12. package/SqlEntityManager.js +44 -0
  13. package/SqlEntityRepository.d.ts +19 -0
  14. package/SqlEntityRepository.js +26 -0
  15. package/dialects/index.d.ts +4 -0
  16. package/dialects/index.js +4 -0
  17. package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +14 -0
  18. package/dialects/mssql/MsSqlNativeQueryBuilder.js +200 -0
  19. package/dialects/mssql/index.d.ts +1 -0
  20. package/dialects/mssql/index.js +1 -0
  21. package/dialects/mysql/MySqlExceptionConverter.d.ts +9 -0
  22. package/dialects/mysql/MySqlExceptionConverter.js +80 -0
  23. package/dialects/mysql/MySqlNativeQueryBuilder.d.ts +7 -0
  24. package/dialects/mysql/MySqlNativeQueryBuilder.js +77 -0
  25. package/dialects/mysql/MySqlPlatform.d.ts +45 -0
  26. package/dialects/mysql/MySqlPlatform.js +116 -0
  27. package/dialects/mysql/MySqlSchemaHelper.d.ts +36 -0
  28. package/dialects/mysql/MySqlSchemaHelper.js +269 -0
  29. package/dialects/mysql/index.d.ts +4 -0
  30. package/dialects/mysql/index.js +4 -0
  31. package/dialects/postgresql/PostgreSqlNativeQueryBuilder.d.ts +5 -0
  32. package/dialects/postgresql/PostgreSqlNativeQueryBuilder.js +8 -0
  33. package/dialects/postgresql/PostgreSqlTableCompiler.d.ts +1 -0
  34. package/dialects/postgresql/PostgreSqlTableCompiler.js +1 -0
  35. package/dialects/postgresql/index.d.ts +1 -0
  36. package/dialects/postgresql/index.js +1 -0
  37. package/dialects/sqlite/BaseSqliteConnection.d.ts +6 -0
  38. package/dialects/sqlite/BaseSqliteConnection.js +8 -0
  39. package/dialects/sqlite/BaseSqlitePlatform.d.ts +70 -0
  40. package/dialects/sqlite/BaseSqlitePlatform.js +104 -0
  41. package/dialects/sqlite/SqliteExceptionConverter.d.ts +9 -0
  42. package/dialects/sqlite/SqliteExceptionConverter.js +54 -0
  43. package/dialects/sqlite/SqliteNativeQueryBuilder.d.ts +6 -0
  44. package/dialects/sqlite/SqliteNativeQueryBuilder.js +11 -0
  45. package/dialects/sqlite/SqliteSchemaHelper.d.ts +38 -0
  46. package/dialects/sqlite/SqliteSchemaHelper.js +379 -0
  47. package/dialects/sqlite/index.d.ts +5 -0
  48. package/dialects/sqlite/index.js +5 -0
  49. package/index.d.ts +19 -0
  50. package/index.js +19 -0
  51. package/package.json +3 -3
  52. package/plugin/index.d.ts +53 -0
  53. package/plugin/index.js +42 -0
  54. package/plugin/transformer.d.ts +115 -0
  55. package/plugin/transformer.js +883 -0
  56. package/query/ArrayCriteriaNode.d.ts +11 -0
  57. package/query/ArrayCriteriaNode.js +24 -0
  58. package/query/CriteriaNode.d.ts +29 -0
  59. package/query/CriteriaNode.js +121 -0
  60. package/query/CriteriaNodeFactory.d.ts +12 -0
  61. package/query/CriteriaNodeFactory.js +90 -0
  62. package/query/NativeQueryBuilder.d.ts +108 -0
  63. package/query/NativeQueryBuilder.js +425 -0
  64. package/query/ObjectCriteriaNode.d.ts +19 -0
  65. package/query/ObjectCriteriaNode.js +249 -0
  66. package/query/QueryBuilder.d.ts +389 -0
  67. package/query/QueryBuilder.js +1558 -0
  68. package/query/QueryBuilderHelper.d.ts +73 -0
  69. package/query/QueryBuilderHelper.js +756 -0
  70. package/query/ScalarCriteriaNode.d.ts +10 -0
  71. package/query/ScalarCriteriaNode.js +49 -0
  72. package/query/enums.d.ts +18 -0
  73. package/query/enums.js +20 -0
  74. package/query/index.d.ts +10 -0
  75. package/query/index.js +10 -0
  76. package/query/raw.d.ts +59 -0
  77. package/query/raw.js +68 -0
  78. package/schema/DatabaseSchema.d.ts +45 -0
  79. package/schema/DatabaseSchema.js +185 -0
  80. package/schema/DatabaseTable.d.ts +68 -0
  81. package/schema/DatabaseTable.js +793 -0
  82. package/schema/SchemaComparator.d.ts +58 -0
  83. package/schema/SchemaComparator.js +577 -0
  84. package/schema/SchemaHelper.d.ts +76 -0
  85. package/schema/SchemaHelper.js +545 -0
  86. package/schema/SqlSchemaGenerator.d.ts +65 -0
  87. package/schema/SqlSchemaGenerator.js +375 -0
  88. package/schema/index.d.ts +5 -0
  89. package/schema/index.js +5 -0
  90. package/typings.d.ts +272 -0
  91. package/typings.js +1 -0
@@ -0,0 +1,756 @@
1
+ import { ALIAS_REPLACEMENT, ALIAS_REPLACEMENT_RE, ArrayType, isRaw, LockMode, OptimisticLockError, QueryOperator, QueryOrderNumeric, raw, RawQueryFragment, ReferenceKind, Utils, ValidationError, } from '@mikro-orm/core';
2
+ import { JoinType, QueryType } from './enums.js';
3
+ import { NativeQueryBuilder } from './NativeQueryBuilder.js';
4
+ /**
5
+ * @internal
6
+ */
7
+ export class QueryBuilderHelper {
8
+ entityName;
9
+ alias;
10
+ aliasMap;
11
+ subQueries;
12
+ driver;
13
+ platform;
14
+ metadata;
15
+ constructor(entityName, alias, aliasMap, subQueries, driver) {
16
+ this.entityName = entityName;
17
+ this.alias = alias;
18
+ this.aliasMap = aliasMap;
19
+ this.subQueries = subQueries;
20
+ this.driver = driver;
21
+ this.platform = this.driver.getPlatform();
22
+ this.metadata = this.driver.getMetadata();
23
+ }
24
+ mapper(field, type = QueryType.SELECT, value, alias) {
25
+ if (isRaw(field)) {
26
+ return raw(field.sql, field.params);
27
+ }
28
+ /* v8 ignore next */
29
+ if (typeof field !== 'string') {
30
+ return field;
31
+ }
32
+ const isTableNameAliasRequired = this.isTableNameAliasRequired(type);
33
+ const fields = Utils.splitPrimaryKeys(field);
34
+ if (fields.length > 1) {
35
+ const parts = [];
36
+ for (const p of fields) {
37
+ const [a, f] = this.splitField(p);
38
+ const prop = this.getProperty(f, a);
39
+ const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
40
+ if (fkIdx2 !== -1) {
41
+ parts.push(this.mapper(a !== this.alias ? `${a}.${prop.fieldNames[fkIdx2]}` : prop.fieldNames[fkIdx2], type, value, alias));
42
+ }
43
+ else if (prop) {
44
+ parts.push(...prop.fieldNames.map(f => this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias)));
45
+ }
46
+ else {
47
+ parts.push(this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias));
48
+ }
49
+ }
50
+ // flatten the value if we see we are expanding nested composite key
51
+ // hackish, but cleaner solution would require quite a lot of refactoring
52
+ if (fields.length !== parts.length && Array.isArray(value)) {
53
+ value.forEach(row => {
54
+ if (Array.isArray(row)) {
55
+ const tmp = Utils.flatten(row);
56
+ row.length = 0;
57
+ row.push(...tmp);
58
+ }
59
+ });
60
+ }
61
+ return raw('(' + parts.map(part => this.platform.quoteIdentifier(part)).join(', ') + ')');
62
+ }
63
+ const rawField = RawQueryFragment.getKnownFragment(field);
64
+ if (rawField) {
65
+ return rawField;
66
+ }
67
+ const aliasPrefix = isTableNameAliasRequired ? this.alias + '.' : '';
68
+ const [a, f] = this.splitField(field);
69
+ const prop = this.getProperty(f, a);
70
+ const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
71
+ const fkIdx = fkIdx2 === -1 ? 0 : fkIdx2;
72
+ let ret = field;
73
+ // embeddable nested path instead of a regular property with table alias, reset alias
74
+ if (prop?.name === a && prop.embeddedProps[f]) {
75
+ return aliasPrefix + prop.fieldNames[fkIdx];
76
+ }
77
+ if (a === prop?.embedded?.[0]) {
78
+ return aliasPrefix + prop.fieldNames[fkIdx];
79
+ }
80
+ const noPrefix = prop?.persist === false;
81
+ if (prop?.fieldNameRaw) {
82
+ return raw(this.prefix(field, isTableNameAliasRequired));
83
+ }
84
+ if (prop?.formula) {
85
+ const alias2 = this.platform.quoteIdentifier(a).toString();
86
+ const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]).toString();
87
+ const as = alias === null ? '' : ` as ${aliased}`;
88
+ let value = prop.formula(alias2);
89
+ if (!this.isTableNameAliasRequired(type)) {
90
+ value = value.replaceAll(alias2 + '.', '');
91
+ }
92
+ return raw(`${value}${as}`);
93
+ }
94
+ if (prop?.hasConvertToJSValueSQL && type !== QueryType.UPSERT) {
95
+ let valueSQL;
96
+ if (prop.fieldNames.length > 1 && fkIdx !== -1) {
97
+ const fk = prop.targetMeta.getPrimaryProps()[fkIdx];
98
+ const prefixed = this.prefix(field, isTableNameAliasRequired, true, fkIdx);
99
+ valueSQL = fk.customType.convertToJSValueSQL(prefixed, this.platform);
100
+ }
101
+ else {
102
+ const prefixed = this.prefix(field, isTableNameAliasRequired, true);
103
+ valueSQL = prop.customType.convertToJSValueSQL(prefixed, this.platform);
104
+ }
105
+ if (alias === null) {
106
+ return raw(valueSQL);
107
+ }
108
+ return raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[fkIdx])}`);
109
+ }
110
+ // do not wrap custom expressions
111
+ if (!rawField) {
112
+ ret = this.prefix(field, false, false, fkIdx);
113
+ }
114
+ if (alias) {
115
+ ret += ' as ' + alias;
116
+ }
117
+ if (!isTableNameAliasRequired || this.isPrefixed(ret) || noPrefix) {
118
+ return ret;
119
+ }
120
+ return this.alias + '.' + ret;
121
+ }
122
+ processData(data, convertCustomTypes, multi = false) {
123
+ if (Array.isArray(data)) {
124
+ return data.map(d => this.processData(d, convertCustomTypes, true));
125
+ }
126
+ const meta = this.metadata.find(this.entityName);
127
+ data = this.driver.mapDataToFieldNames(data, true, meta?.properties, convertCustomTypes);
128
+ if (!Utils.hasObjectKeys(data) && meta && multi) {
129
+ /* v8 ignore next */
130
+ data[meta.getPrimaryProps()[0].fieldNames[0]] = this.platform.usesDefaultKeyword() ? raw('default') : undefined;
131
+ }
132
+ return data;
133
+ }
134
+ joinOneToReference(prop, ownerAlias, alias, type, cond = {}, schema) {
135
+ const prop2 = prop.targetMeta.properties[prop.mappedBy || prop.inversedBy];
136
+ const table = this.getTableName(prop.type);
137
+ const joinColumns = prop.owner ? prop.referencedColumnNames : prop2.joinColumns;
138
+ const inverseJoinColumns = prop.referencedColumnNames;
139
+ const primaryKeys = prop.owner ? prop.joinColumns : prop2.referencedColumnNames;
140
+ schema ??= prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta);
141
+ cond = Utils.merge(cond, prop.where);
142
+ return {
143
+ prop, type, cond, ownerAlias, alias, table, schema,
144
+ joinColumns, inverseJoinColumns, primaryKeys,
145
+ };
146
+ }
147
+ joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}, schema) {
148
+ return {
149
+ prop, type, cond, ownerAlias, alias,
150
+ table: this.getTableName(prop.type),
151
+ schema: prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(prop.targetMeta, { schema }),
152
+ joinColumns: prop.referencedColumnNames,
153
+ primaryKeys: prop.fieldNames,
154
+ };
155
+ }
156
+ joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path, schema) {
157
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
158
+ const ret = {
159
+ [`${ownerAlias}.${prop.name}#${pivotAlias}`]: {
160
+ prop, type, ownerAlias,
161
+ alias: pivotAlias,
162
+ inverseAlias: alias,
163
+ joinColumns: prop.joinColumns,
164
+ inverseJoinColumns: prop.inverseJoinColumns,
165
+ primaryKeys: prop.referencedColumnNames,
166
+ cond: {},
167
+ table: pivotMeta.tableName,
168
+ schema: prop.targetMeta?.schema === '*' ? '*' : this.driver.getSchemaName(pivotMeta, { schema }),
169
+ path: path.endsWith('[pivot]') ? path : `${path}[pivot]`,
170
+ },
171
+ };
172
+ if (type === JoinType.pivotJoin) {
173
+ return ret;
174
+ }
175
+ const prop2 = prop.owner ? pivotMeta.relations[1] : pivotMeta.relations[0];
176
+ ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type, cond, schema);
177
+ ret[`${pivotAlias}.${prop2.name}#${alias}`].path = path;
178
+ const tmp = prop2.referencedTableName.split('.');
179
+ ret[`${pivotAlias}.${prop2.name}#${alias}`].schema ??= tmp.length > 1 ? tmp[0] : undefined;
180
+ return ret;
181
+ }
182
+ processJoins(qb, joins, schema) {
183
+ Object.values(joins).forEach(join => {
184
+ if ([JoinType.nestedInnerJoin, JoinType.nestedLeftJoin].includes(join.type)) {
185
+ return;
186
+ }
187
+ const { sql, params } = this.createJoinExpression(join, joins, schema);
188
+ qb.join(sql, params);
189
+ });
190
+ }
191
+ createJoinExpression(join, joins, schema) {
192
+ let table = join.table;
193
+ const method = {
194
+ [JoinType.nestedInnerJoin]: 'inner join',
195
+ [JoinType.nestedLeftJoin]: 'left join',
196
+ [JoinType.pivotJoin]: 'left join',
197
+ }[join.type] ?? join.type;
198
+ const conditions = [];
199
+ const params = [];
200
+ schema = join.schema && join.schema !== '*' ? join.schema : schema;
201
+ if (schema && schema !== this.platform.getDefaultSchemaName()) {
202
+ table = `${schema}.${table}`;
203
+ }
204
+ if (join.prop.name !== '__subquery__') {
205
+ join.primaryKeys.forEach((primaryKey, idx) => {
206
+ const right = `${join.alias}.${join.joinColumns[idx]}`;
207
+ if (join.prop.formula) {
208
+ const alias = this.platform.quoteIdentifier(join.ownerAlias);
209
+ const left = join.prop.formula(alias);
210
+ conditions.push(`${left} = ${this.platform.quoteIdentifier(right)}`);
211
+ return;
212
+ }
213
+ const left = join.prop.object && join.prop.fieldNameRaw
214
+ ? join.prop.fieldNameRaw.replaceAll(ALIAS_REPLACEMENT, join.ownerAlias)
215
+ : this.platform.quoteIdentifier(`${join.ownerAlias}.${primaryKey}`);
216
+ conditions.push(`${left} = ${this.platform.quoteIdentifier(right)}`);
217
+ });
218
+ }
219
+ if (join.prop.targetMeta?.discriminatorValue && !join.path?.endsWith('[pivot]')) {
220
+ const typeProperty = join.prop.targetMeta.root.discriminatorColumn;
221
+ const alias = join.inverseAlias ?? join.alias;
222
+ join.cond[`${alias}.${typeProperty}`] = join.prop.targetMeta.discriminatorValue;
223
+ }
224
+ let sql = method + ' ';
225
+ if (join.nested) {
226
+ sql += `(${this.platform.quoteIdentifier(table)} as ${this.platform.quoteIdentifier(join.alias)}`;
227
+ for (const nested of join.nested) {
228
+ const { sql: nestedSql, params: nestedParams } = this.createJoinExpression(nested, joins, schema);
229
+ sql += ' ' + nestedSql;
230
+ params.push(...nestedParams);
231
+ }
232
+ sql += `)`;
233
+ }
234
+ else if (join.subquery) {
235
+ sql += `(${join.subquery}) as ${this.platform.quoteIdentifier(join.alias)}`;
236
+ }
237
+ else {
238
+ sql += `${this.platform.quoteIdentifier(table)} as ${this.platform.quoteIdentifier(join.alias)}`;
239
+ }
240
+ const oldAlias = this.alias;
241
+ this.alias = join.alias;
242
+ const subquery = this._appendQueryCondition(QueryType.SELECT, join.cond);
243
+ this.alias = oldAlias;
244
+ if (subquery.sql) {
245
+ conditions.push(subquery.sql);
246
+ params.push(...subquery.params);
247
+ }
248
+ if (conditions.length > 0) {
249
+ sql += ` on ${conditions.join(' and ')}`;
250
+ }
251
+ return { sql, params };
252
+ }
253
+ mapJoinColumns(type, join) {
254
+ if (join.prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(join.prop.kind)) {
255
+ return join.prop.fieldNames.map((_fieldName, idx) => {
256
+ const columns = join.prop.owner ? join.joinColumns : join.inverseJoinColumns;
257
+ return this.mapper(`${join.alias}.${columns[idx]}`, type, undefined, `${join.alias}__${columns[idx]}`);
258
+ });
259
+ }
260
+ return [
261
+ ...join.joinColumns.map(col => this.mapper(`${join.alias}.${col}`, type, undefined, `fk__${col}`)),
262
+ ...join.inverseJoinColumns.map(col => this.mapper(`${join.alias}.${col}`, type, undefined, `fk__${col}`)),
263
+ ];
264
+ }
265
+ isOneToOneInverse(field, meta) {
266
+ meta ??= this.metadata.find(this.entityName);
267
+ const prop = meta.properties[field.replace(/:ref$/, '')];
268
+ return prop && prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner;
269
+ }
270
+ getTableName(entityName) {
271
+ const meta = this.metadata.find(entityName);
272
+ return meta ? meta.collection : entityName;
273
+ }
274
+ /**
275
+ * Checks whether the RE can be rewritten to simple LIKE query
276
+ */
277
+ isSimpleRegExp(re) {
278
+ if (!(re instanceof RegExp)) {
279
+ return false;
280
+ }
281
+ if (re.flags.includes('i')) {
282
+ return false;
283
+ }
284
+ // when including the opening bracket/paren we consider it complex
285
+ return !re.source.match(/[{[(]/);
286
+ }
287
+ getRegExpParam(re) {
288
+ const value = re.source
289
+ .replace(/\.\*/g, '%') // .* -> %
290
+ .replace(/\./g, '_') // . -> _
291
+ .replace(/\\_/g, '.') // \. -> .
292
+ .replace(/^\^/g, '') // remove ^ from start
293
+ .replace(/\$$/g, ''); // remove $ from end
294
+ if (re.source.startsWith('^') && re.source.endsWith('$')) {
295
+ return value;
296
+ }
297
+ if (re.source.startsWith('^')) {
298
+ return value + '%';
299
+ }
300
+ if (re.source.endsWith('$')) {
301
+ return '%' + value;
302
+ }
303
+ return `%${value}%`;
304
+ }
305
+ appendOnConflictClause(type, onConflict, qb) {
306
+ onConflict.forEach(item => {
307
+ const { fields, ignore } = item;
308
+ const sub = qb.onConflict({ fields, ignore });
309
+ Utils.runIfNotEmpty(() => {
310
+ let mergeParam = item.merge;
311
+ if (Utils.isObject(item.merge)) {
312
+ mergeParam = {};
313
+ Utils.keys(item.merge).forEach(key => {
314
+ const k = this.mapper(key, type);
315
+ mergeParam[k] = item.merge[key];
316
+ });
317
+ }
318
+ if (Array.isArray(item.merge)) {
319
+ mergeParam = item.merge.map(key => this.mapper(key, type));
320
+ }
321
+ sub.merge = mergeParam ?? [];
322
+ if (item.where) {
323
+ sub.where = this._appendQueryCondition(type, item.where);
324
+ }
325
+ }, 'merge' in item);
326
+ });
327
+ }
328
+ appendQueryCondition(type, cond, qb, operator, method = 'where') {
329
+ const { sql, params } = this._appendQueryCondition(type, cond, operator);
330
+ qb[method](sql, params);
331
+ }
332
+ _appendQueryCondition(type, cond, operator) {
333
+ const parts = [];
334
+ const params = [];
335
+ for (const k of Object.keys(cond)) {
336
+ if (k === '$and' || k === '$or') {
337
+ if (operator) {
338
+ this.append(() => this.appendGroupCondition(type, k, cond[k]), parts, params, operator);
339
+ continue;
340
+ }
341
+ this.append(() => this.appendGroupCondition(type, k, cond[k]), parts, params);
342
+ continue;
343
+ }
344
+ if (k === '$not') {
345
+ const res = this._appendQueryCondition(type, cond[k]);
346
+ parts.push(`not (${res.sql})`);
347
+ params.push(...res.params);
348
+ continue;
349
+ }
350
+ this.append(() => this.appendQuerySubCondition(type, cond, k), parts, params);
351
+ }
352
+ return { sql: parts.join(' and '), params };
353
+ }
354
+ append(cb, parts, params, operator) {
355
+ const res = cb();
356
+ if (['', '()'].includes(res.sql)) {
357
+ return;
358
+ }
359
+ parts.push(operator === '$or' ? `(${res.sql})` : res.sql);
360
+ res.params.forEach(p => params.push(p));
361
+ }
362
+ appendQuerySubCondition(type, cond, key) {
363
+ const parts = [];
364
+ const params = [];
365
+ const fields = Utils.splitPrimaryKeys(key);
366
+ if (this.isSimpleRegExp(cond[key])) {
367
+ parts.push(`${this.platform.quoteIdentifier(this.mapper(key, type))} like ?`);
368
+ params.push(this.getRegExpParam(cond[key]));
369
+ return { sql: parts.join(' and '), params };
370
+ }
371
+ if (Utils.isPlainObject(cond[key]) || cond[key] instanceof RegExp) {
372
+ return this.processObjectSubCondition(cond, key, type);
373
+ }
374
+ const op = cond[key] === null ? 'is' : '=';
375
+ const raw = RawQueryFragment.getKnownFragment(key);
376
+ if (raw) {
377
+ const sql = raw.sql.replaceAll(ALIAS_REPLACEMENT, this.alias);
378
+ const value = Utils.asArray(cond[key]);
379
+ params.push(...raw.params);
380
+ if (value.length > 0) {
381
+ const val = this.getValueReplacement(fields, value[0], params, key);
382
+ parts.push(`${sql} ${op} ${val}`);
383
+ return { sql: parts.join(' and '), params };
384
+ }
385
+ parts.push(sql);
386
+ return { sql: parts.join(' and '), params };
387
+ }
388
+ if (this.subQueries[key]) {
389
+ const val = this.getValueReplacement(fields, cond[key], params, key);
390
+ parts.push(`(${this.subQueries[key]}) ${op} ${val}`);
391
+ return { sql: parts.join(' and '), params };
392
+ }
393
+ const val = this.getValueReplacement(fields, cond[key], params, key);
394
+ parts.push(`${this.platform.quoteIdentifier(this.mapper(key, type, cond[key], null))} ${op} ${val}`);
395
+ return { sql: parts.join(' and '), params };
396
+ }
397
+ processObjectSubCondition(cond, key, type) {
398
+ const parts = [];
399
+ const params = [];
400
+ let value = cond[key];
401
+ const size = Utils.getObjectKeysSize(value);
402
+ if (Utils.isPlainObject(value) && size === 0) {
403
+ return { sql: '', params };
404
+ }
405
+ // grouped condition for one field, e.g. `{ age: { $gte: 10, $lt: 50 } }`
406
+ if (size > 1) {
407
+ const rawField = RawQueryFragment.getKnownFragment(key);
408
+ const subCondition = Object.entries(value).map(([subKey, subValue]) => {
409
+ key = rawField?.clone().toString() ?? key;
410
+ return ({ [key]: { [subKey]: subValue } });
411
+ });
412
+ for (const sub of subCondition) {
413
+ this.append(() => this._appendQueryCondition(type, sub, '$and'), parts, params);
414
+ }
415
+ return { sql: parts.join(' and '), params };
416
+ }
417
+ if (value instanceof RegExp) {
418
+ value = this.platform.getRegExpValue(value);
419
+ }
420
+ // operators
421
+ const op = Object.keys(QueryOperator).find(op => op in value);
422
+ /* v8 ignore next */
423
+ if (!op) {
424
+ throw ValidationError.invalidQueryCondition(cond);
425
+ }
426
+ const replacement = this.getOperatorReplacement(op, value);
427
+ const fields = Utils.splitPrimaryKeys(key);
428
+ if (fields.length > 1 && Array.isArray(value[op])) {
429
+ const singleTuple = !value[op].every((v) => Array.isArray(v));
430
+ if (!this.platform.allowsComparingTuples()) {
431
+ const mapped = fields.map(f => this.mapper(f, type));
432
+ if (op === '$in') {
433
+ const conds = value[op].map(() => {
434
+ return `(${mapped.map(field => `${this.platform.quoteIdentifier(field)} = ?`).join(' and ')})`;
435
+ });
436
+ parts.push(`(${conds.join(' or ')})`);
437
+ params.push(...Utils.flatten(value[op]));
438
+ return { sql: parts.join(' and '), params };
439
+ }
440
+ parts.push(...mapped.map(field => `${this.platform.quoteIdentifier(field)} = ?`));
441
+ params.push(...Utils.flatten(value[op]));
442
+ return { sql: parts.join(' and '), params };
443
+ }
444
+ if (singleTuple) {
445
+ const tmp = value[op].length === 1 && Utils.isPlainObject(value[op][0]) ? fields.map(f => value[op][0][f]) : value[op];
446
+ const sql = `(${fields.map(() => '?').join(', ')})`;
447
+ value[op] = raw(sql, tmp);
448
+ }
449
+ }
450
+ if (this.subQueries[key]) {
451
+ const val = this.getValueReplacement(fields, value[op], params, op);
452
+ parts.push(`(${this.subQueries[key]}) ${replacement} ${val}`);
453
+ return { sql: parts.join(' and '), params };
454
+ }
455
+ const [a, f] = this.splitField(key);
456
+ const prop = this.getProperty(f, a);
457
+ if (op === '$fulltext') {
458
+ /* v8 ignore next */
459
+ if (!prop) {
460
+ throw new Error(`Cannot use $fulltext operator on ${key}, property not found`);
461
+ }
462
+ const { sql, params: params2 } = raw(this.platform.getFullTextWhereClause(prop), {
463
+ column: this.mapper(key, type, undefined, null),
464
+ query: value[op],
465
+ });
466
+ parts.push(sql);
467
+ params.push(...params2);
468
+ }
469
+ else if (['$in', '$nin'].includes(op) && Array.isArray(value[op]) && value[op].length === 0) {
470
+ parts.push(`1 = ${op === '$in' ? 0 : 1}`);
471
+ }
472
+ else if (value[op] instanceof RawQueryFragment || value[op] instanceof NativeQueryBuilder) {
473
+ const query = value[op] instanceof NativeQueryBuilder ? value[op].toRaw() : value[op];
474
+ const mappedKey = this.mapper(key, type, query, null);
475
+ let sql = query.sql;
476
+ if (['$in', '$nin'].includes(op)) {
477
+ sql = `(${sql})`;
478
+ }
479
+ parts.push(`${this.platform.quoteIdentifier(mappedKey)} ${replacement} ${sql}`);
480
+ params.push(...query.params);
481
+ }
482
+ else {
483
+ const mappedKey = this.mapper(key, type, value[op], null);
484
+ const val = this.getValueReplacement(fields, value[op], params, op, prop);
485
+ parts.push(`${this.platform.quoteIdentifier(mappedKey)} ${replacement} ${val}`);
486
+ }
487
+ return { sql: parts.join(' and '), params };
488
+ }
489
+ getValueReplacement(fields, value, params, key, prop) {
490
+ if (Array.isArray(value)) {
491
+ if (fields.length > 1) {
492
+ const tmp = [];
493
+ for (const field of value) {
494
+ tmp.push(`(${field.map(() => '?').join(', ')})`);
495
+ params.push(...field);
496
+ }
497
+ return `(${tmp.join(', ')})`;
498
+ }
499
+ if (prop?.customType instanceof ArrayType) {
500
+ const item = prop.customType.convertToDatabaseValue(value, this.platform, { fromQuery: true, key, mode: 'query' });
501
+ params.push(item);
502
+ }
503
+ else {
504
+ value.forEach(v => params.push(v));
505
+ }
506
+ return `(${value.map(() => '?').join(', ')})`;
507
+ }
508
+ if (value === null) {
509
+ return 'null';
510
+ }
511
+ params.push(value);
512
+ return '?';
513
+ }
514
+ getOperatorReplacement(op, value) {
515
+ let replacement = QueryOperator[op];
516
+ if (op === '$exists') {
517
+ replacement = value[op] ? 'is not' : 'is';
518
+ value[op] = null;
519
+ }
520
+ if (value[op] === null && ['$eq', '$ne'].includes(op)) {
521
+ replacement = op === '$eq' ? 'is' : 'is not';
522
+ }
523
+ if (op === '$re') {
524
+ replacement = this.platform.getRegExpOperator(value[op], value.$flags);
525
+ }
526
+ if (replacement.includes('?')) {
527
+ replacement = replacement.replaceAll('?', '\\?');
528
+ }
529
+ return replacement;
530
+ }
531
+ getQueryOrder(type, orderBy, populate) {
532
+ if (Array.isArray(orderBy)) {
533
+ return orderBy.flatMap(o => this.getQueryOrder(type, o, populate));
534
+ }
535
+ return this.getQueryOrderFromObject(type, orderBy, populate);
536
+ }
537
+ getQueryOrderFromObject(type, orderBy, populate) {
538
+ const ret = [];
539
+ for (const key of Object.keys(orderBy)) {
540
+ const direction = orderBy[key];
541
+ const order = typeof direction === 'number' ? QueryOrderNumeric[direction] : direction;
542
+ const raw = RawQueryFragment.getKnownFragment(key);
543
+ if (raw) {
544
+ ret.push(...this.platform.getOrderByExpression(this.platform.formatQuery(raw.sql, raw.params), order));
545
+ continue;
546
+ }
547
+ for (const f of Utils.splitPrimaryKeys(key)) {
548
+ // eslint-disable-next-line prefer-const
549
+ let [alias, field] = this.splitField(f, true);
550
+ alias = populate[alias] || alias;
551
+ const prop = this.getProperty(field, alias);
552
+ const noPrefix = (prop?.persist === false && !prop.formula && !prop.embedded) || RawQueryFragment.isKnownFragment(f);
553
+ const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
554
+ /* v8 ignore next */
555
+ const rawColumn = typeof column === 'string' ? column.split('.').map(e => this.platform.quoteIdentifier(e)).join('.') : column;
556
+ const customOrder = prop?.customOrder;
557
+ let colPart = customOrder
558
+ ? this.platform.generateCustomOrder(rawColumn, customOrder)
559
+ : rawColumn;
560
+ if (isRaw(colPart)) {
561
+ colPart = this.platform.formatQuery(colPart.sql, colPart.params);
562
+ }
563
+ if (Array.isArray(order)) {
564
+ order.forEach(part => ret.push(...this.getQueryOrderFromObject(type, part, populate)));
565
+ }
566
+ else {
567
+ ret.push(...this.platform.getOrderByExpression(colPart, order));
568
+ }
569
+ }
570
+ }
571
+ return ret;
572
+ }
573
+ finalize(type, qb, meta, data, returning) {
574
+ const usesReturningStatement = this.platform.usesReturningStatement() || this.platform.usesOutputStatement();
575
+ if (!meta || !data || !usesReturningStatement) {
576
+ return;
577
+ }
578
+ // always respect explicit returning hint
579
+ if (returning && returning.length > 0) {
580
+ qb.returning(returning.map(field => this.mapper(field, type)));
581
+ return;
582
+ }
583
+ if (type === QueryType.INSERT) {
584
+ const returningProps = meta.hydrateProps
585
+ .filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
586
+ .filter(prop => !(prop.name in data));
587
+ if (returningProps.length > 0) {
588
+ qb.returning(Utils.flatten(returningProps.map(prop => prop.fieldNames)));
589
+ }
590
+ return;
591
+ }
592
+ if (type === QueryType.UPDATE) {
593
+ const returningProps = meta.hydrateProps.filter(prop => prop.fieldNames && isRaw(data[prop.fieldNames[0]]));
594
+ if (returningProps.length > 0) {
595
+ qb.returning(returningProps.flatMap(prop => {
596
+ if (prop.hasConvertToJSValueSQL) {
597
+ const aliased = this.platform.quoteIdentifier(prop.fieldNames[0]);
598
+ const sql = prop.customType.convertToJSValueSQL(aliased, this.platform) + ' as ' + this.platform.quoteIdentifier(prop.fieldNames[0]);
599
+ return [raw(sql)];
600
+ }
601
+ return prop.fieldNames;
602
+ }));
603
+ }
604
+ }
605
+ }
606
+ splitField(field, greedyAlias = false) {
607
+ const parts = field.split('.');
608
+ const ref = parts[parts.length - 1].split(':')[1];
609
+ if (ref) {
610
+ parts[parts.length - 1] = parts[parts.length - 1].substring(0, parts[parts.length - 1].indexOf(':'));
611
+ }
612
+ if (parts.length === 1) {
613
+ return [this.alias, parts[0], ref];
614
+ }
615
+ if (greedyAlias) {
616
+ const fromField = parts.pop();
617
+ const fromAlias = parts.join('.');
618
+ return [fromAlias, fromField, ref];
619
+ }
620
+ const fromAlias = parts.shift();
621
+ const fromField = parts.join('.');
622
+ return [fromAlias, fromField, ref];
623
+ }
624
+ getLockSQL(qb, lockMode, lockTables = [], joinsMap) {
625
+ const meta = this.metadata.find(this.entityName);
626
+ if (lockMode === LockMode.OPTIMISTIC && meta && !meta.versionProperty) {
627
+ throw OptimisticLockError.lockFailed(this.entityName);
628
+ }
629
+ if (lockMode !== LockMode.OPTIMISTIC && lockTables.length === 0 && joinsMap) {
630
+ const joins = Object.values(joinsMap);
631
+ const innerJoins = joins.filter(join => [JoinType.innerJoin, JoinType.innerJoinLateral, JoinType.nestedInnerJoin].includes(join.type));
632
+ if (joins.length > innerJoins.length) {
633
+ lockTables.push(this.alias, ...innerJoins.map(join => join.alias));
634
+ }
635
+ }
636
+ qb.lockMode(lockMode, lockTables);
637
+ }
638
+ updateVersionProperty(qb, data) {
639
+ const meta = this.metadata.find(this.entityName);
640
+ if (!meta?.versionProperty || meta.versionProperty in data) {
641
+ return;
642
+ }
643
+ const versionProperty = meta.properties[meta.versionProperty];
644
+ let sql = this.platform.quoteIdentifier(versionProperty.fieldNames[0]) + ' + 1';
645
+ if (versionProperty.runtimeType === 'Date') {
646
+ sql = this.platform.getCurrentTimestampSQL(versionProperty.length);
647
+ }
648
+ qb.update({ [versionProperty.fieldNames[0]]: raw(sql) });
649
+ }
650
+ prefix(field, always = false, quote = false, idx) {
651
+ let ret;
652
+ if (!this.isPrefixed(field)) {
653
+ const alias = always ? (quote ? this.alias : this.platform.quoteIdentifier(this.alias)) + '.' : '';
654
+ const fieldName = this.fieldName(field, this.alias, always, idx);
655
+ if (fieldName instanceof RawQueryFragment) {
656
+ return fieldName.sql;
657
+ }
658
+ ret = alias + fieldName;
659
+ }
660
+ else {
661
+ const [a, ...rest] = field.split('.');
662
+ const f = rest.join('.');
663
+ const fieldName = this.fieldName(f, a, always, idx);
664
+ if (fieldName instanceof RawQueryFragment) {
665
+ return fieldName.sql;
666
+ }
667
+ ret = a + '.' + fieldName;
668
+ }
669
+ if (quote) {
670
+ return this.platform.quoteIdentifier(ret);
671
+ }
672
+ return ret;
673
+ }
674
+ appendGroupCondition(type, operator, subCondition) {
675
+ const parts = [];
676
+ const params = [];
677
+ // single sub-condition can be ignored to reduce nesting of parens
678
+ if (subCondition.length === 1 || operator === '$and') {
679
+ for (const sub of subCondition) {
680
+ this.append(() => this._appendQueryCondition(type, sub), parts, params);
681
+ }
682
+ return { sql: parts.join(' and '), params };
683
+ }
684
+ for (const sub of subCondition) {
685
+ // skip nesting parens if the value is simple = scalar or object without operators or with only single key, being the operator
686
+ const keys = Object.keys(sub);
687
+ const val = sub[keys[0]];
688
+ const simple = !Utils.isPlainObject(val) || Utils.getObjectKeysSize(val) === 1 || Object.keys(val).every(k => !Utils.isOperator(k));
689
+ if (keys.length === 1 && simple) {
690
+ this.append(() => this._appendQueryCondition(type, sub, operator), parts, params);
691
+ continue;
692
+ }
693
+ this.append(() => this._appendQueryCondition(type, sub), parts, params, operator);
694
+ }
695
+ return { sql: `(${parts.join(' or ')})`, params };
696
+ }
697
+ isPrefixed(field) {
698
+ return !!field.match(/[\w`"[\]]+\./);
699
+ }
700
+ fieldName(field, alias, always, idx = 0) {
701
+ const prop = this.getProperty(field, alias);
702
+ if (!prop) {
703
+ return field;
704
+ }
705
+ if (prop.fieldNameRaw) {
706
+ if (!always) {
707
+ return raw(prop.fieldNameRaw
708
+ .replace(new RegExp(ALIAS_REPLACEMENT_RE + '\\.?', 'g'), '')
709
+ .replace(this.platform.quoteIdentifier('') + '.', ''));
710
+ }
711
+ if (alias) {
712
+ return raw(prop.fieldNameRaw.replace(new RegExp(ALIAS_REPLACEMENT_RE, 'g'), alias));
713
+ }
714
+ /* v8 ignore next */
715
+ return raw(prop.fieldNameRaw);
716
+ }
717
+ /* v8 ignore next */
718
+ return prop.fieldNames?.[idx] ?? field;
719
+ }
720
+ getProperty(field, alias) {
721
+ const entityName = this.aliasMap[alias]?.entityName || this.entityName;
722
+ const meta = this.metadata.find(entityName);
723
+ // check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
724
+ if (alias && meta) {
725
+ const prop = meta.properties[alias];
726
+ if (prop?.kind === ReferenceKind.EMBEDDED) {
727
+ // we want to select the full object property so hydration works as expected
728
+ if (prop.object) {
729
+ return prop;
730
+ }
731
+ const parts = field.split('.');
732
+ const nest = (p) => parts.length > 0 ? nest(p.embeddedProps[parts.shift()]) : p;
733
+ return nest(prop);
734
+ }
735
+ }
736
+ if (meta) {
737
+ if (meta.properties[field]) {
738
+ return meta.properties[field];
739
+ }
740
+ return meta.relations.find(prop => prop.fieldNames?.some(name => field === name));
741
+ }
742
+ return undefined;
743
+ }
744
+ isTableNameAliasRequired(type) {
745
+ return [QueryType.SELECT, QueryType.COUNT].includes(type);
746
+ }
747
+ processOnConflictCondition(cond, schema) {
748
+ const meta = this.metadata.get(this.entityName);
749
+ const tableName = meta.tableName;
750
+ for (const key of Object.keys(cond)) {
751
+ const mapped = this.mapper(key, QueryType.UPSERT);
752
+ Utils.renameKey(cond, key, tableName + '.' + mapped);
753
+ }
754
+ return cond;
755
+ }
756
+ }