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