/**
* Copyright (C) 2013 10gen 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 .
*/
#include "mongo/db/query/index_bounds.h"
namespace {
// Return a value in the set {-1, 0, 1} to represent the sign of parameter i.
int sgn(int i) {
if (i == 0)
return 0;
return i > 0 ? 1 : -1;
}
} // namespace
namespace mongo {
// For debugging.
string IndexBounds::toString() const {
stringstream ss;
for (size_t i = 0; i < fields.size(); ++i) {
if (i > 0) {
ss << ", ";
}
const OrderedIntervalList& oil = fields[i];
ss << "field #" << i << "['" << oil.name << "']: ";
for (size_t j = 0; j < oil.intervals.size(); ++j) {
const Interval& iv = oil.intervals[j];
if (iv.startInclusive) {
ss << "[";
}
else {
ss << "(";
}
// false means omit the field name
ss << iv.start.toString(false);
ss << ", ";
ss << iv.end.toString(false);
if (iv.endInclusive) {
ss << "]";
}
else {
ss << ")";
}
}
}
return ss.str();
}
//
// Validity checking for bounds
//
bool IndexBounds::isValidFor(const BSONObj& keyPattern, int direction) {
BSONObjIterator it(keyPattern);
for (size_t i = 0; i < fields.size(); ++i) {
// We expect a bound for each field in the index.
if (!it.more()) { return false; }
BSONElement elt = it.next();
const OrderedIntervalList& field = fields[i];
// Make sure the names match up.
if (field.name != elt.fieldName()) { return false; }
// Special indices are all inserted increasing. elt.number() will return 0 if it's
// not a number. Special indices are strings, not numbers.
int expectedOrientation = direction * ((elt.number() >= 0) ? 1 : -1);
// Make sure each interval's start is oriented correctly with respect to its end.
for (size_t j = 0; j < field.intervals.size(); ++j) {
// false means don't consider field name.
int cmp = sgn(field.intervals[j].end.woCompare(field.intervals[j].start, false));
if (cmp == 0 && field.intervals[j].startInclusive
&& field.intervals[j].endInclusive) { continue; }
if (cmp != expectedOrientation) { return false; }
}
// Make sure each interval is oriented correctly with respect to its neighbors.
for (size_t j = 1; j < field.intervals.size(); ++j) {
int cmp = sgn(field.intervals[j].start.woCompare(field.intervals[j - 1].end,
false));
if (cmp == 0) {
// The end of one interval is the start of another. This is only valid if
// they're both open intervals. Otherwise, it should have been combined to form
// one interval.
if (field.intervals[j].startInclusive || field.intervals[j - 1].endInclusive) {
return false;
}
}
else if (cmp != expectedOrientation) {
return false;
}
}
}
return !it.more();
}
//
// Iteration over index bounds
//
IndexBoundsChecker::IndexBoundsChecker(const IndexBounds* bounds, const BSONObj& keyPattern,
int scanDirection)
: _bounds(bounds), _curInterval(bounds->fields.size(), 0) {
BSONObjIterator it(keyPattern);
while (it.more()) {
int indexDirection = it.next().number() >= 0 ? 1 : -1;
_expectedDirection.push_back(indexDirection * scanDirection);
}
}
void IndexBoundsChecker::getStartKey(vector* valueOut,
vector* inclusiveOut) {
for (size_t i = 0; i < _bounds->fields.size(); ++i) {
(*valueOut)[i] = &_bounds->fields[i].intervals[0].start;
(*inclusiveOut)[i] = _bounds->fields[i].intervals[0].startInclusive;
}
}
bool IndexBoundsChecker::findLeftmostProblem(const vector& keyValues,
size_t* where,
Location* what) {
// For each field in the index key, see if it's in the interval it should be.
for (size_t i = 0; i < _curInterval.size(); ++i) {
const OrderedIntervalList& field = _bounds->fields[i];
const Interval& currentInterval = field.intervals[_curInterval[i]];
Location cmp = intervalCmp(currentInterval, keyValues[i], _expectedDirection[i]);
// If it's not in the interval we think it is...
if (0 != cmp) {
*where = i;
*what = cmp;
return true;
}
}
return false;
}
bool IndexBoundsChecker::spaceLeftToAdvance(size_t fieldsToCheck,
const vector& keyValues) {
// Check end conditions. Since we need to move the keys before
// firstNonContainedField forward, let's make sure that those fields are not at the
// end of their bounds.
for (size_t i = 0; i < fieldsToCheck; ++i) {
// Field 'i' isn't at its last interval. There's possibly a key we could move forward
// to, either in the current interval or the next one.
if (_curInterval[i] != _bounds->fields[i].intervals.size() - 1) {
return true;
}
// Field 'i' is at its last interval.
const Interval& ival = _bounds->fields[i].intervals[_curInterval[i]];
// We're OK if it's an open interval. There are an infinite number of keys between any
// key and the end point...
if (!ival.endInclusive) {
return true;
}
// If it's a closed interval, we're fine so long as we haven't hit the end point of
// the interval.
if (-_expectedDirection[i] == sgn(keyValues[i].woCompare(ival.end, false))) {
return true;
}
}
return false;
}
IndexBoundsChecker::KeyState IndexBoundsChecker::checkKey(const BSONObj& key,
int* keyEltsToUse,
bool* movePastKeyElts,
vector* out,
vector* incOut) {
verify(_curInterval.size() > 0);
verify(out->size() == _curInterval.size());
verify(incOut->size() == _curInterval.size());
// It's useful later to go from a field number to the value for that field. Store these.
// TODO: on optimization pass, populate the vector as-needed and keep the vector around as a
// member variable
vector keyValues;
BSONObjIterator keyIt(key);
while (keyIt.more()) {
keyValues.push_back(keyIt.next());
}
verify(keyValues.size() == _curInterval.size());
size_t firstNonContainedField;
Location orientation;
if (!findLeftmostProblem(keyValues, &firstNonContainedField, &orientation)) {
// All fields in the index are within the current interval. Caller can use the key.
return VALID;
}
// Field number 'firstNonContainedField' of the index key is before its current interval.
// Tell the caller to move forward to the start of the current interval.
if (BEHIND == orientation) {
*keyEltsToUse = firstNonContainedField;
*movePastKeyElts = false;
for (size_t j = firstNonContainedField; j < _curInterval.size(); ++j) {
const OrderedIntervalList& oil = _bounds->fields[j];
(*out)[j] = &oil.intervals[_curInterval[j]].start;
(*incOut)[j] = oil.intervals[_curInterval[j]].startInclusive;
}
return MUST_ADVANCE;
}
verify(AHEAD == orientation);
// Field number 'firstNonContainedField' of the index key is after interval we think it's
// in. Fields 0 through 'firstNonContained-1' are within their current intervals and we can
// ignore them.
while (firstNonContainedField < _curInterval.size()) {
// Find the interval that contains our field.
size_t newIntervalForField;
Location where = findIntervalForField(keyValues[firstNonContainedField],
_bounds->fields[firstNonContainedField],
_expectedDirection[firstNonContainedField],
&newIntervalForField);
if (WITHIN == where) {
// Found a new interval for field firstNonContainedField. Move our internal choice
// of interval to that.
_curInterval[firstNonContainedField] = newIntervalForField;
// Let's find valid intervals for fields to the right.
++firstNonContainedField;
}
else if (BEHIND == where) {
// firstNonContained field is between the intervals (newIntervalForField-1) and
// newIntervalForField. We have to tell the caller to move forward until he at
// least hits our new current interval.
_curInterval[firstNonContainedField] = newIntervalForField;
// All other fields to the right start at their first interval.
for (size_t i = firstNonContainedField + 1; i < _curInterval.size(); ++i) {
_curInterval[i] = 0;
}
*keyEltsToUse = firstNonContainedField;
*movePastKeyElts = false;
for (size_t i = firstNonContainedField; i < _curInterval.size(); ++i) {
const OrderedIntervalList& oil = _bounds->fields[i];
(*out)[i] = &oil.intervals[_curInterval[i]].start;
(*incOut)[i] = oil.intervals[_curInterval[i]].startInclusive;
}
return MUST_ADVANCE;
}
else {
verify (AHEAD == where);
// Field number 'firstNonContainedField' cannot possibly be placed into an interval,
// as it is already past its last possible interval. The caller must move forward
// to a key with a greater value for the previous field.
// If all fields to the left have hit the end of their intervals, we can't ask them
// to move forward and we should stop iterating.
if (!spaceLeftToAdvance(firstNonContainedField, keyValues)) {
return DONE;
}
*keyEltsToUse = firstNonContainedField;
*movePastKeyElts = true;
for (size_t i = firstNonContainedField; i < _curInterval.size(); ++i) {
_curInterval[i] = 0;
}
// If movePastKeyElts is true, we don't examine any fields after the keyEltsToUse
// fields of the key. As such we don't populate the out/incOut.
return MUST_ADVANCE;
}
}
verify(firstNonContainedField == _curInterval.size());
return VALID;
}
// static
IndexBoundsChecker::Location IndexBoundsChecker::intervalCmp(const Interval& interval,
const BSONElement& key,
const int expectedDirection) {
int cmp = sgn(key.woCompare(interval.start, false));
bool startOK = (cmp == expectedDirection) || (cmp == 0 && interval.startInclusive);
if (!startOK) { return BEHIND; }
cmp = sgn(key.woCompare(interval.end, false));
bool endOK = (cmp == -expectedDirection) || (cmp == 0 && interval.endInclusive);
if (!endOK) { return AHEAD; }
return WITHIN;
}
// static
IndexBoundsChecker::Location IndexBoundsChecker::findIntervalForField(const BSONElement& elt,
const OrderedIntervalList& oil, const int expectedDirection, size_t* newIntervalIndex) {
for (size_t i = 0; i < oil.intervals.size(); ++i) {
Location where = intervalCmp(oil.intervals[i], elt, expectedDirection);
// Intervals are ordered in the same direction as our keys. The first interval we
// aren't ahead of is the one we're looking for.
if (AHEAD != where) {
*newIntervalIndex = i;
return where;
}
}
// If we're here, we're ahead of all intervals.
return AHEAD;
}
} // namespace mongo