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