@mikro-orm/knex 6.0.0-dev.30 → 6.0.0-dev.301
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 +47 -17
- package/AbstractSqlDriver.d.ts +30 -24
- package/AbstractSqlDriver.js +590 -264
- 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 +58 -26
- package/query/QueryBuilder.js +435 -139
- package/query/QueryBuilderHelper.d.ts +13 -18
- package/query/QueryBuilderHelper.js +196 -169
- 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 -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 +3 -4
- package/schema/SqlSchemaGenerator.js +66 -23
- 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,28 +57,25 @@ 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)) {
|
|
@@ -79,52 +85,64 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
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,82 +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
|
-
const
|
|
171
|
+
joinedProps.forEach(hint => {
|
|
172
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
173
|
+
const prop = meta.properties[propName];
|
|
154
174
|
/* istanbul ignore next */
|
|
155
|
-
if (!
|
|
175
|
+
if (!prop) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const pivotRefJoin = prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref;
|
|
179
|
+
const meta2 = this.metadata.find(prop.type);
|
|
180
|
+
let path = parentJoinPath ? `${parentJoinPath}.${prop.name}` : `${meta.name}.${prop.name}`;
|
|
181
|
+
if (!parentJoinPath) {
|
|
182
|
+
path = '[populate]' + path;
|
|
183
|
+
}
|
|
184
|
+
if (pivotRefJoin) {
|
|
185
|
+
path += '[pivot]';
|
|
186
|
+
}
|
|
187
|
+
const relationAlias = qb.getAliasForJoinPath(path, { matchPopulateJoins: true });
|
|
188
|
+
// pivot ref joins via joined strategy need to be handled separately here, as they dont join the target entity
|
|
189
|
+
if (pivotRefJoin) {
|
|
190
|
+
let item;
|
|
191
|
+
if (prop.inverseJoinColumns.length > 1) { // composite keys
|
|
192
|
+
item = prop.inverseJoinColumns.map(name => root[`${relationAlias}__${name}`]);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const alias = `${relationAlias}__${prop.inverseJoinColumns[0]}`;
|
|
196
|
+
item = root[alias];
|
|
197
|
+
}
|
|
198
|
+
prop.joinColumns.forEach(name => delete root[`${relationAlias}__${name}`]);
|
|
199
|
+
prop.inverseJoinColumns.forEach(name => delete root[`${relationAlias}__${name}`]);
|
|
200
|
+
result[prop.name] ??= [];
|
|
201
|
+
if (item) {
|
|
202
|
+
result[prop.name].push(item);
|
|
203
|
+
}
|
|
156
204
|
return;
|
|
157
205
|
}
|
|
158
|
-
const meta2 = this.metadata.find(relation.type);
|
|
159
|
-
const path = parentJoinPath ? `${parentJoinPath}.${relation.name}` : `${meta.name}.${relation.name}`;
|
|
160
|
-
const relationAlias = qb.getAliasForJoinPath(path);
|
|
161
|
-
const relationPojo = {};
|
|
162
206
|
// If the primary key value for the relation is null, we know we haven't joined to anything
|
|
163
207
|
// and therefore we don't return any record (since all values would be null)
|
|
164
208
|
const hasPK = meta2.primaryKeys.every(pk => meta2.properties[pk].fieldNames.every(name => {
|
|
165
209
|
return root[`${relationAlias}__${name}`] != null;
|
|
166
210
|
}));
|
|
167
211
|
if (!hasPK) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
212
|
+
if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(prop.kind)) {
|
|
213
|
+
result[prop.name] ??= [];
|
|
214
|
+
}
|
|
215
|
+
if ([core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
216
|
+
result[prop.name] ??= null;
|
|
171
217
|
}
|
|
172
218
|
return;
|
|
173
219
|
}
|
|
220
|
+
let relationPojo = {};
|
|
174
221
|
meta2.props
|
|
175
|
-
.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))
|
|
176
224
|
.forEach(prop => {
|
|
225
|
+
/* istanbul ignore if */
|
|
177
226
|
if (prop.fieldNames.length > 1) { // composite keys
|
|
178
227
|
relationPojo[prop.name] = prop.fieldNames.map(name => root[`${relationAlias}__${name}`]);
|
|
179
|
-
prop.fieldNames.map(name => delete root[`${relationAlias}__${name}`]);
|
|
180
228
|
}
|
|
181
229
|
else {
|
|
182
230
|
const alias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
183
231
|
relationPojo[prop.name] = root[alias];
|
|
184
|
-
delete root[alias];
|
|
185
232
|
}
|
|
186
233
|
});
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
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
|
+
}
|
|
190
260
|
}
|
|
191
|
-
|
|
192
|
-
|
|
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}`]);
|
|
193
265
|
}
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
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);
|
|
197
274
|
}
|
|
198
275
|
else {
|
|
199
|
-
result[
|
|
276
|
+
result[prop.name] = relationPojo;
|
|
200
277
|
}
|
|
201
|
-
const populateChildren =
|
|
278
|
+
const populateChildren = hint.children || [];
|
|
202
279
|
this.mapJoinedProps(relationPojo, meta2, populateChildren, qb, root, map, path);
|
|
203
280
|
});
|
|
204
281
|
}
|
|
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
282
|
async count(entityName, where, options = {}) {
|
|
217
283
|
const meta = this.metadata.find(entityName);
|
|
218
284
|
if (meta?.virtual) {
|
|
219
285
|
return this.countVirtual(entityName, where, options);
|
|
220
286
|
}
|
|
221
287
|
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false)
|
|
288
|
+
.indexHint(options.indexHint)
|
|
289
|
+
.comment(options.comments)
|
|
290
|
+
.hintComment(options.hintComments)
|
|
222
291
|
.groupBy(options.groupBy)
|
|
223
292
|
.having(options.having)
|
|
224
293
|
.populate(options.populate ?? [])
|
|
@@ -249,14 +318,18 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
249
318
|
async nativeInsertMany(entityName, data, options = {}) {
|
|
250
319
|
options.processCollections ??= true;
|
|
251
320
|
options.convertCustomTypes ??= true;
|
|
252
|
-
const meta = this.metadata.find(entityName);
|
|
321
|
+
const meta = this.metadata.find(entityName)?.root;
|
|
253
322
|
const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
|
|
254
323
|
const pks = this.getPrimaryKeyFields(entityName);
|
|
255
324
|
const set = new Set();
|
|
256
325
|
data.forEach(row => core_1.Utils.keys(row).forEach(k => set.add(k)));
|
|
257
326
|
const props = [...set].map(name => meta?.properties[name] ?? { name, fieldNames: [name] });
|
|
258
|
-
|
|
327
|
+
let fields = core_1.Utils.flatten(props.map(prop => prop.fieldNames));
|
|
328
|
+
const duplicates = core_1.Utils.findDuplicates(fields);
|
|
259
329
|
const params = [];
|
|
330
|
+
if (duplicates.length) {
|
|
331
|
+
fields = core_1.Utils.unique(fields);
|
|
332
|
+
}
|
|
260
333
|
/* istanbul ignore next */
|
|
261
334
|
const tableName = meta ? this.getTableName(meta, options) : this.platform.quoteIdentifier(entityName);
|
|
262
335
|
let sql = `insert into ${tableName} `;
|
|
@@ -267,29 +340,62 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
267
340
|
else {
|
|
268
341
|
sql += ' ' + data.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`).join(' union all ');
|
|
269
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
|
+
};
|
|
270
362
|
if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
|
|
271
363
|
sql += data.map(row => {
|
|
272
364
|
const keys = [];
|
|
365
|
+
const usedDups = [];
|
|
273
366
|
props.forEach(prop => {
|
|
274
367
|
if (prop.fieldNames.length > 1) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
+
});
|
|
281
377
|
}
|
|
282
378
|
else {
|
|
283
|
-
|
|
284
|
-
|
|
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
|
+
}
|
|
285
390
|
}
|
|
286
391
|
});
|
|
287
392
|
return '(' + (keys.join(', ') || 'default') + ')';
|
|
288
393
|
}).join(', ');
|
|
289
394
|
}
|
|
290
|
-
if (this.platform.usesReturningStatement()) {
|
|
291
|
-
|
|
292
|
-
|
|
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]));
|
|
293
399
|
const returningFields = core_1.Utils.flatten(returningProps.map(prop => prop.fieldNames));
|
|
294
400
|
/* istanbul ignore next */
|
|
295
401
|
sql += returningFields.length > 0 ? ` returning ${returningFields.map(field => this.platform.quoteIdentifier(field)).join(', ')}` : '';
|
|
@@ -326,14 +432,27 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
326
432
|
.withSchema(this.getSchemaName(meta, options));
|
|
327
433
|
if (options.upsert) {
|
|
328
434
|
/* istanbul ignore next */
|
|
329
|
-
const uniqueFields = core_1.Utils.isPlainObject(where) ? core_1.Utils.keys(where) : meta.primaryKeys;
|
|
330
|
-
|
|
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);
|
|
331
437
|
qb.insert(data)
|
|
332
|
-
.onConflict(uniqueFields
|
|
333
|
-
.
|
|
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
|
+
}
|
|
334
447
|
}
|
|
335
448
|
else {
|
|
336
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);
|
|
337
456
|
}
|
|
338
457
|
res = await this.rethrow(qb.execute('run', false));
|
|
339
458
|
}
|
|
@@ -347,22 +466,61 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
347
466
|
options.convertCustomTypes ??= true;
|
|
348
467
|
const meta = this.metadata.get(entityName);
|
|
349
468
|
if (options.upsert) {
|
|
350
|
-
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);
|
|
351
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);
|
|
352
472
|
qb.insert(data)
|
|
353
|
-
.onConflict(uniqueFields
|
|
354
|
-
.
|
|
355
|
-
|
|
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));
|
|
356
483
|
}
|
|
357
484
|
const collections = options.processCollections ? data.map(d => this.extractManyToMany(entityName, d)) : [];
|
|
358
485
|
const keys = new Set();
|
|
359
|
-
|
|
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));
|
|
360
500
|
const pkCond = core_1.Utils.flatten(meta.primaryKeys.map(pk => meta.properties[pk].fieldNames)).map(pk => `${this.platform.quoteIdentifier(pk)} = ?`).join(' and ');
|
|
361
501
|
const params = [];
|
|
362
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
|
+
};
|
|
363
517
|
keys.forEach(key => {
|
|
364
518
|
const prop = meta.properties[key];
|
|
365
519
|
prop.fieldNames.forEach((fieldName, fieldNameIdx) => {
|
|
520
|
+
if (fields.has(fieldName)) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
fields.add(fieldName);
|
|
366
524
|
sql += `${this.platform.quoteIdentifier(fieldName)} = case`;
|
|
367
525
|
where.forEach((cond, idx) => {
|
|
368
526
|
if (key in data[idx]) {
|
|
@@ -374,7 +532,8 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
374
532
|
else {
|
|
375
533
|
sql += '?';
|
|
376
534
|
}
|
|
377
|
-
params.push(...pks
|
|
535
|
+
params.push(...pks);
|
|
536
|
+
addParams(prop, prop.fieldNames.length > 1 ? data[idx][key]?.[fieldNameIdx] : data[idx][key]);
|
|
378
537
|
}
|
|
379
538
|
});
|
|
380
539
|
sql += ` else ${this.platform.quoteIdentifier(fieldName)} end, `;
|
|
@@ -385,7 +544,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
385
544
|
const versionProperty = meta.properties[meta.versionProperty];
|
|
386
545
|
const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
|
|
387
546
|
sql += `${quotedFieldName} = `;
|
|
388
|
-
if (versionProperty.
|
|
547
|
+
if (versionProperty.runtimeType === 'Date') {
|
|
389
548
|
sql += this.platform.getCurrentTimestampSQL(versionProperty.length);
|
|
390
549
|
}
|
|
391
550
|
else {
|
|
@@ -416,6 +575,11 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
416
575
|
return '?';
|
|
417
576
|
});
|
|
418
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
|
+
}
|
|
419
583
|
const res = await this.rethrow(this.execute(sql, params, 'run', options.ctx));
|
|
420
584
|
for (let i = 0; i < collections.length; i++) {
|
|
421
585
|
await this.processManyToMany(meta, where[i], collections[i], false, options);
|
|
@@ -431,49 +595,84 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
431
595
|
const qb = this.createQueryBuilder(entityName, options.ctx, 'write', false).delete(where).withSchema(this.getSchemaName(meta, options));
|
|
432
596
|
return this.rethrow(qb.execute('run', false));
|
|
433
597
|
}
|
|
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
|
-
|
|
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
|
+
}
|
|
466
642
|
/* istanbul ignore next */
|
|
467
|
-
|
|
468
|
-
|
|
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());
|
|
469
658
|
}
|
|
470
|
-
return this.rethrow(this.updateCollectionDiff(meta, coll.property, pks, deleteDiff, insertDiff, options));
|
|
471
659
|
}
|
|
472
|
-
async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options) {
|
|
473
|
-
const pivotProp2 = this.getPivotInverseProperty(prop);
|
|
474
|
-
const ownerMeta = this.metadata.find(pivotProp2.type);
|
|
660
|
+
async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
|
|
475
661
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
476
|
-
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
|
+
};
|
|
477
676
|
/* istanbul ignore if */
|
|
478
677
|
if (!core_1.Utils.isEmpty(where) && Object.keys(where).every(k => core_1.Utils.isOperator(k, false))) {
|
|
479
678
|
where = cond;
|
|
@@ -481,29 +680,51 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
481
680
|
else {
|
|
482
681
|
where = { ...where, ...cond };
|
|
483
682
|
}
|
|
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
|
-
|
|
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
|
+
});
|
|
500
713
|
});
|
|
501
714
|
}
|
|
502
|
-
|
|
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));
|
|
503
724
|
const map = {};
|
|
504
725
|
const pkProps = ownerMeta.getPrimaryProps();
|
|
505
726
|
owners.forEach(owner => {
|
|
506
|
-
const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((
|
|
727
|
+
const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((_col, idx) => {
|
|
507
728
|
const pkProp = pkProps[idx];
|
|
508
729
|
return pkProp.customType ? pkProp.customType.convertToJSValue(owner[idx], this.platform) : owner[idx];
|
|
509
730
|
}));
|
|
@@ -516,10 +737,25 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
516
737
|
}));
|
|
517
738
|
map[key].push(item);
|
|
518
739
|
prop.joinColumns.forEach(col => delete item[`fk__${col}`]);
|
|
519
|
-
prop.inverseJoinColumns.forEach(col =>
|
|
740
|
+
prop.inverseJoinColumns.forEach((col, idx) => {
|
|
741
|
+
core_1.Utils.renameKey(item, `fk__${col}`, prop.targetMeta.primaryKeys[idx]);
|
|
742
|
+
});
|
|
520
743
|
});
|
|
521
744
|
return map;
|
|
522
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
|
+
}
|
|
523
759
|
async execute(queryOrKnex, params = [], method = 'all', ctx) {
|
|
524
760
|
return this.rethrow(this.connection.execute(queryOrKnex, params, method, ctx));
|
|
525
761
|
}
|
|
@@ -534,67 +770,115 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
534
770
|
const toPopulate = meta.relations
|
|
535
771
|
.filter(prop => prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner && !relationsToPopulate.includes(prop.name))
|
|
536
772
|
.filter(prop => fields.length === 0 || fields.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
|
|
537
|
-
.map(prop => ({ field: prop.name
|
|
773
|
+
.map(prop => ({ field: `${prop.name}:ref`, strategy: prop.strategy }));
|
|
538
774
|
return [...populate, ...toPopulate];
|
|
539
775
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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);
|
|
544
790
|
});
|
|
545
791
|
}
|
|
546
792
|
/**
|
|
547
793
|
* @internal
|
|
548
794
|
*/
|
|
549
|
-
mergeJoinedResult(rawResults, meta) {
|
|
550
|
-
// group by the root entity primary key first
|
|
551
|
-
const map = {};
|
|
795
|
+
mergeJoinedResult(rawResults, meta, joinedProps) {
|
|
552
796
|
const res = [];
|
|
553
|
-
|
|
797
|
+
const map = {};
|
|
798
|
+
for (const item of rawResults) {
|
|
554
799
|
const pk = core_1.Utils.getCompositeKeyHash(item, meta);
|
|
555
800
|
if (map[pk]) {
|
|
556
|
-
|
|
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
|
+
}
|
|
557
822
|
}
|
|
558
823
|
else {
|
|
559
|
-
map[pk] =
|
|
824
|
+
map[pk] = item;
|
|
560
825
|
res.push(item);
|
|
561
826
|
}
|
|
562
|
-
}
|
|
827
|
+
}
|
|
563
828
|
return res;
|
|
564
829
|
}
|
|
565
|
-
getFieldsForJoinedLoad(qb, meta, explicitFields, populate = [], parentTableAlias, parentJoinPath) {
|
|
830
|
+
getFieldsForJoinedLoad(qb, meta, explicitFields, exclude, populate = [], options, parentTableAlias, parentJoinPath) {
|
|
566
831
|
const fields = [];
|
|
567
|
-
const joinedProps = this.joinedProps(meta, populate);
|
|
832
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
568
833
|
if (explicitFields?.includes('*')) {
|
|
569
834
|
fields.push('*');
|
|
570
835
|
}
|
|
571
836
|
const shouldHaveColumn = (prop, populate, fields) => {
|
|
572
|
-
if (!this.platform.shouldHaveColumn(prop, populate)) {
|
|
837
|
+
if (!this.platform.shouldHaveColumn(prop, populate, exclude)) {
|
|
573
838
|
return false;
|
|
574
839
|
}
|
|
575
840
|
if (!fields || prop.primary) {
|
|
576
841
|
return !fields?.includes('*');
|
|
577
842
|
}
|
|
578
|
-
return fields.
|
|
843
|
+
return fields.some(f => f === prop.name || f.toString().startsWith(prop.name + '.'));
|
|
579
844
|
};
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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];
|
|
586
856
|
const meta2 = this.metadata.find(prop.type);
|
|
857
|
+
const pivotRefJoin = prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref;
|
|
587
858
|
const tableAlias = qb.getNextAlias(prop.name);
|
|
588
859
|
const field = parentTableAlias ? `${parentTableAlias}.${prop.name}` : prop.name;
|
|
589
|
-
|
|
590
|
-
|
|
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
|
+
}
|
|
591
872
|
const childExplicitFields = explicitFields?.filter(f => core_1.Utils.isPlainObject(f)).map(o => o[prop.name])[0] || [];
|
|
592
873
|
explicitFields?.forEach(f => {
|
|
593
874
|
if (typeof f === 'string' && f.startsWith(`${prop.name}.`)) {
|
|
594
875
|
childExplicitFields.push(f.substring(prop.name.length + 1));
|
|
595
876
|
}
|
|
596
877
|
});
|
|
597
|
-
|
|
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
|
+
}
|
|
598
882
|
});
|
|
599
883
|
return fields;
|
|
600
884
|
}
|
|
@@ -602,24 +886,35 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
602
886
|
* @internal
|
|
603
887
|
*/
|
|
604
888
|
mapPropToFieldNames(qb, prop, tableAlias) {
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
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}`)];
|
|
609
904
|
}
|
|
610
905
|
if (prop.formula) {
|
|
611
|
-
const alias =
|
|
612
|
-
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}`)];
|
|
613
908
|
}
|
|
614
909
|
if (tableAlias) {
|
|
615
|
-
return prop.fieldNames.map(fieldName =>
|
|
910
|
+
return prop.fieldNames.map(fieldName => knex.ref(fieldName).withSchema(tableAlias).as(`${tableAlias}__${fieldName}`));
|
|
616
911
|
}
|
|
617
912
|
return prop.fieldNames;
|
|
618
913
|
}
|
|
619
914
|
/** @internal */
|
|
620
|
-
createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes) {
|
|
915
|
+
createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes, loggerContext) {
|
|
621
916
|
const connectionType = this.resolveConnectionType({ ctx, connectionType: preferredConnectionType });
|
|
622
|
-
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);
|
|
623
918
|
if (!convertCustomTypes) {
|
|
624
919
|
qb.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
|
|
625
920
|
}
|
|
@@ -656,77 +951,100 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
656
951
|
}
|
|
657
952
|
for (const prop of meta.relations) {
|
|
658
953
|
if (collections[prop.name]) {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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);
|
|
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());
|
|
674
958
|
}
|
|
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
959
|
}
|
|
704
960
|
}
|
|
705
961
|
async lockPessimistic(entity, options) {
|
|
706
962
|
const meta = (0, core_1.helper)(entity).__meta;
|
|
707
|
-
const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).
|
|
963
|
+
const qb = this.createQueryBuilder(entity.constructor.name, options.ctx).withSchema(options.schema ?? meta.schema);
|
|
708
964
|
const cond = core_1.Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
|
|
709
|
-
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);
|
|
710
966
|
await this.rethrow(qb.execute());
|
|
711
967
|
}
|
|
712
|
-
|
|
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) {
|
|
713
979
|
const orderBy = [];
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
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
|
+
if (!prop) {
|
|
985
|
+
throw new Error(`Trying to order by not existing property ${meta.className}.${propName}`);
|
|
986
|
+
}
|
|
987
|
+
const meta2 = this.metadata.find(prop.type);
|
|
988
|
+
const childOrder = orderHint[prop.name];
|
|
989
|
+
let path = `${parentPath}.${propName}`;
|
|
990
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && typeof childOrder !== 'object') {
|
|
991
|
+
path += '[pivot]';
|
|
992
|
+
}
|
|
993
|
+
const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
|
|
994
|
+
const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true }) ?? parentAlias;
|
|
995
|
+
if (!join && parentAlias === qb.alias) {
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
if (![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind) && typeof childOrder === 'object') {
|
|
999
|
+
const children = this.buildPopulateOrderBy(qb, meta2, core_1.Utils.asArray(childOrder), path, propAlias);
|
|
1000
|
+
orderBy.push(...children);
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && join) {
|
|
1004
|
+
if (prop.fixedOrderColumn) {
|
|
1005
|
+
orderBy.push({ [`${join.alias}.${prop.fixedOrderColumn}`]: childOrder });
|
|
1006
|
+
}
|
|
1007
|
+
else {
|
|
1008
|
+
for (const col of prop.inverseJoinColumns) {
|
|
1009
|
+
orderBy.push({ [`${join.ownerAlias}.${col}`]: childOrder });
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
const order = typeof childOrder === 'object' ? childOrder[propName] : childOrder;
|
|
1015
|
+
orderBy.push({ [`${propAlias}.${propName}`]: order });
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return orderBy;
|
|
1019
|
+
}
|
|
1020
|
+
buildJoinedPropsOrderBy(qb, meta, populate, options, parentPath) {
|
|
1021
|
+
const orderBy = [];
|
|
1022
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
1023
|
+
for (const hint of joinedProps) {
|
|
1024
|
+
const [propName, ref] = hint.field.split(':', 2);
|
|
1025
|
+
const prop = meta.properties[propName];
|
|
717
1026
|
const propOrderBy = prop.orderBy;
|
|
718
|
-
|
|
719
|
-
|
|
1027
|
+
let path = `${parentPath}.${propName}`;
|
|
1028
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref) {
|
|
1029
|
+
path += '[pivot]';
|
|
1030
|
+
}
|
|
1031
|
+
const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
|
|
1032
|
+
const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true });
|
|
1033
|
+
const meta2 = this.metadata.find(prop.type);
|
|
1034
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && prop.fixedOrder && join) {
|
|
1035
|
+
const alias = ref ? propAlias : join.ownerAlias;
|
|
1036
|
+
orderBy.push({ [`${alias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC });
|
|
1037
|
+
}
|
|
720
1038
|
if (propOrderBy) {
|
|
721
1039
|
core_1.Utils.keys(propOrderBy).forEach(field => {
|
|
722
1040
|
orderBy.push({ [`${propAlias}.${field}`]: propOrderBy[field] });
|
|
723
1041
|
});
|
|
724
1042
|
}
|
|
725
|
-
if (
|
|
726
|
-
const
|
|
727
|
-
orderBy.push(...
|
|
1043
|
+
if (hint.children) {
|
|
1044
|
+
const buildJoinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, meta2, hint.children, options, path);
|
|
1045
|
+
orderBy.push(...buildJoinedPropsOrderBy);
|
|
728
1046
|
}
|
|
729
|
-
}
|
|
1047
|
+
}
|
|
730
1048
|
return orderBy;
|
|
731
1049
|
}
|
|
732
1050
|
normalizeFields(fields, prefix = '') {
|
|
@@ -750,7 +1068,7 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
750
1068
|
}
|
|
751
1069
|
if (prop.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
752
1070
|
if (prop.object) {
|
|
753
|
-
ret.push(prop.
|
|
1071
|
+
ret.push(prop.name);
|
|
754
1072
|
return;
|
|
755
1073
|
}
|
|
756
1074
|
const parts = field.split('.');
|
|
@@ -762,21 +1080,18 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
762
1080
|
}
|
|
763
1081
|
return;
|
|
764
1082
|
}
|
|
765
|
-
|
|
766
|
-
ret.push(...prop.fieldNames);
|
|
767
|
-
}
|
|
1083
|
+
ret.push(prop.name);
|
|
768
1084
|
}
|
|
769
|
-
buildFields(meta, populate, joinedProps, qb,
|
|
1085
|
+
buildFields(meta, populate, joinedProps, qb, alias, options) {
|
|
770
1086
|
const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => p.field === prop.name || p.all));
|
|
771
1087
|
const hasLazyFormulas = meta.props.some(p => p.lazy && p.formula);
|
|
772
|
-
const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL);
|
|
773
|
-
const hasExplicitFields = !!fields;
|
|
1088
|
+
const requiresSQLConversion = meta.props.some(p => p.customType?.convertToJSValueSQL && p.persist !== false);
|
|
1089
|
+
const hasExplicitFields = !!options.fields;
|
|
774
1090
|
const ret = [];
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
for (const field of this.normalizeFields(fields)) {
|
|
1091
|
+
let addFormulas = false;
|
|
1092
|
+
// handle root entity properties first, this is used for both strategies in the same way
|
|
1093
|
+
if (options.fields) {
|
|
1094
|
+
for (const field of this.normalizeFields(options.fields)) {
|
|
780
1095
|
if (field === '*') {
|
|
781
1096
|
ret.push('*');
|
|
782
1097
|
continue;
|
|
@@ -792,28 +1107,39 @@ class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
|
792
1107
|
});
|
|
793
1108
|
this.processField(meta, prop, parts.join('.'), ret, populate, joinedProps, qb);
|
|
794
1109
|
}
|
|
795
|
-
|
|
1110
|
+
if (!options.fields.includes('*') && !options.fields.includes(`${qb.alias}.*`)) {
|
|
1111
|
+
ret.unshift(...meta.primaryKeys.filter(pk => !options.fields.includes(pk)));
|
|
1112
|
+
}
|
|
796
1113
|
}
|
|
797
|
-
else if (lazyProps.
|
|
798
|
-
const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, false));
|
|
1114
|
+
else if (!core_1.Utils.isEmpty(options.exclude) || lazyProps.some(p => !p.formula)) {
|
|
1115
|
+
const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, options.exclude, false));
|
|
799
1116
|
ret.push(...core_1.Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
|
|
1117
|
+
addFormulas = true;
|
|
800
1118
|
}
|
|
801
1119
|
else if (hasLazyFormulas || requiresSQLConversion) {
|
|
802
1120
|
ret.push('*');
|
|
1121
|
+
addFormulas = true;
|
|
803
1122
|
}
|
|
804
|
-
|
|
1123
|
+
else {
|
|
1124
|
+
ret.push('*');
|
|
1125
|
+
}
|
|
1126
|
+
if (ret.length > 0 && !hasExplicitFields && addFormulas) {
|
|
805
1127
|
meta.props
|
|
806
1128
|
.filter(prop => prop.formula && !lazyProps.includes(prop))
|
|
807
1129
|
.forEach(prop => {
|
|
808
|
-
const
|
|
809
|
-
const aliased =
|
|
810
|
-
ret.push(`${prop.formula(
|
|
1130
|
+
const a = this.connection.getKnex().ref(alias).toString();
|
|
1131
|
+
const aliased = this.connection.getKnex().ref(prop.fieldNames[0]).toString();
|
|
1132
|
+
ret.push((0, core_1.raw)(`${prop.formula(a)} as ${aliased}`));
|
|
811
1133
|
});
|
|
812
1134
|
meta.props
|
|
813
|
-
.filter(prop => prop.
|
|
1135
|
+
.filter(prop => prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL)
|
|
814
1136
|
.forEach(prop => ret.push(prop.name));
|
|
815
1137
|
}
|
|
816
|
-
|
|
1138
|
+
// add joined relations after the root entity fields
|
|
1139
|
+
if (joinedProps.length > 0) {
|
|
1140
|
+
ret.push(...this.getFieldsForJoinedLoad(qb, meta, options.fields, options.exclude, populate, options, alias));
|
|
1141
|
+
}
|
|
1142
|
+
return core_1.Utils.unique(ret);
|
|
817
1143
|
}
|
|
818
1144
|
}
|
|
819
1145
|
exports.AbstractSqlDriver = AbstractSqlDriver;
|