setDefaultsOnInsert.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  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 === false) {
  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. if (schemaType.path === '_id' && schemaType.options.auto) {
  68. return;
  69. }
  70. const def = schemaType.getDefault(null, true);
  71. if (isModified(modified, path)) {
  72. return;
  73. }
  74. if (typeof def === 'undefined') {
  75. return;
  76. }
  77. if (schemaType.splitPath().includes('$*')) {
  78. // Skip defaults underneath maps. We should never do `$setOnInsert` on a path with `$*`
  79. return;
  80. }
  81. castedDoc = castedDoc || {};
  82. castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
  83. if (get(castedDoc, path) == null) {
  84. castedDoc.$setOnInsert[path] = def;
  85. }
  86. updatedValues[path] = def;
  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. }