collection.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. // Copyright 2015 The MemDB Authors.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  12. // implied. See the License for the specific language governing
  13. // permissions and limitations under the License. See the AUTHORS file
  14. // for names of contributors.
  15. /* jshint ignore:start */
  16. /*!
  17. * Module dependencies.
  18. */
  19. var MongooseCollection = require('mongoose/lib/collection')
  20. , Collection = require('mongodb').Collection
  21. , STATES = require('mongoose/lib/connectionstate')
  22. , utils = require('mongoose/lib/utils')
  23. , uuid = require('node-uuid')
  24. , logger = require('memdb-logger').getLogger('memdb-client', __filename);
  25. /**
  26. * A [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) collection implementation.
  27. *
  28. * All methods methods from the [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) driver are copied and wrapped in queue management.
  29. *
  30. * @inherits Collection
  31. * @api private
  32. */
  33. function MemdbCollection () {
  34. this.memdb = true;
  35. this.collection = null;
  36. MongooseCollection.apply(this, arguments);
  37. this.id = uuid.v4();
  38. var self = this;
  39. Object.defineProperty(this, '_memdbCollection', {
  40. get : function(){
  41. return require('../mdbgoose').autoconn.collection(self.name);
  42. },
  43. });
  44. }
  45. /*!
  46. * Inherit from abstract Collection.
  47. */
  48. MemdbCollection.prototype.__proto__ = MongooseCollection.prototype;
  49. /**
  50. * Called when the connection opens.
  51. *
  52. * @api private
  53. */
  54. MemdbCollection.prototype.onOpen = function () {
  55. var self = this;
  56. // always get a new collection in case the user changed host:port
  57. // of parent db instance when re-opening the connection.
  58. if (!self.opts.capped.size) {
  59. // non-capped
  60. return self.conn.db.collection(self.name, callback);
  61. }
  62. // capped
  63. return self.conn.db.collection(self.name, function (err, c) {
  64. if (err) return callback(err);
  65. // discover if this collection exists and if it is capped
  66. self.conn.db.collection( 'system.namespaces', function(err, namespaces) {
  67. var namespaceName = self.conn.db.databaseName + '.' + self.name;
  68. namespaces.findOne({ name : namespaceName }, function(err, doc) {
  69. if (err) {
  70. return callback(err);
  71. }
  72. var exists = !!doc;
  73. if (exists) {
  74. if (doc.options && doc.options.capped) {
  75. callback(null, c);
  76. } else {
  77. var msg = 'A non-capped collection exists with the name: '+ self.name +'\n\n'
  78. + ' To use this collection as a capped collection, please '
  79. + 'first convert it.\n'
  80. + ' http://www.mongodb.org/display/DOCS/Capped+Collections#CappedCollections-Convertingacollectiontocapped'
  81. err = new Error(msg);
  82. callback(err);
  83. }
  84. } else {
  85. // create
  86. var opts = utils.clone(self.opts.capped);
  87. opts.capped = true;
  88. self.conn.db.createCollection(self.name, opts, callback);
  89. }
  90. });
  91. });
  92. });
  93. function callback (err, collection) {
  94. if (err) {
  95. // likely a strict mode error
  96. self.conn.emit('error', err);
  97. } else {
  98. self.collection = collection;
  99. MongooseCollection.prototype.onOpen.call(self);
  100. }
  101. };
  102. };
  103. /**
  104. * Called when the connection closes
  105. *
  106. * @api private
  107. */
  108. MemdbCollection.prototype.onClose = function () {
  109. MongooseCollection.prototype.onClose.call(this);
  110. };
  111. /*!
  112. * Copy the collection methods and make them subject to queues
  113. */
  114. for (var i in Collection.prototype) {
  115. // Ignore methods with Async suffix (Generated by bluebird)
  116. if(/Async$/.test(i)){
  117. continue;
  118. }
  119. (function(i){
  120. var needRename = ['find', 'findOne', 'count']//, 'distinct', 'aggregate', 'group', 'mapReduce', 'geoNear', 'geoHaystackSearch'];
  121. var funcName = i;
  122. if (needRename.indexOf(i) != -1) {
  123. funcName = i + 'Mongo';
  124. }
  125. MemdbCollection.prototype[funcName] = function () {
  126. if (this.buffer) {
  127. this.addQueue(i, arguments);
  128. return;
  129. }
  130. var collection = this.collection
  131. , args = arguments
  132. , self = this
  133. , debug = self.conn.base.options.debug;
  134. if (debug) {
  135. if ('function' === typeof debug) {
  136. debug.apply(debug
  137. , [self.name, i].concat(utils.args(args, 0, args.length-1)));
  138. } else {
  139. console.error('\x1B[0;36mMongoose:\x1B[0m %s.%s(%s) %s %s %s'
  140. , self.name
  141. , i
  142. , print(args[0])
  143. , print(args[1])
  144. , print(args[2])
  145. , print(args[3]))
  146. }
  147. }
  148. return collection[i].apply(collection, args);
  149. };
  150. })(i);
  151. }
  152. MemdbCollection.prototype.find = function(query, options, cb){
  153. logger.debug('MemdbCollection.find %j %j', query, options);
  154. if(options.comment === '$mongo'){
  155. delete options.comment;
  156. return this.findMongo(query, options, cb);
  157. }
  158. else if(options.comment === '$readonly'){
  159. delete options.comment;
  160. options.readonly = true;
  161. }
  162. return this._memdbCollection.find(query, options.fields, options)
  163. .then(function(docs){
  164. return {
  165. toArray : function(cb){
  166. cb(null, docs);
  167. }
  168. };
  169. })
  170. .nodeify(cb);
  171. };
  172. MemdbCollection.prototype.findOne = function(query, options, cb){
  173. logger.debug('MemdbCollection.findOne %j %j', query, options);
  174. if(options.comment === '$mongo'){
  175. delete options.comment;
  176. return this.findOneMongo(query, options, cb);
  177. }
  178. else if(options.comment === '$readonly'){
  179. delete options.comment;
  180. options.readonly = true;
  181. }
  182. return this._memdbCollection.findOne(query, options.fields, options)
  183. .nodeify(cb);
  184. };
  185. MemdbCollection.prototype.insert = function(docs, opts, cb) {
  186. logger.debug('MemdbCollection.insert');
  187. return this._memdbCollection.insert(docs)
  188. .nodeify(cb);
  189. };
  190. MemdbCollection.prototype.insertOne = MemdbCollection.prototype.insert;
  191. MemdbCollection.prototype.insertMany = MemdbCollection.prototype.insert;
  192. MemdbCollection.prototype.remove = function(selector, opts, cb) {
  193. logger.debug('MemdbCollection.remove %j', selector);
  194. return this._memdbCollection.remove(selector, opts)
  195. .nodeify(cb);
  196. };
  197. MemdbCollection.prototype.removeOne = function(selector, opts, cb) {
  198. logger.debug('MemdbCollection.removeOne %j', selector);
  199. opts = opts || {};
  200. opts.limit = 1;
  201. return this._memdbCollection.remove(selector, opts)
  202. .nodeify(cb);
  203. };
  204. MemdbCollection.prototype.deleteOne = MemdbCollection.prototype.removeOne;
  205. MemdbCollection.prototype.deleteMany = MemdbCollection.prototype.remove;
  206. MemdbCollection.prototype.update = function(selector, doc, opts, cb) {
  207. logger.debug('MemdbCollection.update %j', selector);
  208. return this._memdbCollection.update(selector, doc, opts)
  209. .nodeify(cb);
  210. };
  211. MemdbCollection.prototype.updateOne = function(selector, doc, opts, cb) {
  212. logger.debug('MemdbCollection.updateOne %j', selector);
  213. opts = opts || {};
  214. opts.limit = 1;
  215. return this._memdbCollection.update(selector, doc, opts)
  216. .nodeify(cb);
  217. };
  218. MemdbCollection.prototype.updateMany = MemdbCollection.prototype.update;
  219. MemdbCollection.prototype.replaceOne = MemdbCollection.prototype.updateOne;
  220. MemdbCollection.prototype.count = function(query, opts, cb) {
  221. logger.debug('MemdbCollection.count %j', query);
  222. return this._memdbCollection.count(query, opts)
  223. .nodeify(cb);
  224. };
  225. var unimplemented = ['bulkWrite', 'save', 'findAndModify', 'findAndRemove', 'findOneAndDelete', 'findOneAndReplace', 'findOneAndUpdate', 'rename', 'drop'];
  226. unimplemented.forEach(function(method){
  227. MemdbCollection.prototype[method] = function(){
  228. throw new Error('Collection#' + method + ' unimplemented by driver');
  229. }
  230. });
  231. /*!
  232. * Debug print helper
  233. */
  234. function print (arg) {
  235. var type = typeof arg;
  236. if ('function' === type || 'undefined' === type) return '';
  237. return format(arg);
  238. }
  239. /*!
  240. * Debug print helper
  241. */
  242. function format (obj, sub) {
  243. var x = utils.clone(obj);
  244. if (x) {
  245. if ('Binary' === x.constructor.name) {
  246. x = '[object Buffer]';
  247. } else if ('ObjectID' === x.constructor.name) {
  248. var representation = 'ObjectId("' + x.toHexString() + '")';
  249. x = { inspect: function() { return representation; } };
  250. } else if ('Date' === x.constructor.name) {
  251. var representation = 'new Date("' + x.toUTCString() + '")';
  252. x = { inspect: function() { return representation; } };
  253. } else if ('Object' === x.constructor.name) {
  254. var keys = Object.keys(x)
  255. , i = keys.length
  256. , key
  257. while (i--) {
  258. key = keys[i];
  259. if (x[key]) {
  260. if ('Binary' === x[key].constructor.name) {
  261. x[key] = '[object Buffer]';
  262. } else if ('Object' === x[key].constructor.name) {
  263. x[key] = format(x[key], true);
  264. } else if ('ObjectID' === x[key].constructor.name) {
  265. ;(function(x){
  266. var representation = 'ObjectId("' + x[key].toHexString() + '")';
  267. x[key] = { inspect: function() { return representation; } };
  268. })(x)
  269. } else if ('Date' === x[key].constructor.name) {
  270. ;(function(x){
  271. var representation = 'new Date("' + x[key].toUTCString() + '")';
  272. x[key] = { inspect: function() { return representation; } };
  273. })(x)
  274. } else if (Array.isArray(x[key])) {
  275. x[key] = x[key].map(function (o) {
  276. return format(o, true)
  277. });
  278. }
  279. }
  280. }
  281. }
  282. if (sub) return x;
  283. }
  284. return require('util')
  285. .inspect(x, false, 10, true)
  286. .replace(/\n/g, '')
  287. .replace(/\s{2,}/g, ' ')
  288. }
  289. /**
  290. * Retreives information about this collections indexes.
  291. *
  292. * @param {Function} callback
  293. * @method getIndexes
  294. * @api public
  295. */
  296. MemdbCollection.prototype.getIndexes = MemdbCollection.prototype.indexInformation;
  297. /*!
  298. * Module exports.
  299. */
  300. module.exports = MemdbCollection;