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