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