@mikro-orm/knex 6.0.0-dev.116 → 6.0.0-dev.118

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.
@@ -1,5 +1,5 @@
1
1
  import type { Knex } from 'knex';
2
- import { type AnyEntity, type Collection, type Configuration, type ConnectionType, type Constructor, type CountOptions, DatabaseDriver, type DeleteOptions, type Dictionary, type DriverMethodOptions, type EntityData, type EntityDictionary, type EntityField, EntityManagerType, type EntityMetadata, type EntityName, type EntityProperty, type FilterQuery, type FindOneOptions, type FindOptions, type IDatabaseDriver, type LockOptions, type LoggingOptions, type NativeInsertUpdateManyOptions, type NativeInsertUpdateOptions, type PopulateOptions, type Primary, type QueryOrderMap, type QueryResult, type Transaction, type UpsertManyOptions, type UpsertOptions, type OrderDefinition } from '@mikro-orm/core';
2
+ import { type AnyEntity, type Collection, type Configuration, type ConnectionType, type Constructor, type CountOptions, DatabaseDriver, type DeleteOptions, type Dictionary, type DriverMethodOptions, type EntityData, type EntityDictionary, type EntityField, EntityManagerType, type EntityMetadata, type EntityName, type EntityProperty, type FilterQuery, type FindOneOptions, type FindOptions, type IDatabaseDriver, type LockOptions, type LoggingOptions, type NativeInsertUpdateManyOptions, type NativeInsertUpdateOptions, type OrderDefinition, type PopulateOptions, type Primary, type QueryOrderMap, type QueryResult, type Transaction, type UpsertManyOptions, type UpsertOptions } from '@mikro-orm/core';
3
3
  import type { AbstractSqlConnection } from './AbstractSqlConnection';
4
4
  import type { AbstractSqlPlatform } from './AbstractSqlPlatform';
5
5
  import { QueryBuilder, QueryType } from './query';
@@ -28,7 +28,7 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
28
28
  nativeUpdate<T extends object>(entityName: string, where: FilterQuery<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T> & UpsertOptions<T>): Promise<QueryResult<T>>;
29
29
  nativeUpdateMany<T extends object>(entityName: string, where: FilterQuery<T>[], data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T> & UpsertManyOptions<T>): Promise<QueryResult<T>>;
30
30
  nativeDelete<T extends object>(entityName: string, where: FilterQuery<T> | string | any, options?: DeleteOptions<T>): Promise<QueryResult<T>>;
31
- syncCollection<T extends object, O extends object>(coll: Collection<T, O>, options?: DriverMethodOptions): Promise<void>;
31
+ syncCollections<T extends object, O extends object>(collections: Iterable<Collection<T, O>>, options?: DriverMethodOptions): Promise<void>;
32
32
  loadFromPivotTable<T extends object, O extends object>(prop: EntityProperty, owners: Primary<O>[][], where?: FilterQuery<any>, orderBy?: OrderDefinition<T>, ctx?: Transaction, options?: FindOptions<T, any, any>, pivotJoin?: boolean): Promise<Dictionary<T[]>>;
33
33
  private getPivotOrderBy;
34
34
  execute<T extends QueryResult | EntityData<AnyEntity> | EntityData<AnyEntity>[] = EntityData<AnyEntity>[]>(queryOrKnex: string | Knex.QueryBuilder | Knex.Raw, params?: any[], method?: 'all' | 'get' | 'run', ctx?: Transaction): Promise<T>;
@@ -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
  */
@@ -54,9 +57,6 @@ export declare abstract class AbstractSqlDriver<Connection extends AbstractSqlCo
54
57
  }): ConnectionType;
55
58
  protected extractManyToMany<T>(entityName: string, data: EntityDictionary<T>): EntityData<T>;
56
59
  protected processManyToMany<T extends object>(meta: EntityMetadata<T> | undefined, pks: Primary<T>[], collections: EntityData<T>, clear: boolean, options?: DriverMethodOptions): Promise<void>;
57
- protected updateCollectionDiff<T extends object, O extends object>(meta: EntityMetadata<O>, prop: EntityProperty<O>, pks: Primary<O>[], deleteDiff: Primary<T>[][] | boolean, insertDiff: Primary<T>[][], options?: DriverMethodOptions & {
58
- ownerSchema?: string;
59
- }): Promise<void>;
60
60
  lockPessimistic<T extends object>(entity: T, options: LockOptions): Promise<void>;
61
61
  protected buildJoinedPropsOrderBy<T extends object>(entityName: string, qb: QueryBuilder<T>, meta: EntityMetadata<T>, populate: PopulateOptions<T>[], parentPath?: string): QueryOrderMap<T>[];
62
62
  protected normalizeFields<T extends object>(fields: Field<T>[], prefix?: string): string[];
@@ -4,6 +4,7 @@ exports.AbstractSqlDriver = void 0;
4
4
  const core_1 = require("@mikro-orm/core");
5
5
  const query_1 = require("./query");
6
6
  const SqlEntityManager_1 = require("./SqlEntityManager");
7
+ const PivotCollectionPersister_1 = require("./PivotCollectionPersister");
7
8
  class AbstractSqlDriver extends core_1.DatabaseDriver {
8
9
  [core_1.EntityManagerType];
9
10
  connection;
@@ -65,9 +66,6 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
65
66
  if (isCursorPagination && !first && !!last) {
66
67
  result.reverse();
67
68
  }
68
- if (joinedProps.length > 0) {
69
- return this.mergeJoinedResult(result, meta, joinedProps);
70
- }
71
69
  return result;
72
70
  }
73
71
  async findOne(entityName, where, options) {
@@ -150,8 +148,6 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
150
148
  if (qb) {
151
149
  // here we map the aliased results (cartesian product) to an object graph
152
150
  this.mapJoinedProps(ret, meta, populate, qb, ret, map);
153
- // we need to remove the cycles from the mapped values
154
- core_1.Utils.removeCycles(ret, meta);
155
151
  }
156
152
  return ret;
157
153
  }
@@ -166,7 +162,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
166
162
  const meta2 = this.metadata.find(relation.type);
167
163
  const path = parentJoinPath ? `${parentJoinPath}.${relation.name}` : `${meta.name}.${relation.name}`;
168
164
  const relationAlias = qb.getAliasForJoinPath(path);
169
- let relationPojo = {};
165
+ const relationPojo = {};
170
166
  // If the primary key value for the relation is null, we know we haven't joined to anything
171
167
  // and therefore we don't return any record (since all values would be null)
172
168
  const hasPK = meta2.primaryKeys.every(pk => meta2.properties[pk].fieldNames.every(name => {
@@ -211,24 +207,6 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
211
207
  delete root[alias];
212
208
  }
213
209
  });
214
- const key = `${meta.name}-${core_1.Utils.getCompositeKeyHash(result, meta)}`;
215
- const key2 = `${meta2.name}-${core_1.Utils.getCompositeKeyHash(relationPojo, meta2)}`;
216
- if (map[key2]) {
217
- const old = map[key2];
218
- core_1.Utils.keys(relationPojo)
219
- .filter(k => relationPojo[k] != null)
220
- .forEach(k => old[k] = relationPojo[k]);
221
- relationPojo = old;
222
- }
223
- else {
224
- map[key2] = relationPojo;
225
- }
226
- if (map[key]) {
227
- result[relation.name] = map[key][relation.name];
228
- }
229
- else {
230
- map[key] = result;
231
- }
232
210
  if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
233
211
  result[relation.name] ??= [];
234
212
  this.appendToCollection(meta2, result[relation.name], relationPojo);
@@ -313,11 +291,23 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
313
291
  sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
314
292
  }
315
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
+ }
316
306
  if (options.convertCustomTypes && prop.customType) {
317
- 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' }));
318
308
  return;
319
309
  }
320
- params.push(row[prop.name]);
310
+ params.push(value);
321
311
  };
322
312
  if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
323
313
  sql += data.map(row => {
@@ -449,6 +439,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
449
439
  const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
450
440
  const params = [];
451
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
+ };
452
456
  keys.forEach(key => {
453
457
  const prop = meta.properties[key];
454
458
  prop.fieldNames.forEach((fieldName, fieldNameIdx) => {
@@ -463,7 +467,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
463
467
  else {
464
468
  sql += '?';
465
469
  }
466
- 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]);
467
472
  }
468
473
  });
469
474
  sql += ` else ${this.platform.quoteIdentifier(fieldName)} end, `;
@@ -525,55 +530,64 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
525
530
  const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false).delete(where).withSchema(this.getSchemaName(meta, options));
526
531
  return this.rethrow(qb.execute('run', false));
527
532
  }
528
- async syncCollection(coll, options) {
529
- const wrapped = (0, core_1.helper)(coll.owner);
530
- const meta = wrapped.__meta;
531
- const pks = wrapped.getPrimaryKeys(true);
532
- const snap = coll.getSnapshot();
533
- const includes = (arr, item) => !!arr.find(i => core_1.Utils.equals(i, item));
534
- const snapshot = snap ? snap.map(item => (0, core_1.helper)(item).getPrimaryKeys(true)) : [];
535
- const current = coll.getItems(false).map(item => (0, core_1.helper)(item).getPrimaryKeys(true));
536
- const deleteDiff = snap ? snapshot.filter(item => !includes(current, item)) : true;
537
- const insertDiff = current.filter(item => !includes(snapshot, item));
538
- const target = snapshot.filter(item => includes(current, item)).concat(...insertDiff);
539
- const equals = core_1.Utils.equals(current, target);
540
- const ctx = options?.ctx;
541
- // wrong order if we just delete and insert to the end (only owning sides can have fixed order)
542
- if (coll.property.owner && coll.property.fixedOrder && !equals && Array.isArray(deleteDiff)) {
543
- deleteDiff.length = insertDiff.length = 0;
544
- deleteDiff.push(...snapshot);
545
- insertDiff.push(...current);
546
- }
547
- if (coll.property.kind === core_1.ReferenceKind.ONE_TO_MANY) {
548
- const cols = coll.property.referencedColumnNames;
549
- const qb = this.createQueryBuilder(coll.property.type, ctx, 'write')
550
- .withSchema(this.getSchemaName(meta, options));
551
- if (coll.getSnapshot() === undefined) {
552
- if (coll.property.orphanRemoval) {
553
- const kqb = qb.delete({ [coll.property.mappedBy]: pks })
533
+ async syncCollections(collections, options) {
534
+ const groups = {};
535
+ for (const coll of collections) {
536
+ const wrapped = (0, core_1.helper)(coll.owner);
537
+ const meta = wrapped.__meta;
538
+ const pks = wrapped.getPrimaryKeys(true);
539
+ const snap = coll.getSnapshot();
540
+ const includes = (arr, item) => !!arr.find(i => core_1.Utils.equals(i, item));
541
+ const snapshot = snap ? snap.map(item => (0, core_1.helper)(item).getPrimaryKeys(true)) : [];
542
+ const current = coll.getItems(false).map(item => (0, core_1.helper)(item).getPrimaryKeys(true));
543
+ const deleteDiff = snap ? snapshot.filter(item => !includes(current, item)) : true;
544
+ const insertDiff = current.filter(item => !includes(snapshot, item));
545
+ const target = snapshot.filter(item => includes(current, item)).concat(...insertDiff);
546
+ const equals = core_1.Utils.equals(current, target);
547
+ // wrong order if we just delete and insert to the end (only owning sides can have fixed order)
548
+ if (coll.property.owner && coll.property.fixedOrder && !equals && Array.isArray(deleteDiff)) {
549
+ deleteDiff.length = insertDiff.length = 0;
550
+ deleteDiff.push(...snapshot);
551
+ insertDiff.push(...current);
552
+ }
553
+ if (coll.property.kind === core_1.ReferenceKind.ONE_TO_MANY) {
554
+ const cols = coll.property.referencedColumnNames;
555
+ const qb = this.createQueryBuilder(coll.property.type, options?.ctx, 'write')
556
+ .withSchema(this.getSchemaName(meta, options));
557
+ if (coll.getSnapshot() === undefined) {
558
+ if (coll.property.orphanRemoval) {
559
+ const kqb = qb.delete({ [coll.property.mappedBy]: pks })
560
+ .getKnexQuery()
561
+ .whereNotIn(cols, insertDiff);
562
+ return this.rethrow(this.execute(kqb));
563
+ }
564
+ const kqb = qb.update({ [coll.property.mappedBy]: null })
554
565
  .getKnexQuery()
555
566
  .whereNotIn(cols, insertDiff);
556
567
  return this.rethrow(this.execute(kqb));
557
568
  }
558
- const kqb = qb.update({ [coll.property.mappedBy]: null })
569
+ const kqb = qb.update({ [coll.property.mappedBy]: pks })
559
570
  .getKnexQuery()
560
- .whereNotIn(cols, insertDiff);
571
+ .whereIn(cols, insertDiff);
561
572
  return this.rethrow(this.execute(kqb));
562
573
  }
563
- const kqb = qb.update({ [coll.property.mappedBy]: pks })
564
- .getKnexQuery()
565
- .whereIn(cols, insertDiff);
566
- return this.rethrow(this.execute(kqb));
567
- }
568
- /* istanbul ignore next */
569
- const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
570
- const pivotMeta = this.metadata.find(coll.property.pivotEntity);
571
- if (pivotMeta.schema === '*') {
572
574
  /* istanbul ignore next */
573
- options ??= {};
574
- options.schema = ownerSchema;
575
+ const pivotMeta = this.metadata.find(coll.property.pivotEntity);
576
+ let schema = pivotMeta.schema;
577
+ if (schema === '*') {
578
+ const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
579
+ schema = coll.property.owner ? ownerSchema : this.config.get('schema');
580
+ }
581
+ else if (schema == null) {
582
+ schema = this.config.get('schema');
583
+ }
584
+ const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
585
+ const persister = groups[tableName] ??= new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, schema);
586
+ persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks);
587
+ }
588
+ for (const persister of core_1.Utils.values(groups)) {
589
+ await this.rethrow(persister.execute());
575
590
  }
576
- return this.rethrow(this.updateCollectionDiff(meta, coll.property, pks, deleteDiff, insertDiff, options));
577
591
  }
578
592
  async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
579
593
  const pivotMeta = this.metadata.find(prop.pivotEntity);
@@ -636,12 +650,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
636
650
  if (owners.length === 1 && (options.offset != null || options.limit != null)) {
637
651
  qb.limit(options.limit, options.offset);
638
652
  }
639
- 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 })) : [];
640
654
  const items = res.map((row) => super.mapResult(row, prop.targetMeta));
641
655
  const map = {};
642
656
  const pkProps = ownerMeta.getPrimaryProps();
643
657
  owners.forEach(owner => {
644
- const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((col, idx) => {
658
+ const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((_col, idx) => {
645
659
  const pkProp = pkProps[idx];
646
660
  return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
647
661
  }));
@@ -690,6 +704,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
690
704
  .map(prop => ({ field: prop.name, strategy: prop.strategy }));
691
705
  return [...populate, ...toPopulate];
692
706
  }
707
+ /**
708
+ * @internal
709
+ */
693
710
  joinedProps(meta, populate) {
694
711
  return populate.filter(p => {
695
712
  const prop = meta.properties[p.field] || {};
@@ -700,26 +717,33 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
700
717
  * @internal
701
718
  */
702
719
  mergeJoinedResult(rawResults, meta, joinedProps) {
703
- // group by the root entity primary key first
704
- const map = {};
705
720
  const res = [];
706
- rawResults.forEach(item => {
721
+ const map = {};
722
+ for (const item of rawResults) {
707
723
  const pk = core_1.Utils.getCompositeKeyHash(item, meta);
708
724
  if (map[pk]) {
709
- joinedProps.forEach(hint => {
710
- // Sometimes we might join a M:N relation with additional filter on the target entity, and as a result, we get
711
- // the first result with `null` for all target values, which is mapped as empty array. When we see that happen,
712
- // we need to merge the results of the next item.
713
- if (Array.isArray(map[pk][hint.field]) && Array.isArray(item[hint.field]) && map[pk][hint.field].length === 0) {
714
- 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;
715
729
  }
716
- });
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
+ }
717
741
  }
718
742
  else {
719
743
  map[pk] = item;
720
744
  res.push(item);
721
745
  }
722
- });
746
+ }
723
747
  return res;
724
748
  }
725
749
  getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
@@ -818,50 +842,13 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
818
842
  }
819
843
  for (const prop of meta.relations) {
820
844
  if (collections[prop.name]) {
821
- await this.rethrow(this.updateCollectionDiff(meta, prop, pks, clear, collections[prop.name], options));
845
+ const pivotMeta = this.metadata.find(prop.pivotEntity);
846
+ const persister = new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema);
847
+ persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
848
+ await this.rethrow(persister.execute());
822
849
  }
823
850
  }
824
851
  }
825
- async updateCollectionDiff(meta, prop, pks, deleteDiff, insertDiff, options) {
826
- if (!deleteDiff) {
827
- deleteDiff = [];
828
- }
829
- const pivotMeta = this.metadata.find(prop.pivotEntity);
830
- if (deleteDiff === true || deleteDiff.length > 0) {
831
- const qb1 = this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write').withSchema(this.getSchemaName(pivotMeta, options));
832
- const knex = qb1.getKnex();
833
- if (Array.isArray(deleteDiff)) {
834
- knex.whereIn(prop.inverseJoinColumns, deleteDiff);
835
- }
836
- prop.joinColumns.forEach((joinColumn, idx) => knex.andWhere(joinColumn, pks[idx]));
837
- await this.execute(knex.delete());
838
- }
839
- if (insertDiff.length === 0) {
840
- return;
841
- }
842
- const items = insertDiff.map(item => {
843
- const cond = {};
844
- prop.joinColumns.forEach((joinColumn, idx) => cond[joinColumn] = pks[idx]);
845
- prop.inverseJoinColumns.forEach((inverseJoinColumn, idx) => cond[inverseJoinColumn] = item[idx]);
846
- return cond;
847
- });
848
- /* istanbul ignore else */
849
- if (this.platform.allowsMultiInsert()) {
850
- await this.nativeInsertMany(prop.pivotEntity, items, {
851
- ...options,
852
- convertCustomTypes: false,
853
- processCollections: false,
854
- });
855
- }
856
- else {
857
- await core_1.Utils.runSerial(items, item => {
858
- return this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
859
- .withSchema(this.getSchemaName(pivotMeta, options))
860
- .insert(item)
861
- .execute('run', false);
862
- });
863
- }
864
- }
865
852
  async lockPessimistic(entity, options) {
866
853
  const meta = (0, core_1.helper)(entity).__meta;
867
854
  const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).withSchema(options.schema ?? meta.schema);
@@ -928,7 +915,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
928
915
  buildFields(meta, populate, joinedProps, qb, alias, fields) {
929
916
  const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
930
917
  const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
931
- const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL);
918
+ const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
932
919
  const hasExplicitFields = !!fields;
933
920
  const ret = [];
934
921
  let addFormulas = false;
@@ -0,0 +1,17 @@
1
+ import { type EntityMetadata, type EntityProperty, type Primary } from '@mikro-orm/core';
2
+ import { type AbstractSqlDriver } from './AbstractSqlDriver';
3
+ export declare class PivotCollectionPersister<Entity extends object> {
4
+ private readonly meta;
5
+ private readonly driver;
6
+ private readonly ctx?;
7
+ private readonly schema?;
8
+ private readonly platform;
9
+ private readonly inserts;
10
+ private readonly deletes;
11
+ private order;
12
+ constructor(meta: EntityMetadata<Entity>, driver: AbstractSqlDriver, ctx?: any, schema?: string | undefined);
13
+ enqueueUpdate(prop: EntityProperty<Entity>, insertDiff: Primary<Entity>[][], deleteDiff: Primary<Entity>[][] | boolean, pks: Primary<Entity>[]): void;
14
+ private enqueueInsert;
15
+ private enqueueDelete;
16
+ execute(): Promise<void>;
17
+ }
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PivotCollectionPersister = void 0;
4
+ const core_1 = require("@mikro-orm/core");
5
+ class InsertStatement {
6
+ keys;
7
+ data;
8
+ order;
9
+ constructor(keys, data, order) {
10
+ this.keys = keys;
11
+ this.data = data;
12
+ this.order = order;
13
+ }
14
+ getHash() {
15
+ return JSON.stringify(this.data);
16
+ }
17
+ getData() {
18
+ const data = {};
19
+ this.keys.forEach((key, idx) => data[key] = this.data[idx]);
20
+ return data;
21
+ }
22
+ }
23
+ class DeleteStatement {
24
+ keys;
25
+ cond;
26
+ constructor(keys, cond) {
27
+ this.keys = keys;
28
+ this.cond = cond;
29
+ }
30
+ getHash() {
31
+ return JSON.stringify(this.cond);
32
+ }
33
+ getCondition() {
34
+ const cond = {};
35
+ this.keys.forEach((key, idx) => cond[key] = this.cond[idx]);
36
+ return cond;
37
+ }
38
+ }
39
+ class PivotCollectionPersister {
40
+ meta;
41
+ driver;
42
+ ctx;
43
+ schema;
44
+ platform;
45
+ inserts = new Map();
46
+ deletes = new Map();
47
+ order = 0;
48
+ constructor(meta, driver, ctx, schema) {
49
+ this.meta = meta;
50
+ this.driver = driver;
51
+ this.ctx = ctx;
52
+ this.schema = schema;
53
+ this.platform = this.driver.getPlatform();
54
+ }
55
+ enqueueUpdate(prop, insertDiff, deleteDiff, pks) {
56
+ if (insertDiff.length) {
57
+ this.enqueueInsert(prop, insertDiff, pks);
58
+ }
59
+ if (deleteDiff === true || (Array.isArray(deleteDiff) && deleteDiff.length)) {
60
+ this.enqueueDelete(prop, deleteDiff, pks);
61
+ }
62
+ }
63
+ enqueueInsert(prop, insertDiff, pks) {
64
+ for (const fks of insertDiff) {
65
+ const data = prop.owner ? [...fks, ...pks] : [...pks, ...fks];
66
+ const keys = prop.owner
67
+ ? [...prop.inverseJoinColumns, ...prop.joinColumns]
68
+ : [...prop.joinColumns, ...prop.inverseJoinColumns];
69
+ const statement = new InsertStatement(keys, data, this.order++);
70
+ const hash = statement.getHash();
71
+ if (prop.owner || !this.inserts.has(hash)) {
72
+ this.inserts.set(hash, statement);
73
+ }
74
+ }
75
+ }
76
+ enqueueDelete(prop, deleteDiff, pks) {
77
+ if (deleteDiff === true) {
78
+ const statement = new DeleteStatement(prop.joinColumns, pks);
79
+ this.deletes.set(statement.getHash(), statement);
80
+ return;
81
+ }
82
+ for (const fks of deleteDiff) {
83
+ const data = prop.owner ? [...fks, ...pks] : [...pks, ...fks];
84
+ const keys = prop.owner
85
+ ? [...prop.inverseJoinColumns, ...prop.joinColumns]
86
+ : [...prop.joinColumns, ...prop.inverseJoinColumns];
87
+ const statement = new DeleteStatement(keys, data);
88
+ this.deletes.set(statement.getHash(), statement);
89
+ }
90
+ }
91
+ async execute() {
92
+ if (this.deletes.size > 0) {
93
+ const knex = this.driver.createQueryBuilder(this.meta.className, this.ctx, 'write')
94
+ .withSchema(this.schema)
95
+ .getKnex();
96
+ for (const item of this.deletes.values()) {
97
+ knex.orWhere(item.getCondition());
98
+ }
99
+ await this.driver.execute(knex.delete());
100
+ }
101
+ if (this.inserts.size === 0) {
102
+ return;
103
+ }
104
+ let items = [];
105
+ for (const insert of this.inserts.values()) {
106
+ items[insert.order] = insert.getData();
107
+ }
108
+ items = items.filter(i => i);
109
+ /* istanbul ignore else */
110
+ if (this.platform.allowsMultiInsert()) {
111
+ await this.driver.nativeInsertMany(this.meta.className, items, {
112
+ ctx: this.ctx,
113
+ schema: this.schema,
114
+ convertCustomTypes: false,
115
+ processCollections: false,
116
+ });
117
+ }
118
+ else {
119
+ await core_1.Utils.runSerial(items, item => {
120
+ return this.driver.createQueryBuilder(this.meta.className, this.ctx, 'write')
121
+ .withSchema(this.schema)
122
+ .insert(item)
123
+ .execute('run', false);
124
+ });
125
+ }
126
+ }
127
+ }
128
+ exports.PivotCollectionPersister = PivotCollectionPersister;
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.116",
3
+ "version": "6.0.0-dev.118",
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.116"
69
+ "@mikro-orm/core": "6.0.0-dev.118"
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
  }