summaryrefslogtreecommitdiffstats
path: root/node_modules/mongoose/lib/helpers/setDefaultsOnInsert.js
blob: 14e3f1b2572d5d8ae205a8432c88850bb9994fb9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
'use strict';
const modifiedPaths = require('./common').modifiedPaths;
const get = require('./get');

/**
 * Applies defaults to update and findOneAndUpdate operations.
 *
 * @param {Object} filter
 * @param {Schema} schema
 * @param {Object} castedDoc
 * @param {Object} options
 * @method setDefaultsOnInsert
 * @api private
 */

module.exports = function(filter, schema, castedDoc, options) {
  options = options || {};

  const shouldSetDefaultsOnInsert =
    options.setDefaultsOnInsert != null ?
      options.setDefaultsOnInsert :
      schema.base.options.setDefaultsOnInsert;

  if (!options.upsert || !shouldSetDefaultsOnInsert) {
    return castedDoc;
  }

  const keys = Object.keys(castedDoc || {});
  const updatedKeys = {};
  const updatedValues = {};
  const numKeys = keys.length;
  const modified = {};

  let hasDollarUpdate = false;

  for (let i = 0; i < numKeys; ++i) {
    if (keys[i].startsWith('$')) {
      modifiedPaths(castedDoc[keys[i]], '', modified);
      hasDollarUpdate = true;
    }
  }

  if (!hasDollarUpdate) {
    modifiedPaths(castedDoc, '', modified);
  }

  const paths = Object.keys(filter);
  const numPaths = paths.length;
  for (let i = 0; i < numPaths; ++i) {
    const path = paths[i];
    const condition = filter[path];
    if (condition && typeof condition === 'object') {
      const conditionKeys = Object.keys(condition);
      const numConditionKeys = conditionKeys.length;
      let hasDollarKey = false;
      for (let j = 0; j < numConditionKeys; ++j) {
        if (conditionKeys[j].startsWith('$')) {
          hasDollarKey = true;
          break;
        }
      }
      if (hasDollarKey) {
        continue;
      }
    }
    updatedKeys[path] = true;
    modified[path] = true;
  }

  if (options && options.overwrite && !hasDollarUpdate) {
    // Defaults will be set later, since we're overwriting we'll cast
    // the whole update to a document
    return castedDoc;
  }

  schema.eachPath(function(path, schemaType) {
    // Skip single nested paths if underneath a map
    const isUnderneathMap = schemaType.path.endsWith('.$*') ||
      schemaType.path.indexOf('.$*.') !== -1;
    if (schemaType.$isSingleNested && !isUnderneathMap) {
      // Only handle nested schemas 1-level deep to avoid infinite
      // recursion re: https://github.com/mongodb-js/mongoose-autopopulate/issues/11
      schemaType.schema.eachPath(function(_path, _schemaType) {
        if (_path === '_id' && _schemaType.auto) {
          // Ignore _id if auto id so we don't create subdocs
          return;
        }

        const def = _schemaType.getDefault(null, true);
        if (!isModified(modified, path + '.' + _path) &&
            typeof def !== 'undefined') {
          castedDoc = castedDoc || {};
          castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
          castedDoc.$setOnInsert[path + '.' + _path] = def;
          updatedValues[path + '.' + _path] = def;
        }
      });
    } else {
      const def = schemaType.getDefault(null, true);
      if (!isModified(modified, path) && typeof def !== 'undefined') {
        castedDoc = castedDoc || {};
        castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
        if (get(castedDoc, path) == null) {
          castedDoc.$setOnInsert[path] = def;
        }
        updatedValues[path] = def;
      }
    }
  });

  return castedDoc;
};

function isModified(modified, path) {
  if (modified[path]) {
    return true;
  }
  const sp = path.split('.');
  let cur = sp[0];
  for (let i = 1; i < sp.length; ++i) {
    if (modified[cur]) {
      return true;
    }
    cur += '.' + sp[i];
  }
  return false;
}