@mikro-orm/knex 6.0.0-dev.4 → 6.0.0-dev.5

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 (53) 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} +218 -70
  5. package/{dist/AbstractSqlPlatform.d.ts → AbstractSqlPlatform.d.ts} +6 -4
  6. package/{dist/AbstractSqlPlatform.js → AbstractSqlPlatform.js} +18 -7
  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} +0 -1
  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} +0 -0
  15. package/{dist/index.js → index.js} +5 -1
  16. package/{dist/index.mjs → index.mjs} +0 -0
  17. package/package.json +27 -22
  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 +58 -24
  27. package/{dist/query → query}/QueryBuilder.js +285 -84
  28. package/{dist/query → query}/QueryBuilderHelper.d.ts +19 -11
  29. package/{dist/query → query}/QueryBuilderHelper.js +206 -108
  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 +1 -1
  39. package/{dist/schema → schema}/DatabaseTable.js +2 -1
  40. package/{dist/schema → schema}/SchemaComparator.d.ts +3 -1
  41. package/{dist/schema → schema}/SchemaComparator.js +58 -10
  42. package/{dist/schema → schema}/SchemaGenerator.d.ts +10 -8
  43. package/{dist/schema → schema}/SchemaGenerator.js +81 -28
  44. package/{dist/schema → schema}/SchemaHelper.d.ts +9 -4
  45. package/{dist/schema → schema}/SchemaHelper.js +46 -8
  46. package/{dist/schema → schema}/index.d.ts +0 -0
  47. package/{dist/schema → schema}/index.js +5 -1
  48. package/{dist/typings.d.ts → typings.d.ts} +7 -3
  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/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,10 +30,10 @@ 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
  }
35
39
  return this.knex.raw('(' + parts.map(part => this.knex.ref(part)).join(', ') + ')');
@@ -38,6 +42,10 @@ class QueryBuilderHelper {
38
42
  const customExpression = QueryBuilderHelper.isCustomExpression(field, !!alias);
39
43
  const [a, f] = this.splitField(field);
40
44
  const prop = this.getProperty(f, a);
45
+ // embeddable nested path instead of a regular property with table alias, reset alias
46
+ if (prop?.name === a && prop.embeddedProps[f]) {
47
+ return this.alias + '.' + prop.fieldNames[0];
48
+ }
41
49
  const noPrefix = prop && prop.persist === false;
42
50
  if (prop?.fieldNameRaw) {
43
51
  return this.knex.raw(this.prefix(field, isTableNameAliasRequired));
@@ -75,47 +83,23 @@ class QueryBuilderHelper {
75
83
  if (Array.isArray(data)) {
76
84
  return data.map(d => this.processData(d, convertCustomTypes, true));
77
85
  }
78
- data = Object.assign({}, data); // copy first
79
86
  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
- });
87
+ data = this.mapData(data, meta?.properties, convertCustomTypes);
105
88
  if (!core_1.Utils.hasObjectKeys(data) && meta && multi) {
89
+ /* istanbul ignore next */
106
90
  data[meta.primaryKeys[0]] = this.platform.usesDefaultKeyword() ? this.knex.raw('default') : undefined;
107
91
  }
108
92
  return data;
109
93
  }
110
94
  joinOneToReference(prop, ownerAlias, alias, type, cond = {}) {
111
- const meta = this.metadata.find(prop.type);
112
- const prop2 = meta.properties[prop.mappedBy || prop.inversedBy];
95
+ const prop2 = prop.targetMeta.properties[prop.mappedBy || prop.inversedBy];
113
96
  const table = this.getTableName(prop.type);
97
+ const schema = this.driver.getSchemaName(prop.targetMeta);
114
98
  const joinColumns = prop.owner ? prop.referencedColumnNames : prop2.joinColumns;
115
99
  const inverseJoinColumns = prop.referencedColumnNames;
116
100
  const primaryKeys = prop.owner ? prop.joinColumns : prop2.referencedColumnNames;
117
101
  return {
118
- prop, type, cond, ownerAlias, alias, table,
102
+ prop, type, cond, ownerAlias, alias, table, schema,
119
103
  joinColumns, inverseJoinColumns, primaryKeys,
120
104
  };
121
105
  }
@@ -123,11 +107,13 @@ class QueryBuilderHelper {
123
107
  return {
124
108
  prop, type, cond, ownerAlias, alias,
125
109
  table: this.getTableName(prop.type),
110
+ schema: this.driver.getSchemaName(prop.targetMeta),
126
111
  joinColumns: prop.referencedColumnNames,
127
112
  primaryKeys: prop.fieldNames,
128
113
  };
129
114
  }
130
115
  joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path) {
116
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
131
117
  const ret = {
132
118
  [`${ownerAlias}.${prop.name}#${pivotAlias}`]: {
133
119
  prop, type, cond, ownerAlias,
@@ -136,21 +122,22 @@ class QueryBuilderHelper {
136
122
  joinColumns: prop.joinColumns,
137
123
  inverseJoinColumns: prop.inverseJoinColumns,
138
124
  primaryKeys: prop.referencedColumnNames,
139
- table: prop.pivotTable,
125
+ table: pivotMeta.tableName,
126
+ schema: this.driver.getSchemaName(pivotMeta),
140
127
  path: path.endsWith('[pivot]') ? path : `${path}[pivot]`,
141
128
  },
142
129
  };
143
130
  if (type === 'pivotJoin') {
144
131
  return ret;
145
132
  }
146
- const prop2 = this.metadata.find(prop.pivotTable).properties[prop.type + (prop.owner ? '_inverse' : '_owner')];
133
+ const prop2 = prop.owner ? pivotMeta.relations[1] : pivotMeta.relations[0];
147
134
  ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type);
148
135
  ret[`${pivotAlias}.${prop2.name}#${alias}`].path = path;
149
136
  return ret;
150
137
  }
151
138
  joinPivotTable(field, prop, ownerAlias, alias, type, cond = {}) {
152
139
  const pivotMeta = this.metadata.find(field);
153
- const prop2 = pivotMeta.properties[prop.mappedBy || prop.inversedBy];
140
+ const prop2 = pivotMeta.relations[0] === prop ? pivotMeta.relations[1] : pivotMeta.relations[0];
154
141
  return {
155
142
  prop, type, cond, ownerAlias, alias,
156
143
  table: pivotMeta.collection,
@@ -163,21 +150,96 @@ class QueryBuilderHelper {
163
150
  processJoins(qb, joins, schema) {
164
151
  Object.values(joins).forEach(join => {
165
152
  let table = `${join.table} as ${join.alias}`;
166
- const method = join.type === 'pivotJoin' ? 'leftJoin' : join.type;
153
+ const method = join.type === 'innerJoin' ? 'inner join' : 'left join';
167
154
  schema = join.schema && join.schema !== '*' ? join.schema : schema;
168
155
  if (schema) {
169
156
  table = `${schema}.${table}`;
170
157
  }
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);
158
+ const conditions = [];
159
+ const params = [];
160
+ join.primaryKeys.forEach((primaryKey, idx) => {
161
+ const left = `${join.ownerAlias}.${primaryKey}`;
162
+ const right = `${join.alias}.${join.joinColumns[idx]}`;
163
+ conditions.push(`${this.knex.ref(left)} = ${this.knex.ref(right)}`);
164
+ });
165
+ Object.keys(join.cond).forEach(key => {
166
+ conditions.push(this.processJoinClause(key, join.cond[key], params));
178
167
  });
168
+ return qb.joinRaw(`${method} ${this.knex.ref(table)} on ${conditions.join(' and ')}`, params);
179
169
  });
180
170
  }
171
+ processJoinClause(key, value, params, operator = '$eq') {
172
+ if (core_1.Utils.isGroupOperator(key) && Array.isArray(value)) {
173
+ const parts = value.map(sub => {
174
+ return this.wrapQueryGroup(Object.keys(sub).map(k => this.processJoinClause(k, sub[k], params)));
175
+ });
176
+ return this.wrapQueryGroup(parts, key);
177
+ }
178
+ if (this.isSimpleRegExp(value)) {
179
+ params.push(this.getRegExpParam(value));
180
+ return `${this.knex.ref(this.mapper(key))} like ?`;
181
+ }
182
+ if (value instanceof RegExp) {
183
+ value = this.platform.getRegExpValue(value);
184
+ }
185
+ if (core_1.Utils.isOperator(key, false) && core_1.Utils.isPlainObject(value)) {
186
+ const parts = Object.keys(value).map(k => this.processJoinClause(k, value[k], params, key));
187
+ return key === '$not' ? `not ${this.wrapQueryGroup(parts)}` : this.wrapQueryGroup(parts);
188
+ }
189
+ if (core_1.Utils.isPlainObject(value) && Object.keys(value).every(k => core_1.Utils.isOperator(k, false))) {
190
+ const parts = Object.keys(value).map(op => this.processJoinClause(key, value[op], params, op));
191
+ return this.wrapQueryGroup(parts);
192
+ }
193
+ operator = operator === '$not' ? '$eq' : operator;
194
+ if (value === null) {
195
+ return `${this.knex.ref(this.mapper(key))} is ${operator === '$ne' ? 'not ' : ''}null`;
196
+ }
197
+ if (operator === '$fulltext') {
198
+ const [fromAlias, fromField] = this.splitField(key);
199
+ const property = this.getProperty(fromField, fromAlias);
200
+ const query = this.knex.raw(this.platform.getFullTextWhereClause(property), {
201
+ column: this.mapper(key),
202
+ query: this.knex.raw('?'),
203
+ }).toSQL().toNative();
204
+ params.push(value);
205
+ return query.sql;
206
+ }
207
+ const replacement = this.getOperatorReplacement(operator, { [operator]: value });
208
+ if (['$in', '$nin'].includes(operator) && Array.isArray(value)) {
209
+ params.push(...value);
210
+ return `${this.knex.ref(this.mapper(key))} ${replacement} (${value.map(() => '?').join(', ')})`;
211
+ }
212
+ if (operator === '$exists') {
213
+ value = null;
214
+ }
215
+ if (QueryBuilderHelper.isCustomExpression(key)) {
216
+ // unwind parameters when ? found in field name
217
+ const count = key.concat('?').match(/\?/g).length - 1;
218
+ const values = core_1.Utils.asArray(value);
219
+ const params1 = values.slice(0, count).map((c) => core_1.Utils.isObject(c) ? JSON.stringify(c) : c);
220
+ const params2 = values.slice(count);
221
+ const k = this.mapper(key, enums_1.QueryType.SELECT, params1);
222
+ const { sql, bindings } = k.toSQL().toNative();
223
+ if (params2.length > 0) {
224
+ params.push(...bindings);
225
+ params.push(...params2);
226
+ return sql + ' = ?';
227
+ }
228
+ params.push(...bindings);
229
+ return sql;
230
+ }
231
+ const sql = this.mapper(key);
232
+ if (value !== null) {
233
+ params.push(value);
234
+ }
235
+ return `${this.knex.ref(sql)} ${replacement} ${value === null ? 'null' : '?'}`;
236
+ }
237
+ wrapQueryGroup(parts, operator = '$and') {
238
+ if (parts.length === 1) {
239
+ return parts[0];
240
+ }
241
+ return `(${parts.join(` ${core_1.GroupOperator[operator]} `)})`;
242
+ }
181
243
  mapJoinColumns(type, join) {
182
244
  if (join.prop && join.prop.reference === core_1.ReferenceType.ONE_TO_ONE && !join.prop.owner) {
183
245
  return join.prop.fieldNames.map((fieldName, idx) => {
@@ -205,6 +267,9 @@ class QueryBuilderHelper {
205
267
  if (!(re instanceof RegExp)) {
206
268
  return false;
207
269
  }
270
+ if (re.flags.includes('i')) {
271
+ return false;
272
+ }
208
273
  // when including the opening bracket/paren we consider it complex
209
274
  return !re.source.match(/[{[(]/);
210
275
  }
@@ -280,7 +345,7 @@ class QueryBuilderHelper {
280
345
  }
281
346
  qb[m](this.mapper(key, type, cond[key], null), op, cond[key]);
282
347
  }
283
- processCustomExpression(clause, m, key, cond, type = enums_1.QueryType.SELECT) {
348
+ processCustomExpression(clause, m, key, cond, type) {
284
349
  // unwind parameters when ? found in field name
285
350
  const count = key.concat('?').match(/\?/g).length - 1;
286
351
  const value = core_1.Utils.asArray(cond[key]);
@@ -301,7 +366,7 @@ class QueryBuilderHelper {
301
366
  return subCondition.forEach(sub => this.appendQueryCondition(type, sub, qb, '$and', method));
302
367
  }
303
368
  if (value instanceof RegExp) {
304
- value = { $re: value.source };
369
+ value = this.platform.getRegExpValue(value);
305
370
  }
306
371
  // operators
307
372
  const op = Object.keys(core_1.QueryOperator).find(op => op in value);
@@ -311,68 +376,41 @@ class QueryBuilderHelper {
311
376
  const replacement = this.getOperatorReplacement(op, value);
312
377
  const fields = core_1.Utils.splitPrimaryKeys(key);
313
378
  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]);
379
+ value[op] = this.knex.raw(`(${fields.map(() => '?').join(', ')})`, value[op]);
316
380
  }
317
381
  if (this.subQueries[key]) {
318
382
  return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), replacement, value[op]);
319
383
  }
320
- qb[m](this.mapper(key, type, undefined, null), replacement, value[op]);
384
+ if (op === '$fulltext') {
385
+ const [a, f] = this.splitField(key);
386
+ const prop = this.getProperty(f, a);
387
+ /* istanbul ignore next */
388
+ if (!prop) {
389
+ throw new Error(`Cannot use $fulltext operator on ${key}, property not found`);
390
+ }
391
+ qb[m](this.knex.raw(this.platform.getFullTextWhereClause(prop), {
392
+ column: this.mapper(key, type, undefined, null),
393
+ query: value[op],
394
+ }));
395
+ }
396
+ else {
397
+ qb[m](this.mapper(key, type, undefined, null), replacement, value[op]);
398
+ }
321
399
  }
322
400
  getOperatorReplacement(op, value) {
323
401
  let replacement = core_1.QueryOperator[op];
402
+ if (op === '$exists') {
403
+ replacement = value[op] ? 'is not' : 'is';
404
+ value[op] = null;
405
+ }
324
406
  if (value[op] === null && ['$eq', '$ne'].includes(op)) {
325
407
  replacement = op === '$eq' ? 'is' : 'is not';
326
408
  }
327
409
  if (op === '$re') {
328
- replacement = this.platform.getRegExpOperator();
410
+ replacement = this.platform.getRegExpOperator(value[op], value.$flags);
329
411
  }
330
412
  return replacement;
331
413
  }
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
414
  getQueryOrder(type, orderBy, populate) {
377
415
  if (Array.isArray(orderBy)) {
378
416
  return orderBy
@@ -392,12 +430,12 @@ class QueryBuilderHelper {
392
430
  return;
393
431
  }
394
432
  // eslint-disable-next-line prefer-const
395
- let [alias, field] = this.splitField(k);
433
+ let [alias, field] = this.splitField(k, true);
396
434
  alias = populate[alias] || alias;
397
435
  core_1.Utils.splitPrimaryKeys(field).forEach(f => {
398
436
  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);
437
+ const noPrefix = (prop && prop.persist === false && !prop.formula) || QueryBuilderHelper.isCustomExpression(f);
438
+ const column = this.mapper(noPrefix ? f : `${alias}.${f}`, type, undefined, null);
401
439
  /* istanbul ignore next */
402
440
  const rawColumn = core_1.Utils.isString(column) ? column.split('.').map(e => this.knex.ref(e)).join('.') : column;
403
441
  const customOrder = prop?.customOrder;
@@ -412,14 +450,22 @@ class QueryBuilderHelper {
412
450
  finalize(type, qb, meta) {
413
451
  const useReturningStatement = type === enums_1.QueryType.INSERT && this.platform.usesReturningStatement() && meta && !meta.compositePK;
414
452
  if (useReturningStatement) {
415
- const returningProps = meta.props.filter(prop => prop.primary || prop.defaultRaw);
453
+ const returningProps = meta.hydrateProps.filter(prop => prop.persist !== false && (prop.primary || prop.defaultRaw));
416
454
  qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
417
455
  }
418
456
  }
419
- splitField(field) {
457
+ splitField(field, greedyAlias = false) {
420
458
  const parts = field.split('.');
421
- const fromField = parts.pop();
422
- const fromAlias = parts.length > 0 ? parts.join('.') : this.alias;
459
+ if (parts.length === 1) {
460
+ return [this.alias, parts[0]];
461
+ }
462
+ if (greedyAlias) {
463
+ const fromField = parts.pop();
464
+ const fromAlias = parts.join('.');
465
+ return [fromAlias, fromField];
466
+ }
467
+ const fromAlias = parts.shift();
468
+ const fromField = parts.join('.');
423
469
  return [fromAlias, fromField];
424
470
  }
425
471
  getLockSQL(qb, lockMode, lockTables = []) {
@@ -438,7 +484,7 @@ class QueryBuilderHelper {
438
484
  }
439
485
  updateVersionProperty(qb, data) {
440
486
  const meta = this.metadata.find(this.entityName);
441
- if (!meta || !meta.versionProperty || meta.versionProperty in data) {
487
+ if (!meta?.versionProperty || meta.versionProperty in data) {
442
488
  return;
443
489
  }
444
490
  const versionProperty = meta.properties[meta.versionProperty];
@@ -464,7 +510,8 @@ class QueryBuilderHelper {
464
510
  ret = alias + fieldName;
465
511
  }
466
512
  else {
467
- const [a, f] = field.split('.');
513
+ const [a, ...rest] = field.split('.');
514
+ const f = rest.join('.');
468
515
  ret = a + '.' + this.fieldName(f, a);
469
516
  }
470
517
  if (quote) {
@@ -509,15 +556,66 @@ class QueryBuilderHelper {
509
556
  return prop.fieldNameRaw;
510
557
  }
511
558
  /* istanbul ignore next */
512
- return prop.fieldNames[0] ?? field;
559
+ return prop.fieldNames?.[0] ?? field;
513
560
  }
514
561
  getProperty(field, alias) {
515
- const entityName = this.aliasMap[alias] || this.entityName;
562
+ const entityName = this.aliasMap[alias]?.entityName || this.entityName;
516
563
  const meta = this.metadata.find(entityName);
517
- return meta ? meta.properties[field] : undefined;
564
+ // check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
565
+ if (alias && meta) {
566
+ const prop = meta.properties[alias];
567
+ if (prop?.reference === core_1.ReferenceType.EMBEDDED) {
568
+ // we want to select the full object property so hydration works as expected
569
+ if (prop.object) {
570
+ return prop;
571
+ }
572
+ const parts = field.split('.');
573
+ const nest = (p) => parts.length > 0 ? nest(p.embeddedProps[parts.shift()]) : p;
574
+ return nest(prop);
575
+ }
576
+ }
577
+ return meta?.properties[field];
518
578
  }
519
579
  isTableNameAliasRequired(type) {
520
- return [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(type);
580
+ return [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(type ?? enums_1.QueryType.SELECT);
581
+ }
582
+ mapData(data, properties, convertCustomTypes) {
583
+ if (!properties) {
584
+ return data;
585
+ }
586
+ data = Object.assign({}, data); // copy first
587
+ Object.keys(data).forEach(k => {
588
+ const prop = properties[k];
589
+ if (!prop) {
590
+ return;
591
+ }
592
+ if (prop.embeddedProps && !prop.object) {
593
+ const copy = data[k];
594
+ delete data[k];
595
+ Object.assign(data, this.mapData(copy, prop.embeddedProps, convertCustomTypes));
596
+ return;
597
+ }
598
+ if (prop.joinColumns && Array.isArray(data[k])) {
599
+ const copy = data[k];
600
+ delete data[k];
601
+ prop.joinColumns.forEach((joinColumn, idx) => data[joinColumn] = copy[idx]);
602
+ return;
603
+ }
604
+ if (prop.customType && convertCustomTypes && !this.platform.isRaw(data[k])) {
605
+ data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, { fromQuery: true, key: k, mode: 'query' });
606
+ }
607
+ if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(data[k])) {
608
+ const quoted = this.platform.quoteValue(data[k]);
609
+ data[k] = this.knex.raw(prop.customType.convertToDatabaseValueSQL(quoted, this.platform).replace(/\?/, '\\?'));
610
+ }
611
+ if (!prop.customType && (Array.isArray(data[k]) || core_1.Utils.isPlainObject(data[k]))) {
612
+ data[k] = JSON.stringify(data[k]);
613
+ }
614
+ if (prop.fieldNames) {
615
+ core_1.Utils.renameKey(data, k, prop.fieldNames[0]);
616
+ }
617
+ });
618
+ return data;
521
619
  }
522
620
  }
523
621
  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;
@@ -13,9 +13,10 @@ class DatabaseSchema {
13
13
  this.tables = [];
14
14
  this.namespaces = new Set();
15
15
  }
16
- addTable(name, schema) {
16
+ addTable(name, schema, comment) {
17
17
  const namespaceName = schema ?? this.name;
18
18
  const table = new DatabaseTable_1.DatabaseTable(this.platform, name, namespaceName);
19
+ table.comment = comment;
19
20
  this.tables.push(table);
20
21
  if (namespaceName != null) {
21
22
  this.namespaces.add(namespaceName);
@@ -39,21 +40,12 @@ class DatabaseSchema {
39
40
  }
40
41
  static async create(connection, platform, config, schemaName) {
41
42
  const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema'));
42
- const tables = await connection.execute(platform.getSchemaHelper().getListTablesSQL());
43
- for (const t of tables) {
44
- if (t.table_name === config.get('migrations').tableName) {
45
- continue;
46
- }
47
- const table = schema.addTable(t.table_name, t.schema_name);
48
- table.comment = t.table_comment;
49
- const cols = await platform.getSchemaHelper().getColumns(connection, table.name, table.schema);
50
- const indexes = await platform.getSchemaHelper().getIndexes(connection, table.name, table.schema);
51
- const checks = await platform.getSchemaHelper().getChecks(connection, table.name, table.schema);
52
- const pks = await platform.getSchemaHelper().getPrimaryKeys(connection, indexes, table.name, table.schema);
53
- const fks = await platform.getSchemaHelper().getForeignKeys(connection, table.name, table.schema);
54
- const enums = await platform.getSchemaHelper().getEnumDefinitions(connection, checks, table.name, table.schema);
55
- table.init(cols, indexes, checks, pks, fks, enums);
56
- }
43
+ const allTables = await connection.execute(platform.getSchemaHelper().getListTablesSQL());
44
+ const parts = config.get('migrations').tableName.split('.');
45
+ const migrationsTableName = parts[1] ?? parts[0];
46
+ const migrationsSchemaName = parts.length > 1 ? parts[0] : config.get('schema', platform.getDefaultSchemaName());
47
+ const tables = allTables.filter(t => t.table_name !== migrationsTableName || (t.schema_name && t.schema_name !== migrationsSchemaName));
48
+ await platform.getSchemaHelper().loadInformationSchema(schema, connection, tables);
57
49
  return schema;
58
50
  }
59
51
  static fromMetadata(metadata, platform, config, schemaName) {
@@ -19,7 +19,7 @@ export declare class DatabaseTable {
19
19
  getColumn(name: string): Column | undefined;
20
20
  getIndexes(): Index[];
21
21
  getChecks(): Check[];
22
- init(cols: Column[], indexes: Index[], checks: Check[], pks: string[], fks: Dictionary<ForeignKey>, enums: Dictionary<string[]>): void;
22
+ init(cols: Column[], indexes: Index[] | undefined, checks: Check<unknown>[] | undefined, pks: string[], fks?: Dictionary<ForeignKey>, enums?: Dictionary<string[]>): void;
23
23
  addColumn(column: Column): void;
24
24
  addColumnFromProperty(prop: EntityProperty, meta: EntityMetadata): void;
25
25
  private getIndexName;