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