@yandjin-mikro-orm/knex 6.1.4-rc-sti-changes-1
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 +58 -0
- package/AbstractSqlConnection.js +206 -0
- package/AbstractSqlDriver.d.ts +73 -0
- package/AbstractSqlDriver.js +1378 -0
- package/AbstractSqlPlatform.d.ts +25 -0
- package/AbstractSqlPlatform.js +86 -0
- package/LICENSE +21 -0
- package/MonkeyPatchable.d.ts +11 -0
- package/MonkeyPatchable.js +39 -0
- package/PivotCollectionPersister.d.ts +17 -0
- package/PivotCollectionPersister.js +131 -0
- package/README.md +383 -0
- package/SqlEntityManager.d.ts +25 -0
- package/SqlEntityManager.js +40 -0
- package/SqlEntityRepository.d.ts +24 -0
- package/SqlEntityRepository.js +36 -0
- package/index.d.ts +18 -0
- package/index.js +40 -0
- package/index.mjs +215 -0
- package/package.json +71 -0
- package/query/ArrayCriteriaNode.d.ts +10 -0
- package/query/ArrayCriteriaNode.js +25 -0
- package/query/CriteriaNode.d.ts +31 -0
- package/query/CriteriaNode.js +147 -0
- package/query/CriteriaNodeFactory.d.ts +12 -0
- package/query/CriteriaNodeFactory.js +90 -0
- package/query/ObjectCriteriaNode.d.ts +15 -0
- package/query/ObjectCriteriaNode.js +233 -0
- package/query/QueryBuilder.d.ts +291 -0
- package/query/QueryBuilder.js +1445 -0
- package/query/QueryBuilderHelper.d.ts +64 -0
- package/query/QueryBuilderHelper.js +747 -0
- package/query/ScalarCriteriaNode.d.ts +10 -0
- package/query/ScalarCriteriaNode.js +56 -0
- package/query/enums.d.ts +15 -0
- package/query/enums.js +20 -0
- package/query/index.d.ts +8 -0
- package/query/index.js +24 -0
- package/schema/DatabaseSchema.d.ts +29 -0
- package/schema/DatabaseSchema.js +140 -0
- package/schema/DatabaseTable.d.ts +61 -0
- package/schema/DatabaseTable.js +727 -0
- package/schema/SchemaComparator.d.ts +59 -0
- package/schema/SchemaComparator.js +603 -0
- package/schema/SchemaHelper.d.ts +56 -0
- package/schema/SchemaHelper.js +274 -0
- package/schema/SqlSchemaGenerator.d.ts +63 -0
- package/schema/SqlSchemaGenerator.js +598 -0
- package/schema/index.d.ts +5 -0
- package/schema/index.js +21 -0
- package/typings.d.ts +174 -0
- package/typings.js +2 -0
|
@@ -0,0 +1,1378 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AbstractSqlDriver = void 0;
|
|
4
|
+
const core_1 = require("@yandjin-mikro-orm/core");
|
|
5
|
+
const query_1 = require("./query");
|
|
6
|
+
const SqlEntityManager_1 = require("./SqlEntityManager");
|
|
7
|
+
const PivotCollectionPersister_1 = require("./PivotCollectionPersister");
|
|
8
|
+
class AbstractSqlDriver extends core_1.DatabaseDriver {
|
|
9
|
+
[core_1.EntityManagerType];
|
|
10
|
+
connection;
|
|
11
|
+
replicas = [];
|
|
12
|
+
platform;
|
|
13
|
+
constructor(config, platform, connection, connector) {
|
|
14
|
+
super(config, connector);
|
|
15
|
+
this.connection = new connection(this.config);
|
|
16
|
+
this.replicas = this.createReplicas((conf) => new connection(this.config, conf, "read"));
|
|
17
|
+
this.platform = platform;
|
|
18
|
+
}
|
|
19
|
+
getPlatform() {
|
|
20
|
+
return this.platform;
|
|
21
|
+
}
|
|
22
|
+
createEntityManager(useContext) {
|
|
23
|
+
const EntityManagerClass = this.config.get("entityManager", SqlEntityManager_1.SqlEntityManager);
|
|
24
|
+
return new EntityManagerClass(this.config, this, this.metadata, useContext);
|
|
25
|
+
}
|
|
26
|
+
async find(entityName, where, options = {}) {
|
|
27
|
+
options = { populate: [], orderBy: [], ...options };
|
|
28
|
+
const meta = this.metadata.find(entityName);
|
|
29
|
+
if (meta?.virtual) {
|
|
30
|
+
return this.findVirtual(entityName, where, options);
|
|
31
|
+
}
|
|
32
|
+
const populate = this.autoJoinOneToOneOwner(meta, options.populate, options.fields);
|
|
33
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
34
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging);
|
|
35
|
+
const fields = this.buildFields(meta, populate, joinedProps, qb, qb.alias, options);
|
|
36
|
+
const orderBy = this.buildOrderBy(qb, meta, populate, options);
|
|
37
|
+
const populateWhere = this.buildPopulateWhere(meta, joinedProps, options);
|
|
38
|
+
core_1.Utils.asArray(options.flags).forEach((flag) => qb.setFlag(flag));
|
|
39
|
+
if (core_1.Utils.isPrimaryKey(where, meta.compositePK)) {
|
|
40
|
+
where = {
|
|
41
|
+
[core_1.Utils.getPrimaryKeyHash(meta.primaryKeys)]: where,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const { first, last, before, after } = options;
|
|
45
|
+
const isCursorPagination = [first, last, before, after].some((v) => v != null);
|
|
46
|
+
qb.__populateWhere = options._populateWhere;
|
|
47
|
+
qb.select(fields)
|
|
48
|
+
// only add populateWhere if we are populate-joining, as this will be used to add `on` conditions
|
|
49
|
+
.populate(populate, joinedProps.length > 0 ? populateWhere : undefined)
|
|
50
|
+
.where(where)
|
|
51
|
+
.groupBy(options.groupBy)
|
|
52
|
+
.having(options.having)
|
|
53
|
+
.indexHint(options.indexHint)
|
|
54
|
+
.comment(options.comments)
|
|
55
|
+
.hintComment(options.hintComments)
|
|
56
|
+
.withSchema(this.getSchemaName(meta, options));
|
|
57
|
+
if (isCursorPagination) {
|
|
58
|
+
const { orderBy: newOrderBy, where } = this.processCursorOptions(meta, options, orderBy);
|
|
59
|
+
qb.andWhere(where).orderBy(newOrderBy);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
qb.orderBy(orderBy);
|
|
63
|
+
}
|
|
64
|
+
if (options.limit != null || options.offset != null) {
|
|
65
|
+
qb.limit(options.limit, options.offset);
|
|
66
|
+
}
|
|
67
|
+
if (options.lockMode) {
|
|
68
|
+
qb.setLockMode(options.lockMode, options.lockTableAliases);
|
|
69
|
+
}
|
|
70
|
+
const result = await this.rethrow(qb.execute("all"));
|
|
71
|
+
if (isCursorPagination && !first && !!last) {
|
|
72
|
+
result.reverse();
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
async findOne(entityName, where, options) {
|
|
77
|
+
const opts = { populate: [], ...(options || {}) };
|
|
78
|
+
const meta = this.metadata.find(entityName);
|
|
79
|
+
const populate = this.autoJoinOneToOneOwner(meta, opts.populate, opts.fields);
|
|
80
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
81
|
+
const hasToManyJoins = joinedProps.some((hint) => this.hasToManyJoins(hint, meta));
|
|
82
|
+
if (joinedProps.length === 0 || !hasToManyJoins) {
|
|
83
|
+
opts.limit = 1;
|
|
84
|
+
}
|
|
85
|
+
if (opts.limit > 0 && !opts.flags?.includes(core_1.QueryFlag.DISABLE_PAGINATE)) {
|
|
86
|
+
opts.flags ??= [];
|
|
87
|
+
opts.flags.push(core_1.QueryFlag.DISABLE_PAGINATE);
|
|
88
|
+
}
|
|
89
|
+
const res = await this.find(entityName, where, opts);
|
|
90
|
+
return res[0] || null;
|
|
91
|
+
}
|
|
92
|
+
hasToManyJoins(hint, meta) {
|
|
93
|
+
const [propName] = hint.field.split(":", 2);
|
|
94
|
+
const prop = meta.properties[propName];
|
|
95
|
+
if (prop &&
|
|
96
|
+
[core_1.ReferenceKind.ONE_TO_MANY, core_1.ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (hint.children && prop.targetMeta) {
|
|
100
|
+
return hint.children.some((hint) => this.hasToManyJoins(hint, prop.targetMeta));
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
async findVirtual(entityName, where, options) {
|
|
105
|
+
return this.findFromVirtual(entityName, where, options, query_1.QueryType.SELECT);
|
|
106
|
+
}
|
|
107
|
+
async countVirtual(entityName, where, options) {
|
|
108
|
+
return this.findFromVirtual(entityName, where, options, query_1.QueryType.COUNT);
|
|
109
|
+
}
|
|
110
|
+
async findFromVirtual(entityName, where, options, type) {
|
|
111
|
+
const meta = this.metadata.get(entityName);
|
|
112
|
+
/* istanbul ignore next */
|
|
113
|
+
if (!meta.expression) {
|
|
114
|
+
return type === query_1.QueryType.SELECT ? [] : 0;
|
|
115
|
+
}
|
|
116
|
+
if (typeof meta.expression === "string") {
|
|
117
|
+
return this.wrapVirtualExpressionInSubquery(meta, meta.expression, where, options, type);
|
|
118
|
+
}
|
|
119
|
+
const em = this.createEntityManager();
|
|
120
|
+
em.setTransactionContext(options.ctx);
|
|
121
|
+
const res = meta.expression(em, where, options);
|
|
122
|
+
if (typeof res === "string") {
|
|
123
|
+
return this.wrapVirtualExpressionInSubquery(meta, res, where, options, type);
|
|
124
|
+
}
|
|
125
|
+
if (res instanceof query_1.QueryBuilder) {
|
|
126
|
+
return this.wrapVirtualExpressionInSubquery(meta, res.getFormattedQuery(), where, options, type);
|
|
127
|
+
}
|
|
128
|
+
if (core_1.Utils.isObject(res)) {
|
|
129
|
+
const { sql, bindings } = res.toSQL();
|
|
130
|
+
const query = this.platform.formatQuery(sql, bindings);
|
|
131
|
+
return this.wrapVirtualExpressionInSubquery(meta, query, where, options, type);
|
|
132
|
+
}
|
|
133
|
+
/* istanbul ignore next */
|
|
134
|
+
return res;
|
|
135
|
+
}
|
|
136
|
+
async wrapVirtualExpressionInSubquery(meta, expression, where, options, type) {
|
|
137
|
+
const qb = this.createQueryBuilder(meta.className, options?.ctx, options.connectionType, options.convertCustomTypes, options.logging)
|
|
138
|
+
.indexHint(options.indexHint)
|
|
139
|
+
.comment(options.comments)
|
|
140
|
+
.hintComment(options.hintComments);
|
|
141
|
+
if (type !== query_1.QueryType.COUNT) {
|
|
142
|
+
qb.limit(options?.limit, options?.offset);
|
|
143
|
+
if (options.orderBy) {
|
|
144
|
+
qb.orderBy(options.orderBy);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
qb.where(where);
|
|
148
|
+
const kqb = qb.getKnexQuery(false).clear("select");
|
|
149
|
+
if (type === query_1.QueryType.COUNT) {
|
|
150
|
+
kqb.select(this.connection.getKnex().raw("count(*) as count"));
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// select
|
|
154
|
+
kqb.select("*");
|
|
155
|
+
}
|
|
156
|
+
kqb.fromRaw(`(${expression}) as ${this.platform.quoteIdentifier(qb.alias)}`);
|
|
157
|
+
const res = await this.execute(kqb);
|
|
158
|
+
if (type === query_1.QueryType.COUNT) {
|
|
159
|
+
return res[0].count;
|
|
160
|
+
}
|
|
161
|
+
return res.map((row) => this.mapResult(row, meta));
|
|
162
|
+
}
|
|
163
|
+
mapResult(result, meta, populate = [], qb, map = {}) {
|
|
164
|
+
const ret = super.mapResult(result, meta);
|
|
165
|
+
/* istanbul ignore if */
|
|
166
|
+
if (!ret) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
if (qb) {
|
|
170
|
+
// here we map the aliased results (cartesian product) to an object graph
|
|
171
|
+
this.mapJoinedProps(ret, meta, populate, qb, ret, map);
|
|
172
|
+
}
|
|
173
|
+
return ret;
|
|
174
|
+
}
|
|
175
|
+
mapJoinedProps(result, meta, populate, qb, root, map, parentJoinPath) {
|
|
176
|
+
const joinedProps = this.joinedProps(meta, populate);
|
|
177
|
+
joinedProps.forEach((hint) => {
|
|
178
|
+
const [propName, ref] = hint.field.split(":", 2);
|
|
179
|
+
const prop = meta.properties[propName];
|
|
180
|
+
/* istanbul ignore next */
|
|
181
|
+
if (!prop) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const pivotRefJoin = prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref;
|
|
185
|
+
const meta2 = this.metadata.find(prop.type);
|
|
186
|
+
let path = parentJoinPath
|
|
187
|
+
? `${parentJoinPath}.${prop.name}`
|
|
188
|
+
: `${meta.name}.${prop.name}`;
|
|
189
|
+
if (!parentJoinPath) {
|
|
190
|
+
path = "[populate]" + path;
|
|
191
|
+
}
|
|
192
|
+
if (pivotRefJoin) {
|
|
193
|
+
path += "[pivot]";
|
|
194
|
+
}
|
|
195
|
+
const relationAlias = qb.getAliasForJoinPath(path, {
|
|
196
|
+
matchPopulateJoins: true,
|
|
197
|
+
});
|
|
198
|
+
// pivot ref joins via joined strategy need to be handled separately here, as they dont join the target entity
|
|
199
|
+
if (pivotRefJoin) {
|
|
200
|
+
let item;
|
|
201
|
+
if (prop.inverseJoinColumns.length > 1) {
|
|
202
|
+
// composite keys
|
|
203
|
+
item = prop.inverseJoinColumns.map((name) => root[`${relationAlias}__${name}`]);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
const alias = `${relationAlias}__${prop.inverseJoinColumns[0]}`;
|
|
207
|
+
item = root[alias];
|
|
208
|
+
}
|
|
209
|
+
prop.joinColumns.forEach((name) => delete root[`${relationAlias}__${name}`]);
|
|
210
|
+
prop.inverseJoinColumns.forEach((name) => delete root[`${relationAlias}__${name}`]);
|
|
211
|
+
result[prop.name] ??= [];
|
|
212
|
+
if (item) {
|
|
213
|
+
result[prop.name].push(item);
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// If the primary key value for the relation is null, we know we haven't joined to anything
|
|
218
|
+
// and therefore we don't return any record (since all values would be null)
|
|
219
|
+
const hasPK = meta2.primaryKeys.every((pk) => meta2.properties[pk].fieldNames.every((name) => {
|
|
220
|
+
return root[`${relationAlias}__${name}`] != null;
|
|
221
|
+
}));
|
|
222
|
+
if (!hasPK) {
|
|
223
|
+
if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(prop.kind)) {
|
|
224
|
+
result[prop.name] ??= [];
|
|
225
|
+
}
|
|
226
|
+
if ([core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
227
|
+
result[prop.name] ??= null;
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
let relationPojo = {};
|
|
232
|
+
meta2.props
|
|
233
|
+
.filter((prop) => !ref && prop.persist === false && prop.fieldNames)
|
|
234
|
+
.filter((prop) => !prop.lazy || populate.some((p) => p.field === prop.name || p.all))
|
|
235
|
+
.forEach((prop) => {
|
|
236
|
+
/* istanbul ignore if */
|
|
237
|
+
if (prop.fieldNames.length > 1) {
|
|
238
|
+
// composite keys
|
|
239
|
+
relationPojo[prop.name] = prop.fieldNames.map((name) => root[`${relationAlias}__${name}`]);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
const alias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
243
|
+
relationPojo[prop.name] = root[alias];
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
const targetProps = ref
|
|
247
|
+
? meta2.getPrimaryProps()
|
|
248
|
+
: meta2.props.filter((prop) => this.platform.shouldHaveColumn(prop, hint.children || []));
|
|
249
|
+
for (const prop of targetProps) {
|
|
250
|
+
if (prop.fieldNames.length > 1) {
|
|
251
|
+
// composite keys
|
|
252
|
+
const fk = prop.fieldNames.map((name) => root[`${relationAlias}__${name}`]);
|
|
253
|
+
const pk = core_1.Utils.mapFlatCompositePrimaryKey(fk, prop);
|
|
254
|
+
relationPojo[prop.name] = pk.every((val) => val != null)
|
|
255
|
+
? pk
|
|
256
|
+
: null;
|
|
257
|
+
}
|
|
258
|
+
else if (prop.runtimeType === "Date") {
|
|
259
|
+
const alias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
260
|
+
relationPojo[prop.name] = (typeof root[alias] === "string"
|
|
261
|
+
? new Date(root[alias])
|
|
262
|
+
: root[alias]);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
const alias = `${relationAlias}__${prop.fieldNames[0]}`;
|
|
266
|
+
relationPojo[prop.name] = root[alias];
|
|
267
|
+
if (prop.kind === core_1.ReferenceKind.EMBEDDED &&
|
|
268
|
+
(prop.object || meta.embeddable)) {
|
|
269
|
+
const item = (0, core_1.parseJsonSafe)(relationPojo[prop.name]);
|
|
270
|
+
if (Array.isArray(item)) {
|
|
271
|
+
relationPojo[prop.name] = item.map((row) => row == null ? row : this.comparator.mapResult(prop.type, row));
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
relationPojo[prop.name] =
|
|
275
|
+
item == null
|
|
276
|
+
? item
|
|
277
|
+
: this.comparator.mapResult(prop.type, item);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// properties can be mapped to multiple places, e.g. when sharing a column in multiple FKs,
|
|
283
|
+
// so we need to delete them after everything is mapped from given level
|
|
284
|
+
for (const prop of targetProps) {
|
|
285
|
+
prop.fieldNames.map((name) => delete root[`${relationAlias}__${name}`]);
|
|
286
|
+
}
|
|
287
|
+
if (ref) {
|
|
288
|
+
const tmp = Object.values(relationPojo);
|
|
289
|
+
/* istanbul ignore next */
|
|
290
|
+
relationPojo = (meta2.compositePK ? tmp : tmp[0]);
|
|
291
|
+
}
|
|
292
|
+
if ([core_1.ReferenceKind.MANY_TO_MANY, core_1.ReferenceKind.ONE_TO_MANY].includes(prop.kind)) {
|
|
293
|
+
result[prop.name] ??= [];
|
|
294
|
+
result[prop.name].push(relationPojo);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
result[prop.name] = relationPojo;
|
|
298
|
+
}
|
|
299
|
+
const populateChildren = hint.children || [];
|
|
300
|
+
this.mapJoinedProps(relationPojo, meta2, populateChildren, qb, root, map, path);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
async count(entityName, where, options = {}) {
|
|
304
|
+
const meta = this.metadata.find(entityName);
|
|
305
|
+
if (meta?.virtual) {
|
|
306
|
+
return this.countVirtual(entityName, where, options);
|
|
307
|
+
}
|
|
308
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, options.connectionType, false, options.logging)
|
|
309
|
+
.indexHint(options.indexHint)
|
|
310
|
+
.comment(options.comments)
|
|
311
|
+
.hintComment(options.hintComments)
|
|
312
|
+
.groupBy(options.groupBy)
|
|
313
|
+
.having(options.having)
|
|
314
|
+
.populate(options.populate ?? [])
|
|
315
|
+
.withSchema(this.getSchemaName(meta, options))
|
|
316
|
+
.where(where);
|
|
317
|
+
return this.rethrow(qb.getCount());
|
|
318
|
+
}
|
|
319
|
+
async nativeInsert(entityName, data, options = {}) {
|
|
320
|
+
options.convertCustomTypes ??= true;
|
|
321
|
+
const meta = this.metadata.find(entityName);
|
|
322
|
+
const collections = this.extractManyToMany(entityName, data);
|
|
323
|
+
const pks = meta?.primaryKeys ?? [
|
|
324
|
+
this.config.getNamingStrategy().referenceColumnName(),
|
|
325
|
+
];
|
|
326
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, "write", options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
|
|
327
|
+
const res = await this.rethrow(qb.insert(data).execute("run", false));
|
|
328
|
+
res.row = res.row || {};
|
|
329
|
+
let pk;
|
|
330
|
+
if (pks.length > 1) {
|
|
331
|
+
// owner has composite pk
|
|
332
|
+
pk = core_1.Utils.getPrimaryKeyCond(data, pks);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
/* istanbul ignore next */
|
|
336
|
+
res.insertId = data[pks[0]] ?? res.insertId ?? res.row[pks[0]];
|
|
337
|
+
pk = [res.insertId];
|
|
338
|
+
}
|
|
339
|
+
await this.processManyToMany(meta, pk, collections, false, options);
|
|
340
|
+
return res;
|
|
341
|
+
}
|
|
342
|
+
async nativeInsertMany(entityName, data, options = {}) {
|
|
343
|
+
options.processCollections ??= true;
|
|
344
|
+
options.convertCustomTypes ??= true;
|
|
345
|
+
const meta = this.metadata.find(entityName)?.root;
|
|
346
|
+
const collections = options.processCollections
|
|
347
|
+
? data.map((d) => this.extractManyToMany(entityName, d))
|
|
348
|
+
: [];
|
|
349
|
+
const pks = this.getPrimaryKeyFields(entityName);
|
|
350
|
+
const set = new Set();
|
|
351
|
+
data.forEach((row) => core_1.Utils.keys(row).forEach((k) => set.add(k)));
|
|
352
|
+
const props = [...set].map((name) => meta?.properties[name] ?? { name, fieldNames: [name] });
|
|
353
|
+
let fields = core_1.Utils.flatten(props.map((prop) => prop.fieldNames));
|
|
354
|
+
const duplicates = core_1.Utils.findDuplicates(fields);
|
|
355
|
+
const params = [];
|
|
356
|
+
if (duplicates.length) {
|
|
357
|
+
fields = core_1.Utils.unique(fields);
|
|
358
|
+
}
|
|
359
|
+
/* istanbul ignore next */
|
|
360
|
+
const tableName = meta
|
|
361
|
+
? this.getTableName(meta, options)
|
|
362
|
+
: this.platform.quoteIdentifier(entityName);
|
|
363
|
+
let sql = `insert into ${tableName} `;
|
|
364
|
+
sql +=
|
|
365
|
+
fields.length > 0
|
|
366
|
+
? "(" +
|
|
367
|
+
fields.map((k) => this.platform.quoteIdentifier(k)).join(", ") +
|
|
368
|
+
")"
|
|
369
|
+
: `(${this.platform.quoteIdentifier(pks[0])})`;
|
|
370
|
+
if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
|
|
371
|
+
sql += " values ";
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
sql +=
|
|
375
|
+
" " +
|
|
376
|
+
data
|
|
377
|
+
.map(() => `select null as ${this.platform.quoteIdentifier(pks[0])}`)
|
|
378
|
+
.join(" union all ");
|
|
379
|
+
}
|
|
380
|
+
const addParams = (prop, row) => {
|
|
381
|
+
let value = row[prop.name];
|
|
382
|
+
if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
|
|
383
|
+
if (prop.array) {
|
|
384
|
+
for (let i = 0; i < value.length; i++) {
|
|
385
|
+
const item = value[i];
|
|
386
|
+
value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (options.convertCustomTypes && prop.customType) {
|
|
394
|
+
params.push(prop.customType.convertToDatabaseValue(value, this.platform, {
|
|
395
|
+
key: prop.name,
|
|
396
|
+
mode: "query-data",
|
|
397
|
+
}));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
params.push(value);
|
|
401
|
+
};
|
|
402
|
+
if (fields.length > 0 || this.platform.usesDefaultKeyword()) {
|
|
403
|
+
sql += data
|
|
404
|
+
.map((row) => {
|
|
405
|
+
const keys = [];
|
|
406
|
+
const usedDups = [];
|
|
407
|
+
props.forEach((prop) => {
|
|
408
|
+
if (prop.fieldNames.length > 1) {
|
|
409
|
+
const param = core_1.Utils.flatten([
|
|
410
|
+
...(row[prop.name] ?? prop.fieldNames.map(() => null)),
|
|
411
|
+
]);
|
|
412
|
+
const key = param.map(() => "?");
|
|
413
|
+
prop.fieldNames.forEach((field, idx) => {
|
|
414
|
+
if (!duplicates.includes(field) || !usedDups.includes(field)) {
|
|
415
|
+
params.push(param[idx]);
|
|
416
|
+
keys.push(key[idx]);
|
|
417
|
+
usedDups.push(field);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
const field = prop.fieldNames[0];
|
|
423
|
+
if (!duplicates.includes(field) || !usedDups.includes(field)) {
|
|
424
|
+
if (prop.customType &&
|
|
425
|
+
!prop.object &&
|
|
426
|
+
"convertToDatabaseValueSQL" in prop.customType &&
|
|
427
|
+
!this.platform.isRaw(row[prop.name])) {
|
|
428
|
+
keys.push(prop.customType.convertToDatabaseValueSQL("?", this.platform));
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
keys.push("?");
|
|
432
|
+
}
|
|
433
|
+
addParams(prop, row);
|
|
434
|
+
usedDups.push(field);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
return "(" + (keys.join(", ") || "default") + ")";
|
|
439
|
+
})
|
|
440
|
+
.join(", ");
|
|
441
|
+
}
|
|
442
|
+
if (meta && this.platform.usesReturningStatement()) {
|
|
443
|
+
const returningProps = meta.props
|
|
444
|
+
.filter((prop) => (prop.persist !== false && prop.defaultRaw) ||
|
|
445
|
+
prop.autoincrement ||
|
|
446
|
+
prop.generated)
|
|
447
|
+
.filter((prop) => !(prop.name in data[0]) || core_1.Utils.isRawSql(data[0][prop.name]));
|
|
448
|
+
const returningFields = core_1.Utils.flatten(returningProps.map((prop) => prop.fieldNames));
|
|
449
|
+
/* istanbul ignore next */
|
|
450
|
+
sql +=
|
|
451
|
+
returningFields.length > 0
|
|
452
|
+
? ` returning ${returningFields.map((field) => this.platform.quoteIdentifier(field)).join(", ")}`
|
|
453
|
+
: "";
|
|
454
|
+
}
|
|
455
|
+
const res = await this.execute(sql, params, "run", options.ctx);
|
|
456
|
+
let pk;
|
|
457
|
+
/* istanbul ignore next */
|
|
458
|
+
if (pks.length > 1) {
|
|
459
|
+
// owner has composite pk
|
|
460
|
+
pk = data.map((d) => core_1.Utils.getPrimaryKeyCond(d, pks));
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
res.row ??= {};
|
|
464
|
+
res.rows ??= [];
|
|
465
|
+
pk = data
|
|
466
|
+
.map((d, i) => d[pks[0]] ?? res.rows[i]?.[pks[0]])
|
|
467
|
+
.map((d) => [d]);
|
|
468
|
+
res.insertId = res.insertId || res.row[pks[0]];
|
|
469
|
+
}
|
|
470
|
+
for (let i = 0; i < collections.length; i++) {
|
|
471
|
+
await this.processManyToMany(meta, pk[i], collections[i], false, options);
|
|
472
|
+
}
|
|
473
|
+
return res;
|
|
474
|
+
}
|
|
475
|
+
async nativeUpdate(entityName, where, data, options = {}) {
|
|
476
|
+
options.convertCustomTypes ??= true;
|
|
477
|
+
const meta = this.metadata.find(entityName);
|
|
478
|
+
const pks = this.getPrimaryKeyFields(entityName);
|
|
479
|
+
const collections = this.extractManyToMany(entityName, data);
|
|
480
|
+
let res = { affectedRows: 0, insertId: 0, row: {} };
|
|
481
|
+
if (core_1.Utils.isPrimaryKey(where) && pks.length === 1) {
|
|
482
|
+
/* istanbul ignore next */
|
|
483
|
+
where = { [meta?.primaryKeys[0] ?? pks[0]]: where };
|
|
484
|
+
}
|
|
485
|
+
if (core_1.Utils.hasObjectKeys(data)) {
|
|
486
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, "write", options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
|
|
487
|
+
if (options.upsert) {
|
|
488
|
+
/* istanbul ignore next */
|
|
489
|
+
const uniqueFields = options.onConflictFields ??
|
|
490
|
+
(core_1.Utils.isPlainObject(where)
|
|
491
|
+
? core_1.Utils.keys(where)
|
|
492
|
+
: meta.primaryKeys);
|
|
493
|
+
const returning = (0, core_1.getOnConflictReturningFields)(meta, data, uniqueFields, options);
|
|
494
|
+
qb.insert(data)
|
|
495
|
+
.onConflict(uniqueFields)
|
|
496
|
+
.returning(returning);
|
|
497
|
+
if (!options.onConflictAction || options.onConflictAction === "merge") {
|
|
498
|
+
const fields = (0, core_1.getOnConflictFields)(data, uniqueFields, options);
|
|
499
|
+
qb.merge(fields);
|
|
500
|
+
}
|
|
501
|
+
if (options.onConflictAction === "ignore") {
|
|
502
|
+
qb.ignore();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
qb.update(data).where(where);
|
|
507
|
+
// reload generated columns and version fields
|
|
508
|
+
const returning = [];
|
|
509
|
+
meta?.props
|
|
510
|
+
.filter((prop) => (prop.generated && !prop.primary) || prop.version)
|
|
511
|
+
.forEach((prop) => returning.push(prop.name));
|
|
512
|
+
qb.returning(returning);
|
|
513
|
+
}
|
|
514
|
+
res = await this.rethrow(qb.execute("run", false));
|
|
515
|
+
}
|
|
516
|
+
/* istanbul ignore next */
|
|
517
|
+
const pk = pks.map((pk) => core_1.Utils.extractPK(data[pk] || where, meta));
|
|
518
|
+
await this.processManyToMany(meta, pk, collections, true, options);
|
|
519
|
+
return res;
|
|
520
|
+
}
|
|
521
|
+
async nativeUpdateMany(entityName, where, data, options = {}) {
|
|
522
|
+
options.processCollections ??= true;
|
|
523
|
+
options.convertCustomTypes ??= true;
|
|
524
|
+
const meta = this.metadata.get(entityName);
|
|
525
|
+
if (options.upsert) {
|
|
526
|
+
const uniqueFields = options.onConflictFields ??
|
|
527
|
+
(core_1.Utils.isPlainObject(where[0])
|
|
528
|
+
? Object.keys(where[0]).flatMap((key) => core_1.Utils.splitPrimaryKeys(key))
|
|
529
|
+
: meta.primaryKeys);
|
|
530
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, "write", options.convertCustomTypes).withSchema(this.getSchemaName(meta, options));
|
|
531
|
+
const returning = (0, core_1.getOnConflictReturningFields)(meta, data[0], uniqueFields, options);
|
|
532
|
+
qb.insert(data)
|
|
533
|
+
.onConflict(uniqueFields)
|
|
534
|
+
.returning(returning);
|
|
535
|
+
if (!options.onConflictAction || options.onConflictAction === "merge") {
|
|
536
|
+
const fields = (0, core_1.getOnConflictFields)(data[0], uniqueFields, options);
|
|
537
|
+
qb.merge(fields);
|
|
538
|
+
}
|
|
539
|
+
if (options.onConflictAction === "ignore") {
|
|
540
|
+
qb.ignore();
|
|
541
|
+
}
|
|
542
|
+
return this.rethrow(qb.execute("run", false));
|
|
543
|
+
}
|
|
544
|
+
const collections = options.processCollections
|
|
545
|
+
? data.map((d) => this.extractManyToMany(entityName, d))
|
|
546
|
+
: [];
|
|
547
|
+
const keys = new Set();
|
|
548
|
+
const fields = new Set();
|
|
549
|
+
const returning = new Set();
|
|
550
|
+
data.forEach((row) => {
|
|
551
|
+
core_1.Utils.keys(row).forEach((k) => {
|
|
552
|
+
keys.add(k);
|
|
553
|
+
if (core_1.Utils.isRawSql(row[k])) {
|
|
554
|
+
returning.add(k);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
// reload generated columns and version fields
|
|
559
|
+
meta?.props
|
|
560
|
+
.filter((prop) => (prop.generated && !prop.primary) || prop.version)
|
|
561
|
+
.forEach((prop) => returning.add(prop.name));
|
|
562
|
+
const pkCond = core_1.Utils.flatten(meta.primaryKeys.map((pk) => meta.properties[pk].fieldNames))
|
|
563
|
+
.map((pk) => `${this.platform.quoteIdentifier(pk)} = ?`)
|
|
564
|
+
.join(" and ");
|
|
565
|
+
const params = [];
|
|
566
|
+
let sql = `update ${this.getTableName(meta, options)} set `;
|
|
567
|
+
const addParams = (prop, value) => {
|
|
568
|
+
if (prop.kind === core_1.ReferenceKind.EMBEDDED && prop.object) {
|
|
569
|
+
if (prop.array) {
|
|
570
|
+
for (let i = 0; i < value.length; i++) {
|
|
571
|
+
const item = value[i];
|
|
572
|
+
value[i] = this.mapDataToFieldNames(item, false, prop.embeddedProps, options.convertCustomTypes);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
value = this.mapDataToFieldNames(value, false, prop.embeddedProps, options.convertCustomTypes);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
params.push(value);
|
|
580
|
+
};
|
|
581
|
+
keys.forEach((key) => {
|
|
582
|
+
const prop = meta.properties[key];
|
|
583
|
+
prop.fieldNames.forEach((fieldName, fieldNameIdx) => {
|
|
584
|
+
if (fields.has(fieldName)) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
fields.add(fieldName);
|
|
588
|
+
sql += `${this.platform.quoteIdentifier(fieldName)} = case`;
|
|
589
|
+
where.forEach((cond, idx) => {
|
|
590
|
+
if (key in data[idx]) {
|
|
591
|
+
const pks = core_1.Utils.getOrderedPrimaryKeys(cond, meta);
|
|
592
|
+
sql += ` when (${pkCond}) then `;
|
|
593
|
+
if (prop.customType &&
|
|
594
|
+
!prop.object &&
|
|
595
|
+
"convertToDatabaseValueSQL" in prop.customType &&
|
|
596
|
+
!this.platform.isRaw(data[idx][key])) {
|
|
597
|
+
sql += prop.customType.convertToDatabaseValueSQL("?", this.platform);
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
sql += "?";
|
|
601
|
+
}
|
|
602
|
+
params.push(...pks);
|
|
603
|
+
addParams(prop, prop.fieldNames.length > 1
|
|
604
|
+
? data[idx][key]?.[fieldNameIdx]
|
|
605
|
+
: data[idx][key]);
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
sql += ` else ${this.platform.quoteIdentifier(fieldName)} end, `;
|
|
609
|
+
return sql;
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
if (meta.versionProperty) {
|
|
613
|
+
const versionProperty = meta.properties[meta.versionProperty];
|
|
614
|
+
const quotedFieldName = this.platform.quoteIdentifier(versionProperty.fieldNames[0]);
|
|
615
|
+
sql += `${quotedFieldName} = `;
|
|
616
|
+
if (versionProperty.runtimeType === "Date") {
|
|
617
|
+
sql += this.platform.getCurrentTimestampSQL(versionProperty.length);
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
sql += `${quotedFieldName} + 1`;
|
|
621
|
+
}
|
|
622
|
+
sql += `, `;
|
|
623
|
+
}
|
|
624
|
+
sql = sql.substring(0, sql.length - 2) + " where ";
|
|
625
|
+
const pkProps = meta.primaryKeys.concat(...meta.concurrencyCheckKeys);
|
|
626
|
+
const pks = core_1.Utils.flatten(pkProps.map((pk) => meta.properties[pk].fieldNames));
|
|
627
|
+
sql +=
|
|
628
|
+
pks.length > 1
|
|
629
|
+
? `(${pks.map((pk) => `${this.platform.quoteIdentifier(pk)}`).join(", ")})`
|
|
630
|
+
: this.platform.quoteIdentifier(pks[0]);
|
|
631
|
+
const conds = where.map((cond) => {
|
|
632
|
+
if (core_1.Utils.isPlainObject(cond) && core_1.Utils.getObjectKeysSize(cond) === 1) {
|
|
633
|
+
cond = Object.values(cond)[0];
|
|
634
|
+
}
|
|
635
|
+
if (pks.length > 1) {
|
|
636
|
+
pkProps.forEach((pk) => {
|
|
637
|
+
if (Array.isArray(cond[pk])) {
|
|
638
|
+
params.push(...core_1.Utils.flatten(cond[pk]));
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
params.push(cond[pk]);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
return `(${new Array(pks.length).fill("?").join(", ")})`;
|
|
645
|
+
}
|
|
646
|
+
params.push(cond);
|
|
647
|
+
return "?";
|
|
648
|
+
});
|
|
649
|
+
sql += ` in (${conds.join(", ")})`;
|
|
650
|
+
if (this.platform.usesReturningStatement() && returning.size > 0) {
|
|
651
|
+
const returningFields = core_1.Utils.flatten([...returning].map((prop) => meta.properties[prop].fieldNames));
|
|
652
|
+
/* istanbul ignore next */
|
|
653
|
+
sql +=
|
|
654
|
+
returningFields.length > 0
|
|
655
|
+
? ` returning ${returningFields.map((field) => this.platform.quoteIdentifier(field)).join(", ")}`
|
|
656
|
+
: "";
|
|
657
|
+
}
|
|
658
|
+
const res = await this.rethrow(this.execute(sql, params, "run", options.ctx));
|
|
659
|
+
for (let i = 0; i < collections.length; i++) {
|
|
660
|
+
await this.processManyToMany(meta, where[i], collections[i], false, options);
|
|
661
|
+
}
|
|
662
|
+
return res;
|
|
663
|
+
}
|
|
664
|
+
async nativeDelete(entityName, where, options = {}) {
|
|
665
|
+
const meta = this.metadata.find(entityName);
|
|
666
|
+
const pks = this.getPrimaryKeyFields(entityName);
|
|
667
|
+
if (core_1.Utils.isPrimaryKey(where) && pks.length === 1) {
|
|
668
|
+
where = { [pks[0]]: where };
|
|
669
|
+
}
|
|
670
|
+
const qb = this.createQueryBuilder(entityName, options.ctx, "write", false)
|
|
671
|
+
.delete(where)
|
|
672
|
+
.withSchema(this.getSchemaName(meta, options));
|
|
673
|
+
return this.rethrow(qb.execute("run", false));
|
|
674
|
+
}
|
|
675
|
+
async syncCollections(collections, options) {
|
|
676
|
+
const groups = {};
|
|
677
|
+
for (const coll of collections) {
|
|
678
|
+
const wrapped = (0, core_1.helper)(coll.owner);
|
|
679
|
+
const meta = wrapped.__meta;
|
|
680
|
+
const pks = wrapped.getPrimaryKeys(true);
|
|
681
|
+
const snap = coll.getSnapshot();
|
|
682
|
+
const includes = (arr, item) => !!arr.find((i) => core_1.Utils.equals(i, item));
|
|
683
|
+
const snapshot = snap
|
|
684
|
+
? snap.map((item) => (0, core_1.helper)(item).getPrimaryKeys(true))
|
|
685
|
+
: [];
|
|
686
|
+
const current = coll
|
|
687
|
+
.getItems(false)
|
|
688
|
+
.map((item) => (0, core_1.helper)(item).getPrimaryKeys(true));
|
|
689
|
+
const deleteDiff = snap
|
|
690
|
+
? snapshot.filter((item) => !includes(current, item))
|
|
691
|
+
: true;
|
|
692
|
+
const insertDiff = current.filter((item) => !includes(snapshot, item));
|
|
693
|
+
const target = snapshot
|
|
694
|
+
.filter((item) => includes(current, item))
|
|
695
|
+
.concat(...insertDiff);
|
|
696
|
+
const equals = core_1.Utils.equals(current, target);
|
|
697
|
+
// wrong order if we just delete and insert to the end (only owning sides can have fixed order)
|
|
698
|
+
if (coll.property.owner &&
|
|
699
|
+
coll.property.fixedOrder &&
|
|
700
|
+
!equals &&
|
|
701
|
+
Array.isArray(deleteDiff)) {
|
|
702
|
+
deleteDiff.length = insertDiff.length = 0;
|
|
703
|
+
deleteDiff.push(...snapshot);
|
|
704
|
+
insertDiff.push(...current);
|
|
705
|
+
}
|
|
706
|
+
if (coll.property.kind === core_1.ReferenceKind.ONE_TO_MANY) {
|
|
707
|
+
const cols = coll.property.referencedColumnNames;
|
|
708
|
+
const qb = this.createQueryBuilder(coll.property.type, options?.ctx, "write").withSchema(this.getSchemaName(meta, options));
|
|
709
|
+
if (coll.getSnapshot() === undefined) {
|
|
710
|
+
if (coll.property.orphanRemoval) {
|
|
711
|
+
const kqb = qb
|
|
712
|
+
.delete({ [coll.property.mappedBy]: pks })
|
|
713
|
+
.getKnexQuery()
|
|
714
|
+
.whereNotIn(cols, insertDiff);
|
|
715
|
+
await this.rethrow(this.execute(kqb));
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
const kqb = qb
|
|
719
|
+
.update({ [coll.property.mappedBy]: null })
|
|
720
|
+
.getKnexQuery()
|
|
721
|
+
.whereNotIn(cols, insertDiff);
|
|
722
|
+
await this.rethrow(this.execute(kqb));
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
const kqb = qb
|
|
726
|
+
.update({ [coll.property.mappedBy]: pks })
|
|
727
|
+
.getKnexQuery()
|
|
728
|
+
.whereIn(cols, insertDiff);
|
|
729
|
+
await this.rethrow(this.execute(kqb));
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
/* istanbul ignore next */
|
|
733
|
+
const pivotMeta = this.metadata.find(coll.property.pivotEntity);
|
|
734
|
+
let schema = pivotMeta.schema;
|
|
735
|
+
if (schema === "*") {
|
|
736
|
+
const ownerSchema = wrapped.getSchema() === "*"
|
|
737
|
+
? this.config.get("schema")
|
|
738
|
+
: wrapped.getSchema();
|
|
739
|
+
schema = coll.property.owner ? ownerSchema : this.config.get("schema");
|
|
740
|
+
}
|
|
741
|
+
else if (schema == null) {
|
|
742
|
+
schema = this.config.get("schema");
|
|
743
|
+
}
|
|
744
|
+
const tableName = `${schema ?? "_"}.${pivotMeta.tableName}`;
|
|
745
|
+
const persister = (groups[tableName] ??= new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, schema));
|
|
746
|
+
persister.enqueueUpdate(coll.property, insertDiff, deleteDiff, pks);
|
|
747
|
+
}
|
|
748
|
+
for (const persister of core_1.Utils.values(groups)) {
|
|
749
|
+
await this.rethrow(persister.execute());
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
async loadFromPivotTable(prop, owners, where = {}, orderBy, ctx, options, pivotJoin) {
|
|
753
|
+
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
754
|
+
const pivotProp1 = pivotMeta.relations[prop.owner ? 1 : 0];
|
|
755
|
+
const pivotProp2 = pivotMeta.relations[prop.owner ? 0 : 1];
|
|
756
|
+
const ownerMeta = this.metadata.find(pivotProp2.type);
|
|
757
|
+
options = { ...options };
|
|
758
|
+
const qb = this.createQueryBuilder(prop.pivotEntity, ctx, options.connectionType, undefined, options?.logging)
|
|
759
|
+
.withSchema(this.getSchemaName(pivotMeta, options))
|
|
760
|
+
.indexHint(options.indexHint)
|
|
761
|
+
.comment(options.comments)
|
|
762
|
+
.hintComment(options.hintComments);
|
|
763
|
+
const pivotAlias = qb.alias;
|
|
764
|
+
const pivotKey = pivotProp2.joinColumns
|
|
765
|
+
.map((column) => `${pivotAlias}.${column}`)
|
|
766
|
+
.join(core_1.Utils.PK_SEPARATOR);
|
|
767
|
+
const cond = {
|
|
768
|
+
[pivotKey]: {
|
|
769
|
+
$in: ownerMeta.compositePK ? owners : owners.map((o) => o[0]),
|
|
770
|
+
},
|
|
771
|
+
};
|
|
772
|
+
/* istanbul ignore if */
|
|
773
|
+
if (!core_1.Utils.isEmpty(where) &&
|
|
774
|
+
Object.keys(where).every((k) => core_1.Utils.isOperator(k, false))) {
|
|
775
|
+
where = cond;
|
|
776
|
+
}
|
|
777
|
+
else {
|
|
778
|
+
where = { ...where, ...cond };
|
|
779
|
+
}
|
|
780
|
+
orderBy = this.getPivotOrderBy(prop, pivotProp1, pivotAlias, orderBy);
|
|
781
|
+
const populate = this.autoJoinOneToOneOwner(prop.targetMeta, []);
|
|
782
|
+
const fields = [];
|
|
783
|
+
const k1 = !prop.owner ? "joinColumns" : "inverseJoinColumns";
|
|
784
|
+
const k2 = prop.owner ? "joinColumns" : "inverseJoinColumns";
|
|
785
|
+
const cols = [
|
|
786
|
+
...prop[k1].map((col) => `${pivotAlias}.${col} as fk__${col}`),
|
|
787
|
+
...prop[k2].map((col) => `${pivotAlias}.${col} as fk__${col}`),
|
|
788
|
+
];
|
|
789
|
+
fields.push(...cols);
|
|
790
|
+
if (!pivotJoin) {
|
|
791
|
+
const targetAlias = qb.getNextAlias(prop.targetMeta.tableName);
|
|
792
|
+
const targetSchema = this.getSchemaName(prop.targetMeta, options) ??
|
|
793
|
+
this.platform.getDefaultSchemaName();
|
|
794
|
+
qb.innerJoin(pivotProp1.name, targetAlias, {}, targetSchema);
|
|
795
|
+
const targetFields = this.buildFields(prop.targetMeta, (options.populate ?? []), [], qb, targetAlias, options);
|
|
796
|
+
for (const field of targetFields) {
|
|
797
|
+
const f = field.toString();
|
|
798
|
+
fields.unshift(f.includes(".") ? field : `${targetAlias}.${f}`);
|
|
799
|
+
if (core_1.RawQueryFragment.isKnownFragment(field)) {
|
|
800
|
+
qb.rawFragments.add(f);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
// we need to handle 1:1 owner auto-joins explicitly, as the QB type is the pivot table, not the target
|
|
804
|
+
populate.forEach((hint) => {
|
|
805
|
+
const alias = qb.getNextAlias(prop.targetMeta.tableName);
|
|
806
|
+
qb.leftJoin(`${targetAlias}.${hint.field}`, alias);
|
|
807
|
+
// eslint-disable-next-line dot-notation
|
|
808
|
+
Object.values(qb["_joins"]).forEach((join) => {
|
|
809
|
+
const [propName] = hint.field.split(":", 2);
|
|
810
|
+
if (join.alias === alias && join.prop.name === propName) {
|
|
811
|
+
fields.push(...qb.helper.mapJoinColumns(qb.type, join));
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
qb.select(fields)
|
|
817
|
+
.where({ [pivotProp1.name]: where })
|
|
818
|
+
.orderBy(orderBy)
|
|
819
|
+
.setLockMode(options.lockMode, options.lockTableAliases);
|
|
820
|
+
if (owners.length === 1 &&
|
|
821
|
+
(options.offset != null || options.limit != null)) {
|
|
822
|
+
qb.limit(options.limit, options.offset);
|
|
823
|
+
}
|
|
824
|
+
const res = owners.length
|
|
825
|
+
? await this.rethrow(qb.execute("all", { mergeResults: false, mapResults: false }))
|
|
826
|
+
: [];
|
|
827
|
+
const items = res.map((row) => super.mapResult(row, prop.targetMeta));
|
|
828
|
+
qb.clearRawFragmentsCache();
|
|
829
|
+
const map = {};
|
|
830
|
+
const pkProps = ownerMeta.getPrimaryProps();
|
|
831
|
+
owners.forEach((owner) => {
|
|
832
|
+
const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((_col, idx) => {
|
|
833
|
+
const pkProp = pkProps[idx];
|
|
834
|
+
return pkProp.customType
|
|
835
|
+
? pkProp.customType.convertToJSValue(owner[idx], this.platform)
|
|
836
|
+
: owner[idx];
|
|
837
|
+
}));
|
|
838
|
+
return (map[key] = []);
|
|
839
|
+
});
|
|
840
|
+
items.forEach((item) => {
|
|
841
|
+
const key = core_1.Utils.getPrimaryKeyHash(prop.joinColumns.map((col, idx) => {
|
|
842
|
+
const pkProp = pkProps[idx];
|
|
843
|
+
return pkProp.customType
|
|
844
|
+
? pkProp.customType.convertToJSValue(item[`fk__${col}`], this.platform)
|
|
845
|
+
: item[`fk__${col}`];
|
|
846
|
+
}));
|
|
847
|
+
map[key].push(item);
|
|
848
|
+
prop.joinColumns.forEach((col) => delete item[`fk__${col}`]);
|
|
849
|
+
prop.inverseJoinColumns.forEach((col, idx) => {
|
|
850
|
+
core_1.Utils.renameKey(item, `fk__${col}`, prop.targetMeta.primaryKeys[idx]);
|
|
851
|
+
});
|
|
852
|
+
});
|
|
853
|
+
return map;
|
|
854
|
+
}
|
|
855
|
+
getPivotOrderBy(prop, pivotProp, pivotAlias, orderBy) {
|
|
856
|
+
// FIXME this is ignoring the rest of the array items
|
|
857
|
+
if (!core_1.Utils.isEmpty(orderBy)) {
|
|
858
|
+
return [
|
|
859
|
+
{ [pivotProp.name]: core_1.Utils.asArray(orderBy)[0] },
|
|
860
|
+
];
|
|
861
|
+
}
|
|
862
|
+
if (!core_1.Utils.isEmpty(prop.orderBy)) {
|
|
863
|
+
return [
|
|
864
|
+
{ [pivotProp.name]: core_1.Utils.asArray(prop.orderBy)[0] },
|
|
865
|
+
];
|
|
866
|
+
}
|
|
867
|
+
if (prop.fixedOrder) {
|
|
868
|
+
return [
|
|
869
|
+
{
|
|
870
|
+
[`${pivotAlias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC,
|
|
871
|
+
},
|
|
872
|
+
];
|
|
873
|
+
}
|
|
874
|
+
return [];
|
|
875
|
+
}
|
|
876
|
+
async execute(queryOrKnex, params = [], method = "all", ctx, loggerContext) {
|
|
877
|
+
return this.rethrow(this.connection.execute(queryOrKnex, params, method, ctx, loggerContext));
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* 1:1 owner side needs to be marked for population so QB auto-joins the owner id
|
|
881
|
+
*/
|
|
882
|
+
autoJoinOneToOneOwner(meta, populate, fields = []) {
|
|
883
|
+
if (!this.config.get("autoJoinOneToOneOwner")) {
|
|
884
|
+
return populate;
|
|
885
|
+
}
|
|
886
|
+
const relationsToPopulate = populate.map(({ field }) => field.split(":")[0]);
|
|
887
|
+
const toPopulate = meta.relations
|
|
888
|
+
.filter((prop) => prop.kind === core_1.ReferenceKind.ONE_TO_ONE &&
|
|
889
|
+
!prop.owner &&
|
|
890
|
+
!relationsToPopulate.includes(prop.name))
|
|
891
|
+
.filter((prop) => fields.length === 0 ||
|
|
892
|
+
fields.some((f) => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
|
|
893
|
+
.map((prop) => ({
|
|
894
|
+
field: `${prop.name}:ref`,
|
|
895
|
+
strategy: prop.strategy,
|
|
896
|
+
}));
|
|
897
|
+
return [...populate, ...toPopulate];
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* @internal
|
|
901
|
+
*/
|
|
902
|
+
joinedProps(meta, populate, options) {
|
|
903
|
+
return populate.filter((hint) => {
|
|
904
|
+
const [propName] = hint.field.split(":", 2);
|
|
905
|
+
const prop = meta.properties[propName] || {};
|
|
906
|
+
if (hint.filter && hint.strategy === core_1.LoadStrategy.JOINED) {
|
|
907
|
+
return true;
|
|
908
|
+
}
|
|
909
|
+
if ((options?.strategy ||
|
|
910
|
+
hint.strategy ||
|
|
911
|
+
prop.strategy ||
|
|
912
|
+
this.config.get("loadStrategy")) !== core_1.LoadStrategy.JOINED) {
|
|
913
|
+
return false;
|
|
914
|
+
}
|
|
915
|
+
return ![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind);
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* @internal
|
|
920
|
+
*/
|
|
921
|
+
mergeJoinedResult(rawResults, meta, joinedProps) {
|
|
922
|
+
const res = [];
|
|
923
|
+
const map = {};
|
|
924
|
+
for (const item of rawResults) {
|
|
925
|
+
const pk = core_1.Utils.getCompositeKeyHash(item, meta);
|
|
926
|
+
if (map[pk]) {
|
|
927
|
+
for (const hint of joinedProps) {
|
|
928
|
+
const [propName, ref] = hint.field.split(":", 2);
|
|
929
|
+
const prop = meta.properties[propName];
|
|
930
|
+
if (!item[propName]) {
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref) {
|
|
934
|
+
map[pk][propName] = [
|
|
935
|
+
...map[pk][propName],
|
|
936
|
+
...item[propName],
|
|
937
|
+
];
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
switch (prop.kind) {
|
|
941
|
+
case core_1.ReferenceKind.ONE_TO_MANY:
|
|
942
|
+
case core_1.ReferenceKind.MANY_TO_MANY:
|
|
943
|
+
map[pk][propName] = this.mergeJoinedResult([...map[pk][propName], ...item[propName]], prop.targetMeta, hint.children ?? []);
|
|
944
|
+
break;
|
|
945
|
+
case core_1.ReferenceKind.MANY_TO_ONE:
|
|
946
|
+
case core_1.ReferenceKind.ONE_TO_ONE:
|
|
947
|
+
map[pk][propName] = this.mergeJoinedResult([map[pk][propName], item[propName]], prop.targetMeta, hint.children ?? [])[0];
|
|
948
|
+
break;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
map[pk] = item;
|
|
954
|
+
res.push(item);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return res;
|
|
958
|
+
}
|
|
959
|
+
getFieldsForJoinedLoad(qb, meta, explicitFields, exclude, populate = [], options, parentTableAlias, parentJoinPath) {
|
|
960
|
+
const fields = [];
|
|
961
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
962
|
+
if (explicitFields?.includes("*")) {
|
|
963
|
+
fields.push("*");
|
|
964
|
+
}
|
|
965
|
+
const shouldHaveColumn = (prop, populate, fields) => {
|
|
966
|
+
if (!this.platform.shouldHaveColumn(prop, populate, exclude)) {
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
if (!fields || prop.primary) {
|
|
970
|
+
return !fields?.includes("*");
|
|
971
|
+
}
|
|
972
|
+
return fields.some((f) => f === prop.name || f.toString().startsWith(prop.name + "."));
|
|
973
|
+
};
|
|
974
|
+
const populateWhereAll = options?._populateWhere === "all" ||
|
|
975
|
+
core_1.Utils.isEmpty(options?._populateWhere);
|
|
976
|
+
// root entity is already handled, skip that
|
|
977
|
+
if (parentJoinPath) {
|
|
978
|
+
// alias all fields in the primary table
|
|
979
|
+
meta.props
|
|
980
|
+
.filter((prop) => shouldHaveColumn(prop, populate, explicitFields))
|
|
981
|
+
.forEach((prop) => fields.push(...this.mapPropToFieldNames(qb, prop, parentTableAlias)));
|
|
982
|
+
}
|
|
983
|
+
joinedProps.forEach((hint) => {
|
|
984
|
+
const [propName, ref] = hint.field.split(":", 2);
|
|
985
|
+
const prop = meta.properties[propName];
|
|
986
|
+
// ignore ref joins of known FKs unless it's a filter hint
|
|
987
|
+
if (ref &&
|
|
988
|
+
!hint.filter &&
|
|
989
|
+
(prop.kind === core_1.ReferenceKind.MANY_TO_ONE ||
|
|
990
|
+
(prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner))) {
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
const meta2 = this.metadata.find(prop.type);
|
|
994
|
+
const pivotRefJoin = prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref;
|
|
995
|
+
const tableAlias = qb.getNextAlias(prop.name);
|
|
996
|
+
const field = parentTableAlias
|
|
997
|
+
? `${parentTableAlias}.${prop.name}`
|
|
998
|
+
: prop.name;
|
|
999
|
+
let path = parentJoinPath
|
|
1000
|
+
? `${parentJoinPath}.${prop.name}`
|
|
1001
|
+
: `${meta.name}.${prop.name}`;
|
|
1002
|
+
if (!parentJoinPath &&
|
|
1003
|
+
populateWhereAll &&
|
|
1004
|
+
!hint.filter &&
|
|
1005
|
+
!path.startsWith("[populate]")) {
|
|
1006
|
+
path = "[populate]" + path;
|
|
1007
|
+
}
|
|
1008
|
+
const joinType = pivotRefJoin
|
|
1009
|
+
? query_1.JoinType.pivotJoin
|
|
1010
|
+
: hint.filter && !prop.nullable
|
|
1011
|
+
? query_1.JoinType.innerJoin
|
|
1012
|
+
: query_1.JoinType.leftJoin;
|
|
1013
|
+
qb.join(field, tableAlias, {}, joinType, path);
|
|
1014
|
+
if (pivotRefJoin) {
|
|
1015
|
+
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}`)));
|
|
1016
|
+
}
|
|
1017
|
+
if (prop.kind === core_1.ReferenceKind.ONE_TO_MANY && ref) {
|
|
1018
|
+
fields.push(...this.getFieldsForJoinedLoad(qb, meta2, prop.referencedColumnNames, undefined, hint.children, options, tableAlias, path));
|
|
1019
|
+
}
|
|
1020
|
+
const childExplicitFields = explicitFields
|
|
1021
|
+
?.filter((f) => core_1.Utils.isPlainObject(f))
|
|
1022
|
+
.map((o) => o[prop.name])[0] || [];
|
|
1023
|
+
explicitFields?.forEach((f) => {
|
|
1024
|
+
if (typeof f === "string" && f.startsWith(`${prop.name}.`)) {
|
|
1025
|
+
childExplicitFields.push(f.substring(prop.name.length + 1));
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
const childExclude = exclude
|
|
1029
|
+
? core_1.Utils.extractChildElements(exclude, prop.name)
|
|
1030
|
+
: exclude;
|
|
1031
|
+
if (!ref) {
|
|
1032
|
+
fields.push(...this.getFieldsForJoinedLoad(qb, meta2, childExplicitFields.length === 0 ? undefined : childExplicitFields, childExclude, hint.children, options, tableAlias, path));
|
|
1033
|
+
}
|
|
1034
|
+
else if (hint.filter) {
|
|
1035
|
+
fields.push(field);
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
1038
|
+
return fields;
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* @internal
|
|
1042
|
+
*/
|
|
1043
|
+
mapPropToFieldNames(qb, prop, tableAlias) {
|
|
1044
|
+
const knex = this.connection.getKnex();
|
|
1045
|
+
const aliased = knex
|
|
1046
|
+
.ref(tableAlias
|
|
1047
|
+
? `${tableAlias}__${prop.fieldNames[0]}`
|
|
1048
|
+
: prop.fieldNames[0])
|
|
1049
|
+
.toString();
|
|
1050
|
+
if (tableAlias &&
|
|
1051
|
+
prop.customTypes?.some((type) => type?.convertToJSValueSQL)) {
|
|
1052
|
+
return prop.fieldNames.map((col, idx) => {
|
|
1053
|
+
if (!prop.customTypes[idx]?.convertToJSValueSQL) {
|
|
1054
|
+
return col;
|
|
1055
|
+
}
|
|
1056
|
+
const prefixed = knex.ref(col).withSchema(tableAlias).toString();
|
|
1057
|
+
const aliased = knex.ref(`${tableAlias}__${col}`).toString();
|
|
1058
|
+
return (0, core_1.raw)(`${prop.customTypes[idx].convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`);
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
if (tableAlias && prop.customType?.convertToJSValueSQL) {
|
|
1062
|
+
const prefixed = knex
|
|
1063
|
+
.ref(prop.fieldNames[0])
|
|
1064
|
+
.withSchema(tableAlias)
|
|
1065
|
+
.toString();
|
|
1066
|
+
return [
|
|
1067
|
+
(0, core_1.raw)(`${prop.customType.convertToJSValueSQL(prefixed, this.platform)} as ${aliased}`),
|
|
1068
|
+
];
|
|
1069
|
+
}
|
|
1070
|
+
if (prop.formula) {
|
|
1071
|
+
const alias = knex.ref(tableAlias ?? qb.alias).toString();
|
|
1072
|
+
return [(0, core_1.raw)(`${prop.formula(alias)} as ${aliased}`)];
|
|
1073
|
+
}
|
|
1074
|
+
if (tableAlias) {
|
|
1075
|
+
return prop.fieldNames.map((fieldName) => knex
|
|
1076
|
+
.ref(fieldName)
|
|
1077
|
+
.withSchema(tableAlias)
|
|
1078
|
+
.as(`${tableAlias}__${fieldName}`));
|
|
1079
|
+
}
|
|
1080
|
+
return prop.fieldNames;
|
|
1081
|
+
}
|
|
1082
|
+
/** @internal */
|
|
1083
|
+
createQueryBuilder(entityName, ctx, preferredConnectionType, convertCustomTypes, loggerContext) {
|
|
1084
|
+
const connectionType = this.resolveConnectionType({
|
|
1085
|
+
ctx,
|
|
1086
|
+
connectionType: preferredConnectionType,
|
|
1087
|
+
});
|
|
1088
|
+
const qb = new query_1.QueryBuilder(entityName, this.metadata, this, ctx, undefined, connectionType, undefined, loggerContext);
|
|
1089
|
+
if (!convertCustomTypes) {
|
|
1090
|
+
qb.unsetFlag(core_1.QueryFlag.CONVERT_CUSTOM_TYPES);
|
|
1091
|
+
}
|
|
1092
|
+
return qb;
|
|
1093
|
+
}
|
|
1094
|
+
resolveConnectionType(args) {
|
|
1095
|
+
if (args.ctx) {
|
|
1096
|
+
return "write";
|
|
1097
|
+
}
|
|
1098
|
+
else if (args.connectionType) {
|
|
1099
|
+
return args.connectionType;
|
|
1100
|
+
}
|
|
1101
|
+
else if (this.config.get("preferReadReplicas") === true) {
|
|
1102
|
+
return "read";
|
|
1103
|
+
}
|
|
1104
|
+
return "write";
|
|
1105
|
+
}
|
|
1106
|
+
extractManyToMany(entityName, data) {
|
|
1107
|
+
if (!this.metadata.has(entityName)) {
|
|
1108
|
+
return {};
|
|
1109
|
+
}
|
|
1110
|
+
const ret = {};
|
|
1111
|
+
this.metadata.find(entityName).relations.forEach((prop) => {
|
|
1112
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && data[prop.name]) {
|
|
1113
|
+
ret[prop.name] = data[prop.name].map((item) => core_1.Utils.asArray(item));
|
|
1114
|
+
delete data[prop.name];
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
return ret;
|
|
1118
|
+
}
|
|
1119
|
+
async processManyToMany(meta, pks, collections, clear, options) {
|
|
1120
|
+
if (!meta) {
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
for (const prop of meta.relations) {
|
|
1124
|
+
if (collections[prop.name]) {
|
|
1125
|
+
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
1126
|
+
const persister = new PivotCollectionPersister_1.PivotCollectionPersister(pivotMeta, this, options?.ctx, options?.schema);
|
|
1127
|
+
persister.enqueueUpdate(prop, collections[prop.name], clear, pks);
|
|
1128
|
+
await this.rethrow(persister.execute());
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
async lockPessimistic(entity, options) {
|
|
1133
|
+
const meta = (0, core_1.helper)(entity).__meta;
|
|
1134
|
+
const qb = this.createQueryBuilder(entity.constructor.name, options.ctx, undefined, undefined, options.logging).withSchema(options.schema ?? meta.schema);
|
|
1135
|
+
const cond = core_1.Utils.getPrimaryKeyCond(entity, meta.primaryKeys);
|
|
1136
|
+
qb.select((0, core_1.raw)("1"))
|
|
1137
|
+
.where(cond)
|
|
1138
|
+
.setLockMode(options.lockMode, options.lockTableAliases);
|
|
1139
|
+
await this.rethrow(qb.execute());
|
|
1140
|
+
}
|
|
1141
|
+
buildPopulateWhere(meta, joinedProps, options) {
|
|
1142
|
+
const where = {};
|
|
1143
|
+
for (const hint of joinedProps) {
|
|
1144
|
+
const [propName] = hint.field.split(":", 2);
|
|
1145
|
+
const prop = meta.properties[propName];
|
|
1146
|
+
if (!core_1.Utils.isEmpty(prop.where)) {
|
|
1147
|
+
where[prop.name] = core_1.Utils.copy(prop.where);
|
|
1148
|
+
}
|
|
1149
|
+
if (hint.children) {
|
|
1150
|
+
const inner = this.buildPopulateWhere(prop.targetMeta, hint.children, {});
|
|
1151
|
+
if (!core_1.Utils.isEmpty(inner)) {
|
|
1152
|
+
where[prop.name] ??= {};
|
|
1153
|
+
Object.assign(where[prop.name], inner);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
if (core_1.Utils.isEmpty(options.populateWhere)) {
|
|
1158
|
+
return where;
|
|
1159
|
+
}
|
|
1160
|
+
if (core_1.Utils.isEmpty(where)) {
|
|
1161
|
+
return options.populateWhere;
|
|
1162
|
+
}
|
|
1163
|
+
/* istanbul ignore next */
|
|
1164
|
+
return { $and: [options.populateWhere, where] };
|
|
1165
|
+
}
|
|
1166
|
+
buildOrderBy(qb, meta, populate, options) {
|
|
1167
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
1168
|
+
// `options._populateWhere` is a copy of the value provided by user with a fallback to the global config option
|
|
1169
|
+
// as `options.populateWhere` will be always recomputed to respect filters
|
|
1170
|
+
const populateWhereAll = options._populateWhere !== "infer" &&
|
|
1171
|
+
!core_1.Utils.isEmpty(options._populateWhere);
|
|
1172
|
+
const path = (populateWhereAll ? "[populate]" : "") + meta.className;
|
|
1173
|
+
const populateOrderBy = this.buildPopulateOrderBy(qb, meta, core_1.Utils.asArray(options.populateOrderBy ?? options.orderBy), path, !!options.populateOrderBy);
|
|
1174
|
+
const joinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, meta, joinedProps, options, path);
|
|
1175
|
+
return [
|
|
1176
|
+
...core_1.Utils.asArray(options.orderBy),
|
|
1177
|
+
...populateOrderBy,
|
|
1178
|
+
...joinedPropsOrderBy,
|
|
1179
|
+
];
|
|
1180
|
+
}
|
|
1181
|
+
buildPopulateOrderBy(qb, meta, populateOrderBy, parentPath, explicit, parentAlias = qb.alias) {
|
|
1182
|
+
const orderBy = [];
|
|
1183
|
+
for (let i = 0; i < populateOrderBy.length; i++) {
|
|
1184
|
+
const orderHint = populateOrderBy[i];
|
|
1185
|
+
for (const propName of core_1.Utils.keys(orderHint)) {
|
|
1186
|
+
const raw = core_1.RawQueryFragment.getKnownFragment(propName, explicit);
|
|
1187
|
+
if (raw) {
|
|
1188
|
+
const sql = raw.sql.replace(new RegExp(core_1.ALIAS_REPLACEMENT_RE, "g"), parentAlias);
|
|
1189
|
+
const raw2 = new core_1.RawQueryFragment(sql, raw.params);
|
|
1190
|
+
orderBy.push({
|
|
1191
|
+
[raw2]: orderHint[propName],
|
|
1192
|
+
});
|
|
1193
|
+
continue;
|
|
1194
|
+
}
|
|
1195
|
+
const prop = meta.properties[propName];
|
|
1196
|
+
if (!prop) {
|
|
1197
|
+
throw new Error(`Trying to order by not existing property ${meta.className}.${propName}`);
|
|
1198
|
+
}
|
|
1199
|
+
const meta2 = this.metadata.find(prop.type);
|
|
1200
|
+
const childOrder = orderHint[prop.name];
|
|
1201
|
+
let path = `${parentPath}.${propName}`;
|
|
1202
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY &&
|
|
1203
|
+
typeof childOrder !== "object") {
|
|
1204
|
+
path += "[pivot]";
|
|
1205
|
+
}
|
|
1206
|
+
const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
|
|
1207
|
+
const propAlias = qb.getAliasForJoinPath(join ?? path, { matchPopulateJoins: true }) ??
|
|
1208
|
+
parentAlias;
|
|
1209
|
+
if (!join && parentAlias === qb.alias) {
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
if (![core_1.ReferenceKind.SCALAR, core_1.ReferenceKind.EMBEDDED].includes(prop.kind) &&
|
|
1213
|
+
typeof childOrder === "object") {
|
|
1214
|
+
const children = this.buildPopulateOrderBy(qb, meta2, core_1.Utils.asArray(childOrder), path, explicit, propAlias);
|
|
1215
|
+
orderBy.push(...children);
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && join) {
|
|
1219
|
+
if (prop.fixedOrderColumn) {
|
|
1220
|
+
orderBy.push({
|
|
1221
|
+
[`${join.alias}.${prop.fixedOrderColumn}`]: childOrder,
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1225
|
+
for (const col of prop.inverseJoinColumns) {
|
|
1226
|
+
orderBy.push({
|
|
1227
|
+
[`${join.ownerAlias}.${col}`]: childOrder,
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
const order = typeof childOrder === "object" ? childOrder[propName] : childOrder;
|
|
1234
|
+
orderBy.push({
|
|
1235
|
+
[`${propAlias}.${propName}`]: order,
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
return orderBy;
|
|
1240
|
+
}
|
|
1241
|
+
buildJoinedPropsOrderBy(qb, meta, populate, options, parentPath) {
|
|
1242
|
+
const orderBy = [];
|
|
1243
|
+
const joinedProps = this.joinedProps(meta, populate, options);
|
|
1244
|
+
for (const hint of joinedProps) {
|
|
1245
|
+
const [propName, ref] = hint.field.split(":", 2);
|
|
1246
|
+
const prop = meta.properties[propName];
|
|
1247
|
+
const propOrderBy = prop.orderBy;
|
|
1248
|
+
let path = `${parentPath}.${propName}`;
|
|
1249
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && ref) {
|
|
1250
|
+
path += "[pivot]";
|
|
1251
|
+
}
|
|
1252
|
+
const join = qb.getJoinForPath(path, { matchPopulateJoins: true });
|
|
1253
|
+
const propAlias = qb.getAliasForJoinPath(join ?? path, {
|
|
1254
|
+
matchPopulateJoins: true,
|
|
1255
|
+
});
|
|
1256
|
+
const meta2 = this.metadata.find(prop.type);
|
|
1257
|
+
if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && prop.fixedOrder && join) {
|
|
1258
|
+
const alias = ref ? propAlias : join.ownerAlias;
|
|
1259
|
+
orderBy.push({
|
|
1260
|
+
[`${alias}.${prop.fixedOrderColumn}`]: core_1.QueryOrder.ASC,
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
if (propOrderBy) {
|
|
1264
|
+
core_1.Utils.keys(propOrderBy).forEach((field) => {
|
|
1265
|
+
orderBy.push({
|
|
1266
|
+
[`${propAlias}.${field}`]: propOrderBy[field],
|
|
1267
|
+
});
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
if (hint.children) {
|
|
1271
|
+
const buildJoinedPropsOrderBy = this.buildJoinedPropsOrderBy(qb, meta2, hint.children, options, path);
|
|
1272
|
+
orderBy.push(...buildJoinedPropsOrderBy);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
return orderBy;
|
|
1276
|
+
}
|
|
1277
|
+
normalizeFields(fields, prefix = "") {
|
|
1278
|
+
const ret = [];
|
|
1279
|
+
for (const field of fields) {
|
|
1280
|
+
if (typeof field === "string") {
|
|
1281
|
+
ret.push(prefix + field);
|
|
1282
|
+
continue;
|
|
1283
|
+
}
|
|
1284
|
+
if (core_1.Utils.isPlainObject(field)) {
|
|
1285
|
+
for (const key of Object.keys(field)) {
|
|
1286
|
+
ret.push(...this.normalizeFields(field[key], key + "."));
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return ret;
|
|
1291
|
+
}
|
|
1292
|
+
processField(meta, prop, field, ret, populate, joinedProps, qb) {
|
|
1293
|
+
if (!prop || (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.owner)) {
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
if (prop.kind === core_1.ReferenceKind.EMBEDDED) {
|
|
1297
|
+
if (prop.object) {
|
|
1298
|
+
ret.push(prop.name);
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
const parts = field.split(".");
|
|
1302
|
+
const top = parts.shift();
|
|
1303
|
+
for (const key of Object.keys(prop.embeddedProps)) {
|
|
1304
|
+
if (!top || key === top) {
|
|
1305
|
+
this.processField(meta, prop.embeddedProps[key], parts.join("."), ret, populate, joinedProps, qb);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
ret.push(prop.name);
|
|
1311
|
+
}
|
|
1312
|
+
buildFields(meta, populate, joinedProps, qb, alias, options) {
|
|
1313
|
+
const lazyProps = meta.props.filter((prop) => prop.lazy && !populate.some((p) => p.field === prop.name || p.all));
|
|
1314
|
+
const hasLazyFormulas = meta.props.some((p) => p.lazy && p.formula);
|
|
1315
|
+
const requiresSQLConversion = meta.props.some((p) => p.customType?.convertToJSValueSQL && p.persist !== false);
|
|
1316
|
+
const hasExplicitFields = !!options.fields;
|
|
1317
|
+
const ret = [];
|
|
1318
|
+
let addFormulas = false;
|
|
1319
|
+
// handle root entity properties first, this is used for both strategies in the same way
|
|
1320
|
+
if (options.fields) {
|
|
1321
|
+
for (const field of this.normalizeFields(options.fields)) {
|
|
1322
|
+
if (field === "*") {
|
|
1323
|
+
ret.push("*");
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
const parts = field.split(".");
|
|
1327
|
+
const rootPropName = parts.shift(); // first one is the `prop`
|
|
1328
|
+
const prop = core_1.QueryHelper.findProperty(rootPropName, {
|
|
1329
|
+
metadata: this.metadata,
|
|
1330
|
+
platform: this.platform,
|
|
1331
|
+
entityName: meta.className,
|
|
1332
|
+
where: {},
|
|
1333
|
+
aliasMap: qb.getAliasMap(),
|
|
1334
|
+
});
|
|
1335
|
+
this.processField(meta, prop, parts.join("."), ret, populate, joinedProps, qb);
|
|
1336
|
+
}
|
|
1337
|
+
if (!options.fields.includes("*") &&
|
|
1338
|
+
!options.fields.includes(`${qb.alias}.*`)) {
|
|
1339
|
+
ret.unshift(...meta.primaryKeys.filter((pk) => !options.fields.includes(pk)));
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
else if (!core_1.Utils.isEmpty(options.exclude) ||
|
|
1343
|
+
lazyProps.some((p) => !p.formula)) {
|
|
1344
|
+
const props = meta.props.filter((prop) => this.platform.shouldHaveColumn(prop, populate, options.exclude, false));
|
|
1345
|
+
ret.push(...props.filter((p) => !lazyProps.includes(p)).map((p) => p.name));
|
|
1346
|
+
addFormulas = true;
|
|
1347
|
+
}
|
|
1348
|
+
else if (hasLazyFormulas || requiresSQLConversion) {
|
|
1349
|
+
ret.push("*");
|
|
1350
|
+
addFormulas = true;
|
|
1351
|
+
}
|
|
1352
|
+
else {
|
|
1353
|
+
ret.push("*");
|
|
1354
|
+
}
|
|
1355
|
+
if (ret.length > 0 && !hasExplicitFields && addFormulas) {
|
|
1356
|
+
meta.props
|
|
1357
|
+
.filter((prop) => prop.formula && !lazyProps.includes(prop))
|
|
1358
|
+
.forEach((prop) => {
|
|
1359
|
+
const a = this.connection.getKnex().ref(alias).toString();
|
|
1360
|
+
const aliased = this.connection
|
|
1361
|
+
.getKnex()
|
|
1362
|
+
.ref(prop.fieldNames[0])
|
|
1363
|
+
.toString();
|
|
1364
|
+
ret.push((0, core_1.raw)(`${prop.formula(a)} as ${aliased}`));
|
|
1365
|
+
});
|
|
1366
|
+
meta.props
|
|
1367
|
+
.filter((prop) => !prop.object &&
|
|
1368
|
+
(prop.hasConvertToDatabaseValueSQL || prop.hasConvertToJSValueSQL))
|
|
1369
|
+
.forEach((prop) => ret.push(prop.name));
|
|
1370
|
+
}
|
|
1371
|
+
// add joined relations after the root entity fields
|
|
1372
|
+
if (joinedProps.length > 0) {
|
|
1373
|
+
ret.push(...this.getFieldsForJoinedLoad(qb, meta, options.fields, options.exclude, populate, options, alias));
|
|
1374
|
+
}
|
|
1375
|
+
return core_1.Utils.unique(ret);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
exports.AbstractSqlDriver = AbstractSqlDriver;
|