1
2 /**
3 * Copyright (C) 2018-present MongoDB, Inc.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the Server Side Public License, version 1,
7 * as published by MongoDB, Inc.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * Server Side Public License for more details.
13 *
14 * You should have received a copy of the Server Side Public License
15 * along with this program. If not, see
16 * <http://www.mongodb.com/licensing/server-side-public-license>.
17 *
18 * As a special exception, the copyright holders give permission to link the
19 * code of portions of this program with the OpenSSL library under certain
20 * conditions as described in each individual source file and distribute
21 * linked combinations including the program with the OpenSSL library. You
22 * must comply with the Server Side Public License in all respects for
23 * all of the code used other than as permitted herein. If you modify file(s)
24 * with this exception, you may extend this exception to your version of the
25 * file(s), but you are not obligated to do so. If you do not wish to do so,
26 * delete this exception statement from your version. If you delete this
27 * exception statement from all source files in the program, then also delete
28 * it in the license file.
29 */
30
31 #include "mongo/db/ops/modifier_compare.h"
32
33 #include "mongo/base/error_codes.h"
34 #include "mongo/bson/mutable/document.h"
35 #include "mongo/db/query/collation/collator_interface.h"
36 #include "mongo/db/update/field_checker.h"
37 #include "mongo/db/update/log_builder.h"
38 #include "mongo/db/update/path_support.h"
39 #include "mongo/util/mongoutils/str.h"
40
41 namespace mongo {
42
43 namespace str = mongoutils::str;
44
45 struct ModifierCompare::PreparedState {
PreparedStatemongo::ModifierCompare::PreparedState46 PreparedState(mutablebson::Document& targetDoc)
47 : doc(targetDoc), idxFound(0), elemFound(doc.end()) {}
48
49 // Document that is going to be changed.
50 mutablebson::Document& doc;
51
52 // Index in _fieldRef for which an Element exist in the document.
53 size_t idxFound;
54
55 // Element corresponding to _fieldRef[0.._idxFound].
56 mutablebson::Element elemFound;
57 };
58
ModifierCompare(ModifierCompare::ModifierCompareMode mode)59 ModifierCompare::ModifierCompare(ModifierCompare::ModifierCompareMode mode)
60 : _mode(mode), _pathReplacementPosition(0) {}
61
~ModifierCompare()62 ModifierCompare::~ModifierCompare() {}
63
init(const BSONElement & modExpr,const Options & opts,bool * positional)64 Status ModifierCompare::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
65 _updatePath.parse(modExpr.fieldName());
66 Status status = fieldchecker::isUpdatable(_updatePath);
67 if (!status.isOK()) {
68 return status;
69 }
70
71 // If a $-positional operator was used, get the index in which it occurred
72 // and ensure only one occurrence.
73 size_t foundCount;
74 bool foundDollar =
75 fieldchecker::isPositional(_updatePath, &_pathReplacementPosition, &foundCount);
76
77 if (positional)
78 *positional = foundDollar;
79
80 if (foundDollar && foundCount > 1) {
81 return Status(ErrorCodes::BadValue,
82 str::stream() << "Too many positional (i.e. '$') elements found in path '"
83 << _updatePath.dottedField()
84 << "'");
85 }
86
87 // Store value for later.
88 _val = modExpr;
89 _collator = opts.expCtx->getCollator();
90 return Status::OK();
91 }
92
prepare(mutablebson::Element root,StringData matchedField,ExecInfo * execInfo)93 Status ModifierCompare::prepare(mutablebson::Element root,
94 StringData matchedField,
95 ExecInfo* execInfo) {
96 _preparedState.reset(new PreparedState(root.getDocument()));
97
98 // If we have a $-positional field, it is time to bind it to an actual field part.
99 if (_pathReplacementPosition) {
100 if (matchedField.empty()) {
101 return Status(ErrorCodes::BadValue,
102 str::stream() << "The positional operator did not find the match "
103 "needed from the query. Unexpanded update: "
104 << _updatePath.dottedField());
105 }
106 _updatePath.setPart(_pathReplacementPosition, matchedField);
107 }
108
109 // Locate the field name in 'root'. Note that we may not have all the parts in the path
110 // in the doc -- which is fine. Our goal now is merely to reason about whether this mod
111 // apply is a noOp or whether is can be in place. The remaining path, if missing, will
112 // be created during the apply.
113 Status status = pathsupport::findLongestPrefix(
114 _updatePath, root, &_preparedState->idxFound, &_preparedState->elemFound);
115 const auto elemFoundIsArray =
116 _preparedState->elemFound.ok() && _preparedState->elemFound.getType() == BSONType::Array;
117
118 // FindLongestPrefix may say the path does not exist at all, which is fine here, or
119 // that the path was not viable or otherwise wrong, in which case, the mod cannot
120 // proceed.
121 if (status.code() == ErrorCodes::NonExistentPath) {
122 _preparedState->elemFound = root.getDocument().end();
123 } else if (!status.isOK()) {
124 return status;
125 }
126
127 // We register interest in the field name. The driver needs this info to sort out if
128 // there is any conflict among mods.
129 execInfo->fieldRef[0] = &_updatePath;
130
131 const bool destExists = (_preparedState->elemFound.ok() &&
132 _preparedState->idxFound == (_updatePath.numParts() - 1));
133 if (!destExists) {
134 execInfo->noOp = false;
135
136 if (elemFoundIsArray) {
137 // Report that an existing array will gain a new element as a result of this mod.
138 execInfo->indexOfArrayWithNewElement[0] = _preparedState->idxFound;
139 }
140 } else {
141 const int compareVal =
142 _preparedState->elemFound.compareWithBSONElement(_val, _collator, false);
143 execInfo->noOp = (compareVal == 0) ||
144 ((_mode == ModifierCompare::MAX) ? (compareVal > 0) : (compareVal < 0));
145 }
146
147 return Status::OK();
148 }
149
apply() const150 Status ModifierCompare::apply() const {
151 const bool destExists = (_preparedState->elemFound.ok() &&
152 _preparedState->idxFound == (_updatePath.numParts() - 1));
153 // If there's no need to create any further field part, the $set is simply a value
154 // assignment.
155 if (destExists) {
156 return _preparedState->elemFound.setValueBSONElement(_val);
157 }
158
159 mutablebson::Document& doc = _preparedState->doc;
160 StringData lastPart = _updatePath.getPart(_updatePath.numParts() - 1);
161 // If the element exists and is the same type, then that is what we want to work with
162 mutablebson::Element elemToSet = doc.makeElementWithNewFieldName(lastPart, _val);
163 if (!elemToSet.ok()) {
164 return Status(ErrorCodes::InternalError, "can't create new element");
165 }
166
167 // Now, we can be in two cases here, as far as attaching the element being set goes:
168 // (a) none of the parts in the element's path exist, or (b) some parts of the path
169 // exist but not all.
170 if (!_preparedState->elemFound.ok()) {
171 _preparedState->elemFound = doc.root();
172 _preparedState->idxFound = 0;
173 } else {
174 _preparedState->idxFound++;
175 }
176
177 // createPathAt() will complete the path and attach 'elemToSet' at the end of it.
178 return pathsupport::createPathAt(
179 _updatePath, _preparedState->idxFound, _preparedState->elemFound, elemToSet)
180 .getStatus();
181 }
182
log(LogBuilder * logBuilder) const183 Status ModifierCompare::log(LogBuilder* logBuilder) const {
184 return logBuilder->addToSetsWithNewFieldName(_updatePath.dottedField(), _val);
185 }
186
187 } // namespace mongo
188