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

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