@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.
- package/AbstractSqlDriver.d.ts +6 -6
- package/AbstractSqlDriver.js +109 -122
- package/PivotCollectionPersister.d.ts +17 -0
- package/PivotCollectionPersister.js +128 -0
- package/index.mjs +2 -0
- package/package.json +4 -4
- package/query/CriteriaNodeFactory.js +8 -1
- package/query/QueryBuilder.d.ts +5 -1
- package/query/QueryBuilder.js +30 -17
- package/query/QueryBuilderHelper.d.ts +0 -1
- package/query/QueryBuilderHelper.js +33 -52
- package/schema/DatabaseSchema.js +2 -2
- package/schema/DatabaseTable.js +12 -3
- package/schema/SchemaComparator.js +3 -1
package/AbstractSqlDriver.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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[];
|
package/AbstractSqlDriver.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
307
|
+
params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
|
|
318
308
|
return;
|
|
319
309
|
}
|
|
320
|
-
params.push(
|
|
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
|
|
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
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
.
|
|
551
|
-
|
|
552
|
-
if (coll.
|
|
553
|
-
|
|
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]:
|
|
569
|
+
const kqb = qb.update({ [coll.property.mappedBy]: pks })
|
|
559
570
|
.getKnexQuery()
|
|
560
|
-
.
|
|
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
|
-
|
|
574
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
62
|
+
"knex": "3.0.1",
|
|
63
63
|
"sqlstring": "2.3.3"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@mikro-orm/core": "^5.8.
|
|
66
|
+
"@mikro-orm/core": "^5.8.10"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"@mikro-orm/core": "6.0.0-dev.
|
|
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);
|
package/query/QueryBuilder.d.ts
CHANGED
|
@@ -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',
|
|
186
|
+
execute<U = any>(method?: 'all' | 'get' | 'run', options?: ExecuteOptions | boolean): Promise<U>;
|
|
183
187
|
/**
|
|
184
188
|
* Alias for `qb.getResultList()`
|
|
185
189
|
*/
|
package/query/QueryBuilder.js
CHANGED
|
@@ -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',
|
|
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
|
|
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 === '
|
|
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
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
-
|
|
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 =
|
|
772
|
-
const
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
87
|
-
|
|
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[
|
|
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.
|
|
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?.[
|
|
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
|
-
|
|
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;
|
package/schema/DatabaseSchema.js
CHANGED
|
@@ -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 ||
|
|
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;
|
package/schema/DatabaseTable.js
CHANGED
|
@@ -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
|
|
69
|
-
prop.scale
|
|
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
|
-
|
|
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 (
|
|
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
|
}
|