@mikro-orm/mongodb 7.0.2-dev.9 → 7.0.2

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/MongoDriver.js CHANGED
@@ -1,443 +1,454 @@
1
1
  import { ObjectId } from 'mongodb';
2
- import { DatabaseDriver, EntityManagerType, PolymorphicRef, ReferenceKind, Utils, } from '@mikro-orm/core';
2
+ import { DatabaseDriver, EntityManagerType, PolymorphicRef, ReferenceKind, Utils } from '@mikro-orm/core';
3
3
  import { MongoConnection } from './MongoConnection.js';
4
4
  import { MongoPlatform } from './MongoPlatform.js';
5
5
  import { MongoEntityManager } from './MongoEntityManager.js';
6
6
  import { MongoMikroORM } from './MongoMikroORM.js';
7
+ /** Database driver for MongoDB. */
7
8
  export class MongoDriver extends DatabaseDriver {
8
- [EntityManagerType];
9
- connection = new MongoConnection(this.config);
10
- platform = new MongoPlatform();
11
- constructor(config) {
12
- super(config, ['mongodb']);
13
- }
14
- createEntityManager(useContext) {
15
- const EntityManagerClass = this.config.get('entityManager', MongoEntityManager);
16
- return new EntityManagerClass(this.config, this, this.metadata, useContext);
17
- }
18
- async *stream(entityName, where, options) {
19
- if (this.metadata.find(entityName)?.virtual) {
20
- yield* this.streamVirtual(entityName, where, options);
21
- return;
22
- }
23
- const fields = this.buildFields(entityName, options.populate || [], options.fields, options.exclude);
24
- where = this.renameFields(entityName, where, true);
25
- const orderBy = Utils.asArray(options.orderBy).map(orderBy => this.renameFields(entityName, orderBy, true));
26
- const res = this.getConnection('read').stream(entityName, where, {
27
- orderBy,
28
- limit: options.limit,
29
- offset: options.offset,
30
- fields,
31
- ctx: options.ctx,
32
- ...this.buildQueryOptions(options),
33
- });
34
- for await (const item of res) {
35
- if (options.rawResults) {
36
- yield item;
37
- }
38
- else {
39
- yield this.mapResult(item, this.metadata.find(entityName));
40
- }
41
- }
9
+ [EntityManagerType];
10
+ connection = new MongoConnection(this.config);
11
+ platform = new MongoPlatform();
12
+ constructor(config) {
13
+ super(config, ['mongodb']);
14
+ }
15
+ createEntityManager(useContext) {
16
+ const EntityManagerClass = this.config.get('entityManager', MongoEntityManager);
17
+ return new EntityManagerClass(this.config, this, this.metadata, useContext);
18
+ }
19
+ async *stream(entityName, where, options) {
20
+ if (this.metadata.find(entityName)?.virtual) {
21
+ yield* this.streamVirtual(entityName, where, options);
22
+ return;
42
23
  }
43
- async find(entityName, where, options = {}) {
44
- if (this.metadata.find(entityName)?.virtual) {
45
- return this.findVirtual(entityName, where, options);
46
- }
47
- const { first, last, before, after } = options;
48
- const fields = this.buildFields(entityName, options.populate || [], options.fields, options.exclude);
49
- where = this.renameFields(entityName, where, true);
50
- const isCursorPagination = [first, last, before, after].some(v => v != null);
51
- if (isCursorPagination) {
52
- const andWhere = (cond1, cond2) => {
53
- if (Utils.isEmpty(cond1)) {
54
- return cond2;
55
- }
56
- if (Utils.isEmpty(cond2)) {
57
- return cond1;
58
- }
59
- return { $and: [cond1, cond2] };
60
- };
61
- const meta = this.metadata.find(entityName);
62
- const { orderBy: newOrderBy, where: newWhere } = this.processCursorOptions(meta, options, options.orderBy);
63
- const newWhereConverted = this.renameFields(entityName, newWhere, true);
64
- const orderBy = Utils.asArray(newOrderBy).map(order => this.renameFields(entityName, order, true));
65
- const res = await this.rethrow(this.getConnection('read').find(entityName, andWhere(where, newWhereConverted), {
66
- orderBy,
67
- limit: options.limit,
68
- offset: options.offset,
69
- fields,
70
- ctx: options.ctx,
71
- loggerContext: options.logging,
72
- ...this.buildQueryOptions(options),
73
- }));
74
- if (isCursorPagination && !first && !!last) {
75
- res.reverse();
76
- }
77
- return res.map(r => this.mapResult(r, this.metadata.find(entityName)));
78
- }
79
- const orderBy = Utils.asArray(options.orderBy).map(orderBy => this.renameFields(entityName, orderBy, true));
80
- const res = await this.rethrow(this.getConnection('read').find(entityName, where, {
81
- orderBy,
82
- limit: options.limit,
83
- offset: options.offset,
84
- fields,
85
- ctx: options.ctx,
86
- ...this.buildQueryOptions(options),
87
- }));
88
- return res.map(r => this.mapResult(r, this.metadata.find(entityName)));
89
- }
90
- async findOne(entityName, where, options = { populate: [], orderBy: {} }) {
91
- if (this.metadata.find(entityName)?.virtual) {
92
- const [item] = await this.findVirtual(entityName, where, options);
93
- /* v8 ignore next */
94
- return item ?? null;
95
- }
96
- if (Utils.isPrimaryKey(where)) {
97
- where = this.buildFilterById(entityName, where);
98
- }
99
- const fields = this.buildFields(entityName, options.populate || [], options.fields, options.exclude);
100
- where = this.renameFields(entityName, where, true);
101
- const orderBy = Utils.asArray(options.orderBy).map(orderBy => this.renameFields(entityName, orderBy, true));
102
- const res = await this.rethrow(this.getConnection('read').find(entityName, where, {
103
- orderBy,
104
- limit: 1,
105
- fields,
106
- ctx: options.ctx,
107
- loggerContext: options.logging,
108
- ...this.buildQueryOptions(options),
109
- }));
110
- return this.mapResult(res[0], this.metadata.find(entityName));
111
- }
112
- async findVirtual(entityName, where, options) {
113
- const meta = this.metadata.find(entityName);
114
- if (meta.expression instanceof Function) {
115
- const em = this.createEntityManager();
116
- return meta.expression(em, where, options);
117
- }
118
- /* v8 ignore next */
119
- return super.findVirtual(entityName, where, options);
120
- }
121
- async *streamVirtual(entityName, where, options) {
122
- const meta = this.metadata.find(entityName);
123
- if (meta.expression instanceof Function) {
124
- const em = this.createEntityManager();
125
- const stream = await meta.expression(em, where, options, true);
126
- yield* stream;
127
- return;
128
- }
129
- /* v8 ignore next */
130
- return super.findVirtual(entityName, where, options);
24
+ const fields = this.buildFields(entityName, options.populate || [], options.fields, options.exclude);
25
+ where = this.renameFields(entityName, where, true);
26
+ const orderBy = Utils.asArray(options.orderBy).map(orderBy => this.renameFields(entityName, orderBy, true));
27
+ const res = this.getConnection('read').stream(entityName, where, {
28
+ orderBy,
29
+ limit: options.limit,
30
+ offset: options.offset,
31
+ fields,
32
+ ctx: options.ctx,
33
+ ...this.buildQueryOptions(options),
34
+ });
35
+ for await (const item of res) {
36
+ if (options.rawResults) {
37
+ yield item;
38
+ } else {
39
+ yield this.mapResult(item, this.metadata.find(entityName));
40
+ }
131
41
  }
132
- async count(entityName, where, options = {}) {
133
- /* v8 ignore next */
134
- if (this.metadata.find(entityName)?.virtual) {
135
- return this.countVirtual(entityName, where, options);
136
- }
137
- where = this.renameFields(entityName, where, true);
138
- const queryOpts = this.buildQueryOptions(options);
139
- return this.rethrow(this.getConnection('read').countDocuments(entityName, where, {
140
- ctx: options.ctx,
141
- loggerContext: options.logging,
142
- ...queryOpts,
143
- }));
144
- }
145
- async nativeInsert(entityName, data, options = {}) {
146
- this.handleVersionProperty(entityName, data);
147
- data = this.renameFields(entityName, data);
148
- return this.rethrow(this.getConnection('write').insertOne(entityName, data, options.ctx));
149
- }
150
- async nativeInsertMany(entityName, data, options = {}) {
151
- data = data.map(item => {
152
- this.handleVersionProperty(entityName, item);
153
- return this.renameFields(entityName, item);
154
- });
155
- const meta = this.metadata.find(entityName);
156
- /* v8 ignore next */
157
- const pk = meta?.getPrimaryProps()[0].fieldNames[0] ?? '_id';
158
- const res = await this.rethrow(this.getConnection('write').insertMany(entityName, data, options.ctx));
159
- res.rows = res.insertedIds.map(id => ({ [pk]: id }));
160
- return res;
161
- }
162
- async nativeUpdate(entityName, where, data, options = {}) {
163
- if (Utils.isPrimaryKey(where)) {
164
- where = this.buildFilterById(entityName, where);
165
- }
166
- this.handleVersionProperty(entityName, data, true);
167
- data = this.renameFields(entityName, data);
168
- where = this.renameFields(entityName, where, true);
169
- options = { ...options };
170
- const meta = this.metadata.find(entityName);
171
- /* v8 ignore next */
172
- const rename = (field) => meta ? (meta.properties[field]?.fieldNames[0] ?? field) : field;
173
- if (options.onConflictFields && Array.isArray(options.onConflictFields)) {
174
- options.onConflictFields = options.onConflictFields.map(rename);
175
- }
176
- if (options.onConflictMergeFields) {
177
- options.onConflictMergeFields = options.onConflictMergeFields.map(rename);
178
- }
179
- if (options.onConflictExcludeFields) {
180
- options.onConflictExcludeFields = options.onConflictExcludeFields.map(rename);
181
- }
182
- return this.rethrow(this.getConnection('write').updateMany(entityName, where, data, options.ctx, options.upsert, options));
183
- }
184
- async nativeUpdateMany(entityName, where, data, options = {}) {
185
- where = where.map(row => {
186
- if (Utils.isPlainObject(row)) {
187
- return this.renameFields(entityName, row, true);
188
- }
189
- return row;
190
- });
191
- data = data.map(row => {
192
- this.handleVersionProperty(entityName, row, true);
193
- return this.renameFields(entityName, row);
194
- });
195
- options = { ...options };
196
- const meta = this.metadata.find(entityName);
197
- /* v8 ignore next */
198
- const rename = (field) => meta ? (meta.properties[field]?.fieldNames[0] ?? field) : field;
199
- if (options.onConflictFields && Array.isArray(options.onConflictFields)) {
200
- options.onConflictFields = options.onConflictFields.map(rename);
201
- }
202
- if (options.onConflictMergeFields) {
203
- options.onConflictMergeFields = options.onConflictMergeFields.map(rename);
204
- }
205
- if (options.onConflictExcludeFields) {
206
- options.onConflictExcludeFields = options.onConflictExcludeFields.map(rename);
207
- }
208
- /* v8 ignore next */
209
- const pk = meta?.getPrimaryProps()[0].fieldNames[0] ?? '_id';
210
- const res = await this.rethrow(this.getConnection('write').bulkUpdateMany(entityName, where, data, options.ctx, options.upsert, options));
211
- if (res.insertedIds) {
212
- let i = 0;
213
- res.rows = where.map(cond => {
214
- if (Utils.isEmpty(cond)) {
215
- return { [pk]: res.insertedIds[i++] };
216
- }
217
- return { [pk]: cond[pk] };
218
- });
219
- }
220
- return res;
42
+ }
43
+ async find(entityName, where, options = {}) {
44
+ if (this.metadata.find(entityName)?.virtual) {
45
+ return this.findVirtual(entityName, where, options);
221
46
  }
222
- async nativeDelete(entityName, where, options = {}) {
223
- if (Utils.isPrimaryKey(where)) {
224
- where = this.buildFilterById(entityName, where);
225
- }
226
- where = this.renameFields(entityName, where, true);
227
- return this.rethrow(this.getConnection('write').deleteMany(entityName, where, options.ctx));
47
+ const { first, last, before, after } = options;
48
+ const fields = this.buildFields(entityName, options.populate || [], options.fields, options.exclude);
49
+ where = this.renameFields(entityName, where, true);
50
+ const isCursorPagination = [first, last, before, after].some(v => v != null);
51
+ if (isCursorPagination) {
52
+ const andWhere = (cond1, cond2) => {
53
+ if (Utils.isEmpty(cond1)) {
54
+ return cond2;
55
+ }
56
+ if (Utils.isEmpty(cond2)) {
57
+ return cond1;
58
+ }
59
+ return { $and: [cond1, cond2] };
60
+ };
61
+ const meta = this.metadata.find(entityName);
62
+ const { orderBy: newOrderBy, where: newWhere } = this.processCursorOptions(meta, options, options.orderBy);
63
+ const newWhereConverted = this.renameFields(entityName, newWhere, true);
64
+ const orderBy = Utils.asArray(newOrderBy).map(order => this.renameFields(entityName, order, true));
65
+ const res = await this.rethrow(
66
+ this.getConnection('read').find(entityName, andWhere(where, newWhereConverted), {
67
+ orderBy,
68
+ limit: options.limit,
69
+ offset: options.offset,
70
+ fields,
71
+ ctx: options.ctx,
72
+ loggerContext: options.logging,
73
+ ...this.buildQueryOptions(options),
74
+ }),
75
+ );
76
+ if (isCursorPagination && !first && !!last) {
77
+ res.reverse();
78
+ }
79
+ return res.map(r => this.mapResult(r, this.metadata.find(entityName)));
228
80
  }
229
- async aggregate(entityName, pipeline, ctx) {
230
- return this.rethrow(this.getConnection('read').aggregate(entityName, pipeline, ctx));
81
+ const orderBy = Utils.asArray(options.orderBy).map(orderBy => this.renameFields(entityName, orderBy, true));
82
+ const res = await this.rethrow(
83
+ this.getConnection('read').find(entityName, where, {
84
+ orderBy,
85
+ limit: options.limit,
86
+ offset: options.offset,
87
+ fields,
88
+ ctx: options.ctx,
89
+ ...this.buildQueryOptions(options),
90
+ }),
91
+ );
92
+ return res.map(r => this.mapResult(r, this.metadata.find(entityName)));
93
+ }
94
+ async findOne(entityName, where, options = { populate: [], orderBy: {} }) {
95
+ if (this.metadata.find(entityName)?.virtual) {
96
+ const [item] = await this.findVirtual(entityName, where, options);
97
+ /* v8 ignore next */
98
+ return item ?? null;
231
99
  }
232
- async *streamAggregate(entityName, pipeline, ctx) {
233
- yield* this.getConnection('read').streamAggregate(entityName, pipeline, ctx);
100
+ if (Utils.isPrimaryKey(where)) {
101
+ where = this.buildFilterById(entityName, where);
234
102
  }
235
- getPlatform() {
236
- return this.platform;
103
+ const fields = this.buildFields(entityName, options.populate || [], options.fields, options.exclude);
104
+ where = this.renameFields(entityName, where, true);
105
+ const orderBy = Utils.asArray(options.orderBy).map(orderBy => this.renameFields(entityName, orderBy, true));
106
+ const res = await this.rethrow(
107
+ this.getConnection('read').find(entityName, where, {
108
+ orderBy,
109
+ limit: 1,
110
+ fields,
111
+ ctx: options.ctx,
112
+ loggerContext: options.logging,
113
+ ...this.buildQueryOptions(options),
114
+ }),
115
+ );
116
+ return this.mapResult(res[0], this.metadata.find(entityName));
117
+ }
118
+ async findVirtual(entityName, where, options) {
119
+ const meta = this.metadata.find(entityName);
120
+ if (meta.expression instanceof Function) {
121
+ const em = this.createEntityManager();
122
+ return meta.expression(em, where, options);
237
123
  }
238
- buildQueryOptions(options) {
239
- if (options.collation != null && typeof options.collation === 'string') {
240
- throw new Error("Collation option for MongoDB must be a CollationOptions object (e.g. { locale: 'en' }). Use a string only with SQL drivers.");
241
- }
242
- const ret = {};
243
- if (options.collation) {
244
- ret.collation = options.collation;
245
- }
246
- if (options.indexHint != null) {
247
- ret.indexHint = options.indexHint;
248
- }
249
- if (options.maxTimeMS != null) {
250
- ret.maxTimeMS = options.maxTimeMS;
251
- }
252
- if (options.allowDiskUse != null) {
253
- ret.allowDiskUse = options.allowDiskUse;
254
- }
255
- return ret;
124
+ /* v8 ignore next */
125
+ return super.findVirtual(entityName, where, options);
126
+ }
127
+ async *streamVirtual(entityName, where, options) {
128
+ const meta = this.metadata.find(entityName);
129
+ if (meta.expression instanceof Function) {
130
+ const em = this.createEntityManager();
131
+ const stream = await meta.expression(em, where, options, true);
132
+ yield* stream;
133
+ return;
256
134
  }
257
- renameFields(entityName, data, dotPaths = false, object, root = true) {
258
- if (data == null && root) {
259
- return {};
260
- }
261
- if (typeof data !== 'object' || data === null) {
262
- return data;
263
- }
264
- // copy to new variable to prevent changing the T type or doing as unknown casts
265
- const copiedData = Object.assign({}, data); // copy first
266
- const meta = this.metadata.find(entityName);
267
- if (meta?.serializedPrimaryKey && !meta.embeddable && meta.serializedPrimaryKey !== meta.primaryKeys[0]) {
268
- Utils.renameKey(copiedData, meta.serializedPrimaryKey, meta.primaryKeys[0]);
269
- }
270
- if (meta && !meta.embeddable) {
271
- this.inlineEmbeddables(meta, copiedData, dotPaths);
272
- }
273
- // If we had a query with $fulltext and some filter we end up with $and with $fulltext in it.
274
- // We will try to move $fulltext to top level.
275
- if (copiedData.$and) {
276
- for (let i = 0; i < copiedData.$and.length; i++) {
277
- const and = copiedData.$and[i];
278
- if ('$fulltext' in and) {
279
- /* v8 ignore next */
280
- if ('$fulltext' in copiedData) {
281
- throw new Error('Cannot merge multiple $fulltext conditions to top level of the query object.');
282
- }
283
- copiedData.$fulltext = and.$fulltext;
284
- delete and.$fulltext;
285
- }
286
- }
287
- }
288
- // move search terms from data['$fulltext'] to mongo's structure: data['$text']['search']
289
- if ('$fulltext' in copiedData) {
290
- copiedData.$text = { $search: copiedData.$fulltext };
291
- delete copiedData.$fulltext;
292
- }
293
- // mongo only allows the $text operator in the root of the object and will
294
- // search all documents where the field has a text index.
295
- if (Utils.hasNestedKey(copiedData, '$fulltext')) {
296
- throw new Error('Full text search is only supported on the top level of the query object.');
297
- }
298
- Utils.keys(copiedData).forEach(k => {
299
- if (Utils.isOperator(k)) {
300
- if (Array.isArray(copiedData[k])) {
301
- copiedData[k] = copiedData[k].map(v => this.renameFields(entityName, v, dotPaths, object, false));
302
- }
303
- else {
304
- copiedData[k] = this.renameFields(entityName, copiedData[k], dotPaths, object, false);
305
- }
306
- return;
307
- }
308
- if (meta?.properties[k]) {
309
- const prop = meta.properties[k];
310
- let isObjectId = false;
311
- if (prop.kind === ReferenceKind.SCALAR) {
312
- isObjectId = prop.type === 'ObjectId';
313
- }
314
- else if (prop.kind === ReferenceKind.EMBEDDED) {
315
- if (copiedData[prop.name] == null) {
316
- return;
317
- }
318
- if (prop.array && Array.isArray(copiedData[prop.name])) {
319
- copiedData[prop.name] = copiedData[prop.name].map((item) => this.renameFields(prop.targetMeta.class, item, dotPaths, true, false));
320
- }
321
- else {
322
- copiedData[prop.name] = this.renameFields(prop.targetMeta.class, copiedData[prop.name], dotPaths, prop.object || object, false);
323
- }
324
- }
325
- else if (prop.polymorphic && prop.fieldNames?.length >= 2) {
326
- // Polymorphic M:1: split into discriminator + FK fields
327
- const value = copiedData[k];
328
- delete copiedData[k];
329
- if (value instanceof PolymorphicRef) {
330
- copiedData[prop.fieldNames[0]] = value.discriminator;
331
- const idField = prop.fieldNames[1];
332
- const targetMeta = this.metadata.find(prop.discriminatorMap[value.discriminator]);
333
- const hasObjectId = targetMeta && targetMeta.properties[targetMeta.primaryKeys[0]]?.type === 'ObjectId';
334
- copiedData[idField] = hasObjectId ? this.convertObjectIds(value.id) : value.id;
335
- }
336
- else if (Array.isArray(value)) {
337
- // Tuple format: [discriminator, id]
338
- copiedData[prop.fieldNames[0]] = value[0];
339
- copiedData[prop.fieldNames[1]] = value[1] != null ? this.convertObjectIds(value[1]) : value[1];
340
- }
341
- else if (value == null) {
342
- prop.fieldNames.forEach(f => (copiedData[f] = null));
343
- }
344
- return;
345
- }
346
- else {
347
- const meta2 = this.metadata.find(prop.targetMeta.class);
348
- const pk = meta2.properties[meta2.primaryKeys[0]];
349
- isObjectId = pk.type === 'ObjectId';
350
- }
351
- if (isObjectId) {
352
- copiedData[k] = this.convertObjectIds(copiedData[k]);
353
- }
354
- if (prop.fieldNames) {
355
- Utils.renameKey(copiedData, k, prop.fieldNames[0]);
356
- }
357
- }
358
- if (Utils.isPlainObject(copiedData[k]) && '$re' in copiedData[k]) {
359
- copiedData[k] = new RegExp(copiedData[k].$re);
360
- }
361
- });
362
- return copiedData;
363
- }
364
- convertObjectIds(data) {
365
- if (data instanceof ObjectId) {
366
- return data;
367
- }
368
- if (typeof data === 'string' && /^[0-9a-f]{24}$/i.exec(data)) {
369
- return new ObjectId(data);
370
- }
371
- if (Array.isArray(data)) {
372
- return data.map((item) => this.convertObjectIds(item));
373
- }
374
- if (Utils.isObject(data)) {
375
- Object.keys(data).forEach(k => {
376
- data[k] = this.convertObjectIds(data[k]);
377
- });
378
- }
379
- return data;
135
+ /* v8 ignore next */
136
+ return super.findVirtual(entityName, where, options);
137
+ }
138
+ async count(entityName, where, options = {}) {
139
+ /* v8 ignore next */
140
+ if (this.metadata.find(entityName)?.virtual) {
141
+ return this.countVirtual(entityName, where, options);
380
142
  }
381
- buildFilterById(entityName, id) {
382
- const meta = this.metadata.find(entityName);
383
- if (meta.properties[meta.primaryKeys[0]].type === 'ObjectId') {
384
- return { _id: new ObjectId(id) };
385
- }
386
- return { _id: id };
387
- }
388
- buildFields(entityName, populate, fields, exclude) {
389
- const meta = this.metadata.get(entityName);
390
- const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => this.isPopulated(meta, prop, p)));
391
- const ret = [];
392
- if (fields) {
393
- for (let field of fields) {
394
- /* v8 ignore next */
395
- if (Utils.isPlainObject(field)) {
396
- continue;
397
- }
398
- if (field.toString().includes('.')) {
399
- field = field.toString().substring(0, field.toString().indexOf('.'));
400
- }
401
- let prop = meta.properties[field];
402
- /* v8 ignore next */
403
- if (prop) {
404
- if (!prop.fieldNames) {
405
- continue;
406
- }
407
- prop = prop.serializedPrimaryKey ? meta.getPrimaryProps()[0] : prop;
408
- ret.push(prop.fieldNames[0]);
409
- }
410
- else if (field === '*') {
411
- const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate));
412
- ret.push(...Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
413
- }
414
- else {
415
- ret.push(field);
416
- }
417
- }
418
- ret.unshift(...meta.primaryKeys.filter(pk => !fields.includes(pk)));
419
- }
420
- else if (!Utils.isEmpty(exclude) || lazyProps.some(p => !p.formula)) {
421
- const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, exclude));
422
- ret.push(...Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
423
- }
424
- return ret.length > 0 ? ret : undefined;
143
+ where = this.renameFields(entityName, where, true);
144
+ const queryOpts = this.buildQueryOptions(options);
145
+ return this.rethrow(
146
+ this.getConnection('read').countDocuments(entityName, where, {
147
+ ctx: options.ctx,
148
+ loggerContext: options.logging,
149
+ ...queryOpts,
150
+ }),
151
+ );
152
+ }
153
+ async nativeInsert(entityName, data, options = {}) {
154
+ this.handleVersionProperty(entityName, data);
155
+ data = this.renameFields(entityName, data);
156
+ return this.rethrow(this.getConnection('write').insertOne(entityName, data, options.ctx));
157
+ }
158
+ async nativeInsertMany(entityName, data, options = {}) {
159
+ data = data.map(item => {
160
+ this.handleVersionProperty(entityName, item);
161
+ return this.renameFields(entityName, item);
162
+ });
163
+ const meta = this.metadata.find(entityName);
164
+ /* v8 ignore next */
165
+ const pk = meta?.getPrimaryProps()[0].fieldNames[0] ?? '_id';
166
+ const res = await this.rethrow(this.getConnection('write').insertMany(entityName, data, options.ctx));
167
+ res.rows = res.insertedIds.map(id => ({ [pk]: id }));
168
+ return res;
169
+ }
170
+ async nativeUpdate(entityName, where, data, options = {}) {
171
+ if (Utils.isPrimaryKey(where)) {
172
+ where = this.buildFilterById(entityName, where);
173
+ }
174
+ this.handleVersionProperty(entityName, data, true);
175
+ data = this.renameFields(entityName, data);
176
+ where = this.renameFields(entityName, where, true);
177
+ options = { ...options };
178
+ const meta = this.metadata.find(entityName);
179
+ /* v8 ignore next */
180
+ const rename = field => (meta ? (meta.properties[field]?.fieldNames[0] ?? field) : field);
181
+ if (options.onConflictFields && Array.isArray(options.onConflictFields)) {
182
+ options.onConflictFields = options.onConflictFields.map(rename);
183
+ }
184
+ if (options.onConflictMergeFields) {
185
+ options.onConflictMergeFields = options.onConflictMergeFields.map(rename);
186
+ }
187
+ if (options.onConflictExcludeFields) {
188
+ options.onConflictExcludeFields = options.onConflictExcludeFields.map(rename);
189
+ }
190
+ return this.rethrow(
191
+ this.getConnection('write').updateMany(entityName, where, data, options.ctx, options.upsert, options),
192
+ );
193
+ }
194
+ async nativeUpdateMany(entityName, where, data, options = {}) {
195
+ where = where.map(row => {
196
+ if (Utils.isPlainObject(row)) {
197
+ return this.renameFields(entityName, row, true);
198
+ }
199
+ return row;
200
+ });
201
+ data = data.map(row => {
202
+ this.handleVersionProperty(entityName, row, true);
203
+ return this.renameFields(entityName, row);
204
+ });
205
+ options = { ...options };
206
+ const meta = this.metadata.find(entityName);
207
+ /* v8 ignore next */
208
+ const rename = field => (meta ? (meta.properties[field]?.fieldNames[0] ?? field) : field);
209
+ if (options.onConflictFields && Array.isArray(options.onConflictFields)) {
210
+ options.onConflictFields = options.onConflictFields.map(rename);
211
+ }
212
+ if (options.onConflictMergeFields) {
213
+ options.onConflictMergeFields = options.onConflictMergeFields.map(rename);
214
+ }
215
+ if (options.onConflictExcludeFields) {
216
+ options.onConflictExcludeFields = options.onConflictExcludeFields.map(rename);
217
+ }
218
+ /* v8 ignore next */
219
+ const pk = meta?.getPrimaryProps()[0].fieldNames[0] ?? '_id';
220
+ const res = await this.rethrow(
221
+ this.getConnection('write').bulkUpdateMany(entityName, where, data, options.ctx, options.upsert, options),
222
+ );
223
+ if (res.insertedIds) {
224
+ let i = 0;
225
+ res.rows = where.map(cond => {
226
+ if (Utils.isEmpty(cond)) {
227
+ return { [pk]: res.insertedIds[i++] };
228
+ }
229
+ return { [pk]: cond[pk] };
230
+ });
231
+ }
232
+ return res;
233
+ }
234
+ async nativeDelete(entityName, where, options = {}) {
235
+ if (Utils.isPrimaryKey(where)) {
236
+ where = this.buildFilterById(entityName, where);
237
+ }
238
+ where = this.renameFields(entityName, where, true);
239
+ return this.rethrow(this.getConnection('write').deleteMany(entityName, where, options.ctx));
240
+ }
241
+ async aggregate(entityName, pipeline, ctx) {
242
+ return this.rethrow(this.getConnection('read').aggregate(entityName, pipeline, ctx));
243
+ }
244
+ async *streamAggregate(entityName, pipeline, ctx) {
245
+ yield* this.getConnection('read').streamAggregate(entityName, pipeline, ctx);
246
+ }
247
+ getPlatform() {
248
+ return this.platform;
249
+ }
250
+ buildQueryOptions(options) {
251
+ if (options.collation != null && typeof options.collation === 'string') {
252
+ throw new Error(
253
+ "Collation option for MongoDB must be a CollationOptions object (e.g. { locale: 'en' }). Use a string only with SQL drivers.",
254
+ );
255
+ }
256
+ const ret = {};
257
+ if (options.collation) {
258
+ ret.collation = options.collation;
259
+ }
260
+ if (options.indexHint != null) {
261
+ ret.indexHint = options.indexHint;
425
262
  }
426
- handleVersionProperty(entityName, data, update = false) {
427
- const meta = this.metadata.find(entityName);
428
- if (!meta?.versionProperty) {
263
+ if (options.maxTimeMS != null) {
264
+ ret.maxTimeMS = options.maxTimeMS;
265
+ }
266
+ if (options.allowDiskUse != null) {
267
+ ret.allowDiskUse = options.allowDiskUse;
268
+ }
269
+ return ret;
270
+ }
271
+ renameFields(entityName, data, dotPaths = false, object, root = true) {
272
+ if (data == null && root) {
273
+ return {};
274
+ }
275
+ if (typeof data !== 'object' || data === null) {
276
+ return data;
277
+ }
278
+ // copy to new variable to prevent changing the T type or doing as unknown casts
279
+ const copiedData = Object.assign({}, data); // copy first
280
+ const meta = this.metadata.find(entityName);
281
+ if (meta?.serializedPrimaryKey && !meta.embeddable && meta.serializedPrimaryKey !== meta.primaryKeys[0]) {
282
+ Utils.renameKey(copiedData, meta.serializedPrimaryKey, meta.primaryKeys[0]);
283
+ }
284
+ if (meta && !meta.embeddable) {
285
+ this.inlineEmbeddables(meta, copiedData, dotPaths);
286
+ }
287
+ // If we had a query with $fulltext and some filter we end up with $and with $fulltext in it.
288
+ // We will try to move $fulltext to top level.
289
+ if (copiedData.$and) {
290
+ for (let i = 0; i < copiedData.$and.length; i++) {
291
+ const and = copiedData.$and[i];
292
+ if ('$fulltext' in and) {
293
+ /* v8 ignore next */
294
+ if ('$fulltext' in copiedData) {
295
+ throw new Error('Cannot merge multiple $fulltext conditions to top level of the query object.');
296
+ }
297
+ copiedData.$fulltext = and.$fulltext;
298
+ delete and.$fulltext;
299
+ }
300
+ }
301
+ }
302
+ // move search terms from data['$fulltext'] to mongo's structure: data['$text']['search']
303
+ if ('$fulltext' in copiedData) {
304
+ copiedData.$text = { $search: copiedData.$fulltext };
305
+ delete copiedData.$fulltext;
306
+ }
307
+ // mongo only allows the $text operator in the root of the object and will
308
+ // search all documents where the field has a text index.
309
+ if (Utils.hasNestedKey(copiedData, '$fulltext')) {
310
+ throw new Error('Full text search is only supported on the top level of the query object.');
311
+ }
312
+ Utils.keys(copiedData).forEach(k => {
313
+ if (Utils.isOperator(k)) {
314
+ if (Array.isArray(copiedData[k])) {
315
+ copiedData[k] = copiedData[k].map(v => this.renameFields(entityName, v, dotPaths, object, false));
316
+ } else {
317
+ copiedData[k] = this.renameFields(entityName, copiedData[k], dotPaths, object, false);
318
+ }
319
+ return;
320
+ }
321
+ if (meta?.properties[k]) {
322
+ const prop = meta.properties[k];
323
+ let isObjectId = false;
324
+ if (prop.kind === ReferenceKind.SCALAR) {
325
+ isObjectId = prop.type === 'ObjectId';
326
+ } else if (prop.kind === ReferenceKind.EMBEDDED) {
327
+ if (copiedData[prop.name] == null) {
429
328
  return;
329
+ }
330
+ if (prop.array && Array.isArray(copiedData[prop.name])) {
331
+ copiedData[prop.name] = copiedData[prop.name].map(item =>
332
+ this.renameFields(prop.targetMeta.class, item, dotPaths, true, false),
333
+ );
334
+ } else {
335
+ copiedData[prop.name] = this.renameFields(
336
+ prop.targetMeta.class,
337
+ copiedData[prop.name],
338
+ dotPaths,
339
+ prop.object || object,
340
+ false,
341
+ );
342
+ }
343
+ } else if (prop.polymorphic && prop.fieldNames?.length >= 2) {
344
+ // Polymorphic M:1: split into discriminator + FK fields
345
+ const value = copiedData[k];
346
+ delete copiedData[k];
347
+ if (value instanceof PolymorphicRef) {
348
+ copiedData[prop.fieldNames[0]] = value.discriminator;
349
+ const idField = prop.fieldNames[1];
350
+ const targetMeta = this.metadata.find(prop.discriminatorMap[value.discriminator]);
351
+ const hasObjectId = targetMeta && targetMeta.properties[targetMeta.primaryKeys[0]]?.type === 'ObjectId';
352
+ copiedData[idField] = hasObjectId ? this.convertObjectIds(value.id) : value.id;
353
+ } else if (Array.isArray(value)) {
354
+ // Tuple format: [discriminator, id]
355
+ copiedData[prop.fieldNames[0]] = value[0];
356
+ copiedData[prop.fieldNames[1]] = value[1] != null ? this.convertObjectIds(value[1]) : value[1];
357
+ } else if (value == null) {
358
+ prop.fieldNames.forEach(f => (copiedData[f] = null));
359
+ }
360
+ return;
361
+ } else {
362
+ const meta2 = this.metadata.find(prop.targetMeta.class);
363
+ const pk = meta2.properties[meta2.primaryKeys[0]];
364
+ isObjectId = pk.type === 'ObjectId';
365
+ }
366
+ if (isObjectId) {
367
+ copiedData[k] = this.convertObjectIds(copiedData[k]);
368
+ }
369
+ if (prop.fieldNames) {
370
+ Utils.renameKey(copiedData, k, prop.fieldNames[0]);
371
+ }
372
+ }
373
+ if (Utils.isPlainObject(copiedData[k]) && '$re' in copiedData[k]) {
374
+ copiedData[k] = new RegExp(copiedData[k].$re);
375
+ }
376
+ });
377
+ return copiedData;
378
+ }
379
+ convertObjectIds(data) {
380
+ if (data instanceof ObjectId) {
381
+ return data;
382
+ }
383
+ if (typeof data === 'string' && /^[0-9a-f]{24}$/i.exec(data)) {
384
+ return new ObjectId(data);
385
+ }
386
+ if (Array.isArray(data)) {
387
+ return data.map(item => this.convertObjectIds(item));
388
+ }
389
+ if (Utils.isObject(data)) {
390
+ Object.keys(data).forEach(k => {
391
+ data[k] = this.convertObjectIds(data[k]);
392
+ });
393
+ }
394
+ return data;
395
+ }
396
+ buildFilterById(entityName, id) {
397
+ const meta = this.metadata.find(entityName);
398
+ if (meta.properties[meta.primaryKeys[0]].type === 'ObjectId') {
399
+ return { _id: new ObjectId(id) };
400
+ }
401
+ return { _id: id };
402
+ }
403
+ buildFields(entityName, populate, fields, exclude) {
404
+ const meta = this.metadata.get(entityName);
405
+ const lazyProps = meta.props.filter(prop => prop.lazy && !populate.some(p => this.isPopulated(meta, prop, p)));
406
+ const ret = [];
407
+ if (fields) {
408
+ for (let field of fields) {
409
+ /* v8 ignore next */
410
+ if (Utils.isPlainObject(field)) {
411
+ continue;
430
412
  }
431
- const versionProperty = meta.properties[meta.versionProperty];
432
- if (versionProperty.runtimeType === 'Date') {
433
- data[versionProperty.name] ??= new Date();
434
- }
435
- else {
436
- data[versionProperty.name] ??= update ? { $inc: 1 } : 1;
413
+ if (field.toString().includes('.')) {
414
+ field = field.toString().substring(0, field.toString().indexOf('.'));
437
415
  }
416
+ let prop = meta.properties[field];
417
+ /* v8 ignore next */
418
+ if (prop) {
419
+ if (!prop.fieldNames) {
420
+ continue;
421
+ }
422
+ prop = prop.serializedPrimaryKey ? meta.getPrimaryProps()[0] : prop;
423
+ ret.push(prop.fieldNames[0]);
424
+ } else if (field === '*') {
425
+ const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate));
426
+ ret.push(...Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
427
+ } else {
428
+ ret.push(field);
429
+ }
430
+ }
431
+ ret.unshift(...meta.primaryKeys.filter(pk => !fields.includes(pk)));
432
+ } else if (!Utils.isEmpty(exclude) || lazyProps.some(p => !p.formula)) {
433
+ const props = meta.props.filter(prop => this.platform.shouldHaveColumn(prop, populate, exclude));
434
+ ret.push(...Utils.flatten(props.filter(p => !lazyProps.includes(p)).map(p => p.fieldNames)));
435
+ }
436
+ return ret.length > 0 ? ret : undefined;
437
+ }
438
+ handleVersionProperty(entityName, data, update = false) {
439
+ const meta = this.metadata.find(entityName);
440
+ if (!meta?.versionProperty) {
441
+ return;
438
442
  }
439
- /** @inheritDoc */
440
- getORMClass() {
441
- return MongoMikroORM;
443
+ const versionProperty = meta.properties[meta.versionProperty];
444
+ if (versionProperty.runtimeType === 'Date') {
445
+ data[versionProperty.name] ??= new Date();
446
+ } else {
447
+ data[versionProperty.name] ??= update ? { $inc: 1 } : 1;
442
448
  }
449
+ }
450
+ /** @inheritDoc */
451
+ getORMClass() {
452
+ return MongoMikroORM;
453
+ }
443
454
  }