collection.js 9.0 KB

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