@mikro-orm/knex 6.0.0-dev.2 → 6.0.0-dev.20

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 (54) hide show
  1. package/{dist/AbstractSqlConnection.d.ts → AbstractSqlConnection.d.ts} +0 -0
  2. package/{dist/AbstractSqlConnection.js → AbstractSqlConnection.js} +5 -0
  3. package/AbstractSqlDriver.d.ts +66 -0
  4. package/{dist/AbstractSqlDriver.js → AbstractSqlDriver.js} +230 -71
  5. package/{dist/AbstractSqlPlatform.d.ts → AbstractSqlPlatform.d.ts} +7 -7
  6. package/{dist/AbstractSqlPlatform.js → AbstractSqlPlatform.js} +12 -14
  7. package/{dist/MonkeyPatchable.d.ts → MonkeyPatchable.d.ts} +0 -0
  8. package/{dist/MonkeyPatchable.js → MonkeyPatchable.js} +0 -0
  9. package/{dist/README.md → README.md} +2 -2
  10. package/{dist/SqlEntityManager.d.ts → SqlEntityManager.d.ts} +5 -5
  11. package/{dist/SqlEntityManager.js → SqlEntityManager.js} +1 -2
  12. package/{dist/SqlEntityRepository.d.ts → SqlEntityRepository.d.ts} +3 -3
  13. package/{dist/SqlEntityRepository.js → SqlEntityRepository.js} +1 -1
  14. package/{dist/index.d.ts → index.d.ts} +1 -0
  15. package/{dist/index.js → index.js} +6 -1
  16. package/index.mjs +192 -0
  17. package/package.json +18 -47
  18. package/{dist/query → query}/ArrayCriteriaNode.d.ts +0 -0
  19. package/{dist/query → query}/ArrayCriteriaNode.js +0 -0
  20. package/{dist/query → query}/CriteriaNode.d.ts +0 -0
  21. package/{dist/query → query}/CriteriaNode.js +7 -4
  22. package/{dist/query → query}/CriteriaNodeFactory.d.ts +0 -0
  23. package/{dist/query → query}/CriteriaNodeFactory.js +0 -0
  24. package/{dist/query → query}/ObjectCriteriaNode.d.ts +0 -0
  25. package/{dist/query → query}/ObjectCriteriaNode.js +14 -1
  26. package/{dist/query → query}/QueryBuilder.d.ts +65 -24
  27. package/{dist/query → query}/QueryBuilder.js +318 -84
  28. package/{dist/query → query}/QueryBuilderHelper.d.ts +19 -11
  29. package/{dist/query → query}/QueryBuilderHelper.js +224 -113
  30. package/{dist/query → query}/ScalarCriteriaNode.d.ts +0 -0
  31. package/{dist/query → query}/ScalarCriteriaNode.js +0 -0
  32. package/{dist/query → query}/enums.d.ts +0 -0
  33. package/{dist/query → query}/enums.js +0 -0
  34. package/{dist/query → query}/index.d.ts +0 -0
  35. package/{dist/query → query}/index.js +5 -1
  36. package/{dist/schema → schema}/DatabaseSchema.d.ts +1 -1
  37. package/{dist/schema → schema}/DatabaseSchema.js +8 -16
  38. package/{dist/schema → schema}/DatabaseTable.d.ts +8 -8
  39. package/{dist/schema → schema}/DatabaseTable.js +2 -1
  40. package/{dist/schema → schema}/SchemaComparator.d.ts +7 -5
  41. package/{dist/schema → schema}/SchemaComparator.js +60 -10
  42. package/{dist/schema → schema}/SchemaHelper.d.ts +15 -10
  43. package/{dist/schema → schema}/SchemaHelper.js +46 -8
  44. package/{dist/schema/SchemaGenerator.d.ts → schema/SqlSchemaGenerator.d.ts} +12 -12
  45. package/{dist/schema/SchemaGenerator.js → schema/SqlSchemaGenerator.js} +85 -34
  46. package/{dist/schema → schema}/index.d.ts +1 -1
  47. package/{dist/schema → schema}/index.js +6 -2
  48. package/{dist/typings.d.ts → typings.d.ts} +16 -12
  49. package/{dist/typings.js → typings.js} +0 -0
  50. package/CHANGELOG.md +0 -661
  51. package/dist/AbstractSqlDriver.d.ts +0 -55
  52. package/dist/LICENSE +0 -21
  53. package/dist/index.mjs +0 -26
  54. package/dist/package.json +0 -99
@@ -1,8 +1,9 @@
1
1
  import type { Knex } from 'knex';
2
- import type { Dictionary, EntityData, EntityMetadata, EntityProperty, FlatQueryOrderMap, MetadataStorage, Platform, QBFilterQuery } from '@mikro-orm/core';
2
+ import type { Dictionary, EntityData, EntityMetadata, EntityProperty, FlatQueryOrderMap, QBFilterQuery } from '@mikro-orm/core';
3
3
  import { LockMode } from '@mikro-orm/core';
4
4
  import { QueryType } from './enums';
5
5
  import type { Field, JoinOptions } from '../typings';
6
+ import type { AbstractSqlDriver } from '../AbstractSqlDriver';
6
7
  /**
7
8
  * @internal
8
9
  */
@@ -11,25 +12,28 @@ export declare class QueryBuilderHelper {
11
12
  private readonly alias;
12
13
  private readonly aliasMap;
13
14
  private readonly subQueries;
14
- private readonly metadata;
15
15
  private readonly knex;
16
+ private readonly driver;
16
17
  private readonly platform;
17
- constructor(entityName: string, alias: string, aliasMap: Dictionary<string>, subQueries: Dictionary<string>, metadata: MetadataStorage, knex: Knex, platform: Platform);
18
- mapper(field: string, type?: QueryType): string;
19
- mapper(field: string, type?: QueryType, value?: any, alias?: string | null): string;
18
+ private readonly metadata;
19
+ constructor(entityName: string, alias: string, aliasMap: Dictionary<Alias>, subQueries: Dictionary<string>, knex: Knex, driver: AbstractSqlDriver);
20
+ mapper(field: string | Knex.Raw, type?: QueryType): string;
21
+ mapper(field: string | Knex.Raw, type?: QueryType, value?: any, alias?: string | null): string;
20
22
  processData(data: Dictionary, convertCustomTypes: boolean, multi?: boolean): any;
21
23
  joinOneToReference(prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary): JoinOptions;
22
24
  joinManyToOneReference(prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary): JoinOptions;
23
25
  joinManyToManyReference(prop: EntityProperty, ownerAlias: string, alias: string, pivotAlias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond: Dictionary, path: string): Dictionary<JoinOptions>;
24
26
  joinPivotTable(field: string, prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary): JoinOptions;
25
27
  processJoins(qb: Knex.QueryBuilder, joins: Dictionary<JoinOptions>, schema?: string): void;
28
+ private processJoinClause;
29
+ private wrapQueryGroup;
26
30
  mapJoinColumns(type: QueryType, join: JoinOptions): (string | Knex.Raw)[];
27
31
  isOneToOneInverse(field: string): boolean;
28
32
  getTableName(entityName: string): string;
29
33
  /**
30
34
  * Checks whether the RE can be rewritten to simple LIKE query
31
35
  */
32
- isSimpleRegExp(re: any): boolean;
36
+ isSimpleRegExp(re: any): re is RegExp;
33
37
  getRegExpParam(re: RegExp): string;
34
38
  appendOnConflictClause<T>(type: QueryType, onConflict: {
35
39
  fields: string[];
@@ -42,13 +46,10 @@ export declare class QueryBuilderHelper {
42
46
  private processCustomExpression;
43
47
  private processObjectSubCondition;
44
48
  private getOperatorReplacement;
45
- private appendJoinClause;
46
- private appendJoinSubClause;
47
- private processObjectSubClause;
48
49
  getQueryOrder(type: QueryType, orderBy: FlatQueryOrderMap | FlatQueryOrderMap[], populate: Dictionary<string>): string;
49
50
  getQueryOrderFromObject(type: QueryType, orderBy: FlatQueryOrderMap, populate: Dictionary<string>): string;
50
51
  finalize(type: QueryType, qb: Knex.QueryBuilder, meta?: EntityMetadata): void;
51
- splitField(field: string): [string, string];
52
+ splitField(field: string, greedyAlias?: boolean): [string, string];
52
53
  getLockSQL(qb: Knex.QueryBuilder, lockMode: LockMode, lockTables?: string[]): void;
53
54
  updateVersionProperty(qb: Knex.QueryBuilder, data: Dictionary): void;
54
55
  static isCustomExpression(field: string, hasAlias?: boolean): boolean;
@@ -57,5 +58,12 @@ export declare class QueryBuilderHelper {
57
58
  private isPrefixed;
58
59
  private fieldName;
59
60
  getProperty(field: string, alias?: string): EntityProperty | undefined;
60
- isTableNameAliasRequired(type: QueryType): boolean;
61
+ isTableNameAliasRequired(type?: QueryType): boolean;
62
+ private mapData;
63
+ }
64
+ export interface Alias {
65
+ aliasName: string;
66
+ entityName: string;
67
+ metadata?: EntityMetadata;
68
+ subQuery?: Knex.QueryBuilder;
61
69
  }
@@ -8,16 +8,20 @@ const enums_1 = require("./enums");
8
8
  * @internal
9
9
  */
10
10
  class QueryBuilderHelper {
11
- constructor(entityName, alias, aliasMap, subQueries, metadata, knex, platform) {
11
+ constructor(entityName, alias, aliasMap, subQueries, knex, driver) {
12
12
  this.entityName = entityName;
13
13
  this.alias = alias;
14
14
  this.aliasMap = aliasMap;
15
15
  this.subQueries = subQueries;
16
- this.metadata = metadata;
17
16
  this.knex = knex;
18
- this.platform = platform;
17
+ this.driver = driver;
18
+ this.platform = this.driver.getPlatform();
19
+ this.metadata = this.driver.getMetadata();
19
20
  }
20
21
  mapper(field, type = enums_1.QueryType.SELECT, value, alias) {
22
+ if (typeof field !== 'string') {
23
+ return field;
24
+ }
21
25
  const isTableNameAliasRequired = this.isTableNameAliasRequired(type);
22
26
  const fields = core_1.Utils.splitPrimaryKeys(field);
23
27
  if (fields.length > 1) {
@@ -26,18 +30,33 @@ class QueryBuilderHelper {
26
30
  const [a, f] = this.splitField(p);
27
31
  const prop = this.getProperty(f, a);
28
32
  if (prop) {
29
- parts.push(...prop.fieldNames.map(f => this.mapper(f, type, value, alias)));
33
+ parts.push(...prop.fieldNames.map(f => this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias)));
30
34
  }
31
35
  else {
32
- parts.push(this.mapper(`${a}.${f}`, type, value, alias));
36
+ parts.push(this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias));
33
37
  }
34
38
  }
39
+ // flatten the value if we see we are expanding nested composite key
40
+ // hackish, but cleaner solution would require quite a lot of refactoring
41
+ if (fields.length !== parts.length && Array.isArray(value)) {
42
+ value.forEach(row => {
43
+ if (Array.isArray(row)) {
44
+ const tmp = core_1.Utils.flatten(row);
45
+ row.length = 0;
46
+ row.push(...tmp);
47
+ }
48
+ });
49
+ }
35
50
  return this.knex.raw('(' + parts.map(part => this.knex.ref(part)).join(', ') + ')');
36
51
  }
37
52
  let ret = field;
38
53
  const customExpression = QueryBuilderHelper.isCustomExpression(field, !!alias);
39
54
  const [a, f] = this.splitField(field);
40
55
  const prop = this.getProperty(f, a);
56
+ // embeddable nested path instead of a regular property with table alias, reset alias
57
+ if (prop?.name === a && prop.embeddedProps[f]) {
58
+ return this.alias + '.' + prop.fieldNames[0];
59
+ }
41
60
  const noPrefix = prop && prop.persist === false;
42
61
  if (prop?.fieldNameRaw) {
43
62
  return this.knex.raw(this.prefix(field, isTableNameAliasRequired));
@@ -64,7 +83,8 @@ class QueryBuilderHelper {
64
83
  ret += ' as ' + alias;
65
84
  }
66
85
  if (customExpression) {
67
- return this.knex.raw(ret, value);
86
+ const bindings = core_1.Utils.asArray(value).slice(0, ret.match(/\?/g)?.length ?? 0);
87
+ return this.knex.raw(ret, bindings);
68
88
  }
69
89
  if (!isTableNameAliasRequired || this.isPrefixed(ret) || noPrefix) {
70
90
  return ret;
@@ -75,47 +95,23 @@ class QueryBuilderHelper {
75
95
  if (Array.isArray(data)) {
76
96
  return data.map(d => this.processData(d, convertCustomTypes, true));
77
97
  }
78
- data = Object.assign({}, data); // copy first
79
98
  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
- });
99
+ data = this.mapData(data, meta?.properties, convertCustomTypes);
105
100
  if (!core_1.Utils.hasObjectKeys(data) && meta && multi) {
101
+ /* istanbul ignore next */
106
102
  data[meta.primaryKeys[0]] = this.platform.usesDefaultKeyword() ? this.knex.raw('default') : undefined;
107
103
  }
108
104
  return data;
109
105
  }
110
106
  joinOneToReference(prop, ownerAlias, alias, type, cond = {}) {
111
- const meta = this.metadata.find(prop.type);
112
- const prop2 = meta.properties[prop.mappedBy || prop.inversedBy];
107
+ const prop2 = prop.targetMeta.properties[prop.mappedBy || prop.inversedBy];
113
108
  const table = this.getTableName(prop.type);
109
+ const schema = this.driver.getSchemaName(prop.targetMeta);
114
110
  const joinColumns = prop.owner ? prop.referencedColumnNames : prop2.joinColumns;
115
111
  const inverseJoinColumns = prop.referencedColumnNames;
116
112
  const primaryKeys = prop.owner ? prop.joinColumns : prop2.referencedColumnNames;
117
113
  return {
118
- prop, type, cond, ownerAlias, alias, table,
114
+ prop, type, cond, ownerAlias, alias, table, schema,
119
115
  joinColumns, inverseJoinColumns, primaryKeys,
120
116
  };
121
117
  }
@@ -123,11 +119,13 @@ class QueryBuilderHelper {
123
119
  return {
124
120
  prop, type, cond, ownerAlias, alias,
125
121
  table: this.getTableName(prop.type),
122
+ schema: this.driver.getSchemaName(prop.targetMeta),
126
123
  joinColumns: prop.referencedColumnNames,
127
124
  primaryKeys: prop.fieldNames,
128
125
  };
129
126
  }
130
127
  joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path) {
128
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
131
129
  const ret = {
132
130
  [`${ownerAlias}.${prop.name}#${pivotAlias}`]: {
133
131
  prop, type, cond, ownerAlias,
@@ -136,21 +134,22 @@ class QueryBuilderHelper {
136
134
  joinColumns: prop.joinColumns,
137
135
  inverseJoinColumns: prop.inverseJoinColumns,
138
136
  primaryKeys: prop.referencedColumnNames,
139
- table: prop.pivotTable,
137
+ table: pivotMeta.tableName,
138
+ schema: this.driver.getSchemaName(pivotMeta),
140
139
  path: path.endsWith('[pivot]') ? path : `${path}[pivot]`,
141
140
  },
142
141
  };
143
142
  if (type === 'pivotJoin') {
144
143
  return ret;
145
144
  }
146
- const prop2 = this.metadata.find(prop.pivotTable).properties[prop.type + (prop.owner ? '_inverse' : '_owner')];
145
+ const prop2 = prop.owner ? pivotMeta.relations[1] : pivotMeta.relations[0];
147
146
  ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type);
148
147
  ret[`${pivotAlias}.${prop2.name}#${alias}`].path = path;
149
148
  return ret;
150
149
  }
151
150
  joinPivotTable(field, prop, ownerAlias, alias, type, cond = {}) {
152
151
  const pivotMeta = this.metadata.find(field);
153
- const prop2 = pivotMeta.properties[prop.mappedBy || prop.inversedBy];
152
+ const prop2 = pivotMeta.relations[0] === prop ? pivotMeta.relations[1] : pivotMeta.relations[0];
154
153
  return {
155
154
  prop, type, cond, ownerAlias, alias,
156
155
  table: pivotMeta.collection,
@@ -163,21 +162,96 @@ class QueryBuilderHelper {
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
+ 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);
179
181
  });
180
182
  }
183
+ processJoinClause(key, value, params, operator = '$eq') {
184
+ if (core_1.Utils.isGroupOperator(key) && Array.isArray(value)) {
185
+ const parts = value.map(sub => {
186
+ return this.wrapQueryGroup(Object.keys(sub).map(k => this.processJoinClause(k, sub[k], params)));
187
+ });
188
+ return this.wrapQueryGroup(parts, key);
189
+ }
190
+ if (this.isSimpleRegExp(value)) {
191
+ params.push(this.getRegExpParam(value));
192
+ return `${this.knex.ref(this.mapper(key))} like ?`;
193
+ }
194
+ if (value instanceof RegExp) {
195
+ value = this.platform.getRegExpValue(value);
196
+ }
197
+ 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));
199
+ return key === '$not' ? `not ${this.wrapQueryGroup(parts)}` : this.wrapQueryGroup(parts);
200
+ }
201
+ 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));
203
+ return this.wrapQueryGroup(parts);
204
+ }
205
+ operator = operator === '$not' ? '$eq' : operator;
206
+ if (value === null) {
207
+ return `${this.knex.ref(this.mapper(key))} is ${operator === '$ne' ? 'not ' : ''}null`;
208
+ }
209
+ if (operator === '$fulltext') {
210
+ const [fromAlias, fromField] = this.splitField(key);
211
+ const property = this.getProperty(fromField, fromAlias);
212
+ const query = this.knex.raw(this.platform.getFullTextWhereClause(property), {
213
+ column: this.mapper(key),
214
+ query: this.knex.raw('?'),
215
+ }).toSQL().toNative();
216
+ params.push(value);
217
+ return query.sql;
218
+ }
219
+ const replacement = this.getOperatorReplacement(operator, { [operator]: value });
220
+ if (['$in', '$nin'].includes(operator) && Array.isArray(value)) {
221
+ params.push(...value);
222
+ return `${this.knex.ref(this.mapper(key))} ${replacement} (${value.map(() => '?').join(', ')})`;
223
+ }
224
+ if (operator === '$exists') {
225
+ value = null;
226
+ }
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 + ' = ?';
239
+ }
240
+ params.push(...bindings);
241
+ return sql;
242
+ }
243
+ const sql = this.mapper(key);
244
+ if (value !== null) {
245
+ params.push(value);
246
+ }
247
+ return `${this.knex.ref(sql)} ${replacement} ${value === null ? 'null' : '?'}`;
248
+ }
249
+ wrapQueryGroup(parts, operator = '$and') {
250
+ if (parts.length === 1) {
251
+ return parts[0];
252
+ }
253
+ return `(${parts.join(` ${core_1.GroupOperator[operator]} `)})`;
254
+ }
181
255
  mapJoinColumns(type, join) {
182
256
  if (join.prop && join.prop.reference === core_1.ReferenceType.ONE_TO_ONE && !join.prop.owner) {
183
257
  return join.prop.fieldNames.map((fieldName, idx) => {
@@ -205,6 +279,9 @@ class QueryBuilderHelper {
205
279
  if (!(re instanceof RegExp)) {
206
280
  return false;
207
281
  }
282
+ if (re.flags.includes('i')) {
283
+ return false;
284
+ }
208
285
  // when including the opening bracket/paren we consider it complex
209
286
  return !re.source.match(/[{[(]/);
210
287
  }
@@ -280,7 +357,7 @@ class QueryBuilderHelper {
280
357
  }
281
358
  qb[m](this.mapper(key, type, cond[key], null), op, cond[key]);
282
359
  }
283
- processCustomExpression(clause, m, key, cond, type = enums_1.QueryType.SELECT) {
360
+ processCustomExpression(clause, m, key, cond, type) {
284
361
  // unwind parameters when ? found in field name
285
362
  const count = key.concat('?').match(/\?/g).length - 1;
286
363
  const value = core_1.Utils.asArray(cond[key]);
@@ -301,7 +378,7 @@ class QueryBuilderHelper {
301
378
  return subCondition.forEach(sub => this.appendQueryCondition(type, sub, qb, '$and', method));
302
379
  }
303
380
  if (value instanceof RegExp) {
304
- value = { $re: value.source };
381
+ value = this.platform.getRegExpValue(value);
305
382
  }
306
383
  // operators
307
384
  const op = Object.keys(core_1.QueryOperator).find(op => op in value);
@@ -311,68 +388,42 @@ class QueryBuilderHelper {
311
388
  const replacement = this.getOperatorReplacement(op, value);
312
389
  const fields = core_1.Utils.splitPrimaryKeys(key);
313
390
  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]);
391
+ value[op] = this.knex.raw(`(${fields.map(() => '?').join(', ')})`, value[op]);
316
392
  }
317
393
  if (this.subQueries[key]) {
318
394
  return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), replacement, value[op]);
319
395
  }
320
- qb[m](this.mapper(key, type, undefined, null), replacement, value[op]);
396
+ if (op === '$fulltext') {
397
+ const [a, f] = this.splitField(key);
398
+ const prop = this.getProperty(f, a);
399
+ /* istanbul ignore next */
400
+ if (!prop) {
401
+ throw new Error(`Cannot use $fulltext operator on ${key}, property not found`);
402
+ }
403
+ qb[m](this.knex.raw(this.platform.getFullTextWhereClause(prop), {
404
+ column: this.mapper(key, type, undefined, null),
405
+ query: value[op],
406
+ }));
407
+ }
408
+ else {
409
+ const mappedKey = this.mapper(key, type, value[op], null);
410
+ qb[m](mappedKey, replacement, value[op]);
411
+ }
321
412
  }
322
413
  getOperatorReplacement(op, value) {
323
414
  let replacement = core_1.QueryOperator[op];
415
+ if (op === '$exists') {
416
+ replacement = value[op] ? 'is not' : 'is';
417
+ value[op] = null;
418
+ }
324
419
  if (value[op] === null && ['$eq', '$ne'].includes(op)) {
325
420
  replacement = op === '$eq' ? 'is' : 'is not';
326
421
  }
327
422
  if (op === '$re') {
328
- replacement = this.platform.getRegExpOperator();
423
+ replacement = this.platform.getRegExpOperator(value[op], value.$flags);
329
424
  }
330
425
  return replacement;
331
426
  }
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
427
  getQueryOrder(type, orderBy, populate) {
377
428
  if (Array.isArray(orderBy)) {
378
429
  return orderBy
@@ -391,13 +442,13 @@ class QueryBuilderHelper {
391
442
  ret.push(`${k} ${order.toLowerCase()}`);
392
443
  return;
393
444
  }
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);
445
+ core_1.Utils.splitPrimaryKeys(k).forEach(f => {
446
+ // eslint-disable-next-line prefer-const
447
+ let [alias, field] = this.splitField(f, true);
448
+ alias = populate[alias] || alias;
449
+ const prop = this.getProperty(field, alias);
450
+ const noPrefix = (prop && prop.persist === false && !prop.formula) || QueryBuilderHelper.isCustomExpression(f);
451
+ const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
401
452
  /* istanbul ignore next */
402
453
  const rawColumn = core_1.Utils.isString(column) ? column.split('.').map(e => this.knex.ref(e)).join('.') : column;
403
454
  const customOrder = prop?.customOrder;
@@ -412,14 +463,22 @@ class QueryBuilderHelper {
412
463
  finalize(type, qb, meta) {
413
464
  const useReturningStatement = type === enums_1.QueryType.INSERT && this.platform.usesReturningStatement() && meta && !meta.compositePK;
414
465
  if (useReturningStatement) {
415
- const returningProps = meta.props.filter(prop => prop.primary || prop.defaultRaw);
466
+ const returningProps = meta.hydrateProps.filter(prop => prop.persist !== false && (prop.primary || prop.defaultRaw));
416
467
  qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
417
468
  }
418
469
  }
419
- splitField(field) {
470
+ splitField(field, greedyAlias = false) {
420
471
  const parts = field.split('.');
421
- const fromField = parts.pop();
422
- const fromAlias = parts.length > 0 ? parts.join('.') : this.alias;
472
+ if (parts.length === 1) {
473
+ return [this.alias, parts[0]];
474
+ }
475
+ if (greedyAlias) {
476
+ const fromField = parts.pop();
477
+ const fromAlias = parts.join('.');
478
+ return [fromAlias, fromField];
479
+ }
480
+ const fromAlias = parts.shift();
481
+ const fromField = parts.join('.');
423
482
  return [fromAlias, fromField];
424
483
  }
425
484
  getLockSQL(qb, lockMode, lockTables = []) {
@@ -438,7 +497,7 @@ class QueryBuilderHelper {
438
497
  }
439
498
  updateVersionProperty(qb, data) {
440
499
  const meta = this.metadata.find(this.entityName);
441
- if (!meta || !meta.versionProperty || meta.versionProperty in data) {
500
+ if (!meta?.versionProperty || meta.versionProperty in data) {
442
501
  return;
443
502
  }
444
503
  const versionProperty = meta.properties[meta.versionProperty];
@@ -464,7 +523,8 @@ class QueryBuilderHelper {
464
523
  ret = alias + fieldName;
465
524
  }
466
525
  else {
467
- const [a, f] = field.split('.');
526
+ const [a, ...rest] = field.split('.');
527
+ const f = rest.join('.');
468
528
  ret = a + '.' + this.fieldName(f, a);
469
529
  }
470
530
  if (quote) {
@@ -509,15 +569,66 @@ class QueryBuilderHelper {
509
569
  return prop.fieldNameRaw;
510
570
  }
511
571
  /* istanbul ignore next */
512
- return prop.fieldNames[0] ?? field;
572
+ return prop.fieldNames?.[0] ?? field;
513
573
  }
514
574
  getProperty(field, alias) {
515
- const entityName = this.aliasMap[alias] || this.entityName;
575
+ const entityName = this.aliasMap[alias]?.entityName || this.entityName;
516
576
  const meta = this.metadata.find(entityName);
517
- return meta ? meta.properties[field] : undefined;
577
+ // check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
578
+ if (alias && meta) {
579
+ const prop = meta.properties[alias];
580
+ if (prop?.reference === core_1.ReferenceType.EMBEDDED) {
581
+ // we want to select the full object property so hydration works as expected
582
+ if (prop.object) {
583
+ return prop;
584
+ }
585
+ const parts = field.split('.');
586
+ const nest = (p) => parts.length > 0 ? nest(p.embeddedProps[parts.shift()]) : p;
587
+ return nest(prop);
588
+ }
589
+ }
590
+ return meta?.properties[field];
518
591
  }
519
592
  isTableNameAliasRequired(type) {
520
- return [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(type);
593
+ return [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(type ?? enums_1.QueryType.SELECT);
594
+ }
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;
521
632
  }
522
633
  }
523
634
  exports.QueryBuilderHelper = QueryBuilderHelper;
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -11,7 +11,7 @@ export declare class DatabaseSchema {
11
11
  private tables;
12
12
  private namespaces;
13
13
  constructor(platform: AbstractSqlPlatform, name: string);
14
- addTable(name: string, schema: string | undefined | null): DatabaseTable;
14
+ addTable(name: string, schema: string | undefined | null, comment?: string): DatabaseTable;
15
15
  getTables(): DatabaseTable[];
16
16
  getTable(name: string): DatabaseTable | undefined;
17
17
  hasTable(name: string): boolean;