UNPKG

hydrate-mongodb

Version:
819 lines (818 loc) 32.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var async = require("async"); var callback_1 = require("./core/callback"); var queryKind_1 = require("./query/queryKind"); var criteriaBuilder_1 = require("./query/criteriaBuilder"); var updateDocumentBuilder_1 = require("./query/updateDocumentBuilder"); var readContext_1 = require("./mapping/readContext"); var writeContext_1 = require("./mapping/writeContext"); var persistenceError_1 = require("./persistenceError"); var timerUtil_1 = require("./core/timerUtil"); var PersisterImpl = (function () { function PersisterImpl(session, mapping, collection) { this._session = session; this._mapping = mapping; this._collection = collection; var inheritanceRoot = mapping.inheritanceRoot; this.changeTracking = inheritanceRoot.changeTracking; this.identity = inheritanceRoot.identity; this._versioned = inheritanceRoot.versioned; this._traceEnabled = this._session.factory.logger != null; this._defaultFields = mapping.getDefaultFields(); } PersisterImpl.prototype.dirtyCheck = function (batch, entity, originalDocument) { var document = this._dirtyCheck(batch, entity, originalDocument); if (!document) { return null; } else { if (this._versioned) { var version = this._mapping.getDocumentVersion(originalDocument); this._mapping.setDocumentVersion(document, (version || 0) + 1); } this._getCommand(batch).addReplace(document, version); return document; } }; PersisterImpl.prototype.areDocumentsEqual = function (context, entity, originalDocument) { return this._dirtyCheck(context, entity, originalDocument) == null; }; PersisterImpl.prototype._dirtyCheck = function (context, entity, originalDocument) { var writeContext = new writeContext_1.WriteContext(); var document = this._mapping.write(writeContext, entity); if (writeContext.hasErrors) { context.error = new persistenceError_1.PersistenceError("Error serializing document:\n" + writeContext.getErrorMessage()); return null; } if (this._mapping.areDocumentsEqual(originalDocument, document)) { return null; } else { return document; } }; PersisterImpl.prototype.addInsert = function (batch, entity) { var context = new writeContext_1.WriteContext(); var document = this._mapping.write(context, entity); if (context.hasErrors) { batch.error = new persistenceError_1.PersistenceError("Error serializing document:\n" + context.getErrorMessage()); return null; } if (this._versioned) { this._mapping.setDocumentVersion(document, 1); } this._getCommand(batch).addInsert(document); return document; }; PersisterImpl.prototype.addRemove = function (batch, entity) { this._getCommand(batch).addRemove(entity["_id"]); }; PersisterImpl.prototype.refresh = function (entity, callback) { var _this = this; this.findOneById(entity["_id"], function (err, document) { if (err) return callback(err); _this._refreshFromDocument(entity, document, callback); }); }; PersisterImpl.prototype.watch = function (value, observer) { this._mapping.watchEntity(value, observer); }; PersisterImpl.prototype._refreshFromDocument = function (entity, document, callback) { var context = new readContext_1.ReadContext(this._session); this._mapping.refresh(context, entity, document); if (context.hasErrors) { return callback(new persistenceError_1.PersistenceError("Error deserializing document:\n" + context.getErrorMessage())); } callback(null, document); }; PersisterImpl.prototype.fetch = function (entity, path, callback) { if (typeof path !== "string") { return callback(new persistenceError_1.PersistenceError("Path must be a string.")); } this._mapping.fetch(this._session, undefined, entity, path.split("."), 0, callback); }; PersisterImpl.prototype.fetchPropertyValue = function (entity, property, callback) { var _this = this; var fields = { _id: 0 }; property.setFieldValue(fields, 1); var version; if (this._versioned) { version = this._session.getVersion(entity); this._mapping.setDocumentVersion(fields, 1); } var criteria = { _id: entity["_id"] }; var handleCallback = callback; if (this._traceEnabled) { handleCallback = this._createTraceableCallback({ kind: queryKind_1.QueryKind[queryKind_1.QueryKind.FindOne], criteria: criteria, fields: fields }, callback); } this._collection.findOne(criteria, fields, function (err, document) { if (err) return callback(err); if (_this._versioned) { if (_this._mapping.getDocumentVersion(document) != version) { return callback(new Error("Cannot fetch property value because document version has changed.")); } } var readContext = new readContext_1.ReadContext(_this._session); readContext.path = property.name; var propertyValue = property.mapping.read(readContext, property.getFieldValue(document)); if (readContext.hasErrors) { return callback(new persistenceError_1.PersistenceError("Error deserializing property value: " + readContext.getErrorMessage())); } handleCallback(null, propertyValue); }); }; PersisterImpl.prototype.findInverseOf = function (entity, path, callback) { var criteria = this._prepareInverseQuery(entity, path, callback); if (criteria) { this.findAll(criteria, callback); } }; PersisterImpl.prototype.findOneInverseOf = function (entity, path, callback) { var criteria = this._prepareInverseQuery(entity, path, callback); if (criteria) { this.findOne(criteria, callback); } }; PersisterImpl.prototype._prepareInverseQuery = function (entity, path, callback) { var property = this._mapping.getProperty(path); if (property === undefined) { callback(new persistenceError_1.PersistenceError("Missing property '" + path + "'.")); return null; } var id = entity._id; if (id === undefined) { callback(new persistenceError_1.PersistenceError("Missing identifier on entity that is the inverse side of a relationship.")); return null; } var query = {}; property.setFieldValue(query, id); return query; }; PersisterImpl.prototype.findOneById = function (id, callback) { var entity = this._session.getObject(id); if (entity !== undefined) { process.nextTick(function () { return callback(null, entity); }); return; } (this._findQueue || (this._findQueue = new FindQueue(this))).add(id, callback); }; PersisterImpl.prototype.findOne = function (criteria, callback) { var query = { criteria: criteria, fields: this._defaultFields }; var handleCallback = callback; if (this._traceEnabled) { query.kind = queryKind_1.QueryKind[queryKind_1.QueryKind.FindOne]; handleCallback = this._createTraceableCallback(query, callback); } this._findOne(query, handleCallback); }; PersisterImpl.prototype._findOne = function (query, callback) { var _this = this; this._collection.findOne(query.criteria, { projection: query.fields }, function (err, document) { if (err) return callback(err); _this._loadOne(document, query.isLazy, callback); }); }; PersisterImpl.prototype.findAll = function (criteria, callback) { var query = { criteria: criteria, fields: this._defaultFields }; var handleCallback = callback; if (this._traceEnabled) { query.kind = queryKind_1.QueryKind[queryKind_1.QueryKind.FindAll]; handleCallback = this._createTraceableCallback(query, callback); } this._findAll(query, handleCallback); }; PersisterImpl.prototype._findAll = function (query, callback) { var cursor = this._prepareFind(query), self = this, entities = []; next(); function next(err) { if (err) return error(err); cursor.next(function (err, item) { if (err) return error(err); if (item == null) { return callback(null, entities); } self._loadOne(item, query.isLazy, function (err, value) { if (err) return error(err); if (value !== null) { entities.push(value); } next(); }); }); } function error(err) { cursor.close(null); callback(err); callback = function () { }; } }; PersisterImpl.prototype.executeQuery = function (query, callback) { var _this = this; if (query.criteria) { query.criteria = (this._criteriaBuilder || (this._criteriaBuilder = new criteriaBuilder_1.CriteriaBuilder(this._mapping))) .build(query.criteria); if (this._criteriaBuilder.error) { return callback(this._criteriaBuilder.error); } } if (query.isLazy) { query.fields = { _id: 1 }; } else { query.fields = this._defaultFields; } if (query.updateDocument) { query.updateDocument = (this._updateDocumentBuilder || (this._updateDocumentBuilder = new updateDocumentBuilder_1.UpdateDocumentBuilder(this._mapping))) .build(query.updateDocument); if (this._updateDocumentBuilder.error) { return callback(this._updateDocumentBuilder.error); } if (this._versioned) { var fields = query.updateDocument["$inc"]; if (!fields) { fields = query.updateDocument["$inc"] = {}; } this._mapping.setDocumentVersion(fields, 1); } } if (query.sortValue) { this._prepareOrderDocument(query.sortValue, function (err, preparedOrder) { if (err) return callback(err); query.orderDocument = preparedOrder; _this._executeQuery(query, callback); }); } else { this._executeQuery(query, callback); } }; PersisterImpl.prototype._executeQuery = function (query, callback) { var handleCallback = callback; if (this._traceEnabled && query.kind != queryKind_1.QueryKind.FindOneById) { handleCallback = this._createTraceableCallback(query.toObject(), callback); } switch (query.kind) { case queryKind_1.QueryKind.FindOne: this._findOne(query, this._fetchOne(query, handleCallback)); break; case queryKind_1.QueryKind.FindOneById: this.findOneById(query.id, this._fetchOne(query, handleCallback)); break; case queryKind_1.QueryKind.FindAll: this._findAll(query, this._fetchAll(query, handleCallback)); break; case queryKind_1.QueryKind.FindEach: this._findEach(query, handleCallback); break; case queryKind_1.QueryKind.FindEachSeries: this._findEachSeries(query, handleCallback); break; case queryKind_1.QueryKind.FindCursor: this._findCursor(query, handleCallback); break; case queryKind_1.QueryKind.FindOneAndRemove: case queryKind_1.QueryKind.FindOneAndUpdate: case queryKind_1.QueryKind.FindOneAndUpsert: this._findOneAndModify(query, this._fetchOne(query, handleCallback)); break; case queryKind_1.QueryKind.RemoveOne: this._removeOne(query, handleCallback); break; case queryKind_1.QueryKind.RemoveAll: this._removeAll(query, handleCallback); break; case queryKind_1.QueryKind.UpdateOne: case queryKind_1.QueryKind.Upsert: this._updateOne(query, handleCallback); break; case queryKind_1.QueryKind.UpdateAll: this._updateAll(query, handleCallback); break; case queryKind_1.QueryKind.Distinct: this._distinct(query, handleCallback); break; case queryKind_1.QueryKind.Count: this._count(query, handleCallback); break; default: handleCallback(new persistenceError_1.PersistenceError("Unknown query type '" + query.kind + "'.")); } }; PersisterImpl.prototype._createTraceableCallback = function (query, callback) { var _this = this; var start = process.hrtime(); return function (err, result) { if (err) return callback(err); _this._session.factory.logger.trace({ collection: _this._mapping.inheritanceRoot.collectionName, query: query, duration: timerUtil_1.getDuration(start) }, "[Hydrate] Executed query."); callback(null, result); }; }; PersisterImpl.prototype._prepareOrderDocument = function (sorting, callback) { var order = []; for (var i = 0; i < sorting.length; i++) { var value = sorting[i]; var context = this._mapping.resolve(value[0]); if (context.error) { return callback(context.error); } order.push([context.resolvedPath, value[1]]); } callback(null, order); }; PersisterImpl.prototype._findEach = function (query, callback) { var cursor = this._prepareFind(query); var iterator = this._fetchIterator(query); var completed = 0, started = 0, finished = false, self = this; replenish(); function replenish() { cursor.next(function (err, item) { if (err) return error(err); if (item == null) { if (completed >= started) { callback(); } finished = true; return; } process(err, item); while (cursor.bufferedCount() > 0) { cursor.next(process); } }); } function process(err, item) { if (err) return error(err); started++; self._loadOne(item, query.isLazy, function (err, value) { if (err) return error(err); if (value === null) { done(null); } else { iterator(value, callback_1.onlyOnce(done)); } }); } function done(err) { if (err) return error(err); completed++; if (cursor.bufferedCount() == 0 && completed >= started) { if (finished) return callback(); replenish(); } } function error(err) { cursor.close(null); callback(err); callback = function () { }; } }; PersisterImpl.prototype._findEachSeries = function (query, callback) { var cursor = this._prepareFind(query), iterator = this._fetchIterator(query), self = this; (function next(err) { if (err) return error(err); cursor.next(function (err, item) { if (err) return error(err); if (item == null) { return callback(); } self._loadOne(item, query.isLazy, function (err, value) { if (err) return error(err); if (value === null) { next(); } else { iterator(value, next); } }); }); })(); function error(err) { cursor.close(null); callback(err); callback = function () { }; } }; PersisterImpl.prototype._findCursor = function (query, callback) { var _this = this; callback(null, new CursorImpl(this._prepareFind(query), function (document, callback) { _this._loadOne(document, query.isLazy, function (err, entity) { if (err) return callback(err); if (!query.fetchPaths) { callback(null, entity); return; } _this._session.fetchInternal(entity, query.fetchPaths, callback); }); })); }; PersisterImpl.prototype._prepareFind = function (query) { var cursor = this._collection.find(query.criteria); if (query.fields) { cursor.project(query.fields); } if (query.orderDocument !== undefined) { cursor.sort(query.orderDocument); } if (query.skipCount !== undefined) { cursor.skip(query.skipCount); } if (query.limitCount !== undefined) { cursor.limit(query.limitCount); } if (query.collationOptions !== undefined) { cursor.collation(query.collationOptions); } if (query.batchSizeValue !== undefined) { cursor.batchSize(query.batchSizeValue); } return cursor; }; PersisterImpl.prototype._findOneAndModify = function (query, callback) { var options = { returnOriginal: !query.wantsUpdated, upsert: query.kind == queryKind_1.QueryKind.FindOneAndUpsert, sort: query.orderDocument }; var self = this; if (query.kind == queryKind_1.QueryKind.FindOneAndRemove) { this._collection.findOneAndDelete(query.criteria, options, handleCallback); } else { this._collection.findOneAndUpdate(query.criteria, query.updateDocument, options, handleCallback); } function handleCallback(err, response) { if (err) return callback(err); var document = response.value; if (!document) return callback(null); var entity = self._session.getObject(document["_id"]); if (entity !== undefined) { var alreadyLoaded = true; finish(); } else { self._loadOne(document, query.isLazy, function (err, value) { if (err) return callback(err); entity = value; finish(); }); } function finish() { if (entity) { if (query.kind == queryKind_1.QueryKind.FindOneAndRemove) { self._session.notifyRemoved(entity); } else if (!options.returnOriginal && alreadyLoaded) { self._refreshFromDocument(entity, document, function (err) { if (err) return callback(err); callback(null, entity); }); return; } } callback(null, entity); } } }; PersisterImpl.prototype._removeOne = function (query, callback) { this._collection.deleteOne(query.criteria, function (err, response) { if (err) return callback(err); callback(null, response.deletedCount); }); }; PersisterImpl.prototype._removeAll = function (query, callback) { this._collection.deleteMany(query.criteria, function (err, response) { if (err) return callback(err); callback(null, response.deletedCount); }); }; PersisterImpl.prototype._updateOne = function (query, callback) { this._collection.updateOne(query.criteria, query.updateDocument, { upsert: query.kind == queryKind_1.QueryKind.Upsert }, function (err, response) { if (err) return callback(err); callback(null, response.modifiedCount); }); }; PersisterImpl.prototype._updateAll = function (query, callback) { this._collection.updateMany(query.criteria, query.updateDocument, function (err, response) { if (err) return callback(err); callback(null, response.modifiedCount); }); }; PersisterImpl.prototype._distinct = function (query, callback) { var _this = this; var context = this._mapping.resolve(query.key); if (context.error) { return callback(context.error); } this._collection.distinct(context.resolvedPath, query.criteria, undefined, function (err, results) { if (err) return callback(err); var readContext = new readContext_1.ReadContext(_this._session); for (var i = 0, l = results.length; i < l; i++) { readContext.path = context.resolvedPath; results[i] = context.resolvedMapping.read(readContext, results[i]); if (readContext.hasErrors) { return callback(new persistenceError_1.PersistenceError("Error deserializing distinct values for: " + readContext.getErrorMessage())); } } callback(null, results); }); }; PersisterImpl.prototype._count = function (query, callback) { var options = { limit: query.limitCount, skip: query.skipCount }; this._collection.count(query.criteria, options, function (err, result) { if (err) return callback(err); callback(null, result); }); }; PersisterImpl.prototype._fetchOne = function (query, callback) { var _this = this; if (!query.fetchPaths) { return callback; } return function (err, entity) { if (err) return callback(err); if (!entity) return callback(null); _this._session.fetchInternal(entity, query.fetchPaths, callback); }; }; PersisterImpl.prototype._fetchAll = function (query, callback) { var _this = this; if (!query.fetchPaths) { return callback; } return function (err, entities) { if (err) return callback(err); async.each(entities, function (entity, done) { return _this._session.fetchInternal(entity, query.fetchPaths, done); }, function (err) { if (err) return callback(err); callback(null, entities); }); }; }; PersisterImpl.prototype._fetchIterator = function (query) { var _this = this; if (!query.fetchPaths) { return query.iterator; } return function (entity, done) { if (!entity) return done(); _this._session.fetchInternal(entity, query.fetchPaths, function (err, result) { if (err) return done(err); query.iterator(result, done); }); }; }; PersisterImpl.prototype._loadOne = function (document, reference, callback) { var entity; if (!document) { entity = null; } else if (reference) { entity = this._session.getReferenceInternal(this._mapping, document["_id"]); } else { entity = this._session.getObject(document["_id"]); if (entity === undefined) { var context = new readContext_1.ReadContext(this._session); entity = this._mapping.read(context, document); if (context.hasErrors) { return callback(new persistenceError_1.PersistenceError("Error deserializing document:\n" + context.getErrorMessage())); } this._session.registerManaged(this, entity, document); if (context.fetches) { this._session.fetchInternal(entity, context.fetches, callback); return; } } } process.nextTick(function () { return callback(null, entity); }); }; PersisterImpl.prototype._getCommand = function (batch) { var id = this._mapping.inheritanceRoot.id; var command = batch.getCommand(id); if (!command) { command = new BulkOperationCommand(batch, this._collection, this._mapping); batch.addCommand(id, command); } return command; }; return PersisterImpl; }()); exports.PersisterImpl = PersisterImpl; var BulkOperationCommand = (function () { function BulkOperationCommand(batch, collection, mapping) { this._mapping = mapping; this._batch = batch; this.priority = mapping.inheritanceRoot.flushPriority; this.collectionName = collection.collectionName; this.operation = collection.initializeUnorderedBulkOp(); this.inserted = this.updated = this.removed = 0; } BulkOperationCommand.prototype.addInsert = function (document) { this.inserted++; try { this.operation.insert(document); } catch (err) { this._batch.error = new persistenceError_1.PersistenceError("Error adding insert to batch: " + err.message, err); } }; BulkOperationCommand.prototype.addReplace = function (document, version) { var query = { _id: document["_id"] }; if (version != null) { this._mapping.setDocumentVersion(query, version); } this.updated++; try { this.operation.find(query).replaceOne(document); } catch (err) { this._batch.error = new persistenceError_1.PersistenceError("Error adding replace to batch: " + err.message, err); } }; BulkOperationCommand.prototype.addRemove = function (id) { var query = { _id: id }; this.removed++; try { this.operation.find(query).removeOne(); } catch (err) { this._batch.error = new persistenceError_1.PersistenceError("Error adding remove to batch: " + err.message, err); } }; BulkOperationCommand.prototype.execute = function (callback) { var _this = this; this.operation.execute(function (err, result) { if (err) return callback(err); if ((result.nInserted || 0) != _this.inserted) { return callback(new persistenceError_1.PersistenceError("Flush failed for collection '" + _this.collectionName + "'. Expected to insert " + _this.inserted + " documents but only inserted " + (result.nInserted || 0) + ".")); } if ((result.nModified || 0) != _this.updated) { return callback(new persistenceError_1.PersistenceError("Flush failed for collection '" + _this.collectionName + "'. Expected to update " + _this.updated + " documents but only updated " + (result.nModified || 0) + ".")); } if ((result.nRemoved || 0) != _this.removed) { return callback(new persistenceError_1.PersistenceError("Flush failed for collection '" + _this.collectionName + "'. Expected to remove " + _this.removed + " documents but only removed " + (result.nRemoved || 0) + ".")); } callback(); }); }; return BulkOperationCommand; }()); var FindQueue = (function () { function FindQueue(persister) { this._persister = persister; } FindQueue.prototype.add = function (id, callback) { var _this = this; if (!this._ids) { this._ids = []; this._callbacksMap = new Map(); process.nextTick(function () { return _this._process(); }); } var normalizedId = this._normalizeIdentifier(id); if (normalizedId == null) { callback(new persistenceError_1.PersistenceError("'" + id + "' is not a valid identifier.")); return; } var key = normalizedId.toString(); var callbacks = this._callbacksMap.get(key); if (callbacks === undefined) { callbacks = []; this._ids.push(normalizedId); this._callbacksMap.set(key, callbacks); } callbacks.push(callback); }; FindQueue.prototype._normalizeIdentifier = function (id) { if (typeof id === "string") { return this._persister.identity.fromString(id); } else if (this._persister.identity.validate(id)) { return id; } return null; }; FindQueue.prototype._process = function () { var callbacksMap = this._callbacksMap, ids = this._ids; this._ids = this._callbacksMap = undefined; if (ids.length == 1) { var id = ids[0]; this._persister.findOne({ _id: id }, function (err, entity) { if (err) return handleCallback(id, err); if (!entity) { return handleCallback(id, new persistenceError_1.EntityNotFoundError("Unable to find document with identifier '" + id.toString() + "'.")); } handleCallback(id, null, entity); }); return; } this._persister.findAll({ _id: { $in: ids } }, function (err, entities) { if (!err) { for (var i = 0, l = entities.length; i < l; i++) { var entity = entities[i]; handleCallback(entity["_id"], null, entity); } } callbacksMap.forEach(function (callbacks, id) { return callCallbacks(callbacks, err || new persistenceError_1.EntityNotFoundError("Unable to find document with identifier '" + id + "'.")); }); }); function handleCallback(id, err, entity) { var key = id.toString(), callbacks = callbacksMap.get(key); callbacksMap.delete(key); callCallbacks(callbacks, err, entity); } function callCallbacks(callbacks, err, entity) { for (var i = 0; i < callbacks.length; i++) { callbacks[i](err, entity); } } }; return FindQueue; }()); var CursorImpl = (function () { function CursorImpl(cursor, loader) { this._cursor = cursor; this._loader = loader; } CursorImpl.prototype.next = function (callback) { var _this = this; this._cursor.next(function (err, item) { if (err) { _this.close(); callback(err); return; } if (item == null) { callback(null, null); return; } _this._loader(item, function (err, result) { if (err) { _this.close(); callback(err); return; } callback(null, result); }); }); }; CursorImpl.prototype.close = function (callback) { this._cursor.close(callback); }; return CursorImpl; }());