@mikro-orm/knex 6.0.0-dev.9 → 6.0.0-dev.90

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 (39) hide show
  1. package/AbstractSqlConnection.d.ts +5 -2
  2. package/AbstractSqlConnection.js +25 -17
  3. package/AbstractSqlDriver.d.ts +19 -18
  4. package/AbstractSqlDriver.js +171 -70
  5. package/AbstractSqlPlatform.d.ts +1 -0
  6. package/AbstractSqlPlatform.js +26 -5
  7. package/MonkeyPatchable.d.ts +1 -0
  8. package/MonkeyPatchable.js +3 -0
  9. package/README.md +64 -107
  10. package/SqlEntityManager.d.ts +2 -5
  11. package/SqlEntityManager.js +5 -9
  12. package/SqlEntityRepository.d.ts +6 -4
  13. package/SqlEntityRepository.js +10 -8
  14. package/index.mjs +20 -6
  15. package/package.json +7 -7
  16. package/query/ArrayCriteriaNode.d.ts +3 -3
  17. package/query/CriteriaNode.d.ts +8 -8
  18. package/query/CriteriaNode.js +9 -9
  19. package/query/CriteriaNodeFactory.d.ts +6 -6
  20. package/query/CriteriaNodeFactory.js +10 -13
  21. package/query/ObjectCriteriaNode.d.ts +3 -3
  22. package/query/ObjectCriteriaNode.js +12 -8
  23. package/query/QueryBuilder.d.ts +27 -16
  24. package/query/QueryBuilder.js +250 -91
  25. package/query/QueryBuilderHelper.d.ts +6 -9
  26. package/query/QueryBuilderHelper.js +112 -96
  27. package/query/ScalarCriteriaNode.d.ts +3 -2
  28. package/query/ScalarCriteriaNode.js +9 -6
  29. package/query/enums.js +1 -1
  30. package/schema/DatabaseSchema.d.ts +4 -1
  31. package/schema/DatabaseSchema.js +22 -6
  32. package/schema/DatabaseTable.d.ts +2 -1
  33. package/schema/DatabaseTable.js +25 -24
  34. package/schema/SchemaComparator.js +9 -2
  35. package/schema/SchemaHelper.d.ts +5 -3
  36. package/schema/SchemaHelper.js +10 -4
  37. package/schema/SqlSchemaGenerator.d.ts +3 -5
  38. package/schema/SqlSchemaGenerator.js +41 -20
  39. package/typings.d.ts +10 -7
@@ -1,5 +1,5 @@
1
1
  import type { Knex } from 'knex';
2
- import type { Dictionary, EntityData, EntityMetadata, EntityProperty, FlatQueryOrderMap, QBFilterQuery } from '@mikro-orm/core';
2
+ import type { Dictionary, EntityData, EntityKey, 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';
@@ -20,10 +20,9 @@ export declare class QueryBuilderHelper {
20
20
  mapper(field: string | Knex.Raw, type?: QueryType): string;
21
21
  mapper(field: string | Knex.Raw, type?: QueryType, value?: any, alias?: string | null): string;
22
22
  processData(data: Dictionary, convertCustomTypes: boolean, multi?: boolean): any;
23
- joinOneToReference(prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary): JoinOptions;
24
- joinManyToOneReference(prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary): JoinOptions;
25
- joinManyToManyReference(prop: EntityProperty, ownerAlias: string, alias: string, pivotAlias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond: Dictionary, path: string): Dictionary<JoinOptions>;
26
- joinPivotTable(field: string, prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary): JoinOptions;
23
+ joinOneToReference(prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary, schema?: string): JoinOptions;
24
+ joinManyToOneReference(prop: EntityProperty, ownerAlias: string, alias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond?: Dictionary, schema?: string): JoinOptions;
25
+ joinManyToManyReference(prop: EntityProperty, ownerAlias: string, alias: string, pivotAlias: string, type: 'leftJoin' | 'innerJoin' | 'pivotJoin', cond: Dictionary, path: string, schema?: string): Dictionary<JoinOptions>;
27
26
  processJoins(qb: Knex.QueryBuilder, joins: Dictionary<JoinOptions>, schema?: string): void;
28
27
  private processJoinClause;
29
28
  private wrapQueryGroup;
@@ -43,16 +42,14 @@ export declare class QueryBuilderHelper {
43
42
  }[], qb: Knex.QueryBuilder): void;
44
43
  appendQueryCondition(type: QueryType, cond: any, qb: Knex.QueryBuilder, operator?: '$and' | '$or', method?: 'where' | 'having'): void;
45
44
  private appendQuerySubCondition;
46
- private processCustomExpression;
47
45
  private processObjectSubCondition;
48
46
  private getOperatorReplacement;
49
47
  getQueryOrder(type: QueryType, orderBy: FlatQueryOrderMap | FlatQueryOrderMap[], populate: Dictionary<string>): string;
50
48
  getQueryOrderFromObject(type: QueryType, orderBy: FlatQueryOrderMap, populate: Dictionary<string>): string;
51
- finalize(type: QueryType, qb: Knex.QueryBuilder, meta?: EntityMetadata): void;
52
- splitField(field: string, greedyAlias?: boolean): [string, string];
49
+ finalize(type: QueryType, qb: Knex.QueryBuilder, meta?: EntityMetadata, data?: Dictionary, returning?: Field<any>[]): void;
50
+ splitField<T>(field: EntityKey<T>, greedyAlias?: boolean): [string, EntityKey<T>];
53
51
  getLockSQL(qb: Knex.QueryBuilder, lockMode: LockMode, lockTables?: string[]): void;
54
52
  updateVersionProperty(qb: Knex.QueryBuilder, data: Dictionary): void;
55
- static isCustomExpression(field: string, hasAlias?: boolean): boolean;
56
53
  private prefix;
57
54
  private appendGroupCondition;
58
55
  private isPrefixed;
@@ -19,6 +19,10 @@ class QueryBuilderHelper {
19
19
  this.metadata = this.driver.getMetadata();
20
20
  }
21
21
  mapper(field, type = enums_1.QueryType.SELECT, value, alias) {
22
+ if (core_1.Utils.isRawSql(field)) {
23
+ return this.knex.raw(field.sql, field.params);
24
+ }
25
+ /* istanbul ignore next */
22
26
  if (typeof field !== 'string') {
23
27
  return field;
24
28
  }
@@ -36,12 +40,26 @@ class QueryBuilderHelper {
36
40
  parts.push(this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias));
37
41
  }
38
42
  }
43
+ // flatten the value if we see we are expanding nested composite key
44
+ // hackish, but cleaner solution would require quite a lot of refactoring
45
+ if (fields.length !== parts.length && Array.isArray(value)) {
46
+ value.forEach(row => {
47
+ if (Array.isArray(row)) {
48
+ const tmp = core_1.Utils.flatten(row);
49
+ row.length = 0;
50
+ row.push(...tmp);
51
+ }
52
+ });
53
+ }
39
54
  return this.knex.raw('(' + parts.map(part => this.knex.ref(part)).join(', ') + ')');
40
55
  }
41
- let ret = field;
42
- const customExpression = QueryBuilderHelper.isCustomExpression(field, !!alias);
56
+ const rawField = core_1.RawQueryFragment.getKnownFragment(field);
57
+ if (rawField) {
58
+ return this.knex.raw(rawField.sql, rawField.params);
59
+ }
43
60
  const [a, f] = this.splitField(field);
44
61
  const prop = this.getProperty(f, a);
62
+ let ret = field;
45
63
  // embeddable nested path instead of a regular property with table alias, reset alias
46
64
  if (prop?.name === a && prop.embeddedProps[f]) {
47
65
  return this.alias + '.' + prop.fieldNames[0];
@@ -56,7 +74,7 @@ class QueryBuilderHelper {
56
74
  const as = alias === null ? '' : ` as ${aliased}`;
57
75
  return this.knex.raw(`${prop.formula(alias2)}${as}`);
58
76
  }
59
- if (prop?.customType?.convertToJSValueSQL) {
77
+ if (prop?.hasConvertToJSValueSQL) {
60
78
  const prefixed = this.prefix(field, isTableNameAliasRequired, true);
61
79
  const valueSQL = prop.customType.convertToJSValueSQL(prefixed, this.platform);
62
80
  if (alias === null) {
@@ -65,15 +83,12 @@ class QueryBuilderHelper {
65
83
  return this.knex.raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[0])}`);
66
84
  }
67
85
  // do not wrap custom expressions
68
- if (!customExpression) {
86
+ if (!rawField) {
69
87
  ret = this.prefix(field);
70
88
  }
71
89
  if (alias) {
72
90
  ret += ' as ' + alias;
73
91
  }
74
- if (customExpression) {
75
- return this.knex.raw(ret, value);
76
- }
77
92
  if (!isTableNameAliasRequired || this.isPrefixed(ret) || noPrefix) {
78
93
  return ret;
79
94
  }
@@ -91,28 +106,28 @@ class QueryBuilderHelper {
91
106
  }
92
107
  return data;
93
108
  }
94
- joinOneToReference(prop, ownerAlias, alias, type, cond = {}) {
109
+ joinOneToReference(prop, ownerAlias, alias, type, cond = {}, schema) {
95
110
  const prop2 = prop.targetMeta.properties[prop.mappedBy || prop.inversedBy];
96
111
  const table = this.getTableName(prop.type);
97
- const schema = this.driver.getSchemaName(prop.targetMeta);
98
112
  const joinColumns = prop.owner ? prop.referencedColumnNames : prop2.joinColumns;
99
113
  const inverseJoinColumns = prop.referencedColumnNames;
100
114
  const primaryKeys = prop.owner ? prop.joinColumns : prop2.referencedColumnNames;
101
115
  return {
102
- prop, type, cond, ownerAlias, alias, table, schema,
116
+ prop, type, cond, ownerAlias, alias, table,
103
117
  joinColumns, inverseJoinColumns, primaryKeys,
118
+ schema: this.driver.getSchemaName(prop.targetMeta, { schema }),
104
119
  };
105
120
  }
106
- joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}) {
121
+ joinManyToOneReference(prop, ownerAlias, alias, type, cond = {}, schema) {
107
122
  return {
108
123
  prop, type, cond, ownerAlias, alias,
109
124
  table: this.getTableName(prop.type),
110
- schema: this.driver.getSchemaName(prop.targetMeta),
125
+ schema: this.driver.getSchemaName(prop.targetMeta, { schema }),
111
126
  joinColumns: prop.referencedColumnNames,
112
127
  primaryKeys: prop.fieldNames,
113
128
  };
114
129
  }
115
- joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path) {
130
+ joinManyToManyReference(prop, ownerAlias, alias, pivotAlias, type, cond, path, schema) {
116
131
  const pivotMeta = this.metadata.find(prop.pivotEntity);
117
132
  const ret = {
118
133
  [`${ownerAlias}.${prop.name}#${pivotAlias}`]: {
@@ -123,7 +138,7 @@ class QueryBuilderHelper {
123
138
  inverseJoinColumns: prop.inverseJoinColumns,
124
139
  primaryKeys: prop.referencedColumnNames,
125
140
  table: pivotMeta.tableName,
126
- schema: this.driver.getSchemaName(pivotMeta),
141
+ schema: this.driver.getSchemaName(pivotMeta, { schema }),
127
142
  path: path.endsWith('[pivot]') ? path : `${path}[pivot]`,
128
143
  },
129
144
  };
@@ -131,22 +146,10 @@ class QueryBuilderHelper {
131
146
  return ret;
132
147
  }
133
148
  const prop2 = prop.owner ? pivotMeta.relations[1] : pivotMeta.relations[0];
134
- ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type);
149
+ ret[`${pivotAlias}.${prop2.name}#${alias}`] = this.joinManyToOneReference(prop2, pivotAlias, alias, type, undefined, schema);
135
150
  ret[`${pivotAlias}.${prop2.name}#${alias}`].path = path;
136
151
  return ret;
137
152
  }
138
- joinPivotTable(field, prop, ownerAlias, alias, type, cond = {}) {
139
- const pivotMeta = this.metadata.find(field);
140
- const prop2 = pivotMeta.relations[0] === prop ? pivotMeta.relations[1] : pivotMeta.relations[0];
141
- return {
142
- prop, type, cond, ownerAlias, alias,
143
- table: pivotMeta.collection,
144
- schema: pivotMeta.schema,
145
- joinColumns: prop.joinColumns,
146
- inverseJoinColumns: prop2.joinColumns,
147
- primaryKeys: prop.referencedColumnNames,
148
- };
149
- }
150
153
  processJoins(qb, joins, schema) {
151
154
  Object.values(joins).forEach(join => {
152
155
  let table = `${join.table} as ${join.alias}`;
@@ -162,6 +165,11 @@ class QueryBuilderHelper {
162
165
  const right = `${join.alias}.${join.joinColumns[idx]}`;
163
166
  conditions.push(`${this.knex.ref(left)} = ${this.knex.ref(right)}`);
164
167
  });
168
+ if (join.prop.targetMeta.discriminatorValue && !join.path?.endsWith('[pivot]')) {
169
+ const typeProperty = join.prop.targetMeta.root.discriminatorColumn;
170
+ const alias = join.inverseAlias ?? join.alias;
171
+ join.cond[`${alias}.${typeProperty}`] = join.prop.targetMeta.discriminatorValue;
172
+ }
165
173
  Object.keys(join.cond).forEach(key => {
166
174
  conditions.push(this.processJoinClause(key, join.cond[key], params));
167
175
  });
@@ -212,20 +220,14 @@ class QueryBuilderHelper {
212
220
  if (operator === '$exists') {
213
221
  value = null;
214
222
  }
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 + ' = ?';
223
+ const rawField = core_1.RawQueryFragment.getKnownFragment(key);
224
+ if (rawField) {
225
+ let sql = rawField.sql;
226
+ params.push(...rawField.params);
227
+ params.push(...core_1.Utils.asArray(value));
228
+ if (core_1.Utils.asArray(value).length > 0) {
229
+ sql += ' = ?';
227
230
  }
228
- params.push(...bindings);
229
231
  return sql;
230
232
  }
231
233
  const sql = this.mapper(key);
@@ -241,7 +243,7 @@ class QueryBuilderHelper {
241
243
  return `(${parts.join(` ${core_1.GroupOperator[operator]} `)})`;
242
244
  }
243
245
  mapJoinColumns(type, join) {
244
- if (join.prop && join.prop.reference === core_1.ReferenceType.ONE_TO_ONE && !join.prop.owner) {
246
+ if (join.prop && join.prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !join.prop.owner) {
245
247
  return join.prop.fieldNames.map((fieldName, idx) => {
246
248
  return this.mapper(`${join.alias}.${join.inverseJoinColumns[idx]}`, type, undefined, fieldName);
247
249
  });
@@ -254,7 +256,7 @@ class QueryBuilderHelper {
254
256
  isOneToOneInverse(field) {
255
257
  const meta = this.metadata.find(this.entityName);
256
258
  const prop = meta.properties[field];
257
- return prop && prop.reference === core_1.ReferenceType.ONE_TO_ONE && !prop.owner;
259
+ return prop && prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner;
258
260
  }
259
261
  getTableName(entityName) {
260
262
  const meta = this.metadata.find(entityName);
@@ -293,13 +295,13 @@ class QueryBuilderHelper {
293
295
  }
294
296
  appendOnConflictClause(type, onConflict, qb) {
295
297
  onConflict.forEach(item => {
296
- const sub = qb.onConflict(item.fields);
298
+ const sub = item.fields.length > 0 ? qb.onConflict(item.fields) : qb.onConflict();
297
299
  core_1.Utils.runIfNotEmpty(() => sub.ignore(), item.ignore);
298
300
  core_1.Utils.runIfNotEmpty(() => {
299
301
  let mergeParam = item.merge;
300
302
  if (core_1.Utils.isObject(item.merge)) {
301
303
  mergeParam = {};
302
- Object.keys(item.merge).forEach(key => {
304
+ core_1.Utils.keys(item.merge).forEach(key => {
303
305
  const k = this.mapper(key, type);
304
306
  mergeParam[k] = item.merge[key];
305
307
  });
@@ -330,34 +332,29 @@ class QueryBuilderHelper {
330
332
  }
331
333
  appendQuerySubCondition(qb, type, method, cond, key, operator) {
332
334
  const m = operator === '$or' ? 'orWhere' : method;
335
+ if (cond[key] instanceof core_1.RawQueryFragment) {
336
+ cond[key] = this.knex.raw(cond[key].sql, cond[key].params);
337
+ }
333
338
  if (this.isSimpleRegExp(cond[key])) {
334
339
  return void qb[m](this.mapper(key, type), 'like', this.getRegExpParam(cond[key]));
335
340
  }
336
341
  if (core_1.Utils.isPlainObject(cond[key]) || cond[key] instanceof RegExp) {
337
342
  return this.processObjectSubCondition(cond, key, qb, method, m, type);
338
343
  }
339
- if (QueryBuilderHelper.isCustomExpression(key)) {
340
- return this.processCustomExpression(qb, m, key, cond, type);
341
- }
342
344
  const op = cond[key] === null ? 'is' : '=';
345
+ const raw = core_1.RawQueryFragment.getKnownFragment(key);
346
+ if (raw) {
347
+ const value = core_1.Utils.asArray(cond[key]);
348
+ if (value.length > 0) {
349
+ return void qb[m](this.knex.raw(raw.sql, raw.params), op, value[0]);
350
+ }
351
+ return void qb[m](this.knex.raw(raw.sql, raw.params));
352
+ }
343
353
  if (this.subQueries[key]) {
344
354
  return void qb[m](this.knex.raw(`(${this.subQueries[key]})`), op, cond[key]);
345
355
  }
346
356
  qb[m](this.mapper(key, type, cond[key], null), op, cond[key]);
347
357
  }
348
- processCustomExpression(clause, m, key, cond, type) {
349
- // unwind parameters when ? found in field name
350
- const count = key.concat('?').match(/\?/g).length - 1;
351
- const value = core_1.Utils.asArray(cond[key]);
352
- const params1 = value.slice(0, count).map((c) => core_1.Utils.isObject(c) ? JSON.stringify(c) : c);
353
- const params2 = value.slice(count);
354
- const k = this.mapper(key, type, params1);
355
- if (params2.length > 0) {
356
- const val = params2.length === 1 && params2[0] === null ? null : this.knex.raw('?', params2);
357
- return void clause[m](k, val);
358
- }
359
- clause[m](k);
360
- }
361
358
  processObjectSubCondition(cond, key, qb, method, m, type) {
362
359
  // grouped condition for one field
363
360
  let value = cond[key];
@@ -394,7 +391,8 @@ class QueryBuilderHelper {
394
391
  }));
395
392
  }
396
393
  else {
397
- qb[m](this.mapper(key, type, undefined, null), replacement, value[op]);
394
+ const mappedKey = this.mapper(key, type, value[op], null);
395
+ qb[m](mappedKey, replacement, value[op]);
398
396
  }
399
397
  }
400
398
  getOperatorReplacement(op, value) {
@@ -422,36 +420,58 @@ class QueryBuilderHelper {
422
420
  }
423
421
  getQueryOrderFromObject(type, orderBy, populate) {
424
422
  const ret = [];
425
- Object.keys(orderBy).forEach(k => {
426
- const direction = orderBy[k];
423
+ Object.keys(orderBy).forEach(key => {
424
+ const direction = orderBy[key];
427
425
  const order = core_1.Utils.isNumber(direction) ? core_1.QueryOrderNumeric[direction] : direction;
428
- if (QueryBuilderHelper.isCustomExpression(k)) {
429
- ret.push(`${k} ${order.toLowerCase()}`);
426
+ const raw = core_1.RawQueryFragment.getKnownFragment(key);
427
+ if (raw) {
428
+ ret.push(`${this.platform.formatQuery(raw.sql, raw.params)} ${order.toLowerCase()}`);
430
429
  return;
431
430
  }
432
- // eslint-disable-next-line prefer-const
433
- let [alias, field] = this.splitField(k, true);
434
- alias = populate[alias] || alias;
435
- core_1.Utils.splitPrimaryKeys(field).forEach(f => {
436
- const prop = this.getProperty(f, alias);
437
- const noPrefix = (prop && prop.persist === false && !prop.formula) || QueryBuilderHelper.isCustomExpression(f);
438
- const column = this.mapper(noPrefix ? f : `${alias}.${f}`, type, undefined, null);
431
+ core_1.Utils.splitPrimaryKeys(key).forEach(f => {
432
+ // eslint-disable-next-line prefer-const
433
+ let [alias, field] = this.splitField(f, true);
434
+ alias = populate[alias] || alias;
435
+ const prop = this.getProperty(field, alias);
436
+ const noPrefix = (prop && prop.persist === false && !prop.formula) || core_1.RawQueryFragment.isKnownFragment(f);
437
+ const column = this.mapper(noPrefix ? field : `${alias}.${field}`, type, undefined, null);
439
438
  /* istanbul ignore next */
440
439
  const rawColumn = core_1.Utils.isString(column) ? column.split('.').map(e => this.knex.ref(e)).join('.') : column;
441
440
  const customOrder = prop?.customOrder;
442
- const colPart = customOrder
441
+ let colPart = customOrder
443
442
  ? this.platform.generateCustomOrder(rawColumn, customOrder)
444
443
  : rawColumn;
444
+ if (core_1.Utils.isRawSql(colPart)) {
445
+ colPart = this.platform.formatQuery(colPart.sql, colPart.params);
446
+ }
445
447
  ret.push(`${colPart} ${order.toLowerCase()}`);
446
448
  });
447
449
  });
448
450
  return ret.join(', ');
449
451
  }
450
- finalize(type, qb, meta) {
451
- const useReturningStatement = type === enums_1.QueryType.INSERT && this.platform.usesReturningStatement() && meta && !meta.compositePK;
452
- if (useReturningStatement) {
453
- const returningProps = meta.hydrateProps.filter(prop => prop.persist !== false && (prop.primary || prop.defaultRaw));
454
- qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
452
+ finalize(type, qb, meta, data, returning) {
453
+ if (!meta || !data || !this.platform.usesReturningStatement()) {
454
+ return;
455
+ }
456
+ // always respect explicit returning hint
457
+ if (returning && returning.length > 0) {
458
+ qb.returning(returning.map(field => this.mapper(field, type)));
459
+ return;
460
+ }
461
+ if (type === enums_1.QueryType.INSERT) {
462
+ const returningProps = meta.hydrateProps
463
+ .filter(prop => prop.returning || (prop.persist !== false && ((prop.primary && prop.autoincrement) || prop.defaultRaw)))
464
+ .filter(prop => !(prop.name in data));
465
+ if (returningProps.length > 0) {
466
+ qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
467
+ }
468
+ return;
469
+ }
470
+ if (type === enums_1.QueryType.UPDATE) {
471
+ const returningProps = meta.hydrateProps.filter(prop => core_1.Utils.isRawSql(data[prop.name]));
472
+ if (returningProps.length > 0) {
473
+ qb.returning(core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames)));
474
+ }
455
475
  }
456
476
  }
457
477
  splitField(field, greedyAlias = false) {
@@ -489,23 +509,18 @@ class QueryBuilderHelper {
489
509
  }
490
510
  const versionProperty = meta.properties[meta.versionProperty];
491
511
  let sql = this.platform.quoteIdentifier(versionProperty.fieldNames[0]) + ' + 1';
492
- if (versionProperty.type.toLowerCase() === 'date') {
512
+ if (versionProperty.runtimeType === 'Date') {
493
513
  sql = this.platform.getCurrentTimestampSQL(versionProperty.length);
494
514
  }
495
515
  qb.update(versionProperty.fieldNames[0], this.knex.raw(sql));
496
516
  }
497
- static isCustomExpression(field, hasAlias = false) {
498
- // if we do not have alias, we don't consider spaces as custom expressions
499
- const re = hasAlias ? /[ ?<>=()'"`:]|^\d/ : /[?<>=()'"`:]|^\d/;
500
- return !!field.match(re);
501
- }
502
517
  prefix(field, always = false, quote = false) {
503
518
  let ret;
504
519
  if (!this.isPrefixed(field)) {
505
520
  const alias = always ? (quote ? this.alias : this.platform.quoteIdentifier(this.alias)) + '.' : '';
506
521
  const fieldName = this.fieldName(field, this.alias, always);
507
- if (QueryBuilderHelper.isCustomExpression(fieldName)) {
508
- return fieldName;
522
+ if (fieldName instanceof core_1.RawQueryFragment) {
523
+ return fieldName.sql;
509
524
  }
510
525
  ret = alias + fieldName;
511
526
  }
@@ -545,15 +560,15 @@ class QueryBuilderHelper {
545
560
  }
546
561
  if (prop.fieldNameRaw) {
547
562
  if (!always) {
548
- return prop.fieldNameRaw
549
- .replace(/\[::alias::]\.?/g, '')
550
- .replace(this.platform.quoteIdentifier('') + '.', '');
563
+ return (0, core_1.raw)(prop.fieldNameRaw
564
+ .replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE + '\\.?', 'g'), '')
565
+ .replace(this.platform.quoteIdentifier('') + '.', ''));
551
566
  }
552
567
  if (alias) {
553
- return prop.fieldNameRaw.replace(/\[::alias::]/g, alias);
568
+ return (0, core_1.raw)(prop.fieldNameRaw.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE, 'g'), alias));
554
569
  }
555
570
  /* istanbul ignore next */
556
- return prop.fieldNameRaw;
571
+ return (0, core_1.raw)(prop.fieldNameRaw);
557
572
  }
558
573
  /* istanbul ignore next */
559
574
  return prop.fieldNames?.[0] ?? field;
@@ -564,7 +579,7 @@ class QueryBuilderHelper {
564
579
  // check if `alias` is not matching an embedded property name instead of alias, e.g. `address.city`
565
580
  if (alias && meta) {
566
581
  const prop = meta.properties[alias];
567
- if (prop?.reference === core_1.ReferenceType.EMBEDDED) {
582
+ if (prop?.kind === core_1.ReferenceKind.EMBEDDED) {
568
583
  // we want to select the full object property so hydration works as expected
569
584
  if (prop.object) {
570
585
  return prop;
@@ -596,17 +611,18 @@ class QueryBuilderHelper {
596
611
  return;
597
612
  }
598
613
  if (prop.joinColumns && Array.isArray(data[k])) {
599
- const copy = data[k];
614
+ const copy = core_1.Utils.flatten(data[k]);
600
615
  delete data[k];
601
616
  prop.joinColumns.forEach((joinColumn, idx) => data[joinColumn] = copy[idx]);
602
617
  return;
603
618
  }
604
619
  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' });
620
+ data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, { fromQuery: true, key: k, mode: 'query-data' });
606
621
  }
607
- if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(data[k])) {
622
+ if (prop.hasConvertToDatabaseValueSQL && !this.platform.isRaw(data[k])) {
608
623
  const quoted = this.platform.quoteValue(data[k]);
609
- data[k] = this.knex.raw(prop.customType.convertToDatabaseValueSQL(quoted, this.platform).replace(/\?/, '\\?'));
624
+ const sql = prop.customType.convertToDatabaseValueSQL(quoted, this.platform);
625
+ data[k] = this.knex.raw(sql.replace(/\?/, '\\?'));
610
626
  }
611
627
  if (!prop.customType && (Array.isArray(data[k]) || core_1.Utils.isPlainObject(data[k]))) {
612
628
  data[k] = JSON.stringify(data[k]);
@@ -3,7 +3,8 @@ import type { IQueryBuilder } from '../typings';
3
3
  /**
4
4
  * @internal
5
5
  */
6
- export declare class ScalarCriteriaNode extends CriteriaNode {
7
- process<T>(qb: IQueryBuilder<T>, alias?: string): any;
6
+ export declare class ScalarCriteriaNode<T extends object> extends CriteriaNode<T> {
7
+ process(qb: IQueryBuilder<T>, alias?: string): any;
8
+ willAutoJoin<T>(qb: IQueryBuilder<T>, alias?: string): boolean;
8
9
  shouldJoin(): boolean;
9
10
  }
@@ -13,23 +13,26 @@ class ScalarCriteriaNode extends CriteriaNode_1.CriteriaNode {
13
13
  const parentPath = this.parent.getPath(); // the parent is always there, otherwise `shouldJoin` would return `false`
14
14
  const nestedAlias = qb.getAliasForJoinPath(path) || qb.getNextAlias(this.prop?.pivotTable ?? this.entityName);
15
15
  const field = `${alias}.${this.prop.name}`;
16
- const type = this.prop.reference === core_1.ReferenceType.MANY_TO_MANY ? 'pivotJoin' : 'leftJoin';
16
+ const type = this.prop.kind === core_1.ReferenceKind.MANY_TO_MANY ? 'pivotJoin' : 'leftJoin';
17
17
  qb.join(field, nestedAlias, undefined, type, path);
18
18
  // select the owner as virtual property when joining from 1:1 inverse side, but only if the parent is root entity
19
- if (this.prop.reference === core_1.ReferenceType.ONE_TO_ONE && !parentPath.includes('.')) {
19
+ if (this.prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !parentPath.includes('.')) {
20
20
  qb.addSelect(field);
21
21
  }
22
22
  }
23
23
  return this.payload;
24
24
  }
25
+ willAutoJoin(qb, alias) {
26
+ return this.shouldJoin();
27
+ }
25
28
  shouldJoin() {
26
29
  if (!this.parent || !this.prop) {
27
30
  return false;
28
31
  }
29
- switch (this.prop.reference) {
30
- case core_1.ReferenceType.ONE_TO_MANY: return true;
31
- case core_1.ReferenceType.MANY_TO_MANY: return true;
32
- case core_1.ReferenceType.ONE_TO_ONE: return !this.prop.owner;
32
+ switch (this.prop.kind) {
33
+ case core_1.ReferenceKind.ONE_TO_MANY: return true;
34
+ case core_1.ReferenceKind.MANY_TO_MANY: return true;
35
+ case core_1.ReferenceKind.ONE_TO_ONE: return !this.prop.owner;
33
36
  default: return false; // SCALAR, MANY_TO_ONE
34
37
  }
35
38
  }
package/query/enums.js CHANGED
@@ -9,4 +9,4 @@ var QueryType;
9
9
  QueryType["INSERT"] = "INSERT";
10
10
  QueryType["UPDATE"] = "UPDATE";
11
11
  QueryType["DELETE"] = "DELETE";
12
- })(QueryType = exports.QueryType || (exports.QueryType = {}));
12
+ })(QueryType || (exports.QueryType = QueryType = {}));
@@ -10,14 +10,17 @@ export declare class DatabaseSchema {
10
10
  readonly name: string;
11
11
  private tables;
12
12
  private namespaces;
13
+ private nativeEnums;
13
14
  constructor(platform: AbstractSqlPlatform, name: string);
14
15
  addTable(name: string, schema: string | undefined | null, comment?: string): DatabaseTable;
15
16
  getTables(): DatabaseTable[];
16
17
  getTable(name: string): DatabaseTable | undefined;
17
18
  hasTable(name: string): boolean;
19
+ setNativeEnums(nativeEnums: Dictionary<unknown[]>): void;
20
+ getNativeEnums(): Dictionary<unknown[]>;
18
21
  hasNamespace(namespace: string): boolean;
19
22
  getNamespaces(): string[];
20
- static create(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, config: Configuration, schemaName?: string): Promise<DatabaseSchema>;
23
+ static create(connection: AbstractSqlConnection, platform: AbstractSqlPlatform, config: Configuration, schemaName?: string, schemas?: string[]): Promise<DatabaseSchema>;
21
24
  static fromMetadata(metadata: EntityMetadata[], platform: AbstractSqlPlatform, config: Configuration, schemaName?: string): DatabaseSchema;
22
25
  private static getSchemaName;
23
26
  private static shouldHaveColumn;
@@ -12,10 +12,12 @@ class DatabaseSchema {
12
12
  this.name = name;
13
13
  this.tables = [];
14
14
  this.namespaces = new Set();
15
+ this.nativeEnums = {}; // for postgres
15
16
  }
16
17
  addTable(name, schema, comment) {
17
18
  const namespaceName = schema ?? this.name;
18
19
  const table = new DatabaseTable_1.DatabaseTable(this.platform, name, namespaceName);
20
+ table.nativeEnums = this.nativeEnums;
19
21
  table.comment = comment;
20
22
  this.tables.push(table);
21
23
  if (namespaceName != null) {
@@ -32,24 +34,38 @@ class DatabaseSchema {
32
34
  hasTable(name) {
33
35
  return !!this.getTable(name);
34
36
  }
37
+ setNativeEnums(nativeEnums) {
38
+ this.nativeEnums = nativeEnums;
39
+ this.tables.forEach(t => t.nativeEnums = nativeEnums);
40
+ }
41
+ getNativeEnums() {
42
+ return this.nativeEnums;
43
+ }
35
44
  hasNamespace(namespace) {
36
45
  return this.namespaces.has(namespace);
37
46
  }
38
47
  getNamespaces() {
39
48
  return [...this.namespaces];
40
49
  }
41
- static async create(connection, platform, config, schemaName) {
42
- const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema'));
50
+ static async create(connection, platform, config, schemaName, schemas) {
51
+ const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema') ?? platform.getDefaultSchemaName());
43
52
  const allTables = await connection.execute(platform.getSchemaHelper().getListTablesSQL());
44
53
  const parts = config.get('migrations').tableName.split('.');
45
54
  const migrationsTableName = parts[1] ?? parts[0];
46
55
  const migrationsSchemaName = parts.length > 1 ? parts[0] : config.get('schema', platform.getDefaultSchemaName());
47
56
  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
+ await platform.getSchemaHelper().loadInformationSchema(schema, connection, tables, schemas && schemas.length > 0 ? schemas : undefined);
49
58
  return schema;
50
59
  }
51
60
  static fromMetadata(metadata, platform, config, schemaName) {
52
61
  const schema = new DatabaseSchema(platform, schemaName ?? config.get('schema'));
62
+ const nativeEnums = {};
63
+ for (const meta of metadata) {
64
+ meta.props
65
+ .filter(prop => prop.nativeEnumName)
66
+ .forEach(prop => nativeEnums[prop.nativeEnumName] = prop.items?.map(val => '' + val) ?? []);
67
+ }
68
+ schema.setNativeEnums(nativeEnums);
53
69
  for (const meta of metadata) {
54
70
  const table = schema.addTable(meta.collection, this.getSchemaName(meta, config, schemaName));
55
71
  table.comment = meta.comment;
@@ -78,15 +94,15 @@ class DatabaseSchema {
78
94
  if (prop.persist === false || !prop.fieldNames) {
79
95
  return false;
80
96
  }
81
- if (meta.pivotTable || (core_1.ReferenceType.EMBEDDED && prop.object)) {
97
+ if (meta.pivotTable || (core_1.ReferenceKind.EMBEDDED && prop.object)) {
82
98
  return true;
83
99
  }
84
100
  const getRootProperty = (prop) => prop.embedded ? getRootProperty(meta.properties[prop.embedded[0]]) : prop;
85
101
  const rootProp = getRootProperty(prop);
86
- if (rootProp.reference === core_1.ReferenceType.EMBEDDED) {
102
+ if (rootProp.kind === core_1.ReferenceKind.EMBEDDED) {
87
103
  return prop === rootProp || !rootProp.object;
88
104
  }
89
- return [core_1.ReferenceType.SCALAR, core_1.ReferenceType.MANY_TO_ONE].includes(prop.reference) || (prop.reference === core_1.ReferenceType.ONE_TO_ONE && prop.owner);
105
+ return [core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.MANY_TO_ONE].includes(prop.kind) || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && prop.owner);
90
106
  }
91
107
  toJSON() {
92
108
  const { platform, namespaces, ...rest } = this;
@@ -13,6 +13,7 @@ export declare class DatabaseTable {
13
13
  private indexes;
14
14
  private checks;
15
15
  private foreignKeys;
16
+ nativeEnums: Dictionary<unknown[]>;
16
17
  comment?: string;
17
18
  constructor(platform: AbstractSqlPlatform, name: string, schema?: string | undefined);
18
19
  getColumns(): Column[];
@@ -37,7 +38,7 @@ export declare class DatabaseTable {
37
38
  getPrimaryKey(): IndexDef | undefined;
38
39
  hasPrimaryKey(): boolean;
39
40
  private getPropertyDeclaration;
40
- private getReferenceType;
41
+ private getReferenceKind;
41
42
  private getPropertyName;
42
43
  private getPropertyType;
43
44
  private getPropertyDefaultValue;