@mikro-orm/knex 6.0.0-dev.117 → 6.0.0-dev.119

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.
@@ -36,7 +36,10 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
36
36
  * 1:1 owner side needs to be marked for population so QB auto-joins the owner id
37
37
  */
38
38
  protected autoJoinOneToOneOwner<T extends object, P extends string = never>(meta: EntityMetadata<T>, populate: PopulateOptions<T>[], fields?: readonly EntityField<T, P>[]): PopulateOptions<T>[];
39
- protected joinedProps<T>(meta: EntityMetadata, populate: PopulateOptions<T>[]): PopulateOptions<T>[];
39
+ /**
40
+ * @internal
41
+ */
42
+ joinedProps<T>(meta: EntityMetadata, populate: PopulateOptions<T>[]): PopulateOptions<T>[];
40
43
  /**
41
44
  * @internal
42
45
  */
@@ -66,9 +66,6 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
66
66
  if (isCursorPagination && !first && !!last) {
67
67
  result.reverse();
68
68
  }
69
- if (joinedProps.length > 0) {
70
- return this.mergeJoinedResult(result, meta, joinedProps);
71
- }
72
69
  return result;
73
70
  }
74
71
  async findOne(entityName, where, options) {
@@ -151,8 +148,6 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
151
148
  if (qb) {
152
149
  // here we map the aliased results (cartesian product) to an object graph
153
150
  this.mapJoinedProps(ret, meta, populate, qb, ret, map);
154
- // we need to remove the cycles from the mapped values
155
- core_1.Utils.removeCycles(ret, meta);
156
151
  }
157
152
  return ret;
158
153
  }
@@ -167,7 +162,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
167
162
  const meta2 = this.metadata.find(relation.type);
168
163
  const path = parentJoinPath ? `${parentJoinPath}.${relation.name}` : `${meta.name}.${relation.name}`;
169
164
  const relationAlias = qb.getAliasForJoinPath(path);
170
- let relationPojo = {};
165
+ const relationPojo = {};
171
166
  // If the primary key value for the relation is null, we know we haven't joined to anything
172
167
  // and therefore we don't return any record (since all values would be null)
173
168
  const hasPK = meta2.primaryKeys.every(pk => meta2.properties[pk].fieldNames.every(name => {
@@ -212,24 +207,6 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
212
207
  delete root[alias];
213
208
  }
214
209
  });
215
- const key = `${meta.name}-${core_1.Utils.getCompositeKeyHash(result, meta)}`;
216
- const key2 = `${meta2.name}-${core_1.Utils.getCompositeKeyHash(relationPojo, meta2)}`;
217
- if (map[key2]) {
218
- const old = map[key2];
219
- core_1.Utils.keys(relationPojo)
220
- .filter(k => relationPojo[k] != null)
221
- .forEach(k => old[k] = relationPojo[k]);
222
- relationPojo = old;
223
- }
224
- else {
225
- map[key2] = relationPojo;
226
- }
227
- if (map[key]) {
228
- result[relation.name] = map[key][relation.name];
229
- }
230
- else {
231
- map[key] = result;
232
- }
233
210
  if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
234
211
  result[relation.name] ??= [];
235
212
  this.appendToCollection(meta2, result[relation.name], relationPojo);
@@ -314,11 +291,23 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
314
291
  sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
315
292
  }
316
293
  const addParams = (prop, row) => {
294
+ let value = row[prop.name];
295
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
296
+ if (prop.array) {
297
+ for (let i = 0; i < value.length; i++) {
298
+ const item = value[i];
299
+ value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
300
+ }
301
+ }
302
+ else {
303
+ value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
304
+ }
305
+ }
317
306
  if (options.convertCustomTypes && prop.customType) {
318
- params.push(prop.customType.convertToDatabaseValue(row[prop.name], this.platform, { key: prop.name, mode: 'query-data' }));
307
+ params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
319
308
  return;
320
309
  }
321
- params.push(row[prop.name]);
310
+ params.push(value);
322
311
  };
323
312
  if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
324
313
  sql += data.map(row => {
@@ -450,6 +439,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
450
439
  const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
451
440
  const params = [];
452
441
  let sql = `update ${this.getTableName(meta, options)} set `;
442
+ const addParams = (prop, value) => {
443
+ if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
444
+ if (prop.array) {
445
+ for (let i = 0; i < value.length; i++) {
446
+ const item = value[i];
447
+ value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
448
+ }
449
+ }
450
+ else {
451
+ value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
452
+ }
453
+ }
454
+ params.push(value);
455
+ };
453
456
  keys.forEach(key => {
454
457
  const prop = meta.properties[key];
455
458
  prop.fieldNames.forEach((fieldName, fieldNameIdx) => {
@@ -464,7 +467,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
464
467
  else {
465
468
  sql += '?';
466
469
  }
467
- params.push(...pks, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
470
+ params.push(...pks);
471
+ addParams(prop, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
468
472
  }
469
473
  });
470
474
  sql += ` else ${this.platform.quoteIdentifier(fieldName)} end, `;
@@ -646,12 +650,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
646
650
  if (owners.length === 1 && (options.offset != null || options.limit != null)) {
647
651
  qb.limit(options.limit, options.offset);
648
652
  }
649
- const res = owners.length ? await this.rethrow(qb.execute('all', false)) : [];
653
+ const res = owners.length ? await this.rethrow(qb.execute('all', { mergeResults: false, mapResults: false })) : [];
650
654
  const items = res.map((row) => super.mapResult(row, prop.targetMeta));
651
655
  const map = {};
652
656
  const pkProps = ownerMeta.getPrimaryProps();
653
657
  owners.forEach(owner => {
654
- const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((col, idx) => {
658
+ const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((_col, idx) => {
655
659
  const pkProp = pkProps[idx];
656
660
  return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
657
661
  }));
@@ -700,6 +704,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
700
704
  .map(prop => ({ field: prop.name, strategy: prop.strategy }));
701
705
  return [...populate, ...toPopulate];
702
706
  }
707
+ /**
708
+ * @internal
709
+ */
703
710
  joinedProps(meta, populate) {
704
711
  return populate.filter(p => {
705
712
  const prop = meta.properties[p.field] || {};
@@ -710,26 +717,33 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
710
717
  * @internal
711
718
  */
712
719
  mergeJoinedResult(rawResults, meta, joinedProps) {
713
- // group by the root entity primary key first
714
- const map = {};
715
720
  const res = [];
716
- rawResults.forEach(item => {
721
+ const map = {};
722
+ for (const item of rawResults) {
717
723
  const pk = core_1.Utils.getCompositeKeyHash(item, meta);
718
724
  if (map[pk]) {
719
- joinedProps.forEach(hint => {
720
- // Sometimes we might join a M:N relation with additional filter on the target entity, and as a result, we get
721
- // the first result with `null` for all target values, which is mapped as empty array. When we see that happen,
722
- // we need to merge the results of the next item.
723
- if (Array.isArray(map[pk][hint.field]) && Array.isArray(item[hint.field]) && map[pk][hint.field].length === 0) {
724
- item[hint.field].forEach((el) => map[pk][hint.field].push(el));
725
+ for (const hint of joinedProps) {
726
+ const prop = meta.properties[hint.field];
727
+ if (!item[hint.field]) {
728
+ continue;
725
729
  }
726
- });
730
+ switch (prop.kind) {
731
+ case core_1.ReferenceKind.ONE_TO_MANY:
732
+ case core_1.ReferenceKind.MANY_TO_MANY:
733
+ map[pk][hint.field] = this.mergeJoinedResult([...map[pk][hint.field], ...item[hint.field]], prop.targetMeta, hint.children ?? []);
734
+ break;
735
+ case core_1.ReferenceKind.MANY_TO_ONE:
736
+ case core_1.ReferenceKind.ONE_TO_ONE:
737
+ map[pk][hint.field] = this.mergeJoinedResult([map[pk][hint.field], item[hint.field]], prop.targetMeta, hint.children ?? [])[0];
738
+ break;
739
+ }
740
+ }
727
741
  }
728
742
  else {
729
743
  map[pk] = item;
730
744
  res.push(item);
731
745
  }
732
- });
746
+ }
733
747
  return res;
734
748
  }
735
749
  getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
@@ -901,7 +915,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
901
915
  buildFields(meta, populate, joinedProps, qb, alias, fields) {
902
916
  const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
903
917
  const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
904
- const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL);
918
+ const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
905
919
  const hasExplicitFields = !!fields;
906
920
  const ret = [];
907
921
  let addFormulas = false;
package/index.mjs CHANGED
@@ -47,6 +47,8 @@ export const DatabaseObjectExistsException = mod.DatabaseObjectExistsException;
47
47
  export const DatabaseObjectNotFoundException = mod.DatabaseObjectNotFoundException;
48
48
  export const DatabaseSchema = mod.DatabaseSchema;
49
49
  export const DatabaseTable = mod.DatabaseTable;
50
+ export const Dataloader = mod.Dataloader;
51
+ export const DataloaderUtils = mod.DataloaderUtils;
50
52
  export const DateTimeType = mod.DateTimeType;
51
53
  export const DateType = mod.DateType;
52
54
  export const DeadlockException = mod.DeadlockException;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/knex",
3
- "version": "6.0.0-dev.117",
3
+ "version": "6.0.0-dev.119",
4
4
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -59,13 +59,13 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "fs-extra": "11.1.1",
62
- "knex": "2.5.1",
62
+ "knex": "3.0.1",
63
63
  "sqlstring": "2.3.3"
64
64
  },
65
65
  "devDependencies": {
66
- "@mikro-orm/core": "^5.8.6"
66
+ "@mikro-orm/core": "^5.8.10"
67
67
  },
68
68
  "peerDependencies": {
69
- "@mikro-orm/core": "6.0.0-dev.117"
69
+ "@mikro-orm/core": "6.0.0-dev.119"
70
70
  }
71
71
  }
@@ -46,10 +46,17 @@ class CriteriaNodeFactory {
46
46
  }
47
47
  static createObjectItemNode(metadata, entityName, node, payload, key, meta) {
48
48
  const prop = meta?.properties[key];
49
+ const childEntity = prop && prop.kind !== core_1.ReferenceKind.SCALAR ? prop.type : entityName;
49
50
  if (prop?.kind !== core_1.ReferenceKind.EMBEDDED) {
50
- const childEntity = prop && prop.kind !== core_1.ReferenceKind.SCALAR ? prop.type : entityName;
51
51
  return this.createNode(metadata, childEntity, payload[key], node, key);
52
52
  }
53
+ if (payload[key] == null) {
54
+ const map = Object.keys(prop.embeddedProps).reduce((oo, k) => {
55
+ oo[prop.embeddedProps[k].name] = null;
56
+ return oo;
57
+ }, {});
58
+ return this.createNode(metadata, entityName, map, node, key);
59
+ }
53
60
  const operator = Object.keys(payload[key]).some(f => core_1.Utils.isOperator(f));
54
61
  if (operator) {
55
62
  throw core_1.ValidationError.cannotUseOperatorsInsideEmbeddables(entityName, prop.name, payload);
@@ -8,6 +8,10 @@ import { type Alias, QueryBuilderHelper } from './QueryBuilderHelper';
8
8
  import type { SqlEntityManager } from '../SqlEntityManager';
9
9
  import type { Field } from '../typings';
10
10
  import type { AbstractSqlPlatform } from '../AbstractSqlPlatform';
11
+ export interface ExecuteOptions {
12
+ mapResults?: boolean;
13
+ mergeResults?: boolean;
14
+ }
11
15
  /**
12
16
  * SQL query builder with fluent interface.
13
17
  *
@@ -179,7 +183,7 @@ export declare class QueryBuilder<T extends object = AnyEntity> {
179
183
  * Executes this QB and returns the raw results, mapped to the property names (unless disabled via last parameter).
180
184
  * Use `method` to specify what kind of result you want to get (array/single/meta).
181
185
  */
182
- execute<U = any>(method?: 'all' | 'get' | 'run', mapResults?: boolean): Promise<U>;
186
+ execute<U = any>(method?: 'all' | 'get' | 'run', options?: ExecuteOptions | boolean): Promise<U>;
183
187
  /**
184
188
  * Alias for `qb.getResultList()`
185
189
  */
@@ -508,7 +508,10 @@ class QueryBuilder {
508
508
  * Executes this QB and returns the raw results, mapped to the property names (unless disabled via last parameter).
509
509
  * Use `method` to specify what kind of result you want to get (array/single/meta).
510
510
  */
511
- async execute(method = 'all', mapResults = true) {
511
+ async execute(method = 'all', options) {
512
+ options = typeof options === 'boolean' ? { mapResults: options } : (options ?? {});
513
+ options.mergeResults ??= true;
514
+ options.mapResults ??= true;
512
515
  if (!this.connectionType && method !== 'run' && [enums_1.QueryType.INSERT, enums_1.QueryType.UPDATE, enums_1.QueryType.DELETE, enums_1.QueryType.TRUNCATE].includes(this.type ?? enums_1.QueryType.SELECT)) {
513
516
  this.connectionType = 'write';
514
517
  }
@@ -517,21 +520,33 @@ class QueryBuilder {
517
520
  if (cached?.data) {
518
521
  return cached.data;
519
522
  }
520
- const type = this.connectionType || (method === 'run' ? 'write' : 'read');
523
+ const write = method === 'run' || !this.platform.getConfig().get('preferReadReplicas');
524
+ const type = this.connectionType || (write ? 'write' : 'read');
521
525
  const res = await this.driver.getConnection(type).execute(query.sql, query.bindings, method, this.context, this.logging);
522
526
  const meta = this.mainAlias.metadata;
523
- if (!mapResults || !meta) {
527
+ if (!options.mapResults || !meta) {
524
528
  await this.em?.storeCache(this._cache, cached, res);
525
529
  return res;
526
530
  }
527
- if (method === 'all' && Array.isArray(res)) {
531
+ if (method === 'run') {
532
+ return res;
533
+ }
534
+ const joinedProps = this.driver.joinedProps(meta, this._populate);
535
+ let mapped;
536
+ if (Array.isArray(res)) {
528
537
  const map = {};
529
- const mapped = res.map(r => this.driver.mapResult(r, meta, this._populate, this, map));
530
- await this.em?.storeCache(this._cache, cached, mapped);
531
- return mapped;
538
+ mapped = res.map(r => this.driver.mapResult(r, meta, this._populate, this, map));
539
+ if (options.mergeResults && joinedProps.length > 0) {
540
+ mapped = this.driver.mergeJoinedResult(mapped, this.mainAlias.metadata, joinedProps);
541
+ }
542
+ }
543
+ else {
544
+ mapped = [this.driver.mapResult(res, meta, joinedProps, this)];
532
545
  }
533
- const mapped = this.driver.mapResult(res, meta, this._populate, this);
534
546
  await this.em?.storeCache(this._cache, cached, mapped);
547
+ if (method === 'get') {
548
+ return mapped[0];
549
+ }
535
550
  return mapped;
536
551
  }
537
552
  /**
@@ -545,10 +560,7 @@ class QueryBuilder {
545
560
  */
546
561
  async getResultList() {
547
562
  await this.em.tryFlush(this.mainAlias.entityName, { flushMode: this.flushMode });
548
- let res = await this.execute('all', true);
549
- if (this._joinedProps.size > 0) {
550
- res = this.driver.mergeJoinedResult(res, this.mainAlias.metadata, [...this._joinedProps.values()]);
551
- }
563
+ const res = await this.execute('all', true);
552
564
  const entities = [];
553
565
  function propagatePopulateHint(entity, hint) {
554
566
  (0, core_1.helper)(entity).__serializationContext.populate ??= hint;
@@ -768,8 +780,9 @@ class QueryBuilder {
768
780
  return;
769
781
  }
770
782
  if (prop?.embedded) {
771
- const name = this._aliases[a] ? `${a}.${prop.fieldNames[0]}` : prop.fieldNames[0];
772
- const fieldName = this.helper.mapper(name, this.type);
783
+ const name = prop.embeddedPath?.join('.') ?? prop.fieldNames[0];
784
+ const aliased = this._aliases[a] ? `${a}.${name}` : name;
785
+ const fieldName = this.helper.mapper(aliased, this.type);
773
786
  ret.push(fieldName);
774
787
  return;
775
788
  }
@@ -780,7 +793,7 @@ class QueryBuilder {
780
793
  else {
781
794
  const nest = (prop) => {
782
795
  for (const childProp of Object.values(prop.embeddedProps)) {
783
- if (childProp.fieldNames) {
796
+ if (childProp.fieldNames && (childProp.kind !== core_1.ReferenceKind.EMBEDDED || childProp.object) && childProp.persist !== false) {
784
797
  ret.push(this.helper.mapper(childProp.fieldNames[0], this.type));
785
798
  }
786
799
  else {
@@ -800,7 +813,7 @@ class QueryBuilder {
800
813
  });
801
814
  const meta = this.mainAlias.metadata;
802
815
  /* istanbul ignore next */
803
- const requiresSQLConversion = meta?.props.filter(p => p.hasConvertToJSValueSQL) ?? [];
816
+ const requiresSQLConversion = meta?.props.filter(p => p.hasConvertToJSValueSQL && p.persist !== false) ?? [];
804
817
  if (this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES) && (fields.includes('*') || fields.includes(`${this.mainAlias.aliasName}.*`)) && requiresSQLConversion.length > 0) {
805
818
  requiresSQLConversion.forEach(p => ret.push(this.helper.mapper(p.name, this.type)));
806
819
  }
@@ -830,7 +843,7 @@ class QueryBuilder {
830
843
  if (core_1.Utils.isEntity(data)) {
831
844
  data = this.em?.getComparator().prepareEntity(data) ?? (0, core_1.serialize)(data);
832
845
  }
833
- this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES));
846
+ this._data = this.helper.processData(data, this.flags.has(core_1.QueryFlag.CONVERT_CUSTOM_TYPES), false);
834
847
  }
835
848
  if (cond) {
836
849
  this.where(cond);
@@ -55,7 +55,6 @@ export declare class QueryBuilderHelper {
55
55
  private fieldName;
56
56
  getProperty(field: string, alias?: string): EntityProperty | undefined;
57
57
  isTableNameAliasRequired(type?: QueryType): boolean;
58
- private mapData;
59
58
  }
60
59
  export interface Alias<T> {
61
60
  aliasName: string;
@@ -41,7 +41,11 @@ class QueryBuilderHelper {
41
41
  for (const p of fields) {
42
42
  const [a, f] = this.splitField(p);
43
43
  const prop = this.getProperty(f, a);
44
- if (prop) {
44
+ const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
45
+ if (fkIdx2 !== -1) {
46
+ parts.push(this.mapper(a !== this.alias ? `${a}.${prop.fieldNames[fkIdx2]}` : prop.fieldNames[fkIdx2], type, value, alias));
47
+ }
48
+ else if (prop) {
45
49
  parts.push(...prop.fieldNames.map(f => this.mapper(a !== this.alias ? `${a}.${f}` : f, type, value, alias)));
46
50
  }
47
51
  else {
@@ -67,10 +71,12 @@ class QueryBuilderHelper {
67
71
  }
68
72
  const [a, f] = this.splitField(field);
69
73
  const prop = this.getProperty(f, a);
74
+ const fkIdx2 = prop?.fieldNames.findIndex(name => name === f) ?? -1;
75
+ const fkIdx = fkIdx2 === -1 ? 0 : fkIdx2;
70
76
  let ret = field;
71
77
  // embeddable nested path instead of a regular property with table alias, reset alias
72
78
  if (prop?.name === a && prop.embeddedProps[f]) {
73
- return this.alias + '.' + prop.fieldNames[0];
79
+ return this.alias + '.' + prop.fieldNames[fkIdx];
74
80
  }
75
81
  const noPrefix = prop && prop.persist === false;
76
82
  if (prop?.fieldNameRaw) {
@@ -83,16 +89,24 @@ class QueryBuilderHelper {
83
89
  return this.knex.raw(`${prop.formula(alias2)}${as}`);
84
90
  }
85
91
  if (prop?.hasConvertToJSValueSQL) {
86
- const prefixed = this.prefix(field, isTableNameAliasRequired, true);
87
- const valueSQL = prop.customType.convertToJSValueSQL(prefixed, this.platform);
92
+ let valueSQL;
93
+ if (prop.fieldNames.length > 1 && fkIdx !== -1) {
94
+ const fk = prop.targetMeta.getPrimaryProps()[fkIdx];
95
+ const prefixed = this.prefix(field, isTableNameAliasRequired, true, fkIdx);
96
+ valueSQL = fk.customType.convertToJSValueSQL(prefixed, this.platform);
97
+ }
98
+ else {
99
+ const prefixed = this.prefix(field, isTableNameAliasRequired, true);
100
+ valueSQL = prop.customType.convertToJSValueSQL(prefixed, this.platform);
101
+ }
88
102
  if (alias === null) {
89
103
  return this.knex.raw(valueSQL);
90
104
  }
91
- return this.knex.raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[0])}`);
105
+ return this.knex.raw(`${valueSQL} as ${this.platform.quoteIdentifier(alias ?? prop.fieldNames[fkIdx])}`);
92
106
  }
93
107
  // do not wrap custom expressions
94
108
  if (!rawField) {
95
- ret = this.prefix(field);
109
+ ret = this.prefix(field, false, false, fkIdx);
96
110
  }
97
111
  if (alias) {
98
112
  ret += ' as ' + alias;
@@ -107,7 +121,7 @@ class QueryBuilderHelper {
107
121
  return data.map(d => this.processData(d, convertCustomTypes, true));
108
122
  }
109
123
  const meta = this.metadata.find(this.entityName);
110
- data = this.mapData(data, meta?.properties, convertCustomTypes);
124
+ data = this.driver.mapDataToFieldNames(data, true, meta?.properties, convertCustomTypes);
111
125
  if (!core_1.Utils.hasObjectKeys(data) && meta && multi) {
112
126
  /* istanbul ignore next */
113
127
  data[meta.primaryKeys[0]] = this.platform.usesDefaultKeyword() ? this.knex.raw('default') : undefined;
@@ -551,11 +565,11 @@ class QueryBuilderHelper {
551
565
  }
552
566
  qb.update(versionProperty.fieldNames[0], this.knex.raw(sql));
553
567
  }
554
- prefix(field, always = false, quote = false) {
568
+ prefix(field, always = false, quote = false, idx) {
555
569
  let ret;
556
570
  if (!this.isPrefixed(field)) {
557
571
  const alias = always ? (quote ? this.alias : this.platform.quoteIdentifier(this.alias)) + '.' : '';
558
- const fieldName = this.fieldName(field, this.alias, always);
572
+ const fieldName = this.fieldName(field, this.alias, always, idx);
559
573
  if (fieldName instanceof core_1.RawQueryFragment) {
560
574
  return fieldName.sql;
561
575
  }
@@ -564,7 +578,7 @@ class QueryBuilderHelper {
564
578
  else {
565
579
  const [a, ...rest] = field.split('.');
566
580
  const f = rest.join('.');
567
- ret = a + '.' + this.fieldName(f, a);
581
+ ret = a + '.' + this.fieldName(f, a, always, idx);
568
582
  }
569
583
  if (quote) {
570
584
  return this.platform.quoteIdentifier(ret);
@@ -590,7 +604,7 @@ class QueryBuilderHelper {
590
604
  isPrefixed(field) {
591
605
  return !!field.match(/[\w`"[\]]+\./);
592
606
  }
593
- fieldName(field, alias, always) {
607
+ fieldName(field, alias, always, idx = 0) {
594
608
  const prop = this.getProperty(field, alias);
595
609
  if (!prop) {
596
610
  return field;
@@ -608,7 +622,7 @@ class QueryBuilderHelper {
608
622
  return (0, core_1.raw)(prop.fieldNameRaw);
609
623
  }
610
624
  /* istanbul ignore next */
611
- return prop.fieldNames?.[0] ?? field;
625
+ return prop.fieldNames?.[idx] ?? field;
612
626
  }
613
627
  getProperty(field, alias) {
614
628
  const entityName = this.aliasMap[alias]?.entityName || this.entityName;
@@ -626,49 +640,16 @@ class QueryBuilderHelper {
626
640
  return nest(prop);
627
641
  }
628
642
  }
629
- return meta?.properties[field];
643
+ if (meta) {
644
+ if (meta.properties[field]) {
645
+ return meta.properties[field];
646
+ }
647
+ return meta.relations.find(prop => prop.fieldNames?.some(name => field === name));
648
+ }
649
+ return undefined;
630
650
  }
631
651
  isTableNameAliasRequired(type) {
632
652
  return [enums_1.QueryType.SELECT, enums_1.QueryType.COUNT].includes(type ?? enums_1.QueryType.SELECT);
633
653
  }
634
- mapData(data, properties, convertCustomTypes) {
635
- if (!properties) {
636
- return data;
637
- }
638
- data = Object.assign({}, data); // copy first
639
- Object.keys(data).forEach(k => {
640
- const prop = properties[k];
641
- if (!prop) {
642
- return;
643
- }
644
- if (prop.embeddedProps && !prop.object) {
645
- const copy = data[k];
646
- delete data[k];
647
- Object.assign(data, this.mapData(copy, prop.embeddedProps, convertCustomTypes));
648
- return;
649
- }
650
- if (prop.joinColumns && Array.isArray(data[k])) {
651
- const copy = core_1.Utils.flatten(data[k]);
652
- delete data[k];
653
- prop.joinColumns.forEach((joinColumn, idx) => data[joinColumn] = copy[idx]);
654
- return;
655
- }
656
- if (prop.customType && convertCustomTypes && !this.platform.isRaw(data[k])) {
657
- data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, { fromQuery: true, key: k, mode: 'query-data' });
658
- }
659
- if (prop.hasConvertToDatabaseValueSQL && !this.platform.isRaw(data[k])) {
660
- const quoted = this.platform.quoteValue(data[k]);
661
- const sql = prop.customType.convertToDatabaseValueSQL(quoted, this.platform);
662
- data[k] = this.knex.raw(sql.replace(/\?/g, '\\?'));
663
- }
664
- if (!prop.customType && (Array.isArray(data[k]) || core_1.Utils.isPlainObject(data[k]))) {
665
- data[k] = JSON.stringify(data[k]);
666
- }
667
- if (prop.fieldNames) {
668
- core_1.Utils.renameKey(data, k, prop.fieldNames[0]);
669
- }
670
- });
671
- return data;
672
- }
673
654
  }
674
655
  exports.QueryBuilderHelper = QueryBuilderHelper;
@@ -93,10 +93,10 @@ class DatabaseSchema {
93
93
  return (meta.schema === '*' ? schema : meta.schema) ?? config.get('schema');
94
94
  }
95
95
  static shouldHaveColumn(meta, prop) {
96
- if (prop.persist === false || !prop.fieldNames) {
96
+ if (prop.persist === false || (prop.columnTypes?.length ?? 0) === 0) {
97
97
  return false;
98
98
  }
99
- if (meta.pivotTable || (core_1.ReferenceKind.EMBEDDED && prop.object)) {
99
+ if (meta.pivotTable || (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object)) {
100
100
  return true;
101
101
  }
102
102
  const getRootProperty = (prop) => prop.embedded ? getRootProperty(meta.properties[prop.embedded[0]]) : prop;
@@ -65,13 +65,19 @@ class DatabaseTable {
65
65
  const match = prop.columnTypes[idx].match(/\w+\((\d+), ?(\d+)\)/);
66
66
  /* istanbul ignore else */
67
67
  if (match) {
68
- prop.precision = +match[1];
69
- prop.scale = +match[2];
68
+ prop.precision ??= +match[1];
69
+ prop.scale ??= +match[2];
70
70
  prop.length = undefined;
71
71
  }
72
72
  }
73
73
  if (mappedType instanceof core_1.DateTimeType) {
74
- prop.length ??= this.platform.getDefaultDateTimeLength();
74
+ const match = prop.columnTypes[idx].match(/\w+\((\d+)\)/);
75
+ if (match) {
76
+ prop.length ??= +match[1];
77
+ }
78
+ else {
79
+ prop.length ??= this.platform.getDefaultDateTimeLength();
80
+ }
75
81
  }
76
82
  const primary = !meta.compositePK && !!prop.primary && prop.kind === core_1.ReferenceKind.SCALAR && this.platform.isNumericColumn(mappedType);
77
83
  this.columns[field] = {
@@ -303,6 +309,9 @@ class DatabaseTable {
303
309
  const properties = core_1.Utils.unique(core_1.Utils.flatten(core_1.Utils.asArray(index.properties).map(prop => {
304
310
  const root = prop.replace(/\..+$/, '');
305
311
  if (meta.properties[prop]) {
312
+ if (meta.properties[prop].embeddedPath) {
313
+ return [meta.properties[prop].embeddedPath.join('.')];
314
+ }
306
315
  return meta.properties[prop].fieldNames;
307
316
  }
308
317
  // json index, we need to rename the column only
@@ -366,7 +366,9 @@ class SchemaComparator {
366
366
  log(`'comment' changed for column ${tableName}.${column1.name}`, { column1, column2 });
367
367
  changedProperties.add('comment');
368
368
  }
369
- if (this.diffEnumItems(column1.enumItems, column2.enumItems)) {
369
+ if (!(column1.mappedType instanceof core_1.ArrayType) &&
370
+ !(column2.mappedType instanceof core_1.ArrayType) &&
371
+ this.diffEnumItems(column1.enumItems, column2.enumItems)) {
370
372
  log(`'enumItems' changed for column ${tableName}.${column1.name}`, { column1, column2 });
371
373
  changedProperties.add('enumItems');
372
374
  }