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