summaryrefslogtreecommitdiff
path: root/src/mongo/db/exec/distinct_scan.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/exec/distinct_scan.cpp')
-rw-r--r--src/mongo/db/exec/distinct_scan.cpp239
1 files changed, 239 insertions, 0 deletions
diff --git a/src/mongo/db/exec/distinct_scan.cpp b/src/mongo/db/exec/distinct_scan.cpp
new file mode 100644
index 00000000000..5c58ff40e08
--- /dev/null
+++ b/src/mongo/db/exec/distinct_scan.cpp
@@ -0,0 +1,239 @@
+/**
+ * Copyright (C) 2014 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/db/exec/distinct_scan.h"
+
+#include "mongo/db/exec/filter.h"
+#include "mongo/db/exec/working_set_computed_data.h"
+#include "mongo/db/index/index_access_method.h"
+#include "mongo/db/index/index_cursor.h"
+#include "mongo/db/index/index_descriptor.h"
+
+namespace mongo {
+
+ DistinctScan::DistinctScan(const DistinctParams& params, WorkingSet* workingSet)
+ : _workingSet(workingSet),
+ _descriptor(params.descriptor),
+ _iam(params.descriptor->getIndexCatalog()->getIndex(params.descriptor)),
+ _btreeCursor(NULL),
+ _scanState(INITIALIZING),
+ _params(params) { }
+
+ void DistinctScan::initIndexCursor() {
+ // This function transitions from the initializing state to CHECKING_END. If
+ // the initialization fails, however, then the state transitions to HIT_END.
+ invariant(INITIALIZING == _scanState);
+
+ // Create an IndexCursor over the btree we're distinct-ing over.
+ CursorOptions cursorOptions;
+
+ if (1 == _params.direction) {
+ cursorOptions.direction = CursorOptions::INCREASING;
+ }
+ else {
+ cursorOptions.direction = CursorOptions::DECREASING;
+ }
+
+ IndexCursor *cursor;
+ Status s = _iam->newCursor(&cursor);
+ verify(s.isOK());
+ verify(cursor);
+ // Is this assumption always valid? See SERVER-12397
+ _btreeCursor.reset(static_cast<BtreeIndexCursor*>(cursor));
+ _btreeCursor->setOptions(cursorOptions);
+
+ // Create a new bounds checker. The bounds checker gets our start key and assists in
+ // executing the scan and staying within the required bounds.
+ _checker.reset(new IndexBoundsChecker(&_params.bounds,
+ _descriptor->keyPattern(),
+ _params.direction));
+
+ int nFields = _descriptor->keyPattern().nFields();
+ // The start key is dumped into these two.
+ vector<const BSONElement*> key;
+ vector<bool> inc;
+ key.resize(nFields);
+ inc.resize(nFields);
+ if (_checker->getStartKey(&key, &inc)) {
+ _btreeCursor->seek(key, inc);
+ _keyElts.resize(nFields);
+ _keyEltsInc.resize(nFields);
+ }
+ else {
+ _scanState = HIT_END;
+ }
+
+ // This method may throw an exception while it's doing initialization. If we've gotten
+ // here, then we've done all the initialization without an exception being thrown. This
+ // means it is safe to transition to the CHECKING_END state. In error cases, we transition
+ // to HIT_END, so we should not change state again here.
+ if (HIT_END != _scanState) {
+ _scanState = CHECKING_END;
+ }
+ }
+
+ PlanStage::StageState DistinctScan::work(WorkingSetID* out) {
+ ++_commonStats.works;
+
+ if (INITIALIZING == _scanState) {
+ invariant(NULL == _btreeCursor.get());
+ initIndexCursor();
+ }
+
+ if (CHECKING_END == _scanState) {
+ checkEnd();
+ }
+
+ if (isEOF()) {
+ _commonStats.isEOF = true;
+ return PlanStage::IS_EOF;
+ }
+
+ if (GETTING_NEXT == _scanState) {
+ // Grab the next (key, value) from the index.
+ BSONObj ownedKeyObj = _btreeCursor->getKey().getOwned();
+ DiskLoc loc = _btreeCursor->getValue();
+
+ // The underlying IndexCursor points at the *next* thing we want to return. We do this
+ // so that if we're scanning an index looking for docs to delete we don't continually
+ // clobber the thing we're pointing at.
+
+ // We skip to the next value of the _params.fieldNo-th field in the index key pattern.
+ // This is the field we're distinct-ing over.
+ _btreeCursor->skip(_btreeCursor->getKey(),
+ _params.fieldNo + 1,
+ true,
+ _keyElts,
+ _keyEltsInc);
+
+ // On the next call to work, make sure that the cursor is still within the bounds.
+ _scanState = CHECKING_END;
+
+ // Package up the result for the caller.
+ WorkingSetID id = _workingSet->allocate();
+ WorkingSetMember* member = _workingSet->get(id);
+ member->loc = loc;
+ member->keyData.push_back(IndexKeyDatum(_descriptor->keyPattern(), ownedKeyObj));
+ member->state = WorkingSetMember::LOC_AND_IDX;
+
+ *out = id;
+ ++_commonStats.advanced;
+ return PlanStage::ADVANCED;
+ }
+
+ ++_commonStats.needTime;
+ return PlanStage::NEED_TIME;
+ }
+
+ bool DistinctScan::isEOF() {
+ if (INITIALIZING == _scanState) {
+ // Have to call work() at least once.
+ return false;
+ }
+
+ return HIT_END == _scanState || _btreeCursor->isEOF();
+ }
+
+ void DistinctScan::prepareToYield() {
+ ++_commonStats.yields;
+
+ if (isEOF() || INITIALIZING == _scanState) { return; }
+ // We save these so that we know if the cursor moves during the yield. If it moves, we have
+ // to make sure its ending position is valid w.r.t. our bounds.
+ _savedKey = _btreeCursor->getKey().getOwned();
+ _savedLoc = _btreeCursor->getValue();
+ _btreeCursor->savePosition();
+ }
+
+ void DistinctScan::recoverFromYield() {
+ ++_commonStats.unyields;
+
+ if (isEOF() || INITIALIZING == _scanState) { return; }
+
+ // We can have a valid position before we check isEOF(), restore the position, and then be
+ // EOF upon restore.
+ if (!_btreeCursor->restorePosition().isOK() || _btreeCursor->isEOF()) {
+ _scanState = HIT_END;
+ return;
+ }
+
+ if (!_savedKey.binaryEqual(_btreeCursor->getKey()) || _savedLoc != _btreeCursor->getValue()) {
+ // Our restored position might be past endKey, see if we've hit the end.
+ _scanState = CHECKING_END;
+ }
+ }
+
+ void DistinctScan::invalidate(const DiskLoc& dl, InvalidationType type) {
+ ++_commonStats.invalidates;
+ }
+
+ void DistinctScan::checkEnd() {
+ if (isEOF()) {
+ _commonStats.isEOF = true;
+ return;
+ }
+
+ // Use _checker to see how things are.
+ IndexBoundsChecker::KeyState keyState;
+ keyState = _checker->checkKey(_btreeCursor->getKey(),
+ &_keyEltsToUse,
+ &_movePastKeyElts,
+ &_keyElts,
+ &_keyEltsInc);
+
+ if (IndexBoundsChecker::DONE == keyState) {
+ _scanState = HIT_END;
+ return;
+ }
+
+ // This seems weird but it's the old definition of nscanned.
+ ++_specificStats.keysExamined;
+
+ if (IndexBoundsChecker::VALID == keyState) {
+ _scanState = GETTING_NEXT;
+ return;
+ }
+
+ verify(IndexBoundsChecker::MUST_ADVANCE == keyState);
+ _btreeCursor->skip(_btreeCursor->getKey(), _keyEltsToUse, _movePastKeyElts,
+ _keyElts, _keyEltsInc);
+
+ // Must check underlying cursor EOF after every cursor movement.
+ if (_btreeCursor->isEOF()) {
+ _scanState = HIT_END;
+ }
+ }
+
+ PlanStageStats* DistinctScan::getStats() {
+ _commonStats.isEOF = isEOF();
+ auto_ptr<PlanStageStats> ret(new PlanStageStats(_commonStats, STAGE_DISTINCT));
+ ret->specific.reset(new DistinctScanStats(_specificStats));
+ return ret.release();
+ }
+
+} // namespace mongo