setDefaultsOnInsert.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. 'use strict';
  2. const modifiedPaths = require('./common').modifiedPaths;
  3. /**
  4. * Applies defaults to update and findOneAndUpdate operations.
  5. *
  6. * @param {Object} filter
  7. * @param {Schema} schema
  8. * @param {Object} castedDoc
  9. * @param {Object} options
  10. * @method setDefaultsOnInsert
  11. * @api private
  12. */
  13. module.exports = function(filter, schema, castedDoc, options) {
  14. const keys = Object.keys(castedDoc || {});
  15. const updatedKeys = {};
  16. const updatedValues = {};
  17. const numKeys = keys.length;
  18. const modified = {};
  19. let hasDollarUpdate = false;
  20. options = options || {};
  21. if (!options.upsert || !options.setDefaultsOnInsert) {
  22. return castedDoc;
  23. }
  24. for (let i = 0; i < numKeys; ++i) {
  25. if (keys[i].charAt(0) === '$') {
  26. modifiedPaths(castedDoc[keys[i]], '', modified);
  27. hasDollarUpdate = true;
  28. }
  29. }
  30. if (!hasDollarUpdate) {
  31. modifiedPaths(castedDoc, '', modified);
  32. }
  33. const paths = Object.keys(filter);
  34. const numPaths = paths.length;
  35. for (let i = 0; i < numPaths; ++i) {
  36. const path = paths[i];
  37. const condition = filter[path];
  38. if (condition && typeof condition === 'object') {
  39. const conditionKeys = Object.keys(condition);
  40. const numConditionKeys = conditionKeys.length;
  41. let hasDollarKey = false;
  42. for (let j = 0; j < numConditionKeys; ++j) {
  43. if (conditionKeys[j].charAt(0) === '$') {
  44. hasDollarKey = true;
  45. break;
  46. }
  47. }
  48. if (hasDollarKey) {
  49. continue;
  50. }
  51. }
  52. updatedKeys[path] = true;
  53. modified[path] = true;
  54. }
  55. if (options && options.overwrite && !hasDollarUpdate) {
  56. // Defaults will be set later, since we're overwriting we'll cast
  57. // the whole update to a document
  58. return castedDoc;
  59. }
  60. schema.eachPath(function(path, schemaType) {
  61. if (schemaType.$isSingleNested) {
  62. // Only handle nested schemas 1-level deep to avoid infinite
  63. // recursion re: https://github.com/mongodb-js/mongoose-autopopulate/issues/11
  64. schemaType.schema.eachPath(function(_path, _schemaType) {
  65. if (_path === '_id' && _schemaType.auto) {
  66. // Ignore _id if auto id so we don't create subdocs
  67. return;
  68. }
  69. const def = _schemaType.getDefault(null, true);
  70. if (!isModified(modified, path + '.' + _path) &&
  71. typeof def !== 'undefined') {
  72. castedDoc = castedDoc || {};
  73. castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
  74. castedDoc.$setOnInsert[path + '.' + _path] = def;
  75. updatedValues[path + '.' + _path] = def;
  76. }
  77. });
  78. } else {
  79. const def = schemaType.getDefault(null, true);
  80. if (!isModified(modified, path) && typeof def !== 'undefined') {
  81. castedDoc = castedDoc || {};
  82. castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
  83. castedDoc.$setOnInsert[path] = def;
  84. updatedValues[path] = def;
  85. }
  86. }
  87. });
  88. return castedDoc;
  89. };
  90. function isModified(modified, path) {
  91. if (modified[path]) {
  92. return true;
  93. }
  94. const sp = path.split('.');
  95. let cur = sp[0];
  96. for (let i = 1; i < sp.length; ++i) {
  97. if (modified[cur]) {
  98. return true;
  99. }
  100. cur += '.' + sp[i];
  101. }
  102. return false;
  103. }