From 6675dcf71500e5520a8de30122d7407dd960ebb7 Mon Sep 17 00:00:00 2001 From: Zotov Dmitry Date: Sat, 20 May 2017 15:49:45 +0500 Subject: [PATCH] refactored MongoEntityManager and MongoRepository; --- src/entity-manager/MongoEntityManager.ts | 302 +++++++++++++++++++---- src/repository/MongoRepository.ts | 289 ++++++---------------- 2 files changed, 328 insertions(+), 263 deletions(-) diff --git a/src/entity-manager/MongoEntityManager.ts b/src/entity-manager/MongoEntityManager.ts index ab04ac5c4..5ce9e5c8a 100644 --- a/src/entity-manager/MongoEntityManager.ts +++ b/src/entity-manager/MongoEntityManager.ts @@ -4,38 +4,45 @@ import {EntityManager} from "./EntityManager"; import {QueryBuilder} from "../query-builder/QueryBuilder"; import {ObjectType} from "../common/ObjectType"; import { - Cursor, - Collection, - MongoCountPreferences, - CollectionAggregationOptions, AggregationCursor, - CollectionBluckWriteOptions, BulkWriteOpResultObject, - MongodbIndexOptions, + Code, + Collection, + CollectionAggregationOptions, + CollectionBluckWriteOptions, + CollectionInsertManyOptions, + CollectionInsertOneOptions, CollectionOptions, + CollStats, + CommandCursor, + Cursor, + CursorResult, DeleteWriteOpResultObject, FindAndModifyWriteOpResultObject, FindOneAndReplaceOption, GeoHaystackSearchOptions, GeoNearOptions, - ReadPreference, - Code, - OrderedBulkOperation, - UnorderedBulkOperation, - InsertWriteOpResult, - CollectionInsertManyOptions, - CollectionInsertOneOptions, InsertOneWriteOpResult, - CommandCursor, + InsertWriteOpResult, MapReduceOptions, + MongoCallback, + MongoCountPreferences, + MongodbIndexOptions, + MongoError, + OrderedBulkOperation, ParallelCollectionScanOptions, + ReadPreference, ReplaceOneOptions, - UpdateWriteOpResult, - CollStats + UnorderedBulkOperation, + UpdateWriteOpResult } from "../driver/mongodb/typings"; import {ObjectLiteral} from "../common/ObjectLiteral"; import {MongoQueryRunner} from "../driver/mongodb/MongoQueryRunner"; import {MongoDriver} from "../driver/mongodb/MongoDriver"; +import {DocumentToEntityTransformer} from "../query-builder/transformer/DocumentToEntityTransformer"; +import {FindManyOptions} from "../find-options/FindManyOptions"; +import {FindOptionsUtils} from "../find-options/FindOptionsUtils"; +import {FindOneOptions} from "../find-options/FindOneOptions"; /** * Entity manager supposed to work with any entity, automatically find its repository and call its methods, @@ -80,6 +87,107 @@ export class MongoEntityManager extends EntityManager { throw new Error(`Query Builder is not supported by MongoDB.`); } + /** + * Finds entities that match given find options or conditions. + */ + async find(entityClassOrName: ObjectType|string, optionsOrConditions?: FindManyOptions|Partial): Promise { + const query = this.convertFindManyOptionsOrConditionsToMongodbQuery(optionsOrConditions); + const cursor = await this.createEntityCursor(entityClassOrName, query); + if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) { + if (optionsOrConditions.skip) + cursor.skip(optionsOrConditions.skip); + if (optionsOrConditions.take) + cursor.limit(optionsOrConditions.take); + if (optionsOrConditions.order) + cursor.sort(this.convertFindOptionsOrderToOrderCriteria(optionsOrConditions.order)); + } + return cursor.toArray(); + } + + /** + * Finds entities that match given find options or conditions. + * Also counts all entities that match given conditions, + * but ignores pagination settings (from and take options). + */ + async findAndCount(entityClassOrName: ObjectType|string, optionsOrConditions?: FindManyOptions|Partial): Promise<[ Entity[], number ]> { + const query = this.convertFindManyOptionsOrConditionsToMongodbQuery(optionsOrConditions); + const cursor = await this.createEntityCursor(entityClassOrName, query); + if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) { + if (optionsOrConditions.skip) + cursor.skip(optionsOrConditions.skip); + if (optionsOrConditions.take) + cursor.limit(optionsOrConditions.take); + if (optionsOrConditions.order) + cursor.sort(this.convertFindOptionsOrderToOrderCriteria(optionsOrConditions.order)); + } + const [results, count] = await Promise.all([ + cursor.toArray(), + this.count(entityClassOrName, query), + ]); + return [results, parseInt(count)]; + } + + /** + * Finds entities by ids. + * Optionally find options can be applied. + */ + async findByIds(entityClassOrName: ObjectType|string, ids: any[], optionsOrConditions?: FindManyOptions|Partial): Promise { + const metadata = this.connection.getMetadata(entityClassOrName); + const query = this.convertFindManyOptionsOrConditionsToMongodbQuery(optionsOrConditions) || {}; + const objectIdInstance = require("mongodb").ObjectID; + query["_id"] = { $in: ids.map(id => { + if (id instanceof objectIdInstance) + return id; + + return id[metadata.objectIdColumn!.propertyName]; + }) }; + + const cursor = await this.createEntityCursor(entityClassOrName, query); + if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) { + if (optionsOrConditions.skip) + cursor.skip(optionsOrConditions.skip); + if (optionsOrConditions.take) + cursor.limit(optionsOrConditions.take); + if (optionsOrConditions.order) + cursor.sort(this.convertFindOptionsOrderToOrderCriteria(optionsOrConditions.order)); + } + return await cursor.toArray(); + } + + /** + * Finds first entity that matches given conditions and/or find options. + */ + async findOne(entityClassOrName: ObjectType|string, optionsOrConditions?: FindOneOptions|Partial): Promise { + const query = this.convertFindOneOptionsOrConditionsToMongodbQuery(optionsOrConditions); + const cursor = await this.createEntityCursor(entityClassOrName, query); + if (FindOptionsUtils.isFindOneOptions(optionsOrConditions)) { + if (optionsOrConditions.order) + cursor.sort(this.convertFindOptionsOrderToOrderCriteria(optionsOrConditions.order)); + } + + // const result = await cursor.limit(1).next(); + const result = await cursor.limit(1).toArray(); + return result.length > 0 ? result[0] : undefined; + } + + /** + * Finds entity by given id. + * Optionally find options or conditions can be applied. + */ + async findOneById(entityClassOrName: ObjectType|string, id: any, optionsOrConditions?: FindOneOptions|Partial): Promise { + const query = this.convertFindOneOptionsOrConditionsToMongodbQuery(optionsOrConditions) || {}; + query["_id"] = id; + const cursor = await this.createEntityCursor(entityClassOrName, query); + if (FindOptionsUtils.isFindOneOptions(optionsOrConditions)) { + if (optionsOrConditions.order) + cursor.sort(this.convertFindOptionsOrderToOrderCriteria(optionsOrConditions.order)); + } + + // const result = await cursor.limit(1).next(); + const result = await cursor.limit(1).toArray(); + return result.length > 0 ? result[0] : undefined; + } + // ------------------------------------------------------------------------- // Public Methods // ------------------------------------------------------------------------- @@ -97,35 +205,80 @@ export class MongoEntityManager extends EntityManager { * This returns modified version of cursor that transforms each result into Entity model. */ createEntityCursor(entityClassOrName: ObjectType|string, query?: ObjectLiteral): Cursor { - return this.getMongoRepository(entityClassOrName as any).createEntityCursor(query); + + const metadata = this.connection.getMetadata(entityClassOrName); + const cursor = this.createCursor(entityClassOrName, query); + const ParentCursor = require("mongodb").Cursor; + cursor.toArray = function (callback?: MongoCallback) { + if (callback) { + ParentCursor.prototype.toArray.call(this, (error: MongoError, results: Entity[]): void => { + if (error) { + callback(error, results); + return; + } + + const transformer = new DocumentToEntityTransformer(); + return callback(error, transformer.transformAll(results, metadata)); + }); + } else { + return ParentCursor.prototype.toArray.call(this).then((results: Entity[]) => { + const transformer = new DocumentToEntityTransformer(); + return transformer.transformAll(results, metadata); + }); + } + }; + cursor.next = function (callback?: MongoCallback) { + if (callback) { + ParentCursor.prototype.next.call(this, (error: MongoError, result: CursorResult): void => { + if (error || !result) { + callback(error, result); + return; + } + + const transformer = new DocumentToEntityTransformer(); + return callback(error, transformer.transform(result, metadata)); + }); + } else { + return ParentCursor.prototype.next.call(this).then((result: Entity) => { + if (!result) return result; + const transformer = new DocumentToEntityTransformer(); + return transformer.transform(result, metadata); + }); + } + }; + return cursor; } /** * Execute an aggregation framework pipeline against the collection. */ aggregate(entityClassOrName: ObjectType|string, pipeline: ObjectLiteral[], options?: CollectionAggregationOptions): AggregationCursor { - return this.getMongoRepository(entityClassOrName as any).aggregate(pipeline, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.aggregate(metadata.tableName, pipeline, options); } /** * Perform a bulkWrite operation without a fluent API. */ bulkWrite(entityClassOrName: ObjectType|string, operations: ObjectLiteral[], options?: CollectionBluckWriteOptions): Promise { - return this.getMongoRepository(entityClassOrName as any).bulkWrite(operations, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.bulkWrite(metadata.tableName, operations, options); } /** * Count number of matching documents in the db to a query. */ count(entityClassOrName: ObjectType|string, query?: ObjectLiteral, options?: MongoCountPreferences): Promise { - return this.getMongoRepository(entityClassOrName as any).count(query, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.count(metadata.tableName, query, options); } /** * Creates an index on the db and collection. */ createCollectionIndex(entityClassOrName: ObjectType|string, fieldOrSpec: string|any, options?: MongodbIndexOptions): Promise { - return this.getMongoRepository(entityClassOrName as any).createCollectionIndex(fieldOrSpec, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.createCollectionIndex(metadata.tableName, fieldOrSpec, options); } /** @@ -134,154 +287,176 @@ export class MongoEntityManager extends EntityManager { * Index specifications are defined at http://docs.mongodb.org/manual/reference/command/createIndexes/. */ createCollectionIndexes(entityClassOrName: ObjectType|string, indexSpecs: ObjectLiteral[]): Promise { - return this.getMongoRepository(entityClassOrName as any).createCollectionIndexes(indexSpecs); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.createCollectionIndexes(metadata.tableName, indexSpecs); } /** * Delete multiple documents on MongoDB. */ deleteMany(entityClassOrName: ObjectType|string, query: ObjectLiteral, options?: CollectionOptions): Promise { - return this.getMongoRepository(entityClassOrName as any).deleteMany(query, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.deleteMany(metadata.tableName, query, options); } /** * Delete a document on MongoDB. */ deleteOne(entityClassOrName: ObjectType|string, query: ObjectLiteral, options?: CollectionOptions): Promise { - return this.getMongoRepository(entityClassOrName as any).deleteOne(query, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.deleteOne(metadata.tableName, query, options); } /** * The distinct command returns returns a list of distinct values for the given key across a collection. */ distinct(entityClassOrName: ObjectType|string, key: string, query: ObjectLiteral, options?: { readPreference?: ReadPreference|string }): Promise { - return this.getMongoRepository(entityClassOrName as any).distinct(key, query, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.distinct(metadata.tableName, key, query, options); } /** * Drops an index from this collection. */ dropCollectionIndex(entityClassOrName: ObjectType|string, indexName: string, options?: CollectionOptions): Promise { - return this.getMongoRepository(entityClassOrName as any).dropCollectionIndex(indexName, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.dropCollectionIndex(metadata.tableName, indexName, options); } /** * Drops all indexes from the collection. */ dropCollectionIndexes(entityClassOrName: ObjectType|string): Promise { - return this.getMongoRepository(entityClassOrName as any).dropCollectionIndexes(); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.dropCollectionIndexes(metadata.tableName); } /** * Find a document and delete it in one atomic operation, requires a write lock for the duration of the operation. */ findOneAndDelete(entityClassOrName: ObjectType|string, query: ObjectLiteral, options?: { projection?: Object, sort?: Object, maxTimeMS?: number }): Promise { - return this.getMongoRepository(entityClassOrName as any).findOneAndDelete(query, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.findOneAndDelete(metadata.tableName, query, options); } /** * Find a document and replace it in one atomic operation, requires a write lock for the duration of the operation. */ findOneAndReplace(entityClassOrName: ObjectType|string, query: ObjectLiteral, replacement: Object, options?: FindOneAndReplaceOption): Promise { - return this.getMongoRepository(entityClassOrName as any).findOneAndReplace(query, replacement, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.findOneAndReplace(metadata.tableName, query, replacement, options); } /** * Find a document and update it in one atomic operation, requires a write lock for the duration of the operation. */ findOneAndUpdate(entityClassOrName: ObjectType|string, query: ObjectLiteral, update: Object, options?: FindOneAndReplaceOption): Promise { - return this.getMongoRepository(entityClassOrName as any).findOneAndUpdate(query, update, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.findOneAndUpdate(metadata.tableName, query, update, options); } /** * Execute a geo search using a geo haystack index on a collection. */ geoHaystackSearch(entityClassOrName: ObjectType|string, x: number, y: number, options?: GeoHaystackSearchOptions): Promise { - return this.getMongoRepository(entityClassOrName as any).geoHaystackSearch(x, y, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.geoHaystackSearch(metadata.tableName, x, y, options); } /** * Execute the geoNear command to search for items in the collection. */ geoNear(entityClassOrName: ObjectType|string, x: number, y: number, options?: GeoNearOptions): Promise { - return this.getMongoRepository(entityClassOrName as any).geoNear(x, y, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.geoNear(metadata.tableName, x, y, options); } /** * Run a group command across a collection. */ group(entityClassOrName: ObjectType|string, keys: Object|Array|Function|Code, condition: Object, initial: Object, reduce: Function|Code, finalize: Function|Code, command: boolean, options?: { readPreference?: ReadPreference | string }): Promise { - return this.getMongoRepository(entityClassOrName as any).group(keys, condition, initial, reduce, finalize, command, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.group(metadata.tableName, keys, condition, initial, reduce, finalize, command, options); } /** * Retrieve all the indexes on the collection. */ collectionIndexes(entityClassOrName: ObjectType|string): Promise { - return this.getMongoRepository(entityClassOrName as any).collectionIndexes(); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.collectionIndexes(metadata.tableName); } /** * Retrieve all the indexes on the collection. */ collectionIndexExists(entityClassOrName: ObjectType|string, indexes: string|string[]): Promise { - return this.getMongoRepository(entityClassOrName as any).collectionIndexExists(indexes); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.collectionIndexExists(metadata.tableName, indexes); } /** * Retrieves this collections index info. */ collectionIndexInformation(entityClassOrName: ObjectType|string, options?: { full: boolean }): Promise { - return this.getMongoRepository(entityClassOrName as any).collectionIndexInformation(options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.collectionIndexInformation(metadata.tableName, options); } /** * 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. */ initializeOrderedBulkOp(entityClassOrName: ObjectType|string, options?: CollectionOptions): OrderedBulkOperation { - return this.getMongoRepository(entityClassOrName as any).initializeOrderedBulkOp(options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.initializeOrderedBulkOp(metadata.tableName, options); } /** * Initiate a Out of order batch write operation. All operations will be buffered into insert/update/remove commands executed out of order. */ initializeUnorderedBulkOp(entityClassOrName: ObjectType|string, options?: CollectionOptions): UnorderedBulkOperation { - return this.getMongoRepository(entityClassOrName as any).initializeUnorderedBulkOp(options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.initializeUnorderedBulkOp(metadata.tableName, options); } /** * Inserts an array of documents into MongoDB. */ insertMany(entityClassOrName: ObjectType|string, docs: ObjectLiteral[], options?: CollectionInsertManyOptions): Promise { - return this.getMongoRepository(entityClassOrName as any).insertMany(docs, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.insertMany(metadata.tableName, docs, options); } /** * Inserts a single document into MongoDB. */ insertOne(entityClassOrName: ObjectType|string, doc: ObjectLiteral, options?: CollectionInsertOneOptions): Promise { - return this.getMongoRepository(entityClassOrName as any).insertOne(doc, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.insertOne(metadata.tableName, doc, options); } /** * Returns if the collection is a capped collection. */ isCapped(entityClassOrName: ObjectType|string): Promise { - return this.getMongoRepository(entityClassOrName as any).isCapped(); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.isCapped(metadata.tableName); } /** * Get the list of all indexes information for the collection. */ listCollectionIndexes(entityClassOrName: ObjectType|string, options?: { batchSize?: number, readPreference?: ReadPreference|string }): CommandCursor { - return this.getMongoRepository(entityClassOrName as any).listCollectionIndexes(options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.listCollectionIndexes(metadata.tableName, options); } /** * Run Map Reduce across a collection. Be aware that the inline option for out will return an array of results not a collection. */ mapReduce(entityClassOrName: ObjectType|string, map: Function|string, reduce: Function|string, options?: MapReduceOptions): Promise { - return this.getMongoRepository(entityClassOrName as any).mapReduce(map, reduce, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.mapReduce(metadata.tableName, map, reduce, options); } /** @@ -289,49 +464,56 @@ export class MongoEntityManager extends EntityManager { * There are no ordering guarantees for returned results. */ parallelCollectionScan(entityClassOrName: ObjectType|string, options?: ParallelCollectionScanOptions): Promise[]> { - return this.getMongoRepository(entityClassOrName as any).parallelCollectionScan(options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.parallelCollectionScan(metadata.tableName, options); } /** * 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. */ reIndex(entityClassOrName: ObjectType|string): Promise { - return this.getMongoRepository(entityClassOrName as any).reIndex(); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.reIndex(metadata.tableName); } /** * 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. */ rename(entityClassOrName: ObjectType|string, newName: string, options?: { dropTarget?: boolean }): Promise { - return this.getMongoRepository(entityClassOrName as any).rename(newName, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.rename(metadata.tableName, newName, options); } /** * Replace a document on MongoDB. */ replaceOne(entityClassOrName: ObjectType|string, query: ObjectLiteral, doc: ObjectLiteral, options?: ReplaceOneOptions): Promise { - return this.getMongoRepository(entityClassOrName as any).replaceOne(query, doc, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.replaceOne(metadata.tableName, query, doc, options); } /** * Get all the collection statistics. */ stats(entityClassOrName: ObjectType|string, options?: { scale: number }): Promise { - return this.getMongoRepository(entityClassOrName as any).stats(options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.stats(metadata.tableName, options); } /** * Update multiple documents on MongoDB. */ updateMany(entityClassOrName: ObjectType|string, query: ObjectLiteral, update: ObjectLiteral, options?: { upsert?: boolean, w?: any, wtimeout?: number, j?: boolean }): Promise { - return this.getMongoRepository(entityClassOrName as any).updateMany(query, update, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.updateMany(metadata.tableName, query, update, options); } /** * Update a single document on MongoDB. */ updateOne(entityClassOrName: ObjectType|string, query: ObjectLiteral, update: ObjectLiteral, options?: ReplaceOneOptions): Promise { - return this.getMongoRepository(entityClassOrName as any).updateOne(query, update, options); + const metadata = this.connection.getMetadata(entityClassOrName); + return this.queryRunner.updateOne(metadata.tableName, query, update, options); } // ------------------------------------------------------------------------- @@ -342,4 +524,24 @@ export class MongoEntityManager extends EntityManager { return (this.connection.driver as MongoDriver).queryRunner; } + protected convertFindManyOptionsOrConditionsToMongodbQuery(optionsOrConditions: FindOneOptions|Partial|undefined): ObjectLiteral|undefined { + if (!optionsOrConditions) + return undefined; + + return FindOptionsUtils.isFindManyOptions(optionsOrConditions) ? optionsOrConditions.where : optionsOrConditions; + } + + protected convertFindOneOptionsOrConditionsToMongodbQuery(optionsOrConditions: FindOneOptions|Partial|undefined): ObjectLiteral|undefined { + if (!optionsOrConditions) + return undefined; + + return FindOptionsUtils.isFindOneOptions(optionsOrConditions) ? optionsOrConditions.where : optionsOrConditions; + } + + protected convertFindOptionsOrderToOrderCriteria(order: { [P in keyof Entity]?: "ASC"|"DESC" }) { + const orderCriteria: ObjectLiteral = {}; + Object.keys(order).forEach(key => orderCriteria[key] = [key, order[key]!.toLowerCase()]); + return orderCriteria; + } + } \ No newline at end of file diff --git a/src/repository/MongoRepository.ts b/src/repository/MongoRepository.ts index dd8fd5868..b015bfd0b 100644 --- a/src/repository/MongoRepository.ts +++ b/src/repository/MongoRepository.ts @@ -3,11 +3,7 @@ import {Repository} from "./Repository"; import {QueryRunnerProvider} from "../query-runner/QueryRunnerProvider"; import {QueryBuilder} from "../query-builder/QueryBuilder"; import {FindManyOptions} from "../find-options/FindManyOptions"; -import {MongoDriver} from "../driver/mongodb/MongoDriver"; -import {MongoQueryRunner} from "../driver/mongodb/MongoQueryRunner"; -import {DocumentToEntityTransformer} from "../query-builder/transformer/DocumentToEntityTransformer"; import {FindOneOptions} from "../find-options/FindOneOptions"; -import {FindOptionsUtils} from "../find-options/FindOptionsUtils"; import { AggregationCursor, BulkWriteOpResultObject, @@ -19,20 +15,18 @@ import { CollectionInsertOneOptions, CollectionOptions, CollStats, - CommandCursor, Cursor, - CursorResult, + CommandCursor, + Cursor, DeleteWriteOpResultObject, FindAndModifyWriteOpResultObject, FindOneAndReplaceOption, GeoHaystackSearchOptions, GeoNearOptions, - MongodbIndexOptions, InsertOneWriteOpResult, InsertWriteOpResult, MapReduceOptions, - MongoCallback, MongoCountPreferences, - MongoError, + MongodbIndexOptions, OrderedBulkOperation, ParallelCollectionScanOptions, ReadPreference, @@ -79,18 +73,8 @@ export class MongoRepository extends Repository|Partial): Promise { - const query = this.convertFindManyOptionsOrConditionsToMongodbQuery(optionsOrConditions); - const cursor = await this.createEntityCursor(query); - if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) { - if (optionsOrConditions.skip) - cursor.skip(optionsOrConditions.skip); - if (optionsOrConditions.take) - cursor.limit(optionsOrConditions.take); - if (optionsOrConditions.order) - cursor.sort(this.convertFindOptionsOrderToOrderCriteria(optionsOrConditions.order)); - } - return cursor.toArray(); + find(optionsOrConditions?: FindManyOptions|Partial): Promise { + return this.manager.find(this.metadata.target, optionsOrConditions); } /** @@ -98,82 +82,31 @@ export class MongoRepository extends Repository|Partial): Promise<[ Entity[], number ]> { - const query = this.convertFindManyOptionsOrConditionsToMongodbQuery(optionsOrConditions); - const cursor = await this.createEntityCursor(query); - if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) { - if (optionsOrConditions.skip) - cursor.skip(optionsOrConditions.skip); - if (optionsOrConditions.take) - cursor.limit(optionsOrConditions.take); - if (optionsOrConditions.order) - cursor.sort(this.convertFindOptionsOrderToOrderCriteria(optionsOrConditions.order)); - } - const [results, count] = await Promise.all([ - cursor.toArray(), - this.queryRunner.count(this.metadata.tableName, query), - ]); - return [results, parseInt(count)]; + findAndCount(optionsOrConditions?: FindManyOptions|Partial): Promise<[ Entity[], number ]> { + return this.manager.findAndCount(this.metadata.target, optionsOrConditions); } /** * Finds entities by ids. * Optionally find options can be applied. */ - async findByIds(ids: any[], optionsOrConditions?: FindManyOptions|Partial): Promise { - const query = this.convertFindManyOptionsOrConditionsToMongodbQuery(optionsOrConditions) || {}; - const objectIdInstance = require("mongodb").ObjectID; - query["_id"] = { $in: ids.map(id => { - if (id instanceof objectIdInstance) - return id; - - return id[this.metadata.objectIdColumn!.propertyName]; - }) }; - - const cursor = await this.createEntityCursor(query); - if (FindOptionsUtils.isFindManyOptions(optionsOrConditions)) { - if (optionsOrConditions.skip) - cursor.skip(optionsOrConditions.skip); - if (optionsOrConditions.take) - cursor.limit(optionsOrConditions.take); - if (optionsOrConditions.order) - cursor.sort(this.convertFindOptionsOrderToOrderCriteria(optionsOrConditions.order)); - } - return await cursor.toArray(); + findByIds(ids: any[], optionsOrConditions?: FindManyOptions|Partial): Promise { + return this.manager.findByIds(this.metadata.target, ids, optionsOrConditions); } /** * Finds first entity that matches given conditions and/or find options. */ - async findOne(optionsOrConditions?: FindOneOptions|Partial): Promise { - const query = this.convertFindOneOptionsOrConditionsToMongodbQuery(optionsOrConditions); - const cursor = await this.createEntityCursor(query); - if (FindOptionsUtils.isFindOneOptions(optionsOrConditions)) { - if (optionsOrConditions.order) - cursor.sort(this.convertFindOptionsOrderToOrderCriteria(optionsOrConditions.order)); - } - - // const result = await cursor.limit(1).next(); - const result = await cursor.limit(1).toArray(); - return result.length > 0 ? result[0] : undefined; + findOne(optionsOrConditions?: FindOneOptions|Partial): Promise { + return this.manager.findOne(this.metadata.target, optionsOrConditions); } /** * Finds entity by given id. * Optionally find options or conditions can be applied. */ - async findOneById(id: any, optionsOrConditions?: FindOneOptions|Partial): Promise { - const query = this.convertFindOneOptionsOrConditionsToMongodbQuery(optionsOrConditions) || {}; - query["_id"] = id; - const cursor = await this.createEntityCursor(query); - if (FindOptionsUtils.isFindOneOptions(optionsOrConditions)) { - if (optionsOrConditions.order) - cursor.sort(this.convertFindOptionsOrderToOrderCriteria(optionsOrConditions.order)); - } - - // const result = await cursor.limit(1).next(); - const result = await cursor.limit(1).toArray(); - return result.length > 0 ? result[0] : undefined; + findOneById(id: any, optionsOrConditions?: FindOneOptions|Partial): Promise { + return this.manager.findOneById(this.metadata.target, id, optionsOrConditions); } /** @@ -188,75 +121,35 @@ export class MongoRepository extends Repository { - const cursor = this.createCursor(query); - const repository = this; - const ParentCursor = require("mongodb").Cursor; - cursor.toArray = function (callback?: MongoCallback) { - if (callback) { - ParentCursor.prototype.toArray.call(this, (error: MongoError, results: Entity[]): void => { - if (error) { - callback(error, results); - return; - } - - const transformer = new DocumentToEntityTransformer(); - return callback(error, transformer.transformAll(results, repository.metadata)); - }); - } else { - return ParentCursor.prototype.toArray.call(this).then((results: Entity[]) => { - const transformer = new DocumentToEntityTransformer(); - return transformer.transformAll(results, repository.metadata); - }); - } - }; - cursor.next = function (callback?: MongoCallback) { - if (callback) { - ParentCursor.prototype.next.call(this, (error: MongoError, result: CursorResult): void => { - if (error || !result) { - callback(error, result); - return; - } - - const transformer = new DocumentToEntityTransformer(); - return callback(error, transformer.transform(result, repository.metadata)); - }); - } else { - return ParentCursor.prototype.next.call(this).then((result: Entity) => { - if (!result) return result; - const transformer = new DocumentToEntityTransformer(); - return transformer.transform(result, repository.metadata); - }); - } - }; - return cursor; + return this.manager.createEntityCursor(this.metadata.target, query); } /** * Execute an aggregation framework pipeline against the collection. */ aggregate(pipeline: ObjectLiteral[], options?: CollectionAggregationOptions): AggregationCursor { - return this.queryRunner.aggregate(this.metadata.tableName, pipeline, options); + return this.manager.aggregate(this.metadata.target, pipeline, options); } /** * Perform a bulkWrite operation without a fluent API. */ - async bulkWrite(operations: ObjectLiteral[], options?: CollectionBluckWriteOptions): Promise { - return await this.queryRunner.bulkWrite(this.metadata.tableName, operations, options); + bulkWrite(operations: ObjectLiteral[], options?: CollectionBluckWriteOptions): Promise { + return this.manager.bulkWrite(this.metadata.target, operations, options); } /** * Count number of matching documents in the db to a query. */ - async count(query?: ObjectLiteral, options?: MongoCountPreferences): Promise { - return await this.queryRunner.count(this.metadata.tableName, query || {}, options); + count(query?: ObjectLiteral, options?: MongoCountPreferences): Promise { + return this.manager.count(this.metadata.target, query || {}, options); } /** * Creates an index on the db and collection. */ - async createCollectionIndex(fieldOrSpec: string|any, options?: MongodbIndexOptions): Promise { - return await this.queryRunner.createCollectionIndex(this.metadata.tableName, fieldOrSpec, options); + createCollectionIndex(fieldOrSpec: string|any, options?: MongodbIndexOptions): Promise { + return this.manager.createCollectionIndex(this.metadata.target, fieldOrSpec, options); } /** @@ -264,235 +157,205 @@ export class MongoRepository extends Repository { - return await this.queryRunner.createCollectionIndexes(this.metadata.tableName, indexSpecs); + createCollectionIndexes(indexSpecs: ObjectLiteral[]): Promise { + return this.manager.createCollectionIndexes(this.metadata.target, indexSpecs); } /** * Delete multiple documents on MongoDB. */ - async deleteMany(query: ObjectLiteral, options?: CollectionOptions): Promise { - return await this.queryRunner.deleteMany(this.metadata.tableName, query, options); + deleteMany(query: ObjectLiteral, options?: CollectionOptions): Promise { + return this.manager.deleteMany(this.metadata.tableName, query, options); } /** * Delete a document on MongoDB. */ - async deleteOne(query: ObjectLiteral, options?: CollectionOptions): Promise { - return await this.queryRunner.deleteOne(this.metadata.tableName, query, options); + deleteOne(query: ObjectLiteral, options?: CollectionOptions): Promise { + return this.manager.deleteOne(this.metadata.tableName, query, options); } /** * The distinct command returns returns a list of distinct values for the given key across a collection. */ - async distinct(key: string, query: ObjectLiteral, options?: { readPreference?: ReadPreference|string }): Promise { - return await this.queryRunner.distinct(this.metadata.tableName, key, query, options); + distinct(key: string, query: ObjectLiteral, options?: { readPreference?: ReadPreference|string }): Promise { + return this.manager.distinct(this.metadata.tableName, key, query, options); } /** * Drops an index from this collection. */ - async dropCollectionIndex(indexName: string, options?: CollectionOptions): Promise { - return await this.queryRunner.dropCollectionIndex(this.metadata.tableName, indexName, options); + dropCollectionIndex(indexName: string, options?: CollectionOptions): Promise { + return this.manager.dropCollectionIndex(this.metadata.tableName, indexName, options); } /** * Drops all indexes from the collection. */ - async dropCollectionIndexes(): Promise { - return await this.queryRunner.dropCollectionIndexes(this.metadata.tableName); + dropCollectionIndexes(): Promise { + return this.manager.dropCollectionIndexes(this.metadata.tableName); } /** * Find a document and delete it in one atomic operation, requires a write lock for the duration of the operation. */ - async findOneAndDelete(query: ObjectLiteral, options?: { projection?: Object, sort?: Object, maxTimeMS?: number }): Promise { - return await this.queryRunner.findOneAndDelete(this.metadata.tableName, query, options); + findOneAndDelete(query: ObjectLiteral, options?: { projection?: Object, sort?: Object, maxTimeMS?: number }): Promise { + return this.manager.findOneAndDelete(this.metadata.tableName, query, options); } /** * Find a document and replace it in one atomic operation, requires a write lock for the duration of the operation. */ - async findOneAndReplace(query: ObjectLiteral, replacement: Object, options?: FindOneAndReplaceOption): Promise { - return await this.queryRunner.findOneAndReplace(this.metadata.tableName, query, replacement, options); + findOneAndReplace(query: ObjectLiteral, replacement: Object, options?: FindOneAndReplaceOption): Promise { + return this.manager.findOneAndReplace(this.metadata.tableName, query, replacement, options); } /** * Find a document and update it in one atomic operation, requires a write lock for the duration of the operation. */ - async findOneAndUpdate(query: ObjectLiteral, update: Object, options?: FindOneAndReplaceOption): Promise { - return await this.queryRunner.findOneAndUpdate(this.metadata.tableName, query, update, options); + findOneAndUpdate(query: ObjectLiteral, update: Object, options?: FindOneAndReplaceOption): Promise { + return this.manager.findOneAndUpdate(this.metadata.tableName, query, update, options); } /** * Execute a geo search using a geo haystack index on a collection. */ - async geoHaystackSearch(x: number, y: number, options?: GeoHaystackSearchOptions): Promise { - return await this.queryRunner.geoHaystackSearch(this.metadata.tableName, x, y, options); + geoHaystackSearch(x: number, y: number, options?: GeoHaystackSearchOptions): Promise { + return this.manager.geoHaystackSearch(this.metadata.tableName, x, y, options); } /** * Execute the geoNear command to search for items in the collection. */ - async geoNear(x: number, y: number, options?: GeoNearOptions): Promise { - return await this.queryRunner.geoNear(this.metadata.tableName, x, y, options); + geoNear(x: number, y: number, options?: GeoNearOptions): Promise { + return this.manager.geoNear(this.metadata.tableName, x, y, options); } /** * Run a group command across a collection. */ - async group(keys: Object|Array|Function|Code, condition: Object, initial: Object, reduce: Function|Code, finalize: Function|Code, command: boolean, options?: { readPreference?: ReadPreference | string }): Promise { - return await this.queryRunner.group(this.metadata.tableName, keys, condition, initial, reduce, finalize, command, options); + group(keys: Object|Array|Function|Code, condition: Object, initial: Object, reduce: Function|Code, finalize: Function|Code, command: boolean, options?: { readPreference?: ReadPreference | string }): Promise { + return this.manager.group(this.metadata.tableName, keys, condition, initial, reduce, finalize, command, options); } /** * Retrieve all the indexes on the collection. */ - async collectionIndexes(): Promise { - return await this.queryRunner.collectionIndexes(this.metadata.tableName); + collectionIndexes(): Promise { + return this.manager.collectionIndexes(this.metadata.tableName); } /** * Retrieve all the indexes on the collection. */ - async collectionIndexExists(indexes: string|string[]): Promise { - return await this.queryRunner.collectionIndexExists(this.metadata.tableName, indexes); + collectionIndexExists(indexes: string|string[]): Promise { + return this.manager.collectionIndexExists(this.metadata.tableName, indexes); } /** * Retrieves this collections index info. */ - async collectionIndexInformation(options?: { full: boolean }): Promise { - return await this.queryRunner.collectionIndexInformation(this.metadata.tableName, options); + collectionIndexInformation(options?: { full: boolean }): Promise { + return this.manager.collectionIndexInformation(this.metadata.tableName, options); } /** * 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. */ initializeOrderedBulkOp(options?: CollectionOptions): OrderedBulkOperation { - return this.queryRunner.initializeOrderedBulkOp(this.metadata.tableName, options); + return this.manager.initializeOrderedBulkOp(this.metadata.tableName, options); } /** * Initiate a Out of order batch write operation. All operations will be buffered into insert/update/remove commands executed out of order. */ initializeUnorderedBulkOp(options?: CollectionOptions): UnorderedBulkOperation { - return this.queryRunner.initializeUnorderedBulkOp(this.metadata.tableName, options); + return this.manager.initializeUnorderedBulkOp(this.metadata.tableName, options); } /** * Inserts an array of documents into MongoDB. */ - async insertMany(docs: ObjectLiteral[], options?: CollectionInsertManyOptions): Promise { - return await this.queryRunner.insertMany(this.metadata.tableName, docs, options); + insertMany(docs: ObjectLiteral[], options?: CollectionInsertManyOptions): Promise { + return this.manager.insertMany(this.metadata.tableName, docs, options); } /** * Inserts a single document into MongoDB. */ - async insertOne(doc: ObjectLiteral, options?: CollectionInsertOneOptions): Promise { - return await this.queryRunner.insertOne(this.metadata.tableName, doc, options); + insertOne(doc: ObjectLiteral, options?: CollectionInsertOneOptions): Promise { + return this.manager.insertOne(this.metadata.tableName, doc, options); } /** * Returns if the collection is a capped collection. */ - async isCapped(): Promise { - return await this.queryRunner.isCapped(this.metadata.tableName); + isCapped(): Promise { + return this.manager.isCapped(this.metadata.tableName); } /** * Get the list of all indexes information for the collection. */ listCollectionIndexes(options?: { batchSize?: number, readPreference?: ReadPreference|string }): CommandCursor { - return this.queryRunner.listCollectionIndexes(this.metadata.tableName, options); + return this.manager.listCollectionIndexes(this.metadata.tableName, options); } /** * Run Map Reduce across a collection. Be aware that the inline option for out will return an array of results not a collection. */ - async mapReduce(map: Function|string, reduce: Function|string, options?: MapReduceOptions): Promise { - return await this.queryRunner.mapReduce(this.metadata.tableName, map, reduce, options); + mapReduce(map: Function|string, reduce: Function|string, options?: MapReduceOptions): Promise { + return this.manager.mapReduce(this.metadata.tableName, map, reduce, options); } /** * Return N number of parallel cursors for a collection allowing parallel reading of entire collection. * There are no ordering guarantees for returned results. */ - async parallelCollectionScan(options?: ParallelCollectionScanOptions): Promise[]> { - return await this.queryRunner.parallelCollectionScan(this.metadata.tableName, options); + parallelCollectionScan(options?: ParallelCollectionScanOptions): Promise[]> { + return this.manager.parallelCollectionScan(this.metadata.tableName, options); } /** * 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. */ - async reIndex(): Promise { - return await this.queryRunner.reIndex(this.metadata.tableName); + reIndex(): Promise { + return this.manager.reIndex(this.metadata.tableName); } /** * 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. */ - async rename(newName: string, options?: { dropTarget?: boolean }): Promise { - return await this.queryRunner.rename(this.metadata.tableName, newName, options); + rename(newName: string, options?: { dropTarget?: boolean }): Promise { + return this.manager.rename(this.metadata.tableName, newName, options); } /** * Replace a document on MongoDB. */ - async replaceOne(query: ObjectLiteral, doc: ObjectLiteral, options?: ReplaceOneOptions): Promise { - return await this.queryRunner.replaceOne(this.metadata.tableName, query, doc, options); + replaceOne(query: ObjectLiteral, doc: ObjectLiteral, options?: ReplaceOneOptions): Promise { + return this.manager.replaceOne(this.metadata.tableName, query, doc, options); } /** * Get all the collection statistics. */ - async stats(options?: { scale: number }): Promise { - return await this.queryRunner.stats(this.metadata.tableName, options); + stats(options?: { scale: number }): Promise { + return this.manager.stats(this.metadata.tableName, options); } /** * Update multiple documents on MongoDB. */ - async updateMany(query: ObjectLiteral, update: ObjectLiteral, options?: { upsert?: boolean, w?: any, wtimeout?: number, j?: boolean }): Promise { - return await this.queryRunner.updateMany(this.metadata.tableName, query, update, options); + updateMany(query: ObjectLiteral, update: ObjectLiteral, options?: { upsert?: boolean, w?: any, wtimeout?: number, j?: boolean }): Promise { + return this.manager.updateMany(this.metadata.tableName, query, update, options); } /** * Update a single document on MongoDB. */ - async updateOne(query: ObjectLiteral, update: ObjectLiteral, options?: ReplaceOneOptions): Promise { - return await this.queryRunner.updateOne(this.metadata.tableName, query, update, options); - } - - // ------------------------------------------------------------------------- - // Protected Methods - // ------------------------------------------------------------------------- - - // todo: extra these methods into separate class - - protected get queryRunner(): MongoQueryRunner { - return (this.manager.connection.driver as MongoDriver).queryRunner; - } - - protected convertFindManyOptionsOrConditionsToMongodbQuery(optionsOrConditions: FindOneOptions|Partial|undefined): ObjectLiteral|undefined { - if (!optionsOrConditions) - return undefined; - - return FindOptionsUtils.isFindManyOptions(optionsOrConditions) ? optionsOrConditions.where : optionsOrConditions; - } - - protected convertFindOneOptionsOrConditionsToMongodbQuery(optionsOrConditions: FindOneOptions|Partial|undefined): ObjectLiteral|undefined { - if (!optionsOrConditions) - return undefined; - - return FindOptionsUtils.isFindOneOptions(optionsOrConditions) ? optionsOrConditions.where : optionsOrConditions; - } - - protected convertFindOptionsOrderToOrderCriteria

(order: { [P in keyof Entity]?: "ASC"|"DESC" }) { - const orderCriteria: ObjectLiteral = {}; - Object.keys(order).forEach(key => orderCriteria[key] = [key, order[key]!.toLowerCase()]); - return orderCriteria; + updateOne(query: ObjectLiteral, update: ObjectLiteral, options?: ReplaceOneOptions): Promise { + return this.manager.updateOne(this.metadata.tableName, query, update, options); } } \ No newline at end of file