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