@mikro-orm/knex 6.0.0-dev.20 → 6.0.0-dev.200
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/AbstractSqlConnection.d.ts +8 -4
- package/AbstractSqlConnection.js +30 -18
- package/AbstractSqlDriver.d.ts +32 -25
- package/AbstractSqlDriver.js +544 -262
- package/AbstractSqlPlatform.d.ts +5 -4
- package/AbstractSqlPlatform.js +34 -5
- package/MonkeyPatchable.d.ts +1 -0
- package/MonkeyPatchable.js +3 -0
- package/PivotCollectionPersister.d.ts +17 -0
- package/PivotCollectionPersister.js +128 -0
- package/README.md +64 -107
- package/SqlEntityManager.d.ts +2 -6
- package/SqlEntityManager.js +4 -8
- package/SqlEntityRepository.d.ts +7 -6
- package/SqlEntityRepository.js +11 -8
- package/index.mjs +24 -5
- package/package.json +8 -8
- package/query/ArrayCriteriaNode.d.ts +5 -4
- package/query/ArrayCriteriaNode.js +7 -2
- package/query/CriteriaNode.d.ts +11 -9
- package/query/CriteriaNode.js +24 -11
- package/query/CriteriaNodeFactory.d.ts +6 -6
- package/query/CriteriaNodeFactory.js +32 -15
- package/query/ObjectCriteriaNode.d.ts +5 -4
- package/query/ObjectCriteriaNode.js +57 -23
- package/query/QueryBuilder.d.ts +58 -26
- package/query/QueryBuilder.js +444 -148
- package/query/QueryBuilderHelper.d.ts +13 -18
- package/query/QueryBuilderHelper.js +194 -167
- package/query/ScalarCriteriaNode.d.ts +4 -3
- package/query/ScalarCriteriaNode.js +12 -8
- package/query/enums.d.ts +7 -0
- package/query/enums.js +10 -2
- package/schema/DatabaseSchema.d.ts +5 -2
- package/schema/DatabaseSchema.js +28 -10
- package/schema/DatabaseTable.d.ts +13 -5
- package/schema/DatabaseTable.js +385 -60
- package/schema/SchemaComparator.d.ts +5 -5
- package/schema/SchemaComparator.js +59 -35
- package/schema/SchemaHelper.d.ts +7 -5
- package/schema/SchemaHelper.js +25 -11
- package/schema/SqlSchemaGenerator.d.ts +3 -6
- package/schema/SqlSchemaGenerator.js +71 -36
- package/typings.d.ts +30 -11
package/AbstractSqlDriver.js
CHANGED
|
@@ -4,10 +4,14 @@ 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 {
|
|
9
|
+
[core_1.EntityManagerType];
|
|
10
|
+
connection;
|
|
11
|
+
replicas = [];
|
|
12
|
+
platform;
|
|
8
13
|
constructor(config, platform, connection, connector) {
|
|
9
14
|
super(config, connector);
|
|
10
|
-
this.replicas = [];
|
|
11
15
|
this.connection = new connection(this.config);
|
|
12
16
|
this.replicas = this.createReplicas(conf => new connection(this.config, conf, 'read'));
|
|
13
17
|
this.platform = platform;
|
|
@@ -25,30 +29,43 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
25
29
|
return this.findVirtual(entityName, where, options);
|
|
26
30
|
}
|
|
27
31
|
const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
|
|
28
|
-
const joinedProps = this.joinedProps(meta, populate);
|
|
29
|
-
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false);
|
|
30
|
-
const fields = this.buildFields(meta, populate, joinedProps, qb, options.fields);
|
|
31
|
-
const
|
|
32
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
33
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging);
|
|
34
|
+
const fields = this.buildFields(meta, populate, joinedProps, qb, qb.alias, options.fields, options);
|
|
35
|
+
const orderBy = this.buildOrderBy(qb, meta, populate, options);
|
|
36
|
+
core_1.Utils.asArray(options.flags).forEach(flag => qb.setFlag(flag));
|
|
32
37
|
if (core_1.Utils.isPrimaryKey(where, meta.compositePK)) {
|
|
33
38
|
where = { [core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: where };
|
|
34
39
|
}
|
|
40
|
+
const { first, last, before, after } = options;
|
|
41
|
+
const isCursorPagination = [first, last, before, after].some(v => v != null);
|
|
42
|
+
qb.__populateWhere = options._populateWhere;
|
|
35
43
|
qb.select(fields)
|
|
44
|
+
// only add populateWhere if we are populate-joining, as this will be used to add `on` conditions
|
|
36
45
|
.populate(populate, joinedProps.length > 0 ? options.populateWhere : undefined)
|
|
37
46
|
.where(where)
|
|
38
|
-
.orderBy([...core_1.Utils.asArray(options.orderBy), ...joinedPropsOrderBy])
|
|
39
47
|
.groupBy(options.groupBy)
|
|
40
48
|
.having(options.having)
|
|
49
|
+
.indexHint(options.indexHint)
|
|
50
|
+
.comment(options.comments)
|
|
51
|
+
.hintComment(options.hintComments)
|
|
41
52
|
.withSchema(this.getSchemaName(meta, options));
|
|
42
|
-
if (
|
|
53
|
+
if (isCursorPagination) {
|
|
54
|
+
const { orderBy: newOrderBy, where } = this.processCursorOptions(meta, options, orderBy);
|
|
55
|
+
qb.andWhere(where).orderBy(newOrderBy);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
qb.orderBy(orderBy);
|
|
59
|
+
}
|
|
60
|
+
if (options.limit != null || options.offset != null) {
|
|
43
61
|
qb.limit(options.limit, options.offset);
|
|
44
62
|
}
|
|
45
63
|
if (options.lockMode) {
|
|
46
64
|
qb.setLockMode(options.lockMode, options.lockTableAliases);
|
|
47
65
|
}
|
|
48
|
-
core_1.Utils.asArray(options.flags).forEach(flag => qb.setFlag(flag));
|
|
49
66
|
const result = await this.rethrow(qb.execute('all'));
|
|
50
|
-
if (
|
|
51
|
-
|
|
67
|
+
if (isCursorPagination && !first && !!last) {
|
|
68
|
+
result.reverse();
|
|
52
69
|
}
|
|
53
70
|
return result;
|
|
54
71
|
}
|
|
@@ -56,63 +73,69 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
56
73
|
const opts = { populate: [], ...(options || {}) };
|
|
57
74
|
const meta = this.metadata.find(entityName);
|
|
58
75
|
const populate = this.autoJoinOneToOneOwner(meta, opts.populate, opts.fields);
|
|
59
|
-
const joinedProps = this.joinedProps(meta, populate);
|
|
60
|
-
|
|
76
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
77
|
+
const hasToManyJoins = joinedProps.some(hint => {
|
|
78
|
+
const [propName] = hint.field.split(':', 2);
|
|
79
|
+
const prop = meta.properties[propName];
|
|
80
|
+
return prop && [core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(prop.kind);
|
|
81
|
+
});
|
|
82
|
+
if (joinedProps.length === 0 || !hasToManyJoins) {
|
|
61
83
|
opts.limit = 1;
|
|
62
84
|
}
|
|
63
85
|
if (opts.limit > 0 && !opts.flags?.includes(core_1.QueryFlag.DISABLE_PAGINATE)) {
|
|
64
|
-
opts.flags
|
|
86
|
+
opts.flags ??= [];
|
|
65
87
|
opts.flags.push(core_1.QueryFlag.DISABLE_PAGINATE);
|
|
66
88
|
}
|
|
67
89
|
const res = await this.find(entityName, where, opts);
|
|
68
90
|
return res[0] || null;
|
|
69
91
|
}
|
|
70
92
|
async findVirtual(entityName, where, options) {
|
|
71
|
-
|
|
72
|
-
/* istanbul ignore next */
|
|
73
|
-
if (!meta.expression) {
|
|
74
|
-
return [];
|
|
75
|
-
}
|
|
76
|
-
if (typeof meta.expression === 'string') {
|
|
77
|
-
return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options);
|
|
78
|
-
}
|
|
79
|
-
const em = this.createEntityManager(false);
|
|
80
|
-
em.setTransactionContext(options.ctx);
|
|
81
|
-
const res = meta.expression(em, where, options);
|
|
82
|
-
if (res instanceof (query_1.QueryBuilder)) {
|
|
83
|
-
return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options);
|
|
84
|
-
}
|
|
85
|
-
/* istanbul ignore next */
|
|
86
|
-
return res;
|
|
93
|
+
return this.findFromVirtual(entityName, where, options, query_1.QueryType.SELECT);
|
|
87
94
|
}
|
|
88
95
|
async countVirtual(entityName, where, options) {
|
|
96
|
+
return this.findFromVirtual(entityName, where, options, query_1.QueryType.COUNT);
|
|
97
|
+
}
|
|
98
|
+
async findFromVirtual(entityName, where, options, type) {
|
|
89
99
|
const meta = this.metadata.get(entityName);
|
|
90
100
|
/* istanbul ignore next */
|
|
91
101
|
if (!meta.expression) {
|
|
92
|
-
return 0;
|
|
102
|
+
return type === query_1.QueryType.SELECT ? [] : 0;
|
|
93
103
|
}
|
|
94
104
|
if (typeof meta.expression === 'string') {
|
|
95
|
-
return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options,
|
|
105
|
+
return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options, type);
|
|
96
106
|
}
|
|
97
107
|
const em = this.createEntityManager(false);
|
|
98
108
|
em.setTransactionContext(options.ctx);
|
|
99
109
|
const res = meta.expression(em, where, options);
|
|
100
|
-
if (res
|
|
101
|
-
return this.wrapVirtualExpressionInSubquery(meta, res
|
|
110
|
+
if (typeof res === 'string') {
|
|
111
|
+
return this.wrapVirtualExpressionInSubquery(meta, res, where, options, type);
|
|
112
|
+
}
|
|
113
|
+
if (res instanceof query_1.QueryBuilder) {
|
|
114
|
+
return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options, type);
|
|
115
|
+
}
|
|
116
|
+
if (core_1.Utils.isObject(res)) {
|
|
117
|
+
const { sql, bindings } = res.toSQL();
|
|
118
|
+
const query = this.platform.formatQuery(sql, bindings);
|
|
119
|
+
return this.wrapVirtualExpressionInSubquery(meta, query, where, options, type);
|
|
102
120
|
}
|
|
103
121
|
/* istanbul ignore next */
|
|
104
122
|
return res;
|
|
105
123
|
}
|
|
106
|
-
async wrapVirtualExpressionInSubquery(meta, expression, where, options, type
|
|
124
|
+
async wrapVirtualExpressionInSubquery(meta, expression, where, options, type) {
|
|
107
125
|
const qb = this.createQueryBuilder(meta.className, options?.ctx, options.connectionType, options.convertCustomTypes)
|
|
108
|
-
.
|
|
109
|
-
|
|
110
|
-
|
|
126
|
+
.indexHint(options.indexHint)
|
|
127
|
+
.comment(options.comments)
|
|
128
|
+
.hintComment(options.hintComments);
|
|
129
|
+
if (type !== query_1.QueryType.COUNT) {
|
|
130
|
+
qb.limit(options?.limit, options?.offset);
|
|
131
|
+
if (options.orderBy) {
|
|
132
|
+
qb.orderBy(options.orderBy);
|
|
133
|
+
}
|
|
111
134
|
}
|
|
112
135
|
qb.where(where);
|
|
113
136
|
const kqb = qb.getKnexQuery().clear('select');
|
|
114
137
|
if (type === query_1.QueryType.COUNT) {
|
|
115
|
-
kqb.select(
|
|
138
|
+
kqb.select(this.connection.getKnex().raw('count(*) as count'));
|
|
116
139
|
}
|
|
117
140
|
else { // select
|
|
118
141
|
kqb.select('*');
|
|
@@ -131,6 +154,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
131
154
|
return null;
|
|
132
155
|
}
|
|
133
156
|
if (qb) {
|
|
157
|
+
// here we map the aliased results (cartesian product) to an object graph
|
|
134
158
|
this.mapJoinedProps(ret, meta, populate, qb, ret, map);
|
|
135
159
|
}
|
|
136
160
|
return ret;
|
|
@@ -138,27 +162,66 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
138
162
|
mapJoinedProps(result, meta, populate, qb, root, map, parentJoinPath) {
|
|
139
163
|
const joinedProps = this.joinedProps(meta, populate);
|
|
140
164
|
joinedProps.forEach(p => {
|
|
141
|
-
const
|
|
165
|
+
const [propName, ref] = p.field.split(':', 2);
|
|
166
|
+
const relation = meta.properties[propName];
|
|
142
167
|
/* istanbul ignore next */
|
|
143
168
|
if (!relation) {
|
|
144
169
|
return;
|
|
145
170
|
}
|
|
171
|
+
const pivotRefJoin = relation.kind === core_1.ReferenceKind.MANY_TO_MANY && ref;
|
|
146
172
|
const meta2 = this.metadata.find(relation.type);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
173
|
+
let path = parentJoinPath ? `${parentJoinPath}.${relation.name}` : `${meta.name}.${relation.name}`;
|
|
174
|
+
if (!parentJoinPath) {
|
|
175
|
+
path = '[populate]' + path;
|
|
176
|
+
}
|
|
177
|
+
if (pivotRefJoin) {
|
|
178
|
+
path += '[pivot]';
|
|
179
|
+
}
|
|
180
|
+
const relationAlias = qb.getAliasForJoinPath(path, { matchPopulateJoins: true });
|
|
181
|
+
// pivot ref joins via joined strategy need to be handled separately here, as they dont join the target entity
|
|
182
|
+
if (pivotRefJoin) {
|
|
183
|
+
let item;
|
|
184
|
+
if (relation.inverseJoinColumns.length > 1) { // composite keys
|
|
185
|
+
item = relation.inverseJoinColumns.map(name => root[`${relationAlias}__${name}`]);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
const alias = `${relationAlias}__${relation.inverseJoinColumns[0]}`;
|
|
189
|
+
item = root[alias];
|
|
190
|
+
}
|
|
191
|
+
relation.joinColumns.forEach(name => delete root[`${relationAlias}__${name}`]);
|
|
192
|
+
relation.inverseJoinColumns.forEach(name => delete root[`${relationAlias}__${name}`]);
|
|
193
|
+
result[relation.name] ??= [];
|
|
194
|
+
if (item) {
|
|
195
|
+
result[relation.name].push(item);
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
150
199
|
// If the primary key value for the relation is null, we know we haven't joined to anything
|
|
151
200
|
// and therefore we don't return any record (since all values would be null)
|
|
152
201
|
const hasPK = meta2.primaryKeys.every(pk => meta2.properties[pk].fieldNames.every(name => {
|
|
153
202
|
return root[`${relationAlias}__${name}`] != null;
|
|
154
203
|
}));
|
|
155
204
|
if (!hasPK) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
205
|
+
if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
|
|
206
|
+
result[relation.name] ??= [];
|
|
207
|
+
}
|
|
208
|
+
if ([core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(relation.kind)) {
|
|
209
|
+
result[relation.name] ??= null;
|
|
159
210
|
}
|
|
160
211
|
return;
|
|
161
212
|
}
|
|
213
|
+
const relationPojo = {};
|
|
214
|
+
meta2.props
|
|
215
|
+
.filter(prop => prop.persist === false && prop.fieldNames)
|
|
216
|
+
.forEach(prop => {
|
|
217
|
+
if (prop.fieldNames.length > 1) { // composite keys
|
|
218
|
+
relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
const alias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
222
|
+
relationPojo[prop.name] = root[alias];
|
|
223
|
+
}
|
|
224
|
+
});
|
|
162
225
|
meta2.props
|
|
163
226
|
.filter(prop => this.platform.shouldHaveColumn(prop, p.children || []))
|
|
164
227
|
.forEach(prop => {
|
|
@@ -166,22 +229,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
166
229
|
relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
|
|
167
230
|
prop.fieldNames.map(name => delete root[`${relationAlias}__${name}`]);
|
|
168
231
|
}
|
|
232
|
+
else if (prop.runtimeType === 'Date') {
|
|
233
|
+
const alias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
234
|
+
relationPojo[prop.name] = (typeof root[alias] === 'string' ? new Date(root[alias]) : root[alias]);
|
|
235
|
+
delete root[alias];
|
|
236
|
+
}
|
|
169
237
|
else {
|
|
170
238
|
const alias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
171
239
|
relationPojo[prop.name] = root[alias];
|
|
172
240
|
delete root[alias];
|
|
173
241
|
}
|
|
174
242
|
});
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
result[relation.name]
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
map[key] = result;
|
|
181
|
-
}
|
|
182
|
-
if ([core_1.ReferenceType.MANY_TO_MANY, core_1.ReferenceType.ONE_TO_MANY].includes(relation.reference)) {
|
|
183
|
-
result[relation.name] = result[relation.name] || [];
|
|
184
|
-
this.appendToCollection(meta2, result[relation.name], relationPojo);
|
|
243
|
+
if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(relation.kind)) {
|
|
244
|
+
result[relation.name] ??= [];
|
|
245
|
+
result[relation.name].push(relationPojo);
|
|
185
246
|
}
|
|
186
247
|
else {
|
|
187
248
|
result[relation.name] = relationPojo;
|
|
@@ -190,23 +251,15 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
190
251
|
this.mapJoinedProps(relationPojo, meta2, populateChildren, qb, root, map, path);
|
|
191
252
|
});
|
|
192
253
|
}
|
|
193
|
-
appendToCollection(meta, collection, relationPojo) {
|
|
194
|
-
if (collection.length === 0) {
|
|
195
|
-
return void collection.push(relationPojo);
|
|
196
|
-
}
|
|
197
|
-
const last = collection[collection.length - 1];
|
|
198
|
-
const pk1 = core_1.Utils.getCompositeKeyHash(last, meta);
|
|
199
|
-
const pk2 = core_1.Utils.getCompositeKeyHash(relationPojo, meta);
|
|
200
|
-
if (pk1 !== pk2) {
|
|
201
|
-
collection.push(relationPojo);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
254
|
async count(entityName, where, options = {}) {
|
|
205
255
|
const meta = this.metadata.find(entityName);
|
|
206
256
|
if (meta?.virtual) {
|
|
207
257
|
return this.countVirtual(entityName, where, options);
|
|
208
258
|
}
|
|
209
259
|
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false)
|
|
260
|
+
.indexHint(options.indexHint)
|
|
261
|
+
.comment(options.comments)
|
|
262
|
+
.hintComment(options.hintComments)
|
|
210
263
|
.groupBy(options.groupBy)
|
|
211
264
|
.having(options.having)
|
|
212
265
|
.populate(options.populate ?? [])
|
|
@@ -215,7 +268,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
215
268
|
return this.rethrow(qb.getCount());
|
|
216
269
|
}
|
|
217
270
|
async nativeInsert(entityName, data, options = {}) {
|
|
218
|
-
options.convertCustomTypes
|
|
271
|
+
options.convertCustomTypes ??= true;
|
|
219
272
|
const meta = this.metadata.find(entityName);
|
|
220
273
|
const collections = this.extractManyToMany(entityName, data);
|
|
221
274
|
const pks = meta?.primaryKeys ?? [this.config.getNamingStrategy().referenceColumnName()];
|
|
@@ -235,16 +288,20 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
235
288
|
return res;
|
|
236
289
|
}
|
|
237
290
|
async nativeInsertMany(entityName, data, options = {}) {
|
|
238
|
-
options.processCollections
|
|
239
|
-
options.convertCustomTypes
|
|
240
|
-
const meta = this.metadata.find(entityName);
|
|
291
|
+
options.processCollections ??= true;
|
|
292
|
+
options.convertCustomTypes ??= true;
|
|
293
|
+
const meta = this.metadata.find(entityName)?.root;
|
|
241
294
|
const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
|
|
242
295
|
const pks = this.getPrimaryKeyFields(entityName);
|
|
243
296
|
const set = new Set();
|
|
244
|
-
data.forEach(row =>
|
|
297
|
+
data.forEach(row => core_1.Utils.keys(row).forEach(k => set.add(k)));
|
|
245
298
|
const props = [...set].map(name => meta?.properties[name] ?? { name, fieldNames: [name] });
|
|
246
|
-
|
|
299
|
+
let fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
|
|
300
|
+
const duplicates = core_1.Utils.findDuplicates(fields);
|
|
247
301
|
const params = [];
|
|
302
|
+
if (duplicates.length) {
|
|
303
|
+
fields = core_1.Utils.unique(fields);
|
|
304
|
+
}
|
|
248
305
|
/* istanbul ignore next */
|
|
249
306
|
const tableName = meta ? this.getTableName(meta, options) : this.platform.quoteIdentifier(entityName);
|
|
250
307
|
let sql = `insert into ${tableName} `;
|
|
@@ -255,29 +312,62 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
255
312
|
else {
|
|
256
313
|
sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
|
|
257
314
|
}
|
|
315
|
+
const addParams = (prop, row) => {
|
|
316
|
+
let value = row[prop.name];
|
|
317
|
+
if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
|
|
318
|
+
if (prop.array) {
|
|
319
|
+
for (let i = 0; i < value.length; i++) {
|
|
320
|
+
const item = value[i];
|
|
321
|
+
value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (options.convertCustomTypes && prop.customType) {
|
|
329
|
+
params.push(prop.customType.convertToDatabaseValue(value, this.platform, { key: prop.name, mode: 'query-data' }));
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
params.push(value);
|
|
333
|
+
};
|
|
258
334
|
if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
|
|
259
335
|
sql += data.map(row => {
|
|
260
336
|
const keys = [];
|
|
337
|
+
const usedDups = [];
|
|
261
338
|
props.forEach(prop => {
|
|
262
339
|
if (prop.fieldNames.length > 1) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
340
|
+
const param = core_1.Utils.flatten([...row[prop.name] ?? prop.fieldNames.map(() => null)]);
|
|
341
|
+
const key = param.map(() => '?');
|
|
342
|
+
prop.fieldNames.forEach((field, idx) => {
|
|
343
|
+
if (!duplicates.includes(field) || !usedDups.includes(field)) {
|
|
344
|
+
params.push(param[idx]);
|
|
345
|
+
keys.push(key[idx]);
|
|
346
|
+
usedDups.push(field);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
269
349
|
}
|
|
270
350
|
else {
|
|
271
|
-
|
|
272
|
-
|
|
351
|
+
const field = prop.fieldNames[0];
|
|
352
|
+
if (!duplicates.includes(field) || !usedDups.includes(field)) {
|
|
353
|
+
if (prop.customType && 'convertToDatabaseValueSQL' in prop.customType && !this.platform.isRaw(row[prop.name])) {
|
|
354
|
+
keys.push(prop.customType.convertToDatabaseValueSQL('?', this.platform));
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
keys.push('?');
|
|
358
|
+
}
|
|
359
|
+
addParams(prop, row);
|
|
360
|
+
usedDups.push(field);
|
|
361
|
+
}
|
|
273
362
|
}
|
|
274
363
|
});
|
|
275
364
|
return '(' + (keys.join(', ') || 'default') + ')';
|
|
276
365
|
}).join(', ');
|
|
277
366
|
}
|
|
278
|
-
if (this.platform.usesReturningStatement()) {
|
|
279
|
-
|
|
280
|
-
|
|
367
|
+
if (meta && this.platform.usesReturningStatement()) {
|
|
368
|
+
const returningProps = meta.props
|
|
369
|
+
.filter(prop => prop.persist !== false && prop.defaultRaw || prop.autoincrement || prop.generated)
|
|
370
|
+
.filter(prop => !(prop.name in data[0]) || core_1.Utils.isRawSql(data[0][prop.name]));
|
|
281
371
|
const returningFields = core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames));
|
|
282
372
|
/* istanbul ignore next */
|
|
283
373
|
sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
|
|
@@ -289,8 +379,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
289
379
|
pk = data.map(d => core_1.Utils.getPrimaryKeyCond(d, pks));
|
|
290
380
|
}
|
|
291
381
|
else {
|
|
292
|
-
res.row
|
|
293
|
-
res.rows
|
|
382
|
+
res.row ??= {};
|
|
383
|
+
res.rows ??= [];
|
|
294
384
|
pk = data.map((d, i) => d[pks[0]] ?? res.rows[i]?.[pks[0]]).map(d => [d]);
|
|
295
385
|
res.insertId = res.insertId || res.row[pks[0]];
|
|
296
386
|
}
|
|
@@ -300,7 +390,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
300
390
|
return res;
|
|
301
391
|
}
|
|
302
392
|
async nativeUpdate(entityName, where, data, options = {}) {
|
|
303
|
-
options.convertCustomTypes
|
|
393
|
+
options.convertCustomTypes ??= true;
|
|
304
394
|
const meta = this.metadata.find(entityName);
|
|
305
395
|
const pks = this.getPrimaryKeyFields(entityName);
|
|
306
396
|
const collections = this.extractManyToMany(entityName, data);
|
|
@@ -314,14 +404,27 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
314
404
|
.withSchema(this.getSchemaName(meta, options));
|
|
315
405
|
if (options.upsert) {
|
|
316
406
|
/* istanbul ignore next */
|
|
317
|
-
const uniqueFields = core_1.Utils.isPlainObject(where) ?
|
|
318
|
-
|
|
407
|
+
const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where) ? core_1.Utils.keys(where) : meta.primaryKeys);
|
|
408
|
+
const returning = (0, core_1.getOnConflictReturningFields)(meta, data, uniqueFields, options);
|
|
319
409
|
qb.insert(data)
|
|
320
|
-
.onConflict(uniqueFields
|
|
321
|
-
.
|
|
410
|
+
.onConflict(uniqueFields)
|
|
411
|
+
.returning(returning);
|
|
412
|
+
if (!options.onConflictAction || options.onConflictAction === 'merge') {
|
|
413
|
+
const fields = (0, core_1.getOnConflictFields)(data, uniqueFields, options);
|
|
414
|
+
qb.merge(fields);
|
|
415
|
+
}
|
|
416
|
+
if (options.onConflictAction === 'ignore') {
|
|
417
|
+
qb.ignore();
|
|
418
|
+
}
|
|
322
419
|
}
|
|
323
420
|
else {
|
|
324
421
|
qb.update(data).where(where);
|
|
422
|
+
// reload generated columns and version fields
|
|
423
|
+
const returning = [];
|
|
424
|
+
meta?.props
|
|
425
|
+
.filter(prop => (prop.generated && !prop.primary) || prop.version)
|
|
426
|
+
.forEach(prop => returning.push(prop.name));
|
|
427
|
+
qb.returning(returning);
|
|
325
428
|
}
|
|
326
429
|
res = await this.rethrow(qb.execute('run', false));
|
|
327
430
|
}
|
|
@@ -331,23 +434,57 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
331
434
|
return res;
|
|
332
435
|
}
|
|
333
436
|
async nativeUpdateMany(entityName, where, data, options = {}) {
|
|
334
|
-
options.processCollections
|
|
335
|
-
options.convertCustomTypes
|
|
437
|
+
options.processCollections ??= true;
|
|
438
|
+
options.convertCustomTypes ??= true;
|
|
336
439
|
const meta = this.metadata.get(entityName);
|
|
337
440
|
if (options.upsert) {
|
|
338
|
-
const uniqueFields = core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]) : meta.primaryKeys;
|
|
441
|
+
const uniqueFields = options.onConflictFields ?? (core_1.Utils.isPlainObject(where[0]) ? Object.keys(where[0]).flatMap(key => core_1.Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
|
|
339
442
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
|
|
443
|
+
const returning = (0, core_1.getOnConflictReturningFields)(meta, data[0], uniqueFields, options);
|
|
340
444
|
qb.insert(data)
|
|
341
|
-
.onConflict(uniqueFields
|
|
342
|
-
.
|
|
343
|
-
|
|
445
|
+
.onConflict(uniqueFields)
|
|
446
|
+
.returning(returning);
|
|
447
|
+
if (!options.onConflictAction || options.onConflictAction === 'merge') {
|
|
448
|
+
const fields = (0, core_1.getOnConflictFields)(data[0], uniqueFields, options);
|
|
449
|
+
qb.merge(fields);
|
|
450
|
+
}
|
|
451
|
+
if (options.onConflictAction === 'ignore') {
|
|
452
|
+
qb.ignore();
|
|
453
|
+
}
|
|
454
|
+
return this.rethrow(qb.execute('run', false));
|
|
344
455
|
}
|
|
345
456
|
const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
|
|
346
457
|
const keys = new Set();
|
|
347
|
-
|
|
458
|
+
const returning = new Set();
|
|
459
|
+
data.forEach(row => {
|
|
460
|
+
core_1.Utils.keys(row).forEach(k => {
|
|
461
|
+
keys.add(k);
|
|
462
|
+
if (core_1.Utils.isRawSql(row[k])) {
|
|
463
|
+
returning.add(k);
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
// reload generated columns and version fields
|
|
468
|
+
meta?.props
|
|
469
|
+
.filter(prop => (prop.generated && !prop.primary) || prop.version)
|
|
470
|
+
.forEach(prop => returning.add(prop.name));
|
|
348
471
|
const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
|
|
349
472
|
const params = [];
|
|
350
473
|
let sql = `update ${this.getTableName(meta, options)} set `;
|
|
474
|
+
const addParams = (prop, value) => {
|
|
475
|
+
if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
|
|
476
|
+
if (prop.array) {
|
|
477
|
+
for (let i = 0; i < value.length; i++) {
|
|
478
|
+
const item = value[i];
|
|
479
|
+
value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
params.push(value);
|
|
487
|
+
};
|
|
351
488
|
keys.forEach(key => {
|
|
352
489
|
const prop = meta.properties[key];
|
|
353
490
|
prop.fieldNames.forEach((fieldName, fieldNameIdx) => {
|
|
@@ -362,7 +499,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
362
499
|
else {
|
|
363
500
|
sql += '?';
|
|
364
501
|
}
|
|
365
|
-
params.push(...pks
|
|
502
|
+
params.push(...pks);
|
|
503
|
+
addParams(prop, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
|
|
366
504
|
}
|
|
367
505
|
});
|
|
368
506
|
sql += ` else ${this.platform.quoteIdentifier(fieldName)} end, `;
|
|
@@ -373,7 +511,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
373
511
|
const versionProperty = meta.properties[meta.versionProperty];
|
|
374
512
|
const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
|
|
375
513
|
sql += `${quotedFieldName} = `;
|
|
376
|
-
if (versionProperty.
|
|
514
|
+
if (versionProperty.runtimeType === 'Date') {
|
|
377
515
|
sql += this.platform.getCurrentTimestampSQL(versionProperty.length);
|
|
378
516
|
}
|
|
379
517
|
else {
|
|
@@ -404,6 +542,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
404
542
|
return '?';
|
|
405
543
|
});
|
|
406
544
|
sql += ` in (${conds.join(', ')})`;
|
|
545
|
+
if (this.platform.usesReturningStatement() && returning.size > 0) {
|
|
546
|
+
const returningFields = core_1.Utils.flatten([...returning].map(prop => meta.properties[prop].fieldNames));
|
|
547
|
+
/* istanbul ignore next */
|
|
548
|
+
sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
|
|
549
|
+
}
|
|
407
550
|
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
|
|
408
551
|
for (let i = 0; i < collections.length; i++) {
|
|
409
552
|
await this.processManyToMany(meta, where[i], collections[i], false, options);
|
|
@@ -419,49 +562,81 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
419
562
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false).delete(where).withSchema(this.getSchemaName(meta, options));
|
|
420
563
|
return this.rethrow(qb.execute('run', false));
|
|
421
564
|
}
|
|
422
|
-
async
|
|
423
|
-
const
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
.
|
|
445
|
-
|
|
446
|
-
.
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
565
|
+
async syncCollections(collections, options) {
|
|
566
|
+
const groups = {};
|
|
567
|
+
for (const coll of collections) {
|
|
568
|
+
const wrapped = (0, core_1.helper)(coll.owner);
|
|
569
|
+
const meta = wrapped.__meta;
|
|
570
|
+
const pks = wrapped.getPrimaryKeys(true);
|
|
571
|
+
const snap = coll.getSnapshot();
|
|
572
|
+
const includes = (arr, item) => !!arr.find(i => core_1.Utils.equals(i, item));
|
|
573
|
+
const snapshot = snap ? snap.map(item => (0, core_1.helper)(item).getPrimaryKeys(true)) : [];
|
|
574
|
+
const current = coll.getItems(false).map(item => (0, core_1.helper)(item).getPrimaryKeys(true));
|
|
575
|
+
const deleteDiff = snap ? snapshot.filter(item => !includes(current, item)) : true;
|
|
576
|
+
const insertDiff = current.filter(item => !includes(snapshot, item));
|
|
577
|
+
const target = snapshot.filter(item => includes(current, item)).concat(...insertDiff);
|
|
578
|
+
const equals = core_1.Utils.equals(current, target);
|
|
579
|
+
// wrong order if we just delete and insert to the end (only owning sides can have fixed order)
|
|
580
|
+
if (coll.property.owner && coll.property.fixedOrder && !equals && Array.isArray(deleteDiff)) {
|
|
581
|
+
deleteDiff.length = insertDiff.length = 0;
|
|
582
|
+
deleteDiff.push(...snapshot);
|
|
583
|
+
insertDiff.push(...current);
|
|
584
|
+
}
|
|
585
|
+
if (coll.property.kind === core_1.ReferenceKind.ONE_TO_MANY) {
|
|
586
|
+
const cols = coll.property.referencedColumnNames;
|
|
587
|
+
const qb = this.createQueryBuilder(coll.property.type, options?.ctx, 'write')
|
|
588
|
+
.withSchema(this.getSchemaName(meta, options));
|
|
589
|
+
if (coll.getSnapshot() === undefined) {
|
|
590
|
+
if (coll.property.orphanRemoval) {
|
|
591
|
+
const kqb = qb.delete({ [coll.property.mappedBy]: pks })
|
|
592
|
+
.getKnexQuery()
|
|
593
|
+
.whereNotIn(cols, insertDiff);
|
|
594
|
+
return this.rethrow(this.execute(kqb));
|
|
595
|
+
}
|
|
596
|
+
const kqb = qb.update({ [coll.property.mappedBy]: null })
|
|
597
|
+
.getKnexQuery()
|
|
598
|
+
.whereNotIn(cols, insertDiff);
|
|
599
|
+
return this.rethrow(this.execute(kqb));
|
|
600
|
+
}
|
|
601
|
+
const kqb = qb.update({ [coll.property.mappedBy]: pks })
|
|
602
|
+
.getKnexQuery()
|
|
603
|
+
.whereIn(cols, insertDiff);
|
|
604
|
+
return this.rethrow(this.execute(kqb));
|
|
605
|
+
}
|
|
454
606
|
/* istanbul ignore next */
|
|
455
|
-
|
|
456
|
-
|
|
607
|
+
const pivotMeta = this.metadata.find(coll.property.pivotEntity);
|
|
608
|
+
let schema = pivotMeta.schema;
|
|
609
|
+
if (schema === '*') {
|
|
610
|
+
const ownerSchema = wrapped.getSchema() === '*' ? this.config.get('schema') : wrapped.getSchema();
|
|
611
|
+
schema = coll.property.owner ? ownerSchema : this.config.get('schema');
|
|
612
|
+
}
|
|
613
|
+
else if (schema == null) {
|
|
614
|
+
schema = this.config.get('schema');
|
|
615
|
+
}
|
|
616
|
+
const tableName = `${schema ?? '_'}.${pivotMeta.tableName}`;
|
|
617
|
+
const persister = groups[tableName] ??= new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, schema);
|
|
618
|
+
persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks);
|
|
619
|
+
}
|
|
620
|
+
for (const persister of core_1.Utils.values(groups)) {
|
|
621
|
+
await this.rethrow(persister.execute());
|
|
457
622
|
}
|
|
458
|
-
return this.rethrow(this.updateCollectionDiff(meta, coll.property, pks, deleteDiff, insertDiff, options));
|
|
459
623
|
}
|
|
460
|
-
async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options) {
|
|
461
|
-
const pivotProp2 = this.getPivotInverseProperty(prop);
|
|
462
|
-
const ownerMeta = this.metadata.find(pivotProp2.type);
|
|
624
|
+
async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
|
|
463
625
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
464
|
-
const
|
|
626
|
+
const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
|
|
627
|
+
const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
|
|
628
|
+
const ownerMeta = this.metadata.find(pivotProp2.type);
|
|
629
|
+
options = { ...options };
|
|
630
|
+
const qb = this.createQueryBuilder(prop.pivotEntity, ctx, options.connectionType)
|
|
631
|
+
.withSchema(this.getSchemaName(pivotMeta, options))
|
|
632
|
+
.indexHint(options.indexHint)
|
|
633
|
+
.comment(options.comments)
|
|
634
|
+
.hintComment(options.hintComments);
|
|
635
|
+
const pivotAlias = qb.alias;
|
|
636
|
+
const pivotKey = pivotProp2.joinColumns.map(column => `${pivotAlias}.${column}`).join(core_1.Utils.PK_SEPARATOR);
|
|
637
|
+
const cond = {
|
|
638
|
+
[pivotKey]: { $in: ownerMeta.compositePK ? owners : owners.map(o => o[0]) },
|
|
639
|
+
};
|
|
465
640
|
/* istanbul ignore if */
|
|
466
641
|
if (!core_1.Utils.isEmpty(where) && Object.keys(where).every(k => core_1.Utils.isOperator(k, false))) {
|
|
467
642
|
where = cond;
|
|
@@ -469,29 +644,51 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
469
644
|
else {
|
|
470
645
|
where = { ...where, ...cond };
|
|
471
646
|
}
|
|
472
|
-
orderBy = this.getPivotOrderBy(prop, orderBy);
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
if (
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
647
|
+
orderBy = this.getPivotOrderBy(prop, pivotProp1, pivotAlias, orderBy);
|
|
648
|
+
const populate = this.autoJoinOneToOneOwner(prop.targetMeta, []);
|
|
649
|
+
const fields = [];
|
|
650
|
+
const k1 = !prop.owner ? 'joinColumns' : 'inverseJoinColumns';
|
|
651
|
+
const k2 = prop.owner ? 'joinColumns' : 'inverseJoinColumns';
|
|
652
|
+
const cols = [
|
|
653
|
+
...prop[k1].map(col => `${pivotAlias}.${col} as fk__${col}`),
|
|
654
|
+
...prop[k2].map(col => `${pivotAlias}.${col} as fk__${col}`),
|
|
655
|
+
];
|
|
656
|
+
fields.push(...cols);
|
|
657
|
+
if (!pivotJoin) {
|
|
658
|
+
const targetAlias = qb.getNextAlias(prop.targetMeta.tableName);
|
|
659
|
+
const targetSchema = this.getSchemaName(prop.targetMeta, options) ?? this.platform.getDefaultSchemaName();
|
|
660
|
+
qb.innerJoin(pivotProp1.name, targetAlias, {}, targetSchema);
|
|
661
|
+
const targetFields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, targetAlias, options.fields, options);
|
|
662
|
+
for (const field of targetFields) {
|
|
663
|
+
const f = field.toString();
|
|
664
|
+
fields.unshift(f.includes('.') ? field : `${targetAlias}.${f}`);
|
|
665
|
+
}
|
|
666
|
+
// we need to handle 1:1 owner auto-joins explicitly, as the QB type is the pivot table, not the target
|
|
667
|
+
populate.forEach(hint => {
|
|
668
|
+
const alias = qb.getNextAlias(prop.targetMeta.tableName);
|
|
669
|
+
qb.leftJoin(`${targetAlias}.${hint.field}`, alias);
|
|
670
|
+
// eslint-disable-next-line dot-notation
|
|
671
|
+
Object.values(qb['_joins']).forEach(join => {
|
|
672
|
+
const [propName] = hint.field.split(':', 2);
|
|
673
|
+
if (join.alias === alias && join.prop.name === propName) {
|
|
674
|
+
fields.push(...qb.helper.mapJoinColumns(qb.type, join));
|
|
675
|
+
}
|
|
676
|
+
});
|
|
488
677
|
});
|
|
489
678
|
}
|
|
490
|
-
|
|
679
|
+
qb.select(fields)
|
|
680
|
+
.where({ [pivotProp1.name]: where })
|
|
681
|
+
.orderBy(orderBy)
|
|
682
|
+
.setLockMode(options.lockMode, options.lockTableAliases);
|
|
683
|
+
if (owners.length === 1 && (options.offset != null || options.limit != null)) {
|
|
684
|
+
qb.limit(options.limit, options.offset);
|
|
685
|
+
}
|
|
686
|
+
const res = owners.length ? await this.rethrow(qb.execute('all', { mergeResults: false, mapResults: false })) : [];
|
|
687
|
+
const items = res.map((row) => super.mapResult(row, prop.targetMeta));
|
|
491
688
|
const map = {};
|
|
492
689
|
const pkProps = ownerMeta.getPrimaryProps();
|
|
493
690
|
owners.forEach(owner => {
|
|
494
|
-
const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((
|
|
691
|
+
const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((_col, idx) => {
|
|
495
692
|
const pkProp = pkProps[idx];
|
|
496
693
|
return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
|
|
497
694
|
}));
|
|
@@ -504,10 +701,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
504
701
|
}));
|
|
505
702
|
map[key].push(item);
|
|
506
703
|
prop.joinColumns.forEach(col => delete item[`fk__${col}`]);
|
|
507
|
-
prop.inverseJoinColumns.forEach(col =>
|
|
704
|
+
prop.inverseJoinColumns.forEach((col, idx) => {
|
|
705
|
+
core_1.Utils.renameKey(item, `fk__${col}`, prop.targetMeta.primaryKeys[idx]);
|
|
706
|
+
});
|
|
508
707
|
});
|
|
509
708
|
return map;
|
|
510
709
|
}
|
|
710
|
+
getPivotOrderBy(prop, pivotProp, pivotAlias, orderBy) {
|
|
711
|
+
// FIXME this is ignoring the rest of the array items
|
|
712
|
+
if (!core_1.Utils.isEmpty(orderBy)) {
|
|
713
|
+
return [{ [pivotProp.name]: core_1.Utils.asArray(orderBy)[0] }];
|
|
714
|
+
}
|
|
715
|
+
if (!core_1.Utils.isEmpty(prop.orderBy)) {
|
|
716
|
+
return [{ [pivotProp.name]: core_1.Utils.asArray(prop.orderBy)[0] }];
|
|
717
|
+
}
|
|
718
|
+
if (prop.fixedOrder) {
|
|
719
|
+
return [{ [`${pivotAlias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC }];
|
|
720
|
+
}
|
|
721
|
+
return [];
|
|
722
|
+
}
|
|
511
723
|
async execute(queryOrKnex, params = [], method = 'all', ctx) {
|
|
512
724
|
return this.rethrow(this.connection.execute(queryOrKnex, params, method, ctx));
|
|
513
725
|
}
|
|
@@ -520,39 +732,62 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
520
732
|
}
|
|
521
733
|
const relationsToPopulate = populate.map(({ field }) => field);
|
|
522
734
|
const toPopulate = meta.relations
|
|
523
|
-
.filter(prop => prop.
|
|
735
|
+
.filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
|
|
524
736
|
.filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
|
|
525
|
-
.map(prop => ({ field: prop.name
|
|
737
|
+
.map(prop => ({ field: `${prop.name}:ref`, strategy: prop.strategy }));
|
|
526
738
|
return [...populate, ...toPopulate];
|
|
527
739
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
740
|
+
/**
|
|
741
|
+
* @internal
|
|
742
|
+
*/
|
|
743
|
+
joinedProps(meta, populate, options) {
|
|
744
|
+
return populate.filter(hint => {
|
|
745
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
746
|
+
const prop = meta.properties[propName] || {};
|
|
747
|
+
return (options?.strategy || hint.strategy || prop.strategy || this.config.get('loadStrategy')) === core_1.LoadStrategy.JOINED && ![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind);
|
|
532
748
|
});
|
|
533
749
|
}
|
|
534
750
|
/**
|
|
535
751
|
* @internal
|
|
536
752
|
*/
|
|
537
|
-
mergeJoinedResult(rawResults, meta) {
|
|
538
|
-
// group by the root entity primary key first
|
|
539
|
-
const map = {};
|
|
753
|
+
mergeJoinedResult(rawResults, meta, joinedProps) {
|
|
540
754
|
const res = [];
|
|
541
|
-
|
|
755
|
+
const map = {};
|
|
756
|
+
for (const item of rawResults) {
|
|
542
757
|
const pk = core_1.Utils.getCompositeKeyHash(item, meta);
|
|
543
758
|
if (map[pk]) {
|
|
544
|
-
|
|
759
|
+
for (const hint of joinedProps) {
|
|
760
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
761
|
+
const prop = meta.properties[propName];
|
|
762
|
+
if (!item[propName]) {
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref) {
|
|
766
|
+
map[pk][propName] = [...map[pk][propName], ...item[propName]];
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
switch (prop.kind) {
|
|
770
|
+
case core_1.ReferenceKind.ONE_TO_MANY:
|
|
771
|
+
case core_1.ReferenceKind.MANY_TO_MANY:
|
|
772
|
+
map[pk][propName] = this.mergeJoinedResult([...map[pk][propName], ...item[propName]], prop.targetMeta, hint.children ?? []);
|
|
773
|
+
break;
|
|
774
|
+
case core_1.ReferenceKind.MANY_TO_ONE:
|
|
775
|
+
case core_1.ReferenceKind.ONE_TO_ONE:
|
|
776
|
+
map[pk][propName] = this.mergeJoinedResult([map[pk][propName], item[propName]], prop.targetMeta, hint.children ?? [])[0];
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
545
780
|
}
|
|
546
781
|
else {
|
|
547
|
-
map[pk] =
|
|
782
|
+
map[pk] = item;
|
|
548
783
|
res.push(item);
|
|
549
784
|
}
|
|
550
|
-
}
|
|
785
|
+
}
|
|
551
786
|
return res;
|
|
552
787
|
}
|
|
553
|
-
getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
|
|
788
|
+
getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], options, parentTableAlias, parentJoinPath) {
|
|
554
789
|
const fields = [];
|
|
555
|
-
const joinedProps = this.joinedProps(meta, populate);
|
|
790
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
556
791
|
if (explicitFields?.includes('*')) {
|
|
557
792
|
fields.push('*');
|
|
558
793
|
}
|
|
@@ -563,26 +798,41 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
563
798
|
if (!fields || prop.primary) {
|
|
564
799
|
return !fields?.includes('*');
|
|
565
800
|
}
|
|
566
|
-
return fields.
|
|
801
|
+
return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
|
|
567
802
|
};
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
803
|
+
const populateWhereAll = options?._populateWhere === 'all' || core_1.Utils.isEmpty(options?._populateWhere);
|
|
804
|
+
// root entity is already handled, skip that
|
|
805
|
+
if (parentJoinPath) {
|
|
806
|
+
// alias all fields in the primary table
|
|
807
|
+
meta.props
|
|
808
|
+
.filter(prop => shouldHaveColumn(prop, populate, explicitFields))
|
|
809
|
+
.forEach(prop => fields.push(...this.mapPropToFieldNames(qb, prop, parentTableAlias)));
|
|
810
|
+
}
|
|
811
|
+
joinedProps.forEach(hint => {
|
|
812
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
813
|
+
const prop = meta.properties[propName];
|
|
574
814
|
const meta2 = this.metadata.find(prop.type);
|
|
815
|
+
const pivotRefJoin = prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref;
|
|
575
816
|
const tableAlias = qb.getNextAlias(prop.name);
|
|
576
817
|
const field = parentTableAlias ? `${parentTableAlias}.${prop.name}` : prop.name;
|
|
577
|
-
|
|
578
|
-
|
|
818
|
+
let path = parentJoinPath ? `${parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`;
|
|
819
|
+
if (!parentJoinPath && populateWhereAll && !path.startsWith('[populate]')) {
|
|
820
|
+
path = '[populate]' + path;
|
|
821
|
+
}
|
|
822
|
+
const joinType = pivotRefJoin ? query_1.JoinType.pivotJoin : query_1.JoinType.leftJoin;
|
|
823
|
+
qb.join(field, tableAlias, {}, joinType, path);
|
|
824
|
+
if (pivotRefJoin) {
|
|
825
|
+
fields.push(...prop.joinColumns.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)), ...prop.inverseJoinColumns.map(col => qb.helper.mapper(`${tableAlias}.${col}`, qb.type, undefined, `${tableAlias}__${col}`)));
|
|
826
|
+
}
|
|
579
827
|
const childExplicitFields = explicitFields?.filter(f => core_1.Utils.isPlainObject(f)).map(o => o[prop.name])[0] || [];
|
|
580
828
|
explicitFields?.forEach(f => {
|
|
581
829
|
if (typeof f === 'string' && f.startsWith(`${prop.name}.`)) {
|
|
582
830
|
childExplicitFields.push(f.substring(prop.name.length + 1));
|
|
583
831
|
}
|
|
584
832
|
});
|
|
585
|
-
|
|
833
|
+
if (!ref) {
|
|
834
|
+
fields.push(...this.getFieldsForJoinedLoad(qb, meta2, childExplicitFields.length === 0 ? undefined : childExplicitFields, hint.children, options, tableAlias, path));
|
|
835
|
+
}
|
|
586
836
|
});
|
|
587
837
|
return fields;
|
|
588
838
|
}
|
|
@@ -590,24 +840,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
590
840
|
* @internal
|
|
591
841
|
*/
|
|
592
842
|
mapPropToFieldNames(qb, prop, tableAlias) {
|
|
593
|
-
const
|
|
843
|
+
const knex = this.connection.getKnex();
|
|
844
|
+
const aliased = knex.ref(tableAlias ? `${tableAlias}__${prop.fieldNames[0]}` : prop.fieldNames[0]).toString();
|
|
594
845
|
if (prop.customType?.convertToJSValueSQL && tableAlias) {
|
|
595
|
-
const prefixed =
|
|
596
|
-
return [
|
|
846
|
+
const prefixed = knex.ref(prop.fieldNames[0]).withSchema(tableAlias).toString();
|
|
847
|
+
return [(0, core_1.raw)(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`)];
|
|
597
848
|
}
|
|
598
849
|
if (prop.formula) {
|
|
599
|
-
const alias =
|
|
600
|
-
return [`${prop.formula(alias)} as ${aliased}`];
|
|
850
|
+
const alias = knex.ref(tableAlias ?? qb.alias).toString();
|
|
851
|
+
return [(0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`)];
|
|
601
852
|
}
|
|
602
853
|
if (tableAlias) {
|
|
603
|
-
return prop.fieldNames.map(fieldName =>
|
|
854
|
+
return prop.fieldNames.map(fieldName => knex.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
|
|
604
855
|
}
|
|
605
856
|
return prop.fieldNames;
|
|
606
857
|
}
|
|
607
858
|
/** @internal */
|
|
608
|
-
createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes) {
|
|
859
|
+
createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes, logging) {
|
|
609
860
|
const connectionType = this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
|
|
610
|
-
const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType);
|
|
861
|
+
const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType, undefined, logging);
|
|
611
862
|
if (!convertCustomTypes) {
|
|
612
863
|
qb.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
|
|
613
864
|
}
|
|
@@ -631,7 +882,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
631
882
|
}
|
|
632
883
|
const ret = {};
|
|
633
884
|
this.metadata.find(entityName).relations.forEach(prop => {
|
|
634
|
-
if (prop.
|
|
885
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && data[prop.name]) {
|
|
635
886
|
ret[prop.name] = data[prop.name].map((item) => core_1.Utils.asArray(item));
|
|
636
887
|
delete data[prop.name];
|
|
637
888
|
}
|
|
@@ -644,73 +895,96 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
644
895
|
}
|
|
645
896
|
for (const prop of meta.relations) {
|
|
646
897
|
if (collections[prop.name]) {
|
|
647
|
-
|
|
898
|
+
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
899
|
+
const persister = new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema);
|
|
900
|
+
persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
|
|
901
|
+
await this.rethrow(persister.execute());
|
|
648
902
|
}
|
|
649
903
|
}
|
|
650
904
|
}
|
|
651
|
-
async updateCollectionDiff(meta, prop, pks, deleteDiff, insertDiff, options) {
|
|
652
|
-
if (!deleteDiff) {
|
|
653
|
-
deleteDiff = [];
|
|
654
|
-
}
|
|
655
|
-
if (deleteDiff === true || deleteDiff.length > 0) {
|
|
656
|
-
const qb1 = this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
|
|
657
|
-
.withSchema(this.getSchemaName(meta, options))
|
|
658
|
-
.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
|
|
659
|
-
const knex = qb1.getKnex();
|
|
660
|
-
if (Array.isArray(deleteDiff)) {
|
|
661
|
-
knex.whereIn(prop.inverseJoinColumns, deleteDiff);
|
|
662
|
-
}
|
|
663
|
-
prop.joinColumns.forEach((joinColumn, idx) => knex.andWhere(joinColumn, pks[idx]));
|
|
664
|
-
await this.execute(knex.delete());
|
|
665
|
-
}
|
|
666
|
-
if (insertDiff.length === 0) {
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
const items = insertDiff.map(item => {
|
|
670
|
-
const cond = {};
|
|
671
|
-
prop.joinColumns.forEach((joinColumn, idx) => cond[joinColumn] = pks[idx]);
|
|
672
|
-
prop.inverseJoinColumns.forEach((inverseJoinColumn, idx) => cond[inverseJoinColumn] = item[idx]);
|
|
673
|
-
return cond;
|
|
674
|
-
});
|
|
675
|
-
/* istanbul ignore else */
|
|
676
|
-
if (this.platform.allowsMultiInsert()) {
|
|
677
|
-
await this.nativeInsertMany(prop.pivotEntity, items, { ...options, convertCustomTypes: false, processCollections: false });
|
|
678
|
-
}
|
|
679
|
-
else {
|
|
680
|
-
await core_1.Utils.runSerial(items, item => {
|
|
681
|
-
return this.createQueryBuilder(prop.pivotEntity, options?.ctx, 'write')
|
|
682
|
-
.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES)
|
|
683
|
-
.withSchema(this.getSchemaName(meta, options))
|
|
684
|
-
.insert(item)
|
|
685
|
-
.execute('run', false);
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
905
|
async lockPessimistic(entity, options) {
|
|
690
906
|
const meta = (0, core_1.helper)(entity).__meta;
|
|
691
|
-
const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).
|
|
907
|
+
const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).withSchema(options.schema ?? meta.schema);
|
|
692
908
|
const cond = core_1.Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
|
|
693
|
-
qb.select('1').where(cond).setLockMode(options.lockMode, options.lockTableAliases);
|
|
909
|
+
qb.select((0, core_1.raw)('1')).where(cond).setLockMode(options.lockMode, options.lockTableAliases);
|
|
694
910
|
await this.rethrow(qb.execute());
|
|
695
911
|
}
|
|
696
|
-
|
|
912
|
+
buildOrderBy(qb, meta, populate, options) {
|
|
913
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
914
|
+
// `options._populateWhere` is a copy of the value provided by user with a fallback to the global config option
|
|
915
|
+
// as `options.populateWhere` will be always recomputed to respect filters
|
|
916
|
+
const populateWhereAll = options._populateWhere !== 'infer' && !core_1.Utils.isEmpty(options._populateWhere);
|
|
917
|
+
const path = (populateWhereAll ? '[populate]' : '') + meta.className;
|
|
918
|
+
const populateOrderBy = this.buildPopulateOrderBy(qb, meta, core_1.Utils.asArray(options.populateOrderBy ?? options.orderBy), path);
|
|
919
|
+
const joinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, meta, joinedProps, options, path);
|
|
920
|
+
return [...core_1.Utils.asArray(options.orderBy), ...populateOrderBy, ...joinedPropsOrderBy];
|
|
921
|
+
}
|
|
922
|
+
buildPopulateOrderBy(qb, meta, populateOrderBy, parentPath, parentAlias = qb.alias) {
|
|
697
923
|
const orderBy = [];
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const
|
|
924
|
+
for (let i = 0; i < populateOrderBy.length; i++) {
|
|
925
|
+
const orderHint = populateOrderBy[i];
|
|
926
|
+
for (const propName of core_1.Utils.keys(orderHint)) {
|
|
927
|
+
const prop = meta.properties[propName];
|
|
928
|
+
const meta2 = this.metadata.find(prop.type);
|
|
929
|
+
const childOrder = orderHint[prop.name];
|
|
930
|
+
let path = `${parentPath}.${propName}`;
|
|
931
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && typeof childOrder !== 'object') {
|
|
932
|
+
path += '[pivot]';
|
|
933
|
+
}
|
|
934
|
+
const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
|
|
935
|
+
const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true }) ?? parentAlias;
|
|
936
|
+
if (!join && parentAlias === qb.alias) {
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
if (![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
|
|
940
|
+
const children = this.buildPopulateOrderBy(qb, meta2, core_1.Utils.asArray(childOrder), path, propAlias);
|
|
941
|
+
orderBy.push(...children);
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && join) {
|
|
945
|
+
if (prop.fixedOrderColumn) {
|
|
946
|
+
orderBy.push({ [`${join.alias}.${prop.fixedOrderColumn}`]: childOrder });
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
for (const col of prop.inverseJoinColumns) {
|
|
950
|
+
orderBy.push({ [`${join.ownerAlias}.${col}`]: childOrder });
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
const order = typeof childOrder === 'object' ? childOrder[propName] : childOrder;
|
|
956
|
+
orderBy.push({ [`${propAlias}.${propName}`]: order });
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return orderBy;
|
|
960
|
+
}
|
|
961
|
+
buildJoinedPropsOrderBy(qb, meta, populate, options, parentPath) {
|
|
962
|
+
const orderBy = [];
|
|
963
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
964
|
+
for (const hint of joinedProps) {
|
|
965
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
966
|
+
const prop = meta.properties[propName];
|
|
701
967
|
const propOrderBy = prop.orderBy;
|
|
702
|
-
|
|
703
|
-
|
|
968
|
+
let path = `${parentPath}.${propName}`;
|
|
969
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref) {
|
|
970
|
+
path += '[pivot]';
|
|
971
|
+
}
|
|
972
|
+
const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
|
|
973
|
+
const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true });
|
|
974
|
+
const meta2 = this.metadata.find(prop.type);
|
|
975
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && prop.fixedOrder && join) {
|
|
976
|
+
orderBy.push({ [`${(join.ownerAlias)}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC });
|
|
977
|
+
}
|
|
704
978
|
if (propOrderBy) {
|
|
705
|
-
|
|
979
|
+
core_1.Utils.keys(propOrderBy).forEach(field => {
|
|
706
980
|
orderBy.push({ [`${propAlias}.${field}`]: propOrderBy[field] });
|
|
707
981
|
});
|
|
708
982
|
}
|
|
709
|
-
if (
|
|
710
|
-
const
|
|
711
|
-
orderBy.push(...
|
|
983
|
+
if (hint.children) {
|
|
984
|
+
const buildJoinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, meta2, hint.children, options, path);
|
|
985
|
+
orderBy.push(...buildJoinedPropsOrderBy);
|
|
712
986
|
}
|
|
713
|
-
}
|
|
987
|
+
}
|
|
714
988
|
return orderBy;
|
|
715
989
|
}
|
|
716
990
|
normalizeFields(fields, prefix = '') {
|
|
@@ -729,12 +1003,12 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
729
1003
|
return ret;
|
|
730
1004
|
}
|
|
731
1005
|
processField(meta, prop, field, ret, populate, joinedProps, qb) {
|
|
732
|
-
if (!prop || (prop.
|
|
1006
|
+
if (!prop || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner)) {
|
|
733
1007
|
return;
|
|
734
1008
|
}
|
|
735
|
-
if (prop.
|
|
1009
|
+
if (prop.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
736
1010
|
if (prop.object) {
|
|
737
|
-
ret.push(prop.
|
|
1011
|
+
ret.push(prop.name);
|
|
738
1012
|
return;
|
|
739
1013
|
}
|
|
740
1014
|
const parts = field.split('.');
|
|
@@ -746,20 +1020,17 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
746
1020
|
}
|
|
747
1021
|
return;
|
|
748
1022
|
}
|
|
749
|
-
|
|
750
|
-
ret.push(prop.fieldNames[0]);
|
|
751
|
-
}
|
|
1023
|
+
ret.push(prop.name);
|
|
752
1024
|
}
|
|
753
|
-
buildFields(meta, populate, joinedProps, qb, fields) {
|
|
1025
|
+
buildFields(meta, populate, joinedProps, qb, alias, fields, options) {
|
|
754
1026
|
const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
|
|
755
1027
|
const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
|
|
756
|
-
const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL);
|
|
1028
|
+
const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
|
|
757
1029
|
const hasExplicitFields = !!fields;
|
|
758
1030
|
const ret = [];
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
else if (fields) {
|
|
1031
|
+
let addFormulas = false;
|
|
1032
|
+
// handle root entity properties first, this is used for both strategies in the same way
|
|
1033
|
+
if (fields) {
|
|
763
1034
|
for (const field of this.normalizeFields(fields)) {
|
|
764
1035
|
if (field === '*') {
|
|
765
1036
|
ret.push('*');
|
|
@@ -776,28 +1047,39 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
776
1047
|
});
|
|
777
1048
|
this.processField(meta, prop, parts.join('.'), ret, populate, joinedProps, qb);
|
|
778
1049
|
}
|
|
779
|
-
|
|
1050
|
+
if (!fields.includes('*') && !fields.includes(`${qb.alias}.*`)) {
|
|
1051
|
+
ret.unshift(...meta.primaryKeys.filter(pk => !fields.includes(pk)));
|
|
1052
|
+
}
|
|
780
1053
|
}
|
|
781
1054
|
else if (lazyProps.filter(p => !p.formula).length > 0) {
|
|
782
1055
|
const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, false));
|
|
783
1056
|
ret.push(...core_1.Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
|
|
1057
|
+
addFormulas = true;
|
|
784
1058
|
}
|
|
785
1059
|
else if (hasLazyFormulas || requiresSQLConversion) {
|
|
786
1060
|
ret.push('*');
|
|
1061
|
+
addFormulas = true;
|
|
787
1062
|
}
|
|
788
|
-
|
|
1063
|
+
else {
|
|
1064
|
+
ret.push('*');
|
|
1065
|
+
}
|
|
1066
|
+
if (ret.length > 0 && !hasExplicitFields && addFormulas) {
|
|
789
1067
|
meta.props
|
|
790
1068
|
.filter(prop => prop.formula && !lazyProps.includes(prop))
|
|
791
1069
|
.forEach(prop => {
|
|
792
|
-
const
|
|
793
|
-
const aliased =
|
|
794
|
-
ret.push(`${prop.formula(
|
|
1070
|
+
const a = this.connection.getKnex().ref(alias).toString();
|
|
1071
|
+
const aliased = this.connection.getKnex().ref(prop.fieldNames[0]).toString();
|
|
1072
|
+
ret.push((0, core_1.raw)(`${prop.formula(a)} as ${aliased}`));
|
|
795
1073
|
});
|
|
796
1074
|
meta.props
|
|
797
|
-
.filter(prop => prop.
|
|
1075
|
+
.filter(prop => prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)
|
|
798
1076
|
.forEach(prop => ret.push(prop.name));
|
|
799
1077
|
}
|
|
800
|
-
|
|
1078
|
+
// add joined relations after the root entity fields
|
|
1079
|
+
if (joinedProps.length > 0) {
|
|
1080
|
+
ret.push(...this.getFieldsForJoinedLoad(qb, meta, fields, populate, options, alias));
|
|
1081
|
+
}
|
|
1082
|
+
return core_1.Utils.unique(ret);
|
|
801
1083
|
}
|
|
802
1084
|
}
|
|
803
1085
|
exports.AbstractSqlDriver = AbstractSqlDriver;
|