void run() { dblock lk; const char *ns = "unittests.cursortests.BtreeCursorTests.MultiRangeGap"; { DBDirectClient c; for( int i = 0; i < 10; ++i ) c.insert( ns, BSON( "a" << i ) ); for( int i = 100; i < 110; ++i ) c.insert( ns, BSON( "a" << i ) ); ASSERT( c.ensureIndex( ns, BSON( "a" << 1 ) ) ); } BoundList b; b.push_back( pair< BSONObj, BSONObj >( BSON( "" << -50 ), BSON( "" << 2 ) ) ); b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 40 ), BSON( "" << 60 ) ) ); b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 109 ), BSON( "" << 200 ) ) ); Client::Context ctx( ns ); BtreeCursor c( nsdetails( ns ), 1, nsdetails( ns )->idx(1), b, 1 ); ASSERT_EQUALS( "BtreeCursor a_1 multi", c.toString() ); double expected[] = { 0, 1, 2, 109 }; for( int i = 0; i < 4; ++i ) { ASSERT( c.ok() ); ASSERT_EQUALS( expected[ i ], c.currKey().firstElement().number() ); c.advance(); } ASSERT( !c.ok() ); }
void ChunkManager::getShardIdsForQuery(OperationContext* txn, const BSONObj& query, set<ShardId>* shardIds) const { auto statusWithCQ = CanonicalQuery::canonicalize(NamespaceString(_ns), query, ExtensionsCallbackNoop()); uassertStatusOK(statusWithCQ.getStatus()); unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue()); // Query validation if (QueryPlannerCommon::hasNode(cq->root(), MatchExpression::GEO_NEAR)) { uassert(13501, "use geoNear command rather than $near query", false); } // Fast path for targeting equalities on the shard key. auto shardKeyToFind = _keyPattern.extractShardKeyFromQuery(*cq); if (shardKeyToFind.isOK() && !shardKeyToFind.getValue().isEmpty()) { auto chunk = findIntersectingChunk(txn, shardKeyToFind.getValue()); shardIds->insert(chunk->getShardId()); return; } // Transforms query into bounds for each field in the shard key // for example : // Key { a: 1, b: 1 }, // Query { a : { $gte : 1, $lt : 2 }, // b : { $gte : 3, $lt : 4 } } // => Bounds { a : [1, 2), b : [3, 4) } IndexBounds bounds = getIndexBoundsForQuery(_keyPattern.toBSON(), *cq); // Transforms bounds for each shard key field into full shard key ranges // for example : // Key { a : 1, b : 1 } // Bounds { a : [1, 2), b : [3, 4) } // => Ranges { a : 1, b : 3 } => { a : 2, b : 4 } BoundList ranges = _keyPattern.flattenBounds(bounds); for (BoundList::const_iterator it = ranges.begin(); it != ranges.end(); ++it) { getShardIdsForRange(*shardIds, it->first /*min*/, it->second /*max*/); // once we know we need to visit all shards no need to keep looping if (shardIds->size() == _shardIds.size()) break; } // SERVER-4914 Some clients of getShardIdsForQuery() assume at least one shard will be // returned. For now, we satisfy that assumption by adding a shard with no matches rather // than return an empty set of shards. if (shardIds->empty()) { massert(16068, "no chunk ranges available", !_chunkRanges.ranges().empty()); shardIds->insert(_chunkRanges.ranges().begin()->second->getShardId()); } }
void ChunkManager::getShardIdsForQuery(set<ShardId>& shardIds, const BSONObj& query) const { CanonicalQuery* canonicalQuery = NULL; Status status = CanonicalQuery::canonicalize( _ns, query, &canonicalQuery, WhereCallbackNoop()); boost::scoped_ptr<CanonicalQuery> canonicalQueryPtr(canonicalQuery); uassert(status.code(), status.reason(), status.isOK()); // Query validation if (QueryPlannerCommon::hasNode(canonicalQuery->root(), MatchExpression::GEO_NEAR)) { uassert(13501, "use geoNear command rather than $near query", false); } // Transforms query into bounds for each field in the shard key // for example : // Key { a: 1, b: 1 }, // Query { a : { $gte : 1, $lt : 2 }, // b : { $gte : 3, $lt : 4 } } // => Bounds { a : [1, 2), b : [3, 4) } IndexBounds bounds = getIndexBoundsForQuery(_keyPattern.toBSON(), canonicalQuery); // Transforms bounds for each shard key field into full shard key ranges // for example : // Key { a : 1, b : 1 } // Bounds { a : [1, 2), b : [3, 4) } // => Ranges { a : 1, b : 3 } => { a : 2, b : 4 } BoundList ranges = _keyPattern.flattenBounds(bounds); for (BoundList::const_iterator it = ranges.begin(); it != ranges.end(); ++it) { getShardIdsForRange(shardIds, it->first /*min*/, it->second /*max*/); // once we know we need to visit all shards no need to keep looping if(shardIds.size() == _shardIds.size()) break; } // SERVER-4914 Some clients of getShardIdsForQuery() assume at least one shard will be // returned. For now, we satisfy that assumption by adding a shard with no matches rather // than return an empty set of shards. if (shardIds.empty()) { massert( 16068, "no chunk ranges available", !_chunkRanges.ranges().empty() ); shardIds.insert(_chunkRanges.ranges().begin()->second->getShardId()); } }
void run() { dblock lk; const char *ns = "unittests.cursortests.BtreeCursorTests.MultiRangeReverse"; { DBDirectClient c; for( int i = 0; i < 10; ++i ) c.insert( ns, BSON( "a" << i ) ); ASSERT( c.ensureIndex( ns, BSON( "a" << 1 ) ) ); } BoundList b; b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 6 ), BSON( "" << 4 ) ) ); b.push_back( pair< BSONObj, BSONObj >( BSON( "" << 2 ), BSON( "" << 1 ) ) ); setClient( ns ); BtreeCursor c( nsdetails( ns ), 1, nsdetails( ns )->indexes[ 1 ], b, -1 ); ASSERT_EQUALS( "BtreeCursor a_1 reverse multi", c.toString() ); double expected[] = { 6, 5, 4, 2, 1 }; for( int i = 0; i < 5; ++i ) { ASSERT( c.ok() ); ASSERT_EQUALS( expected[ i ], c.currKey().firstElement().number() ); c.advance(); } ASSERT( !c.ok() ); }
BoundList ShardKeyPattern::flattenBounds(const IndexBounds& indexBounds) const { invariant(indexBounds.fields.size() == (size_t)_keyPattern.toBSON().nFields()); // If any field is unsatisfied, return empty bound list. for (vector<OrderedIntervalList>::const_iterator it = indexBounds.fields.begin(); it != indexBounds.fields.end(); it++) { if (it->intervals.size() == 0) { return BoundList(); } } // To construct our bounds we will generate intervals based on bounds for // the first field, then compound intervals based on constraints for the first // 2 fields, then compound intervals for the first 3 fields, etc. // As we loop through the fields, we start generating new intervals that will later // get extended in another iteration of the loop. We define these partially constructed // intervals using pairs of BSONObjBuilders (shared_ptrs, since after one iteration of the // loop they still must exist outside their scope). typedef vector<pair<shared_ptr<BSONObjBuilder>, shared_ptr<BSONObjBuilder>>> BoundBuilders; BoundBuilders builders; builders.push_back(make_pair(shared_ptr<BSONObjBuilder>(new BSONObjBuilder()), shared_ptr<BSONObjBuilder>(new BSONObjBuilder()))); BSONObjIterator keyIter(_keyPattern.toBSON()); // until equalityOnly is false, we are just dealing with equality (no range or $in queries). bool equalityOnly = true; for (size_t i = 0; i < indexBounds.fields.size(); i++) { BSONElement e = keyIter.next(); StringData fieldName = e.fieldNameStringData(); // get the relevant intervals for this field, but we may have to transform the // list of what's relevant according to the expression for this field const OrderedIntervalList& oil = indexBounds.fields[i]; const vector<Interval>& intervals = oil.intervals; if (equalityOnly) { if (intervals.size() == 1 && intervals.front().isPoint()) { // this field is only a single point-interval BoundBuilders::const_iterator j; for (j = builders.begin(); j != builders.end(); ++j) { j->first->appendAs(intervals.front().start, fieldName); j->second->appendAs(intervals.front().end, fieldName); } } else { // This clause is the first to generate more than a single point. // We only execute this clause once. After that, we simplify the bound // extensions to prevent combinatorial explosion. equalityOnly = false; BoundBuilders newBuilders; for (BoundBuilders::const_iterator it = builders.begin(); it != builders.end(); ++it) { BSONObj first = it->first->obj(); BSONObj second = it->second->obj(); for (vector<Interval>::const_iterator interval = intervals.begin(); interval != intervals.end(); ++interval) { uassert(17439, "combinatorial limit of $in partitioning of results exceeded", newBuilders.size() < kMaxFlattenedInCombinations); newBuilders.push_back( // make_pair(shared_ptr<BSONObjBuilder>(new BSONObjBuilder()), shared_ptr<BSONObjBuilder>(new BSONObjBuilder()))); newBuilders.back().first->appendElements(first); newBuilders.back().second->appendElements(second); newBuilders.back().first->appendAs(interval->start, fieldName); newBuilders.back().second->appendAs(interval->end, fieldName); } } builders = newBuilders; } } else { // if we've already generated a range or multiple point-intervals // just extend what we've generated with min/max bounds for this field BoundBuilders::const_iterator j; for (j = builders.begin(); j != builders.end(); ++j) { j->first->appendAs(intervals.front().start, fieldName); j->second->appendAs(intervals.back().end, fieldName); } } } BoundList ret; for (BoundBuilders::const_iterator i = builders.begin(); i != builders.end(); ++i) ret.push_back(make_pair(i->first->obj(), i->second->obj())); return ret; }