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/platform/basic.h"
32 
33 #include "mongo/db/exec/working_set_common.h"
34 
35 #include "mongo/bson/simple_bsonobj_comparator.h"
36 #include "mongo/db/catalog/collection.h"
37 #include "mongo/db/exec/working_set.h"
38 #include "mongo/db/index/index_access_method.h"
39 #include "mongo/db/query/canonical_query.h"
40 #include "mongo/db/service_context.h"
41 #include "mongo/db/service_context.h"
42 
43 namespace mongo {
44 
45 // static
fetchAndInvalidateRecordId(OperationContext * opCtx,WorkingSetMember * member,const Collection * collection)46 bool WorkingSetCommon::fetchAndInvalidateRecordId(OperationContext* opCtx,
47                                                   WorkingSetMember* member,
48                                                   const Collection* collection) {
49     // Already in our desired state.
50     if (member->getState() == WorkingSetMember::OWNED_OBJ) {
51         return true;
52     }
53 
54     // We can't do anything without a RecordId.
55     if (!member->hasRecordId()) {
56         return false;
57     }
58 
59     // Do the fetch, invalidate the DL.
60     member->obj = collection->docFor(opCtx, member->recordId);
61     member->obj.setValue(member->obj.value().getOwned());
62     member->recordId = RecordId();
63     member->transitionToOwnedObj();
64 
65     return true;
66 }
67 
prepareForSnapshotChange(WorkingSet * workingSet)68 void WorkingSetCommon::prepareForSnapshotChange(WorkingSet* workingSet) {
69     if (!supportsDocLocking()) {
70         // Non doc-locking storage engines use invalidations, so we don't need to examine the
71         // buffered working set ids. But we do need to clear the set of ids in order to keep our
72         // memory utilization in check.
73         workingSet->getAndClearYieldSensitiveIds();
74         return;
75     }
76 
77     for (auto id : workingSet->getAndClearYieldSensitiveIds()) {
78         if (workingSet->isFree(id)) {
79             continue;
80         }
81 
82         // We may see the same member twice, so anything we do here should be idempotent.
83         WorkingSetMember* member = workingSet->get(id);
84         if (member->getState() == WorkingSetMember::RID_AND_IDX) {
85             member->isSuspicious = true;
86         }
87     }
88 }
89 
90 // static
fetch(OperationContext * opCtx,WorkingSet * workingSet,WorkingSetID id,unowned_ptr<SeekableRecordCursor> cursor)91 bool WorkingSetCommon::fetch(OperationContext* opCtx,
92                              WorkingSet* workingSet,
93                              WorkingSetID id,
94                              unowned_ptr<SeekableRecordCursor> cursor) {
95     WorkingSetMember* member = workingSet->get(id);
96 
97     // The RecordFetcher should already have been transferred out of the WSM and used.
98     invariant(!member->hasFetcher());
99 
100     // We should have a RecordId but need to retrieve the obj. Get the obj now and reset all WSM
101     // state appropriately.
102     invariant(member->hasRecordId());
103 
104     member->obj.reset();
105     auto record = cursor->seekExact(member->recordId);
106     if (!record) {
107         return false;
108     }
109 
110     member->obj = {opCtx->recoveryUnit()->getSnapshotId(), record->data.releaseToBson()};
111 
112     if (member->isSuspicious) {
113         // Make sure that all of the keyData is still valid for this copy of the document.
114         // This ensures both that index-provided filters and sort orders still hold.
115         // TODO provide a way for the query planner to opt out of this checking if it is
116         // unneeded due to the structure of the plan.
117         invariant(!member->keyData.empty());
118         for (size_t i = 0; i < member->keyData.size(); i++) {
119             BSONObjSet keys = SimpleBSONObjComparator::kInstance.makeBSONObjSet();
120             // There's no need to compute the prefixes of the indexed fields that cause the index to
121             // be multikey when ensuring the keyData is still valid.
122             MultikeyPaths* multikeyPaths = nullptr;
123             member->keyData[i].index->getKeys(member->obj.value(),
124                                               IndexAccessMethod::GetKeysMode::kEnforceConstraints,
125                                               IndexAccessMethod::GetKeysContext::kReadOrAddKeys,
126                                               &keys,
127                                               multikeyPaths);
128             if (!keys.count(member->keyData[i].keyData)) {
129                 // document would no longer be at this position in the index.
130                 return false;
131             }
132         }
133 
134         member->isSuspicious = false;
135     }
136 
137     member->keyData.clear();
138     workingSet->transitionToRecordIdAndObj(id);
139     return true;
140 }
141 
142 // static
buildMemberStatusObject(const Status & status)143 BSONObj WorkingSetCommon::buildMemberStatusObject(const Status& status) {
144     BSONObjBuilder bob;
145     bob.append("ok", status.isOK() ? 1.0 : 0.0);
146     bob.append("code", status.code());
147     bob.append("errmsg", status.reason());
148 
149     return bob.obj();
150 }
151 
152 // static
allocateStatusMember(WorkingSet * ws,const Status & status)153 WorkingSetID WorkingSetCommon::allocateStatusMember(WorkingSet* ws, const Status& status) {
154     invariant(ws);
155 
156     WorkingSetID wsid = ws->allocate();
157     WorkingSetMember* member = ws->get(wsid);
158     member->obj = Snapshotted<BSONObj>(SnapshotId(), buildMemberStatusObject(status));
159     member->transitionToOwnedObj();
160 
161     return wsid;
162 }
163 
164 // static
isValidStatusMemberObject(const BSONObj & obj)165 bool WorkingSetCommon::isValidStatusMemberObject(const BSONObj& obj) {
166     return obj.nFields() == 3 && obj.hasField("ok") && obj.hasField("code") &&
167         obj.hasField("errmsg");
168 }
169 
170 // static
getStatusMemberObject(const WorkingSet & ws,WorkingSetID wsid,BSONObj * objOut)171 void WorkingSetCommon::getStatusMemberObject(const WorkingSet& ws,
172                                              WorkingSetID wsid,
173                                              BSONObj* objOut) {
174     invariant(objOut);
175 
176     // Validate ID and working set member.
177     if (WorkingSet::INVALID_ID == wsid) {
178         return;
179     }
180     WorkingSetMember* member = ws.get(wsid);
181     if (!member->hasOwnedObj()) {
182         return;
183     }
184     BSONObj obj = member->obj.value();
185     if (!isValidStatusMemberObject(obj)) {
186         return;
187     }
188     *objOut = obj;
189 }
190 
191 // static
getMemberObjectStatus(const BSONObj & memberObj)192 Status WorkingSetCommon::getMemberObjectStatus(const BSONObj& memberObj) {
193     invariant(WorkingSetCommon::isValidStatusMemberObject(memberObj));
194     return Status(ErrorCodes::Error(memberObj["code"].numberInt()), memberObj["errmsg"]);
195 }
196 
197 // static
getMemberStatus(const WorkingSetMember & member)198 Status WorkingSetCommon::getMemberStatus(const WorkingSetMember& member) {
199     invariant(member.hasObj());
200     return getMemberObjectStatus(member.obj.value());
201 }
202 
203 // static
toStatusString(const BSONObj & obj)204 std::string WorkingSetCommon::toStatusString(const BSONObj& obj) {
205     if (!isValidStatusMemberObject(obj)) {
206         Status unknownStatus(ErrorCodes::UnknownError, "no details available");
207         return unknownStatus.toString();
208     }
209     Status status(ErrorCodes::Error(obj.getIntField("code")), obj.getStringField("errmsg"));
210     return status.toString();
211 }
212 
213 }  // namespace mongo
214