• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

typeorm / typeorm / 15089093306

17 May 2025 09:03PM UTC coverage: 50.109% (-26.2%) from 76.346%
15089093306

Pull #11437

github

naorpeled
add comment about vector <#>
Pull Request #11437: feat(postgres): support vector data type

5836 of 12767 branches covered (45.71%)

Branch coverage included in aggregate %.

16 of 17 new or added lines in 4 files covered. (94.12%)

6283 existing lines in 64 files now uncovered.

12600 of 24025 relevant lines covered (52.45%)

28708.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

2.49
/src/entity-manager/MongoEntityManager.ts
1
import { EntityManager } from "./EntityManager"
6✔
2
import { EntityTarget } from "../common/EntityTarget"
3

4
import { ObjectLiteral } from "../common/ObjectLiteral"
5
import { MongoQueryRunner } from "../driver/mongodb/MongoQueryRunner"
6
import { MongoDriver } from "../driver/mongodb/MongoDriver"
7
import { DocumentToEntityTransformer } from "../query-builder/transformer/DocumentToEntityTransformer"
6✔
8
import { FindManyOptions } from "../find-options/FindManyOptions"
9
import { FindOptionsUtils } from "../find-options/FindOptionsUtils"
6✔
10
import { PlatformTools } from "../platform/PlatformTools"
6✔
11
import { QueryDeepPartialEntity } from "../query-builder/QueryPartialEntity"
12
import { InsertResult } from "../query-builder/result/InsertResult"
6✔
13
import { UpdateResult } from "../query-builder/result/UpdateResult"
6✔
14
import { DeleteResult } from "../query-builder/result/DeleteResult"
6✔
15
import { EntityMetadata } from "../metadata/EntityMetadata"
16

17
import {
18
    BulkWriteResult,
19
    AggregationCursor,
20
    Collection,
21
    FindCursor,
22
    Document,
23
    AggregateOptions,
24
    AnyBulkWriteOperation,
25
    BulkWriteOptions,
26
    Filter,
27
    CountOptions,
28
    IndexSpecification,
29
    CreateIndexesOptions,
30
    IndexDescription,
31
    DeleteResult as DeleteResultMongoDb,
32
    DeleteOptions,
33
    CommandOperationOptions,
34
    FindOneAndDeleteOptions,
35
    FindOneAndReplaceOptions,
36
    UpdateFilter,
37
    FindOneAndUpdateOptions,
38
    RenameOptions,
39
    ReplaceOptions,
40
    UpdateResult as UpdateResultMongoDb,
41
    CollStats,
42
    CollStatsOptions,
43
    ChangeStreamOptions,
44
    ChangeStream,
45
    UpdateOptions,
46
    ListIndexesOptions,
47
    ListIndexesCursor,
48
    OptionalId,
49
    InsertOneOptions,
50
    InsertOneResult,
51
    InsertManyResult,
52
    UnorderedBulkOperation,
53
    OrderedBulkOperation,
54
    IndexInformationOptions,
55
    ObjectId,
56
    FilterOperators,
57
    CountDocumentsOptions,
58
} from "../driver/mongodb/typings"
59
import { DataSource } from "../data-source/DataSource"
60
import { MongoFindManyOptions } from "../find-options/mongodb/MongoFindManyOptions"
61
import { MongoFindOneOptions } from "../find-options/mongodb/MongoFindOneOptions"
62
import {
63
    FindOptionsSelect,
64
    FindOptionsSelectByString,
65
} from "../find-options/FindOptionsSelect"
66
import { ObjectUtils } from "../util/ObjectUtils"
6✔
67
import { ColumnMetadata } from "../metadata/ColumnMetadata"
68

69
/**
70
 * Entity manager supposed to work with any entity, automatically find its repository and call its methods,
71
 * whatever entity type are you passing.
72
 *
73
 * This implementation is used for MongoDB driver which has some specifics in its EntityManager.
74
 */
75
export class MongoEntityManager extends EntityManager {
6✔
UNCOV
76
    readonly "@instanceof" = Symbol.for("MongoEntityManager")
×
77

78
    get mongoQueryRunner(): MongoQueryRunner {
UNCOV
79
        return (this.connection.driver as MongoDriver)
×
80
            .queryRunner as MongoQueryRunner
81
    }
82

83
    // -------------------------------------------------------------------------
84
    // Constructor
85
    // -------------------------------------------------------------------------
86

87
    constructor(connection: DataSource) {
UNCOV
88
        super(connection)
×
89
    }
90

91
    // -------------------------------------------------------------------------
92
    // Overridden Methods
93
    // -------------------------------------------------------------------------
94

95
    /**
96
     * Finds entities that match given find options.
97
     */
98
    /**
99
     * Finds entities that match given find options or conditions.
100
     */
101
    async find<Entity>(
102
        entityClassOrName: EntityTarget<Entity>,
103
        optionsOrConditions?:
104
            | FindManyOptions<Entity>
105
            | Partial<Entity>
106
            | FilterOperators<Entity>,
107
    ): Promise<Entity[]> {
108
        const query =
UNCOV
109
            this.convertFindManyOptionsOrConditionsToMongodbQuery(
×
110
                optionsOrConditions,
111
            )
UNCOV
112
        const cursor = this.createEntityCursor<Entity>(
×
113
            entityClassOrName,
114
            query as Filter<Entity>,
115
        )
116
        const deleteDateColumn =
UNCOV
117
            this.connection.getMetadata(entityClassOrName).deleteDateColumn
×
UNCOV
118
        if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) {
×
UNCOV
119
            if (optionsOrConditions.select)
×
UNCOV
120
                cursor.project(
×
121
                    this.convertFindOptionsSelectToProjectCriteria(
122
                        optionsOrConditions.select,
123
                    ),
124
                )
UNCOV
125
            if (optionsOrConditions.skip) cursor.skip(optionsOrConditions.skip)
×
UNCOV
126
            if (optionsOrConditions.take) cursor.limit(optionsOrConditions.take)
×
UNCOV
127
            if (optionsOrConditions.order)
×
UNCOV
128
                cursor.sort(
×
129
                    this.convertFindOptionsOrderToOrderCriteria(
130
                        optionsOrConditions.order,
131
                    ),
132
                )
UNCOV
133
            if (deleteDateColumn && !optionsOrConditions.withDeleted) {
×
UNCOV
134
                this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
135
            }
UNCOV
136
        } else if (deleteDateColumn) {
×
UNCOV
137
            this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
138
        }
UNCOV
139
        return cursor.toArray()
×
140
    }
141

142
    /**
143
     * Finds entities that match given find options or conditions.
144
     * Also counts all entities that match given conditions,
145
     * but ignores pagination settings (from and take options).
146
     */
147
    async findAndCount<Entity>(
148
        entityClassOrName: EntityTarget<Entity>,
149
        options?: MongoFindManyOptions<Entity>,
150
    ): Promise<[Entity[], number]> {
UNCOV
151
        return this.executeFindAndCount(entityClassOrName, options)
×
152
    }
153

154
    /**
155
     * Finds entities that match given where conditions.
156
     */
157
    async findAndCountBy<Entity>(
158
        entityClassOrName: EntityTarget<Entity>,
159
        where: any,
160
    ): Promise<[Entity[], number]> {
161
        return this.executeFindAndCount(entityClassOrName, where)
×
162
    }
163

164
    /**
165
     * Finds entities by ids.
166
     * Optionally find options can be applied.
167
     *
168
     * @deprecated use `findBy` method instead.
169
     */
170
    async findByIds<Entity>(
171
        entityClassOrName: EntityTarget<Entity>,
172
        ids: any[],
173
        optionsOrConditions?: FindManyOptions<Entity> | Partial<Entity>,
174
    ): Promise<Entity[]> {
UNCOV
175
        const metadata = this.connection.getMetadata(entityClassOrName)
×
176
        const query =
UNCOV
177
            this.convertFindManyOptionsOrConditionsToMongodbQuery(
×
178
                optionsOrConditions,
179
            ) || {}
UNCOV
180
        const objectIdInstance = PlatformTools.load("mongodb").ObjectId
×
UNCOV
181
        query["_id"] = {
×
182
            $in: ids.map((id) => {
UNCOV
183
                if (typeof id === "string") {
×
UNCOV
184
                    return new objectIdInstance(id)
×
185
                }
186

UNCOV
187
                if (typeof id === "object") {
×
UNCOV
188
                    if (id instanceof objectIdInstance) {
×
UNCOV
189
                        return id
×
190
                    }
191

UNCOV
192
                    const propertyName = metadata.objectIdColumn!.propertyName
×
193

UNCOV
194
                    if (id[propertyName] instanceof objectIdInstance) {
×
UNCOV
195
                        return id[propertyName]
×
196
                    }
197
                }
198
            }),
199
        }
200

UNCOV
201
        const cursor = this.createEntityCursor<Entity>(
×
202
            entityClassOrName,
203
            query as Filter<Entity>,
204
        )
UNCOV
205
        if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) {
×
UNCOV
206
            if (optionsOrConditions.select)
×
207
                cursor.project(
×
208
                    this.convertFindOptionsSelectToProjectCriteria(
209
                        optionsOrConditions.select,
210
                    ),
211
                )
UNCOV
212
            if (optionsOrConditions.skip) cursor.skip(optionsOrConditions.skip)
×
UNCOV
213
            if (optionsOrConditions.take) cursor.limit(optionsOrConditions.take)
×
UNCOV
214
            if (optionsOrConditions.order)
×
215
                cursor.sort(
×
216
                    this.convertFindOptionsOrderToOrderCriteria(
217
                        optionsOrConditions.order,
218
                    ),
219
                )
220
        }
UNCOV
221
        return cursor.toArray()
×
222
    }
223

224
    /**
225
     * Finds first entity that matches given conditions and/or find options.
226
     */
227
    async findOne<Entity>(
228
        entityClassOrName: EntityTarget<Entity>,
229
        options: MongoFindOneOptions<Entity>,
230
    ): Promise<Entity | null> {
UNCOV
231
        return this.executeFindOne(entityClassOrName, options)
×
232
    }
233

234
    /**
235
     * Finds first entity that matches given WHERE conditions.
236
     */
237
    async findOneBy<Entity>(
238
        entityClassOrName: EntityTarget<Entity>,
239
        where: any,
240
    ): Promise<Entity | null> {
UNCOV
241
        return this.executeFindOne(entityClassOrName, where)
×
242
    }
243

244
    /**
245
     * Finds entity that matches given id.
246
     *
247
     * @deprecated use `findOneBy` method instead in conjunction with `In` operator, for example:
248
     *
249
     * .findOneBy({
250
     *     id: 1 // where "id" is your primary column name
251
     * })
252
     */
253
    async findOneById<Entity>(
254
        entityClassOrName: EntityTarget<Entity>,
255
        id: string | number | Date | ObjectId,
256
    ): Promise<Entity | null> {
UNCOV
257
        return this.executeFindOne(entityClassOrName, id)
×
258
    }
259

260
    /**
261
     * Inserts a given entity into the database.
262
     * Unlike save method executes a primitive operation without cascades, relations and other operations included.
263
     * Executes fast and efficient INSERT query.
264
     * Does not check if entity exist in the database, so query will fail if duplicate entity is being inserted.
265
     * You can execute bulk inserts using this method.
266
     */
267
    async insert<Entity>(
268
        target: EntityTarget<Entity>,
269
        entity:
270
            | QueryDeepPartialEntity<Entity>
271
            | QueryDeepPartialEntity<Entity>[],
272
    ): Promise<InsertResult> {
273
        // todo: convert entity to its database name
UNCOV
274
        const result = new InsertResult()
×
UNCOV
275
        if (Array.isArray(entity)) {
×
UNCOV
276
            result.raw = await this.insertMany(target, entity)
×
UNCOV
277
            Object.keys(result.raw.insertedIds).forEach((key: any) => {
×
UNCOV
278
                const insertedId = result.raw.insertedIds[key]
×
UNCOV
279
                result.generatedMaps.push(
×
280
                    this.connection.driver.createGeneratedMap(
281
                        this.connection.getMetadata(target),
282
                        insertedId,
283
                    )!,
284
                )
UNCOV
285
                result.identifiers.push(
×
286
                    this.connection.driver.createGeneratedMap(
287
                        this.connection.getMetadata(target),
288
                        insertedId,
289
                    )!,
290
                )
291
            })
292
        } else {
UNCOV
293
            result.raw = await this.insertOne(target, entity)
×
UNCOV
294
            result.generatedMaps.push(
×
295
                this.connection.driver.createGeneratedMap(
296
                    this.connection.getMetadata(target),
297
                    result.raw.insertedId,
298
                )!,
299
            )
UNCOV
300
            result.identifiers.push(
×
301
                this.connection.driver.createGeneratedMap(
302
                    this.connection.getMetadata(target),
303
                    result.raw.insertedId,
304
                )!,
305
            )
306
        }
307

UNCOV
308
        return result
×
309
    }
310

311
    /**
312
     * Updates entity partially. Entity can be found by a given conditions.
313
     * Unlike save method executes a primitive operation without cascades, relations and other operations included.
314
     * Executes fast and efficient UPDATE query.
315
     * Does not check if entity exist in the database.
316
     */
317
    async update<Entity>(
318
        target: EntityTarget<Entity>,
319
        criteria:
320
            | string
321
            | string[]
322
            | number
323
            | number[]
324
            | Date
325
            | Date[]
326
            | ObjectId
327
            | ObjectId[]
328
            | ObjectLiteral,
329
        partialEntity: QueryDeepPartialEntity<Entity>,
330
    ): Promise<UpdateResult> {
UNCOV
331
        const result = new UpdateResult()
×
332

UNCOV
333
        if (Array.isArray(criteria)) {
×
334
            const updateResults = await Promise.all(
×
335
                (criteria as any[]).map((criteriaItem) => {
336
                    return this.update(target, criteriaItem, partialEntity)
×
337
                }),
338
            )
339

340
            result.raw = updateResults.map((r) => r.raw)
×
341
            result.affected = updateResults
×
342
                .map((r) => r.affected || 0)
×
343
                .reduce((c, r) => c + r, 0)
×
344
            result.generatedMaps = updateResults.reduce(
×
345
                (c, r) => c.concat(r.generatedMaps),
×
346
                [] as ObjectLiteral[],
347
            )
348
        } else {
UNCOV
349
            const metadata = this.connection.getMetadata(target)
×
UNCOV
350
            const mongoResult = await this.updateMany(
×
351
                target,
352
                this.convertMixedCriteria(metadata, criteria),
353
                { $set: partialEntity },
354
            )
355

UNCOV
356
            result.raw = mongoResult
×
UNCOV
357
            result.affected = mongoResult.modifiedCount
×
358
        }
359

UNCOV
360
        return result
×
361
    }
362

363
    /**
364
     * Deletes entities by a given conditions.
365
     * Unlike save method executes a primitive operation without cascades, relations and other operations included.
366
     * Executes fast and efficient DELETE query.
367
     * Does not check if entity exist in the database.
368
     */
369
    async delete<Entity>(
370
        target: EntityTarget<Entity>,
371
        criteria:
372
            | string
373
            | string[]
374
            | number
375
            | number[]
376
            | Date
377
            | Date[]
378
            | ObjectId
379
            | ObjectId[]
380
            | ObjectLiteral[],
381
    ): Promise<DeleteResult> {
UNCOV
382
        const result = new DeleteResult()
×
383

UNCOV
384
        if (Array.isArray(criteria)) {
×
UNCOV
385
            const deleteResults = await Promise.all(
×
386
                (criteria as any[]).map((criteriaItem) => {
UNCOV
387
                    return this.delete(target, criteriaItem)
×
388
                }),
389
            )
390

UNCOV
391
            result.raw = deleteResults.map((r) => r.raw)
×
UNCOV
392
            result.affected = deleteResults
×
UNCOV
393
                .map((r) => r.affected || 0)
×
UNCOV
394
                .reduce((c, r) => c + r, 0)
×
395
        } else {
UNCOV
396
            const mongoResult = await this.deleteMany(
×
397
                target,
398
                this.convertMixedCriteria(
399
                    this.connection.getMetadata(target),
400
                    criteria,
401
                ),
402
            )
403

UNCOV
404
            result.raw = mongoResult
×
UNCOV
405
            result.affected = mongoResult.deletedCount
×
406
        }
407

UNCOV
408
        return result
×
409
    }
410

411
    // -------------------------------------------------------------------------
412
    // Public Methods
413
    // -------------------------------------------------------------------------
414

415
    /**
416
     * Creates a cursor for a query that can be used to iterate over results from MongoDB.
417
     */
418
    createCursor<Entity, T = any>(
419
        entityClassOrName: EntityTarget<Entity>,
420
        query: ObjectLiteral = {},
×
421
    ): FindCursor<T> {
UNCOV
422
        const metadata = this.connection.getMetadata(entityClassOrName)
×
UNCOV
423
        return this.mongoQueryRunner.cursor(metadata.tableName, query)
×
424
    }
425

426
    /**
427
     * Creates a cursor for a query that can be used to iterate over results from MongoDB.
428
     * This returns modified version of cursor that transforms each result into Entity model.
429
     */
430
    createEntityCursor<Entity>(
431
        entityClassOrName: EntityTarget<Entity>,
432
        query: ObjectLiteral = {},
×
433
    ): FindCursor<Entity> {
UNCOV
434
        const metadata = this.connection.getMetadata(entityClassOrName)
×
UNCOV
435
        const cursor = this.createCursor(entityClassOrName, query)
×
UNCOV
436
        this.applyEntityTransformationToCursor(metadata, cursor)
×
UNCOV
437
        return cursor
×
438
    }
439

440
    /**
441
     * Execute an aggregation framework pipeline against the collection.
442
     */
443
    aggregate<Entity, R = any>(
444
        entityClassOrName: EntityTarget<Entity>,
445
        pipeline: Document[],
446
        options?: AggregateOptions,
447
    ): AggregationCursor<R> {
UNCOV
448
        const metadata = this.connection.getMetadata(entityClassOrName)
×
UNCOV
449
        return this.mongoQueryRunner.aggregate(
×
450
            metadata.tableName,
451
            pipeline,
452
            options,
453
        )
454
    }
455

456
    /**
457
     * Execute an aggregation framework pipeline against the collection.
458
     * This returns modified version of cursor that transforms each result into Entity model.
459
     */
460
    aggregateEntity<Entity>(
461
        entityClassOrName: EntityTarget<Entity>,
462
        pipeline: Document[],
463
        options?: AggregateOptions,
464
    ): AggregationCursor<Entity> {
465
        const metadata = this.connection.getMetadata(entityClassOrName)
×
466
        const cursor = this.mongoQueryRunner.aggregate(
×
467
            metadata.tableName,
468
            pipeline,
469
            options,
470
        )
471
        this.applyEntityTransformationToCursor(metadata, cursor)
×
472
        return cursor
×
473
    }
474

475
    /**
476
     * Perform a bulkWrite operation without a fluent API.
477
     */
478
    bulkWrite<Entity>(
479
        entityClassOrName: EntityTarget<Entity>,
480
        operations: AnyBulkWriteOperation<Document>[],
481
        options?: BulkWriteOptions,
482
    ): Promise<BulkWriteResult> {
483
        const metadata = this.connection.getMetadata(entityClassOrName)
×
484
        return this.mongoQueryRunner.bulkWrite(
×
485
            metadata.tableName,
486
            operations,
487
            options,
488
        )
489
    }
490

491
    /**
492
     * Count number of matching documents in the db to a query.
493
     */
494
    count<Entity>(
495
        entityClassOrName: EntityTarget<Entity>,
496
        query: Filter<Document> = {},
×
497
        options: CountOptions = {},
×
498
    ): Promise<number> {
UNCOV
499
        const metadata = this.connection.getMetadata(entityClassOrName)
×
UNCOV
500
        return this.mongoQueryRunner.count(metadata.tableName, query, options)
×
501
    }
502

503
    /**
504
     * Count number of matching documents in the db to a query.
505
     */
506
    countDocuments<Entity>(
507
        entityClassOrName: EntityTarget<Entity>,
508
        query: Filter<Document> = {},
×
509
        options: CountDocumentsOptions = {},
×
510
    ): Promise<number> {
511
        const metadata = this.connection.getMetadata(entityClassOrName)
×
512
        return this.mongoQueryRunner.countDocuments(
×
513
            metadata.tableName,
514
            query,
515
            options,
516
        )
517
    }
518

519
    /**
520
     * Count number of matching documents in the db to a query.
521
     */
522
    countBy<Entity>(
523
        entityClassOrName: EntityTarget<Entity>,
524
        query?: ObjectLiteral,
525
        options?: CountOptions,
526
    ): Promise<number> {
UNCOV
527
        return this.count(entityClassOrName, query, options)
×
528
    }
529

530
    /**
531
     * Creates an index on the db and collection.
532
     */
533
    createCollectionIndex<Entity>(
534
        entityClassOrName: EntityTarget<Entity>,
535
        fieldOrSpec: IndexSpecification,
536
        options?: CreateIndexesOptions,
537
    ): Promise<string> {
538
        const metadata = this.connection.getMetadata(entityClassOrName)
×
539
        return this.mongoQueryRunner.createCollectionIndex(
×
540
            metadata.tableName,
541
            fieldOrSpec,
542
            options,
543
        )
544
    }
545

546
    /**
547
     * Creates multiple indexes in the collection, this method is only supported for MongoDB 2.6 or higher.
548
     * Earlier version of MongoDB will throw a command not supported error.
549
     * Index specifications are defined at http://docs.mongodb.org/manual/reference/command/createIndexes/.
550
     */
551
    createCollectionIndexes<Entity>(
552
        entityClassOrName: EntityTarget<Entity>,
553
        indexSpecs: IndexDescription[],
554
    ): Promise<string[]> {
555
        const metadata = this.connection.getMetadata(entityClassOrName)
×
556
        return this.mongoQueryRunner.createCollectionIndexes(
×
557
            metadata.tableName,
558
            indexSpecs,
559
        )
560
    }
561

562
    /**
563
     * Delete multiple documents on MongoDB.
564
     */
565
    deleteMany<Entity>(
566
        entityClassOrName: EntityTarget<Entity>,
567
        query: Filter<Document>,
568
        options: DeleteOptions = {},
×
569
    ): Promise<DeleteResultMongoDb> {
UNCOV
570
        const metadata = this.connection.getMetadata(entityClassOrName)
×
UNCOV
571
        return this.mongoQueryRunner.deleteMany(
×
572
            metadata.tableName,
573
            query,
574
            options,
575
        )
576
    }
577

578
    /**
579
     * Delete a document on MongoDB.
580
     */
581
    deleteOne<Entity>(
582
        entityClassOrName: EntityTarget<Entity>,
583
        query: Filter<Document>,
584
        options: DeleteOptions = {},
×
585
    ): Promise<DeleteResultMongoDb> {
586
        const metadata = this.connection.getMetadata(entityClassOrName)
×
587
        return this.mongoQueryRunner.deleteOne(
×
588
            metadata.tableName,
589
            query,
590
            options,
591
        )
592
    }
593

594
    /**
595
     * The distinct command returns returns a list of distinct values for the given key across a collection.
596
     */
597
    distinct<Entity>(
598
        entityClassOrName: EntityTarget<Entity>,
599
        key: string,
600
        query: Filter<Document>,
601
        options?: CommandOperationOptions,
602
    ): Promise<any> {
603
        const metadata = this.connection.getMetadata(entityClassOrName)
×
604
        return this.mongoQueryRunner.distinct(
×
605
            metadata.tableName,
606
            key,
607
            query,
608
            options,
609
        )
610
    }
611

612
    /**
613
     * Drops an index from this collection.
614
     */
615
    dropCollectionIndex<Entity>(
616
        entityClassOrName: EntityTarget<Entity>,
617
        indexName: string,
618
        options?: CommandOperationOptions,
619
    ): Promise<any> {
620
        const metadata = this.connection.getMetadata(entityClassOrName)
×
621
        return this.mongoQueryRunner.dropCollectionIndex(
×
622
            metadata.tableName,
623
            indexName,
624
            options,
625
        )
626
    }
627

628
    /**
629
     * Drops all indexes from the collection.
630
     */
631
    dropCollectionIndexes<Entity>(
632
        entityClassOrName: EntityTarget<Entity>,
633
    ): Promise<any> {
634
        const metadata = this.connection.getMetadata(entityClassOrName)
×
635
        return this.mongoQueryRunner.dropCollectionIndexes(metadata.tableName)
×
636
    }
637

638
    /**
639
     * Find a document and delete it in one atomic operation, requires a write lock for the duration of the operation.
640
     */
641
    findOneAndDelete<Entity>(
642
        entityClassOrName: EntityTarget<Entity>,
643
        query: ObjectLiteral,
644
        options?: FindOneAndDeleteOptions,
645
    ): Promise<Document | null> {
646
        const metadata = this.connection.getMetadata(entityClassOrName)
×
647
        return this.mongoQueryRunner.findOneAndDelete(
×
648
            metadata.tableName,
649
            query,
650
            options,
651
        )
652
    }
653

654
    /**
655
     * Find a document and replace it in one atomic operation, requires a write lock for the duration of the operation.
656
     */
657
    findOneAndReplace<Entity>(
658
        entityClassOrName: EntityTarget<Entity>,
659
        query: Filter<Document>,
660
        replacement: Document,
661
        options?: FindOneAndReplaceOptions,
662
    ): Promise<Document | null> {
663
        const metadata = this.connection.getMetadata(entityClassOrName)
×
664
        return this.mongoQueryRunner.findOneAndReplace(
×
665
            metadata.tableName,
666
            query,
667
            replacement,
668
            options,
669
        )
670
    }
671

672
    /**
673
     * Find a document and update it in one atomic operation, requires a write lock for the duration of the operation.
674
     */
675
    findOneAndUpdate<Entity>(
676
        entityClassOrName: EntityTarget<Entity>,
677
        query: Filter<Document>,
678
        update: UpdateFilter<Document>,
679
        options?: FindOneAndUpdateOptions,
680
    ): Promise<Document | null> {
681
        const metadata = this.connection.getMetadata(entityClassOrName)
×
682
        return this.mongoQueryRunner.findOneAndUpdate(
×
683
            metadata.tableName,
684
            query,
685
            update,
686
            options,
687
        )
688
    }
689

690
    /**
691
     * Retrieve all the indexes on the collection.
692
     */
693
    collectionIndexes<Entity>(
694
        entityClassOrName: EntityTarget<Entity>,
695
    ): Promise<Document> {
696
        const metadata = this.connection.getMetadata(entityClassOrName)
×
697
        return this.mongoQueryRunner.collectionIndexes(metadata.tableName)
×
698
    }
699

700
    /**
701
     * Retrieve all the indexes on the collection.
702
     */
703
    collectionIndexExists<Entity>(
704
        entityClassOrName: EntityTarget<Entity>,
705
        indexes: string | string[],
706
    ): Promise<boolean> {
707
        const metadata = this.connection.getMetadata(entityClassOrName)
×
708
        return this.mongoQueryRunner.collectionIndexExists(
×
709
            metadata.tableName,
710
            indexes,
711
        )
712
    }
713

714
    /**
715
     * Retrieves this collections index info.
716
     */
717
    collectionIndexInformation<Entity>(
718
        entityClassOrName: EntityTarget<Entity>,
719
        options?: IndexInformationOptions,
720
    ): Promise<any> {
721
        const metadata = this.connection.getMetadata(entityClassOrName)
×
722
        return this.mongoQueryRunner.collectionIndexInformation(
×
723
            metadata.tableName,
724
            options,
725
        )
726
    }
727

728
    /**
729
     * Initiate an In order bulk write operation, operations will be serially executed in the order they are added, creating a new operation for each switch in types.
730
     */
731
    initializeOrderedBulkOp<Entity>(
732
        entityClassOrName: EntityTarget<Entity>,
733
        options?: BulkWriteOptions,
734
    ): OrderedBulkOperation {
735
        const metadata = this.connection.getMetadata(entityClassOrName)
×
736
        return this.mongoQueryRunner.initializeOrderedBulkOp(
×
737
            metadata.tableName,
738
            options,
739
        )
740
    }
741

742
    /**
743
     * Initiate a Out of order batch write operation. All operations will be buffered into insert/update/remove commands executed out of order.
744
     */
745
    initializeUnorderedBulkOp<Entity>(
746
        entityClassOrName: EntityTarget<Entity>,
747
        options?: BulkWriteOptions,
748
    ): UnorderedBulkOperation {
749
        const metadata = this.connection.getMetadata(entityClassOrName)
×
750
        return this.mongoQueryRunner.initializeUnorderedBulkOp(
×
751
            metadata.tableName,
752
            options,
753
        )
754
    }
755

756
    /**
757
     * Inserts an array of documents into MongoDB.
758
     */
759
    insertMany<Entity>(
760
        entityClassOrName: EntityTarget<Entity>,
761
        docs: OptionalId<Document>[],
762
        options?: BulkWriteOptions,
763
    ): Promise<InsertManyResult> {
UNCOV
764
        const metadata = this.connection.getMetadata(entityClassOrName)
×
UNCOV
765
        return this.mongoQueryRunner.insertMany(
×
766
            metadata.tableName,
767
            docs,
768
            options,
769
        )
770
    }
771

772
    /**
773
     * Inserts a single document into MongoDB.
774
     */
775
    insertOne<Entity>(
776
        entityClassOrName: EntityTarget<Entity>,
777
        doc: OptionalId<Document>,
778
        options?: InsertOneOptions,
779
    ): Promise<InsertOneResult> {
UNCOV
780
        const metadata = this.connection.getMetadata(entityClassOrName)
×
UNCOV
781
        return this.mongoQueryRunner.insertOne(metadata.tableName, doc, options)
×
782
    }
783

784
    /**
785
     * Returns if the collection is a capped collection.
786
     */
787
    isCapped<Entity>(entityClassOrName: EntityTarget<Entity>): Promise<any> {
788
        const metadata = this.connection.getMetadata(entityClassOrName)
×
789
        return this.mongoQueryRunner.isCapped(metadata.tableName)
×
790
    }
791

792
    /**
793
     * Get the list of all indexes information for the collection.
794
     */
795
    listCollectionIndexes<Entity>(
796
        entityClassOrName: EntityTarget<Entity>,
797
        options?: ListIndexesOptions,
798
    ): ListIndexesCursor {
799
        const metadata = this.connection.getMetadata(entityClassOrName)
×
800
        return this.mongoQueryRunner.listCollectionIndexes(
×
801
            metadata.tableName,
802
            options,
803
        )
804
    }
805

806
    /**
807
     * Reindex all indexes on the collection Warning: reIndex is a blocking operation (indexes are rebuilt in the foreground) and will be slow for large collections.
808
     */
809
    rename<Entity>(
810
        entityClassOrName: EntityTarget<Entity>,
811
        newName: string,
812
        options?: RenameOptions,
813
    ): Promise<Collection<Document>> {
814
        const metadata = this.connection.getMetadata(entityClassOrName)
×
815
        return this.mongoQueryRunner.rename(
×
816
            metadata.tableName,
817
            newName,
818
            options,
819
        )
820
    }
821

822
    /**
823
     * Replace a document on MongoDB.
824
     */
825
    replaceOne<Entity>(
826
        entityClassOrName: EntityTarget<Entity>,
827
        query: Filter<Document>,
828
        doc: Document,
829
        options?: ReplaceOptions,
830
    ): Promise<Document | UpdateResultMongoDb> {
831
        const metadata = this.connection.getMetadata(entityClassOrName)
×
832
        return this.mongoQueryRunner.replaceOne(
×
833
            metadata.tableName,
834
            query,
835
            doc,
836
            options,
837
        )
838
    }
839

840
    /**
841
     * Get all the collection statistics.
842
     */
843
    stats<Entity>(
844
        entityClassOrName: EntityTarget<Entity>,
845
        options?: CollStatsOptions,
846
    ): Promise<CollStats> {
847
        const metadata = this.connection.getMetadata(entityClassOrName)
×
848
        return this.mongoQueryRunner.stats(metadata.tableName, options)
×
849
    }
850

851
    watch<Entity>(
852
        entityClassOrName: EntityTarget<Entity>,
853
        pipeline?: Document[],
854
        options?: ChangeStreamOptions,
855
    ): ChangeStream {
856
        const metadata = this.connection.getMetadata(entityClassOrName)
×
857
        return this.mongoQueryRunner.watch(
×
858
            metadata.tableName,
859
            pipeline,
860
            options,
861
        )
862
    }
863

864
    /**
865
     * Update multiple documents on MongoDB.
866
     */
867
    updateMany<Entity>(
868
        entityClassOrName: EntityTarget<Entity>,
869
        query: Filter<Document>,
870
        update: UpdateFilter<Document>,
871
        options?: UpdateOptions,
872
    ): Promise<Document | UpdateResultMongoDb> {
UNCOV
873
        const metadata = this.connection.getMetadata(entityClassOrName)
×
UNCOV
874
        return this.mongoQueryRunner.updateMany(
×
875
            metadata.tableName,
876
            query,
877
            update,
878
            options,
879
        )
880
    }
881

882
    /**
883
     * Update a single document on MongoDB.
884
     */
885
    updateOne<Entity>(
886
        entityClassOrName: EntityTarget<Entity>,
887
        query: Filter<Document>,
888
        update: UpdateFilter<Document>,
889
        options?: UpdateOptions,
890
    ): Promise<Document | UpdateResultMongoDb> {
891
        const metadata = this.connection.getMetadata(entityClassOrName)
×
892
        return this.mongoQueryRunner.updateOne(
×
893
            metadata.tableName,
894
            query,
895
            update,
896
            options,
897
        )
898
    }
899

900
    // -------------------------------------------------------------------------
901
    // Protected Methods
902
    // -------------------------------------------------------------------------
903

904
    /**
905
     * Converts FindManyOptions to mongodb query.
906
     */
907
    protected convertFindManyOptionsOrConditionsToMongodbQuery<Entity>(
908
        optionsOrConditions:
909
            | MongoFindManyOptions<Entity>
910
            | Partial<Entity>
911
            | FilterOperators<Entity>
912
            | any[]
913
            | undefined,
914
    ): ObjectLiteral | undefined {
UNCOV
915
        if (!optionsOrConditions) return undefined
×
916

UNCOV
917
        if (FindOptionsUtils.isFindManyOptions<Entity>(optionsOrConditions))
×
918
            // If where condition is passed as a string which contains sql we have to ignore
919
            // as mongo is not a sql database
UNCOV
920
            return typeof optionsOrConditions.where === "string"
×
921
                ? {}
922
                : optionsOrConditions.where
923

UNCOV
924
        return optionsOrConditions
×
925
    }
926

927
    /**
928
     * Converts FindOneOptions to mongodb query.
929
     */
930
    protected convertFindOneOptionsOrConditionsToMongodbQuery<Entity>(
931
        optionsOrConditions:
932
            | MongoFindOneOptions<Entity>
933
            | Partial<Entity>
934
            | undefined,
935
    ): ObjectLiteral | undefined {
UNCOV
936
        if (!optionsOrConditions) return undefined
×
937

UNCOV
938
        if (FindOptionsUtils.isFindOneOptions<Entity>(optionsOrConditions))
×
939
            // If where condition is passed as a string which contains sql we have to ignore
940
            // as mongo is not a sql database
UNCOV
941
            return typeof optionsOrConditions.where === "string"
×
942
                ? {}
943
                : optionsOrConditions.where
944

UNCOV
945
        return optionsOrConditions
×
946
    }
947

948
    /**
949
     * Converts FindOptions into mongodb order by criteria.
950
     */
951
    protected convertFindOptionsOrderToOrderCriteria(order: ObjectLiteral) {
UNCOV
952
        return Object.keys(order).reduce((orderCriteria, key) => {
×
UNCOV
953
            switch (order[key]) {
×
954
                case "DESC":
UNCOV
955
                    orderCriteria[key] = -1
×
UNCOV
956
                    break
×
957
                case "ASC":
UNCOV
958
                    orderCriteria[key] = 1
×
UNCOV
959
                    break
×
960
                default:
UNCOV
961
                    orderCriteria[key] = order[key]
×
962
            }
UNCOV
963
            return orderCriteria
×
964
        }, {} as ObjectLiteral)
965
    }
966

967
    /**
968
     * Converts FindOptions into mongodb select by criteria.
969
     */
970
    protected convertFindOptionsSelectToProjectCriteria(
971
        selects: FindOptionsSelect<any> | FindOptionsSelectByString<any>,
972
    ) {
UNCOV
973
        if (Array.isArray(selects)) {
×
974
            return selects.reduce((projectCriteria, key) => {
×
975
                projectCriteria[key] = 1
×
976
                return projectCriteria
×
977
            }, {} as any)
978
        } else {
979
            // todo: implement
UNCOV
980
            return {}
×
981
        }
982
    }
983

984
    /**
985
     * Ensures given id is an id for query.
986
     */
987
    protected convertMixedCriteria(
988
        metadata: EntityMetadata,
989
        idMap: any,
990
    ): ObjectLiteral {
UNCOV
991
        const objectIdInstance = PlatformTools.load("mongodb").ObjectId
×
992

993
        // check first if it's ObjectId compatible:
994
        // string, number, Buffer, ObjectId or ObjectId-like
UNCOV
995
        if (objectIdInstance.isValid(idMap)) {
×
UNCOV
996
            return {
×
997
                _id: new objectIdInstance(idMap),
998
            }
999
        }
1000

1001
        // if it's some other type of object build a query from the columns
1002
        // this check needs to be after the ObjectId check, because a valid ObjectId is also an Object instance
UNCOV
1003
        if (ObjectUtils.isObject(idMap)) {
×
UNCOV
1004
            return metadata.columns.reduce((query, column) => {
×
UNCOV
1005
                const columnValue = column.getEntityValue(idMap)
×
UNCOV
1006
                if (columnValue !== undefined)
×
UNCOV
1007
                    query[column.databasePath] = columnValue
×
UNCOV
1008
                return query
×
1009
            }, {} as any)
1010
        }
1011

1012
        // last resort: try to convert it to an ObjectId anyway
1013
        // most likely it will fail, but we want to be backwards compatible and keep the same thrown Errors.
1014
        // it can still pass with null/undefined
1015
        return {
×
1016
            _id: new objectIdInstance(idMap),
1017
        }
1018
    }
1019

1020
    /**
1021
     * Overrides cursor's toArray and next methods to convert results to entity automatically.
1022
     */
1023
    protected applyEntityTransformationToCursor<Entity extends ObjectLiteral>(
1024
        metadata: EntityMetadata,
1025
        cursor: FindCursor<Entity> | AggregationCursor<Entity>,
1026
    ) {
UNCOV
1027
        const queryRunner = this.mongoQueryRunner
×
1028

UNCOV
1029
        ;(cursor as any)["__to_array_func"] = cursor.toArray
×
UNCOV
1030
        cursor.toArray = async () =>
×
UNCOV
1031
            ((cursor as any)["__to_array_func"] as CallableFunction)().then(
×
1032
                async (results: Entity[]) => {
UNCOV
1033
                    const transformer = new DocumentToEntityTransformer()
×
UNCOV
1034
                    const entities = transformer.transformAll(results, metadata)
×
1035
                    // broadcast "load" events
UNCOV
1036
                    await queryRunner.broadcaster.broadcast(
×
1037
                        "Load",
1038
                        metadata,
1039
                        entities,
1040
                    )
UNCOV
1041
                    return entities
×
1042
                },
1043
            )
UNCOV
1044
        ;(cursor as any)["__next_func"] = cursor.next
×
UNCOV
1045
        cursor.next = async () =>
×
UNCOV
1046
            ((cursor as any)["__next_func"] as CallableFunction)().then(
×
1047
                async (result: Entity) => {
UNCOV
1048
                    if (!result) {
×
UNCOV
1049
                        return result
×
1050
                    }
UNCOV
1051
                    const transformer = new DocumentToEntityTransformer()
×
UNCOV
1052
                    const entity = transformer.transform(result, metadata)
×
1053
                    // broadcast "load" events
UNCOV
1054
                    await queryRunner.broadcaster.broadcast("Load", metadata, [
×
1055
                        entity,
1056
                    ])
UNCOV
1057
                    return entity
×
1058
                },
1059
            )
1060
    }
1061

1062
    protected filterSoftDeleted<Entity>(
1063
        cursor: FindCursor<Entity>,
1064
        deleteDateColumn: ColumnMetadata,
1065
        query?: ObjectLiteral,
1066
    ) {
UNCOV
1067
        const { $or, ...restQuery } = query ?? {}
×
UNCOV
1068
        cursor.filter({
×
1069
            $or: [
1070
                { [deleteDateColumn.propertyName]: { $eq: null } },
1071
                ...(Array.isArray($or) ? $or : []),
×
1072
            ],
1073
            ...restQuery,
1074
        })
1075
    }
1076

1077
    /**
1078
     * Finds first entity that matches given conditions and/or find options.
1079
     */
1080
    protected async executeFindOne<Entity>(
1081
        entityClassOrName: EntityTarget<Entity>,
1082
        optionsOrConditions?: any,
1083
        maybeOptions?: MongoFindOneOptions<Entity>,
1084
    ): Promise<Entity | null> {
UNCOV
1085
        const objectIdInstance = PlatformTools.load("mongodb").ObjectId
×
1086
        const id =
UNCOV
1087
            optionsOrConditions instanceof objectIdInstance ||
×
1088
            typeof optionsOrConditions === "string"
1089
                ? optionsOrConditions
1090
                : undefined
1091
        const findOneOptionsOrConditions = (
UNCOV
1092
            id ? maybeOptions : optionsOrConditions
×
1093
        ) as any
1094
        const query =
UNCOV
1095
            this.convertFindOneOptionsOrConditionsToMongodbQuery(
×
1096
                findOneOptionsOrConditions,
1097
            ) || {}
UNCOV
1098
        if (id) {
×
UNCOV
1099
            query["_id"] =
×
1100
                id instanceof objectIdInstance ? id : new objectIdInstance(id)
×
1101
        }
UNCOV
1102
        const cursor = this.createEntityCursor<Entity>(entityClassOrName, query)
×
1103
        const deleteDateColumn =
UNCOV
1104
            this.connection.getMetadata(entityClassOrName).deleteDateColumn
×
UNCOV
1105
        if (FindOptionsUtils.isFindOneOptions(findOneOptionsOrConditions)) {
×
UNCOV
1106
            if (findOneOptionsOrConditions.select)
×
UNCOV
1107
                cursor.project(
×
1108
                    this.convertFindOptionsSelectToProjectCriteria(
1109
                        findOneOptionsOrConditions.select,
1110
                    ),
1111
                )
UNCOV
1112
            if (findOneOptionsOrConditions.order)
×
UNCOV
1113
                cursor.sort(
×
1114
                    this.convertFindOptionsOrderToOrderCriteria(
1115
                        findOneOptionsOrConditions.order,
1116
                    ),
1117
                )
UNCOV
1118
            if (deleteDateColumn && !findOneOptionsOrConditions.withDeleted) {
×
UNCOV
1119
                this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
1120
            }
UNCOV
1121
        } else if (deleteDateColumn) {
×
1122
            this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
1123
        }
1124

1125
        // const result = await cursor.limit(1).next();
UNCOV
1126
        const result = await cursor.limit(1).toArray()
×
UNCOV
1127
        return result.length > 0 ? result[0] : null
×
1128
    }
1129

1130
    protected async executeFind<Entity>(
1131
        entityClassOrName: EntityTarget<Entity>,
1132
        optionsOrConditions?:
1133
            | MongoFindManyOptions<Entity>
1134
            | Partial<Entity>
1135
            | any[],
1136
    ): Promise<Entity[]> {
1137
        const query =
1138
            this.convertFindManyOptionsOrConditionsToMongodbQuery(
×
1139
                optionsOrConditions,
1140
            )
1141
        const cursor = this.createEntityCursor<Entity>(entityClassOrName, query)
×
1142
        const deleteDateColumn =
1143
            this.connection.getMetadata(entityClassOrName).deleteDateColumn
×
1144

1145
        if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) {
×
1146
            if (optionsOrConditions.select)
×
1147
                cursor.project(
×
1148
                    this.convertFindOptionsSelectToProjectCriteria(
1149
                        optionsOrConditions.select,
1150
                    ),
1151
                )
1152
            if (optionsOrConditions.skip) cursor.skip(optionsOrConditions.skip)
×
1153
            if (optionsOrConditions.take) cursor.limit(optionsOrConditions.take)
×
1154
            if (optionsOrConditions.order)
×
1155
                cursor.sort(
×
1156
                    this.convertFindOptionsOrderToOrderCriteria(
1157
                        optionsOrConditions.order,
1158
                    ),
1159
                )
1160
            if (deleteDateColumn && !optionsOrConditions.withDeleted) {
×
1161
                this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
1162
            }
1163
        } else if (deleteDateColumn) {
×
1164
            this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
1165
        }
1166
        return cursor.toArray()
×
1167
    }
1168

1169
    /**
1170
     * Finds entities that match given find options or conditions.
1171
     */
1172
    async executeFindAndCount<Entity>(
1173
        entityClassOrName: EntityTarget<Entity>,
1174
        optionsOrConditions?: MongoFindManyOptions<Entity> | Partial<Entity>,
1175
    ): Promise<[Entity[], number]> {
1176
        const query =
UNCOV
1177
            this.convertFindManyOptionsOrConditionsToMongodbQuery(
×
1178
                optionsOrConditions,
1179
            )
UNCOV
1180
        const cursor = await this.createEntityCursor(entityClassOrName, query)
×
1181
        const deleteDateColumn =
UNCOV
1182
            this.connection.getMetadata(entityClassOrName).deleteDateColumn
×
1183

UNCOV
1184
        if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) {
×
UNCOV
1185
            if (optionsOrConditions.select)
×
UNCOV
1186
                cursor.project(
×
1187
                    this.convertFindOptionsSelectToProjectCriteria(
1188
                        optionsOrConditions.select,
1189
                    ),
1190
                )
UNCOV
1191
            if (optionsOrConditions.skip) cursor.skip(optionsOrConditions.skip)
×
UNCOV
1192
            if (optionsOrConditions.take) cursor.limit(optionsOrConditions.take)
×
UNCOV
1193
            if (optionsOrConditions.order)
×
UNCOV
1194
                cursor.sort(
×
1195
                    this.convertFindOptionsOrderToOrderCriteria(
1196
                        optionsOrConditions.order,
1197
                    ),
1198
                )
UNCOV
1199
            if (deleteDateColumn && !optionsOrConditions.withDeleted) {
×
UNCOV
1200
                this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
1201
            }
UNCOV
1202
        } else if (deleteDateColumn) {
×
UNCOV
1203
            this.filterSoftDeleted(cursor, deleteDateColumn, query)
×
1204
        }
UNCOV
1205
        const [results, count] = await Promise.all<any>([
×
1206
            cursor.toArray(),
1207
            this.count(entityClassOrName, query),
1208
        ])
UNCOV
1209
        return [results, parseInt(count)]
×
1210
    }
1211
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc