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