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