setDefaultsOnInsert.js 3.6 KB

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