@mikro-orm/knex 6.0.0-dev.117 → 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 +4 -1
- package/AbstractSqlDriver.js +55 -41
- 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
|
@@ -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
|
*/
|
package/AbstractSqlDriver.js
CHANGED
|
@@ -66,9 +66,6 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
66
66
|
if (isCursorPagination && !first && !!last) {
|
|
67
67
|
result.reverse();
|
|
68
68
|
}
|
|
69
|
-
if (joinedProps.length > 0) {
|
|
70
|
-
return this.mergeJoinedResult(result, meta, joinedProps);
|
|
71
|
-
}
|
|
72
69
|
return result;
|
|
73
70
|
}
|
|
74
71
|
async findOne(entityName, where, options) {
|
|
@@ -151,8 +148,6 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
151
148
|
if (qb) {
|
|
152
149
|
// here we map the aliased results (cartesian product) to an object graph
|
|
153
150
|
this.mapJoinedProps(ret, meta, populate, qb, ret, map);
|
|
154
|
-
// we need to remove the cycles from the mapped values
|
|
155
|
-
core_1.Utils.removeCycles(ret, meta);
|
|
156
151
|
}
|
|
157
152
|
return ret;
|
|
158
153
|
}
|
|
@@ -167,7 +162,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
167
162
|
const meta2 = this.metadata.find(relation.type);
|
|
168
163
|
const path = parentJoinPath ? `${parentJoinPath}.${relation.name}` : `${meta.name}.${relation.name}`;
|
|
169
164
|
const relationAlias = qb.getAliasForJoinPath(path);
|
|
170
|
-
|
|
165
|
+
const relationPojo = {};
|
|
171
166
|
// If the primary key value for the relation is null, we know we haven't joined to anything
|
|
172
167
|
// and therefore we don't return any record (since all values would be null)
|
|
173
168
|
const hasPK = meta2.primaryKeys.every(pk => meta2.properties[pk].fieldNames.every(name => {
|
|
@@ -212,24 +207,6 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
212
207
|
delete root[alias];
|
|
213
208
|
}
|
|
214
209
|
});
|
|
215
|
-
const key = `${meta.name}-${core_1.Utils.getCompositeKeyHash(result, meta)}`;
|
|
216
|
-
const key2 = `${meta2.name}-${core_1.Utils.getCompositeKeyHash(relationPojo, meta2)}`;
|
|
217
|
-
if (map[key2]) {
|
|
218
|
-
const old = map[key2];
|
|
219
|
-
core_1.Utils.keys(relationPojo)
|
|
220
|
-
.filter(k => relationPojo[k] != null)
|
|
221
|
-
.forEach(k => old[k] = relationPojo[k]);
|
|
222
|
-
relationPojo = old;
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
map[key2] = relationPojo;
|
|
226
|
-
}
|
|
227
|
-
if (map[key]) {
|
|
228
|
-
result[relation.name] = map[key][relation.name];
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
map[key] = result;
|
|
232
|
-
}
|
|
233
210
|
if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
|
|
234
211
|
result[relation.name] ??= [];
|
|
235
212
|
this.appendToCollection(meta2, result[relation.name], relationPojo);
|
|
@@ -314,11 +291,23 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
314
291
|
sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
|
|
315
292
|
}
|
|
316
293
|
const addParams = (prop, row) => {
|
|
294
|
+
let value = row[prop.name];
|
|
295
|
+
if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
|
|
296
|
+
if (prop.array) {
|
|
297
|
+
for (let i = 0; i < value.length; i++) {
|
|
298
|
+
const item = value[i];
|
|
299
|
+
value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
317
306
|
if (options.convertCustomTypes && prop.customType) {
|
|
318
|
-
params.push(prop.customType.convertToDatabaseValue(
|
|
307
|
+
params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
|
|
319
308
|
return;
|
|
320
309
|
}
|
|
321
|
-
params.push(
|
|
310
|
+
params.push(value);
|
|
322
311
|
};
|
|
323
312
|
if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
|
|
324
313
|
sql += data.map(row => {
|
|
@@ -450,6 +439,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
450
439
|
const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
|
|
451
440
|
const params = [];
|
|
452
441
|
let sql = `update ${this.getTableName(meta, options)} set `;
|
|
442
|
+
const addParams = (prop, value) => {
|
|
443
|
+
if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
|
|
444
|
+
if (prop.array) {
|
|
445
|
+
for (let i = 0; i < value.length; i++) {
|
|
446
|
+
const item = value[i];
|
|
447
|
+
value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
params.push(value);
|
|
455
|
+
};
|
|
453
456
|
keys.forEach(key => {
|
|
454
457
|
const prop = meta.properties[key];
|
|
455
458
|
prop.fieldNames.forEach((fieldName, fieldNameIdx) => {
|
|
@@ -464,7 +467,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
464
467
|
else {
|
|
465
468
|
sql += '?';
|
|
466
469
|
}
|
|
467
|
-
params.push(...pks
|
|
470
|
+
params.push(...pks);
|
|
471
|
+
addParams(prop, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
|
|
468
472
|
}
|
|
469
473
|
});
|
|
470
474
|
sql += ` else ${this.platform.quoteIdentifier(fieldName)} end, `;
|
|
@@ -646,12 +650,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
646
650
|
if (owners.length === 1 && (options.offset != null || options.limit != null)) {
|
|
647
651
|
qb.limit(options.limit, options.offset);
|
|
648
652
|
}
|
|
649
|
-
const res = owners.length ? await this.rethrow(qb.execute('all', false)) : [];
|
|
653
|
+
const res = owners.length ? await this.rethrow(qb.execute('all', { mergeResults: false, mapResults: false })) : [];
|
|
650
654
|
const items = res.map((row) => super.mapResult(row, prop.targetMeta));
|
|
651
655
|
const map = {};
|
|
652
656
|
const pkProps = ownerMeta.getPrimaryProps();
|
|
653
657
|
owners.forEach(owner => {
|
|
654
|
-
const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((
|
|
658
|
+
const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((_col, idx) => {
|
|
655
659
|
const pkProp = pkProps[idx];
|
|
656
660
|
return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
|
|
657
661
|
}));
|
|
@@ -700,6 +704,9 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
700
704
|
.map(prop => ({ field: prop.name, strategy: prop.strategy }));
|
|
701
705
|
return [...populate, ...toPopulate];
|
|
702
706
|
}
|
|
707
|
+
/**
|
|
708
|
+
* @internal
|
|
709
|
+
*/
|
|
703
710
|
joinedProps(meta, populate) {
|
|
704
711
|
return populate.filter(p => {
|
|
705
712
|
const prop = meta.properties[p.field] || {};
|
|
@@ -710,26 +717,33 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
710
717
|
* @internal
|
|
711
718
|
*/
|
|
712
719
|
mergeJoinedResult(rawResults, meta, joinedProps) {
|
|
713
|
-
// group by the root entity primary key first
|
|
714
|
-
const map = {};
|
|
715
720
|
const res = [];
|
|
716
|
-
|
|
721
|
+
const map = {};
|
|
722
|
+
for (const item of rawResults) {
|
|
717
723
|
const pk = core_1.Utils.getCompositeKeyHash(item, meta);
|
|
718
724
|
if (map[pk]) {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
if (Array.isArray(map[pk][hint.field]) && Array.isArray(item[hint.field]) && map[pk][hint.field].length === 0) {
|
|
724
|
-
item[hint.field].forEach((el) => map[pk][hint.field].push(el));
|
|
725
|
+
for (const hint of joinedProps) {
|
|
726
|
+
const prop = meta.properties[hint.field];
|
|
727
|
+
if (!item[hint.field]) {
|
|
728
|
+
continue;
|
|
725
729
|
}
|
|
726
|
-
|
|
730
|
+
switch (prop.kind) {
|
|
731
|
+
case core_1.ReferenceKind.ONE_TO_MANY:
|
|
732
|
+
case core_1.ReferenceKind.MANY_TO_MANY:
|
|
733
|
+
map[pk][hint.field] = this.mergeJoinedResult([...map[pk][hint.field], ...item[hint.field]], prop.targetMeta, hint.children ?? []);
|
|
734
|
+
break;
|
|
735
|
+
case core_1.ReferenceKind.MANY_TO_ONE:
|
|
736
|
+
case core_1.ReferenceKind.ONE_TO_ONE:
|
|
737
|
+
map[pk][hint.field] = this.mergeJoinedResult([map[pk][hint.field], item[hint.field]], prop.targetMeta, hint.children ?? [])[0];
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
727
741
|
}
|
|
728
742
|
else {
|
|
729
743
|
map[pk] = item;
|
|
730
744
|
res.push(item);
|
|
731
745
|
}
|
|
732
|
-
}
|
|
746
|
+
}
|
|
733
747
|
return res;
|
|
734
748
|
}
|
|
735
749
|
getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
|
|
@@ -901,7 +915,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
901
915
|
buildFields(meta, populate, joinedProps, qb, alias, fields) {
|
|
902
916
|
const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
|
|
903
917
|
const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
|
|
904
|
-
const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL);
|
|
918
|
+
const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
|
|
905
919
|
const hasExplicitFields = !!fields;
|
|
906
920
|
const ret = [];
|
|
907
921
|
let addFormulas = false;
|
package/index.mjs
CHANGED
|
@@ -47,6 +47,8 @@ export const DatabaseObjectExistsException = mod.DatabaseObjectExistsException;
|
|
|
47
47
|
export const DatabaseObjectNotFoundException = mod.DatabaseObjectNotFoundException;
|
|
48
48
|
export const DatabaseSchema = mod.DatabaseSchema;
|
|
49
49
|
export const DatabaseTable = mod.DatabaseTable;
|
|
50
|
+
export const Dataloader = mod.Dataloader;
|
|
51
|
+
export const DataloaderUtils = mod.DataloaderUtils;
|
|
50
52
|
export const DateTimeType = mod.DateTimeType;
|
|
51
53
|
export const DateType = mod.DateType;
|
|
52
54
|
export const DeadlockException = mod.DeadlockException;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/knex",
|
|
3
|
-
"version": "6.0.0-dev.
|
|
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
|
}
|