collection.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. 'use strict';
  2. /*!
  3. * Module dependencies.
  4. */
  5. const MongooseCollection = require('../../collection');
  6. const Collection = require('mongodb').Collection;
  7. const get = require('../../helpers/get');
  8. const sliced = require('sliced');
  9. const stream = require('stream');
  10. const util = require('util');
  11. /**
  12. * A [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) collection implementation.
  13. *
  14. * All methods methods from the [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) driver are copied and wrapped in queue management.
  15. *
  16. * @inherits Collection
  17. * @api private
  18. */
  19. function NativeCollection() {
  20. this.collection = null;
  21. MongooseCollection.apply(this, arguments);
  22. }
  23. /*!
  24. * Inherit from abstract Collection.
  25. */
  26. NativeCollection.prototype.__proto__ = MongooseCollection.prototype;
  27. /**
  28. * Called when the connection opens.
  29. *
  30. * @api private
  31. */
  32. NativeCollection.prototype.onOpen = function() {
  33. const _this = this;
  34. // always get a new collection in case the user changed host:port
  35. // of parent db instance when re-opening the connection.
  36. if (!_this.opts.capped.size) {
  37. // non-capped
  38. callback(null, _this.conn.db.collection(_this.name));
  39. return _this.collection;
  40. }
  41. // capped
  42. return _this.conn.db.collection(_this.name, function(err, c) {
  43. if (err) return callback(err);
  44. // discover if this collection exists and if it is capped
  45. _this.conn.db.listCollections({name: _this.name}).toArray(function(err, docs) {
  46. if (err) {
  47. return callback(err);
  48. }
  49. const doc = docs[0];
  50. const exists = !!doc;
  51. if (exists) {
  52. if (doc.options && doc.options.capped) {
  53. callback(null, c);
  54. } else {
  55. const msg = 'A non-capped collection exists with the name: ' + _this.name + '\n\n'
  56. + ' To use this collection as a capped collection, please '
  57. + 'first convert it.\n'
  58. + ' http://www.mongodb.org/display/DOCS/Capped+Collections#CappedCollections-Convertingacollectiontocapped';
  59. err = new Error(msg);
  60. callback(err);
  61. }
  62. } else {
  63. // create
  64. const opts = Object.assign({}, _this.opts.capped);
  65. opts.capped = true;
  66. _this.conn.db.createCollection(_this.name, opts, callback);
  67. }
  68. });
  69. });
  70. function callback(err, collection) {
  71. if (err) {
  72. // likely a strict mode error
  73. _this.conn.emit('error', err);
  74. } else {
  75. _this.collection = collection;
  76. MongooseCollection.prototype.onOpen.call(_this);
  77. }
  78. }
  79. };
  80. /**
  81. * Called when the connection closes
  82. *
  83. * @api private
  84. */
  85. NativeCollection.prototype.onClose = function(force) {
  86. MongooseCollection.prototype.onClose.call(this, force);
  87. };
  88. /*!
  89. * ignore
  90. */
  91. const syncCollectionMethods = { watch: true };
  92. /*!
  93. * Copy the collection methods and make them subject to queues
  94. */
  95. function iter(i) {
  96. NativeCollection.prototype[i] = function() {
  97. const collection = this.collection;
  98. const args = arguments;
  99. const _this = this;
  100. const debug = get(_this, 'conn.base.options.debug');
  101. // If user force closed, queueing will hang forever. See #5664
  102. if (this.opts.$wasForceClosed) {
  103. return this.conn.db.collection(this.name)[i].apply(collection, args);
  104. }
  105. if (this.buffer) {
  106. if (syncCollectionMethods[i]) {
  107. throw new Error('Collection method ' + i + ' is synchronous');
  108. }
  109. this.addQueue(i, arguments);
  110. return;
  111. }
  112. if (debug) {
  113. if (typeof debug === 'function') {
  114. debug.apply(_this,
  115. [_this.name, i].concat(sliced(args, 0, args.length - 1)));
  116. } else if (debug instanceof stream.Writable) {
  117. this.$printToStream(_this.name, i, args, debug);
  118. } else {
  119. this.$print(_this.name, i, args);
  120. }
  121. }
  122. try {
  123. return collection[i].apply(collection, args);
  124. } catch (error) {
  125. // Collection operation may throw because of max bson size, catch it here
  126. // See gh-3906
  127. if (args.length > 0 &&
  128. typeof args[args.length - 1] === 'function') {
  129. args[args.length - 1](error);
  130. } else {
  131. throw error;
  132. }
  133. }
  134. };
  135. }
  136. for (const i in Collection.prototype) {
  137. // Janky hack to work around gh-3005 until we can get rid of the mongoose
  138. // collection abstraction
  139. try {
  140. if (typeof Collection.prototype[i] !== 'function') {
  141. continue;
  142. }
  143. } catch (e) {
  144. continue;
  145. }
  146. iter(i);
  147. }
  148. /**
  149. * Debug print helper
  150. *
  151. * @api public
  152. * @method $print
  153. */
  154. NativeCollection.prototype.$print = function(name, i, args) {
  155. const moduleName = '\x1B[0;36mMongoose:\x1B[0m ';
  156. const functionCall = [name, i].join('.');
  157. const _args = [];
  158. for (let j = args.length - 1; j >= 0; --j) {
  159. if (this.$format(args[j]) || _args.length) {
  160. _args.unshift(this.$format(args[j]));
  161. }
  162. }
  163. const params = '(' + _args.join(', ') + ')';
  164. console.info(moduleName + functionCall + params);
  165. };
  166. /**
  167. * Debug print helper
  168. *
  169. * @api public
  170. * @method $print
  171. */
  172. NativeCollection.prototype.$printToStream = function(name, i, args, stream) {
  173. const functionCall = [name, i].join('.');
  174. const _args = [];
  175. for (let j = args.length - 1; j >= 0; --j) {
  176. if (this.$format(args[j]) || _args.length) {
  177. _args.unshift(this.$format(args[j]));
  178. }
  179. }
  180. const params = '(' + _args.join(', ') + ')';
  181. stream.write(functionCall + params, 'utf8');
  182. };
  183. /**
  184. * Formatter for debug print args
  185. *
  186. * @api public
  187. * @method $format
  188. */
  189. NativeCollection.prototype.$format = function(arg) {
  190. const type = typeof arg;
  191. if (type === 'function' || type === 'undefined') return '';
  192. return format(arg);
  193. };
  194. /*!
  195. * Debug print helper
  196. */
  197. function inspectable(representation) {
  198. const ret = {
  199. inspect: function() { return representation; },
  200. };
  201. if (util.inspect.custom) {
  202. ret[util.inspect.custom] = ret.inspect;
  203. }
  204. return ret;
  205. }
  206. function map(o) {
  207. return format(o, true);
  208. }
  209. function formatObjectId(x, key) {
  210. x[key] = inspectable('ObjectId("' + x[key].toHexString() + '")');
  211. }
  212. function formatDate(x, key) {
  213. x[key] = inspectable('new Date("' + x[key].toUTCString() + '")');
  214. }
  215. function format(obj, sub) {
  216. if (obj && typeof obj.toBSON === 'function') {
  217. obj = obj.toBSON();
  218. }
  219. if (obj == null) {
  220. return obj;
  221. }
  222. let x = require('../../utils').clone(obj, {transform: false});
  223. if (x.constructor.name === 'Binary') {
  224. x = 'BinData(' + x.sub_type + ', "' + x.toString('base64') + '")';
  225. } else if (x.constructor.name === 'ObjectID') {
  226. x = inspectable('ObjectId("' + x.toHexString() + '")');
  227. } else if (x.constructor.name === 'Date') {
  228. x = inspectable('new Date("' + x.toUTCString() + '")');
  229. } else if (x.constructor.name === 'Object') {
  230. const keys = Object.keys(x);
  231. const numKeys = keys.length;
  232. let key;
  233. for (let i = 0; i < numKeys; ++i) {
  234. key = keys[i];
  235. if (x[key]) {
  236. let error;
  237. if (typeof x[key].toBSON === 'function') {
  238. try {
  239. // `session.toBSON()` throws an error. This means we throw errors
  240. // in debug mode when using transactions, see gh-6712. As a
  241. // workaround, catch `toBSON()` errors, try to serialize without
  242. // `toBSON()`, and rethrow if serialization still fails.
  243. x[key] = x[key].toBSON();
  244. } catch (_error) {
  245. error = _error;
  246. }
  247. }
  248. if (x[key].constructor.name === 'Binary') {
  249. x[key] = 'BinData(' + x[key].sub_type + ', "' +
  250. x[key].buffer.toString('base64') + '")';
  251. } else if (x[key].constructor.name === 'Object') {
  252. x[key] = format(x[key], true);
  253. } else if (x[key].constructor.name === 'ObjectID') {
  254. formatObjectId(x, key);
  255. } else if (x[key].constructor.name === 'Date') {
  256. formatDate(x, key);
  257. } else if (x[key].constructor.name === 'ClientSession') {
  258. x[key] = inspectable('ClientSession("' +
  259. get(x[key], 'id.id.buffer', '').toString('hex') + '")');
  260. } else if (Array.isArray(x[key])) {
  261. x[key] = x[key].map(map);
  262. } else if (error != null) {
  263. // If there was an error with `toBSON()` and the object wasn't
  264. // already converted to a string representation, rethrow it.
  265. // Open to better ideas on how to handle this.
  266. throw error;
  267. }
  268. }
  269. }
  270. }
  271. if (sub) {
  272. return x;
  273. }
  274. return util.
  275. inspect(x, false, 10, true).
  276. replace(/\n/g, '').
  277. replace(/\s{2,}/g, ' ');
  278. }
  279. /**
  280. * Retrieves information about this collections indexes.
  281. *
  282. * @param {Function} callback
  283. * @method getIndexes
  284. * @api public
  285. */
  286. NativeCollection.prototype.getIndexes = NativeCollection.prototype.indexInformation;
  287. /*!
  288. * Module exports.
  289. */
  290. module.exports = NativeCollection;