diff options
Diffstat (limited to 'src/mongo/dbtests/query_stage_merge_sort.cpp')
-rw-r--r-- | src/mongo/dbtests/query_stage_merge_sort.cpp | 1085 |
1 files changed, 556 insertions, 529 deletions
diff --git a/src/mongo/dbtests/query_stage_merge_sort.cpp b/src/mongo/dbtests/query_stage_merge_sort.cpp index b05799b8588..884ec1bda55 100644 --- a/src/mongo/dbtests/query_stage_merge_sort.cpp +++ b/src/mongo/dbtests/query_stage_merge_sort.cpp @@ -46,541 +46,567 @@ namespace QueryStageMergeSortTests { - using std::unique_ptr; - using std::set; - using std::string; - - class QueryStageMergeSortTestBase { - public: - QueryStageMergeSortTestBase() : _client(&_txn) { - +using std::unique_ptr; +using std::set; +using std::string; + +class QueryStageMergeSortTestBase { +public: + QueryStageMergeSortTestBase() : _client(&_txn) {} + + virtual ~QueryStageMergeSortTestBase() { + OldClientWriteContext ctx(&_txn, ns()); + _client.dropCollection(ns()); + } + + void addIndex(const BSONObj& obj) { + ASSERT_OK(dbtests::createIndex(&_txn, ns(), obj)); + } + + IndexDescriptor* getIndex(const BSONObj& obj, Collection* coll) { + return coll->getIndexCatalog()->findIndexByKeyPattern(&_txn, obj); + } + + void insert(const BSONObj& obj) { + _client.insert(ns(), obj); + } + + void remove(const BSONObj& obj) { + _client.remove(ns(), obj); + } + + void getLocs(set<RecordId>* out, Collection* coll) { + auto cursor = coll->getCursor(&_txn); + while (auto record = cursor->next()) { + out->insert(record->id); } - - virtual ~QueryStageMergeSortTestBase() { - OldClientWriteContext ctx(&_txn, ns()); - _client.dropCollection(ns()); + } + + BSONObj objWithMinKey(int start) { + BSONObjBuilder startKeyBob; + startKeyBob.append("", start); + startKeyBob.appendMinKey(""); + return startKeyBob.obj(); + } + + BSONObj objWithMaxKey(int start) { + BSONObjBuilder endKeyBob; + endKeyBob.append("", start); + endKeyBob.appendMaxKey(""); + return endKeyBob.obj(); + } + + static const char* ns() { + return "unittests.QueryStageMergeSort"; + } + +protected: + OperationContextImpl _txn; + +private: + DBDirectClient _client; +}; + +// SERVER-1205: +// find($or[{a:1}, {b:1}]).sort({c:1}) with indices {a:1, c:1} and {b:1, c:1}. +class QueryStageMergeSortPrefixIndex : public QueryStageMergeSortTestBase { +public: + void run() { + OldClientWriteContext ctx(&_txn, ns()); + Database* db = ctx.db(); + Collection* coll = db->getCollection(ns()); + if (!coll) { + WriteUnitOfWork wuow(&_txn); + coll = db->createCollection(&_txn, ns()); + wuow.commit(); } - void addIndex(const BSONObj& obj) { - ASSERT_OK(dbtests::createIndex(&_txn, ns(), obj)); - } + const int N = 50; - IndexDescriptor* getIndex(const BSONObj& obj, Collection* coll) { - return coll->getIndexCatalog()->findIndexByKeyPattern( &_txn, obj ); + for (int i = 0; i < N; ++i) { + insert(BSON("a" << 1 << "c" << i)); + insert(BSON("b" << 1 << "c" << i)); } - void insert(const BSONObj& obj) { - _client.insert(ns(), obj); + BSONObj firstIndex = BSON("a" << 1 << "c" << 1); + BSONObj secondIndex = BSON("b" << 1 << "c" << 1); + + addIndex(firstIndex); + addIndex(secondIndex); + + WorkingSet* ws = new WorkingSet(); + // Sort by c:1 + MergeSortStageParams msparams; + msparams.pattern = BSON("c" << 1); + MergeSortStage* ms = new MergeSortStage(msparams, ws, coll); + + // a:1 + IndexScanParams params; + params.descriptor = getIndex(firstIndex, coll); + params.bounds.isSimpleRange = true; + params.bounds.startKey = objWithMinKey(1); + params.bounds.endKey = objWithMaxKey(1); + params.bounds.endKeyInclusive = true; + params.direction = 1; + ms->addChild(new IndexScan(&_txn, params, ws, NULL)); + + // b:1 + params.descriptor = getIndex(secondIndex, coll); + ms->addChild(new IndexScan(&_txn, params, ws, NULL)); + + // Must fetch if we want to easily pull out an obj. + PlanExecutor* rawExec; + Status status = PlanExecutor::make(&_txn, + ws, + new FetchStage(&_txn, ws, ms, NULL, coll), + coll, + PlanExecutor::YIELD_MANUAL, + &rawExec); + ASSERT_OK(status); + std::unique_ptr<PlanExecutor> exec(rawExec); + + for (int i = 0; i < N; ++i) { + BSONObj first, second; + ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&first, NULL)); + ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&second, NULL)); + ASSERT_EQUALS(first["c"].numberInt(), second["c"].numberInt()); + ASSERT_EQUALS(i, first["c"].numberInt()); + ASSERT((first.hasField("a") && second.hasField("b")) || + (first.hasField("b") && second.hasField("a"))); } - void remove(const BSONObj& obj) { - _client.remove(ns(), obj); + // Should be done now. + BSONObj foo; + ASSERT_NOT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&foo, NULL)); + } +}; + +// Each inserted document appears in both indices but is deduped and returned properly/sorted. +class QueryStageMergeSortDups : public QueryStageMergeSortTestBase { +public: + void run() { + OldClientWriteContext ctx(&_txn, ns()); + Database* db = ctx.db(); + Collection* coll = db->getCollection(ns()); + if (!coll) { + WriteUnitOfWork wuow(&_txn); + coll = db->createCollection(&_txn, ns()); + wuow.commit(); } - void getLocs(set<RecordId>* out, Collection* coll) { - auto cursor = coll->getCursor(&_txn); - while (auto record = cursor->next()) { - out->insert(record->id); - } - } + const int N = 50; - BSONObj objWithMinKey(int start) { - BSONObjBuilder startKeyBob; - startKeyBob.append("", start); - startKeyBob.appendMinKey(""); - return startKeyBob.obj(); + for (int i = 0; i < N; ++i) { + insert(BSON("a" << 1 << "b" << 1 << "c" << i)); + insert(BSON("a" << 1 << "b" << 1 << "c" << i)); } - BSONObj objWithMaxKey(int start) { - BSONObjBuilder endKeyBob; - endKeyBob.append("", start); - endKeyBob.appendMaxKey(""); - return endKeyBob.obj(); + BSONObj firstIndex = BSON("a" << 1 << "c" << 1); + BSONObj secondIndex = BSON("b" << 1 << "c" << 1); + + addIndex(firstIndex); + addIndex(secondIndex); + + WorkingSet* ws = new WorkingSet(); + // Sort by c:1 + MergeSortStageParams msparams; + msparams.pattern = BSON("c" << 1); + MergeSortStage* ms = new MergeSortStage(msparams, ws, coll); + + // a:1 + IndexScanParams params; + params.descriptor = getIndex(firstIndex, coll); + params.bounds.isSimpleRange = true; + params.bounds.startKey = objWithMinKey(1); + params.bounds.endKey = objWithMaxKey(1); + params.bounds.endKeyInclusive = true; + params.direction = 1; + ms->addChild(new IndexScan(&_txn, params, ws, NULL)); + + // b:1 + params.descriptor = getIndex(secondIndex, coll); + ms->addChild(new IndexScan(&_txn, params, ws, NULL)); + + PlanExecutor* rawExec; + Status status = PlanExecutor::make(&_txn, + ws, + new FetchStage(&_txn, ws, ms, NULL, coll), + coll, + PlanExecutor::YIELD_MANUAL, + &rawExec); + ASSERT_OK(status); + std::unique_ptr<PlanExecutor> exec(rawExec); + + for (int i = 0; i < N; ++i) { + BSONObj first, second; + ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&first, NULL)); + ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&second, NULL)); + ASSERT_EQUALS(first["c"].numberInt(), second["c"].numberInt()); + ASSERT_EQUALS(i, first["c"].numberInt()); + ASSERT((first.hasField("a") && second.hasField("b")) || + (first.hasField("b") && second.hasField("a"))); } - static const char* ns() { return "unittests.QueryStageMergeSort"; } - - protected: - OperationContextImpl _txn; - - private: - DBDirectClient _client; - }; - - // SERVER-1205: - // find($or[{a:1}, {b:1}]).sort({c:1}) with indices {a:1, c:1} and {b:1, c:1}. - class QueryStageMergeSortPrefixIndex : public QueryStageMergeSortTestBase { - public: - void run() { - OldClientWriteContext ctx(&_txn, ns()); - Database* db = ctx.db(); - Collection* coll = db->getCollection(ns()); - if (!coll) { - WriteUnitOfWork wuow(&_txn); - coll = db->createCollection(&_txn, ns()); - wuow.commit(); - } - - const int N = 50; - - for (int i = 0; i < N; ++i) { - insert(BSON("a" << 1 << "c" << i)); - insert(BSON("b" << 1 << "c" << i)); - } - - BSONObj firstIndex = BSON("a" << 1 << "c" << 1); - BSONObj secondIndex = BSON("b" << 1 << "c" << 1); - - addIndex(firstIndex); - addIndex(secondIndex); - - WorkingSet* ws = new WorkingSet(); - // Sort by c:1 - MergeSortStageParams msparams; - msparams.pattern = BSON("c" << 1); - MergeSortStage* ms = new MergeSortStage(msparams, ws, coll); - - // a:1 - IndexScanParams params; - params.descriptor = getIndex(firstIndex, coll); - params.bounds.isSimpleRange = true; - params.bounds.startKey = objWithMinKey(1); - params.bounds.endKey = objWithMaxKey(1); - params.bounds.endKeyInclusive = true; - params.direction = 1; - ms->addChild(new IndexScan(&_txn, params, ws, NULL)); - - // b:1 - params.descriptor = getIndex(secondIndex, coll); - ms->addChild(new IndexScan(&_txn, params, ws, NULL)); - - // Must fetch if we want to easily pull out an obj. - PlanExecutor* rawExec; - Status status = PlanExecutor::make(&_txn, ws, new FetchStage(&_txn, ws, ms, NULL, coll), - coll, PlanExecutor::YIELD_MANUAL, &rawExec); - ASSERT_OK(status); - std::unique_ptr<PlanExecutor> exec(rawExec); - - for (int i = 0; i < N; ++i) { - BSONObj first, second; - ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&first, NULL)); - ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&second, NULL)); - ASSERT_EQUALS(first["c"].numberInt(), second["c"].numberInt()); - ASSERT_EQUALS(i, first["c"].numberInt()); - ASSERT((first.hasField("a") && second.hasField("b")) - || (first.hasField("b") && second.hasField("a"))); - } - - // Should be done now. - BSONObj foo; - ASSERT_NOT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&foo, NULL)); + // Should be done now. + BSONObj foo; + ASSERT_EQUALS(PlanExecutor::IS_EOF, exec->getNext(&foo, NULL)); + } +}; + +// Each inserted document appears in both indices, no deduping, get each result twice. +class QueryStageMergeSortDupsNoDedup : public QueryStageMergeSortTestBase { +public: + void run() { + OldClientWriteContext ctx(&_txn, ns()); + Database* db = ctx.db(); + Collection* coll = db->getCollection(ns()); + if (!coll) { + WriteUnitOfWork wuow(&_txn); + coll = db->createCollection(&_txn, ns()); + wuow.commit(); } - }; - - // Each inserted document appears in both indices but is deduped and returned properly/sorted. - class QueryStageMergeSortDups : public QueryStageMergeSortTestBase { - public: - void run() { - OldClientWriteContext ctx(&_txn, ns()); - Database* db = ctx.db(); - Collection* coll = db->getCollection(ns()); - if (!coll) { - WriteUnitOfWork wuow(&_txn); - coll = db->createCollection(&_txn, ns()); - wuow.commit(); - } - - const int N = 50; - - for (int i = 0; i < N; ++i) { - insert(BSON("a" << 1 << "b" << 1 << "c" << i)); - insert(BSON("a" << 1 << "b" << 1 << "c" << i)); - } - - BSONObj firstIndex = BSON("a" << 1 << "c" << 1); - BSONObj secondIndex = BSON("b" << 1 << "c" << 1); - - addIndex(firstIndex); - addIndex(secondIndex); - - WorkingSet* ws = new WorkingSet(); - // Sort by c:1 - MergeSortStageParams msparams; - msparams.pattern = BSON("c" << 1); - MergeSortStage* ms = new MergeSortStage(msparams, ws, coll); - - // a:1 - IndexScanParams params; - params.descriptor = getIndex(firstIndex, coll); - params.bounds.isSimpleRange = true; - params.bounds.startKey = objWithMinKey(1); - params.bounds.endKey = objWithMaxKey(1); - params.bounds.endKeyInclusive = true; - params.direction = 1; - ms->addChild(new IndexScan(&_txn, params, ws, NULL)); - // b:1 - params.descriptor = getIndex(secondIndex, coll); - ms->addChild(new IndexScan(&_txn, params, ws, NULL)); - - PlanExecutor* rawExec; - Status status = PlanExecutor::make(&_txn, ws, new FetchStage(&_txn, ws, ms, NULL, coll), - coll, PlanExecutor::YIELD_MANUAL, &rawExec); - ASSERT_OK(status); - std::unique_ptr<PlanExecutor> exec(rawExec); - - for (int i = 0; i < N; ++i) { - BSONObj first, second; - ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&first, NULL)); - ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&second, NULL)); - ASSERT_EQUALS(first["c"].numberInt(), second["c"].numberInt()); - ASSERT_EQUALS(i, first["c"].numberInt()); - ASSERT((first.hasField("a") && second.hasField("b")) - || (first.hasField("b") && second.hasField("a"))); - } + const int N = 50; - // Should be done now. - BSONObj foo; - ASSERT_EQUALS(PlanExecutor::IS_EOF, exec->getNext(&foo, NULL)); + for (int i = 0; i < N; ++i) { + insert(BSON("a" << 1 << "b" << 1 << "c" << i)); } - }; - - // Each inserted document appears in both indices, no deduping, get each result twice. - class QueryStageMergeSortDupsNoDedup : public QueryStageMergeSortTestBase { - public: - void run() { - OldClientWriteContext ctx(&_txn, ns()); - Database* db = ctx.db(); - Collection* coll = db->getCollection(ns()); - if (!coll) { - WriteUnitOfWork wuow(&_txn); - coll = db->createCollection(&_txn, ns()); - wuow.commit(); - } - - const int N = 50; - - for (int i = 0; i < N; ++i) { - insert(BSON("a" << 1 << "b" << 1 << "c" << i)); - } - - BSONObj firstIndex = BSON("a" << 1 << "c" << 1); - BSONObj secondIndex = BSON("b" << 1 << "c" << 1); - - addIndex(firstIndex); - addIndex(secondIndex); - - WorkingSet* ws = new WorkingSet(); - // Sort by c:1 - MergeSortStageParams msparams; - msparams.dedup = false; - msparams.pattern = BSON("c" << 1); - MergeSortStage* ms = new MergeSortStage(msparams, ws, coll); - - // a:1 - IndexScanParams params; - params.descriptor = getIndex(firstIndex, coll); - params.bounds.isSimpleRange = true; - params.bounds.startKey = objWithMinKey(1); - params.bounds.endKey = objWithMaxKey(1); - params.bounds.endKeyInclusive = true; - params.direction = 1; - ms->addChild(new IndexScan(&_txn, params, ws, NULL)); - - // b:1 - params.descriptor = getIndex(secondIndex, coll); - ms->addChild(new IndexScan(&_txn, params, ws, NULL)); - PlanExecutor* rawExec; - Status status = PlanExecutor::make(&_txn, ws, new FetchStage(&_txn, ws, ms, NULL, coll), - coll, PlanExecutor::YIELD_MANUAL, &rawExec); - ASSERT_OK(status); - std::unique_ptr<PlanExecutor> exec(rawExec); - - for (int i = 0; i < N; ++i) { - BSONObj first, second; - // We inserted N objects but we get 2 * N from the runner because of dups. - ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&first, NULL)); - ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&second, NULL)); - ASSERT_EQUALS(first["c"].numberInt(), second["c"].numberInt()); - ASSERT_EQUALS(i, first["c"].numberInt()); - ASSERT((first.hasField("a") && second.hasField("b")) - || (first.hasField("b") && second.hasField("a"))); - } + BSONObj firstIndex = BSON("a" << 1 << "c" << 1); + BSONObj secondIndex = BSON("b" << 1 << "c" << 1); + + addIndex(firstIndex); + addIndex(secondIndex); + + WorkingSet* ws = new WorkingSet(); + // Sort by c:1 + MergeSortStageParams msparams; + msparams.dedup = false; + msparams.pattern = BSON("c" << 1); + MergeSortStage* ms = new MergeSortStage(msparams, ws, coll); + + // a:1 + IndexScanParams params; + params.descriptor = getIndex(firstIndex, coll); + params.bounds.isSimpleRange = true; + params.bounds.startKey = objWithMinKey(1); + params.bounds.endKey = objWithMaxKey(1); + params.bounds.endKeyInclusive = true; + params.direction = 1; + ms->addChild(new IndexScan(&_txn, params, ws, NULL)); + + // b:1 + params.descriptor = getIndex(secondIndex, coll); + ms->addChild(new IndexScan(&_txn, params, ws, NULL)); + + PlanExecutor* rawExec; + Status status = PlanExecutor::make(&_txn, + ws, + new FetchStage(&_txn, ws, ms, NULL, coll), + coll, + PlanExecutor::YIELD_MANUAL, + &rawExec); + ASSERT_OK(status); + std::unique_ptr<PlanExecutor> exec(rawExec); + + for (int i = 0; i < N; ++i) { + BSONObj first, second; + // We inserted N objects but we get 2 * N from the runner because of dups. + ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&first, NULL)); + ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&second, NULL)); + ASSERT_EQUALS(first["c"].numberInt(), second["c"].numberInt()); + ASSERT_EQUALS(i, first["c"].numberInt()); + ASSERT((first.hasField("a") && second.hasField("b")) || + (first.hasField("b") && second.hasField("a"))); + } - // Should be done now. - BSONObj foo; - ASSERT_EQUALS(PlanExecutor::IS_EOF, exec->getNext(&foo, NULL)); + // Should be done now. + BSONObj foo; + ASSERT_EQUALS(PlanExecutor::IS_EOF, exec->getNext(&foo, NULL)); + } +}; + +// Decreasing indices merged ok. Basically the test above but decreasing. +class QueryStageMergeSortPrefixIndexReverse : public QueryStageMergeSortTestBase { +public: + void run() { + OldClientWriteContext ctx(&_txn, ns()); + Database* db = ctx.db(); + Collection* coll = db->getCollection(ns()); + if (!coll) { + WriteUnitOfWork wuow(&_txn); + coll = db->createCollection(&_txn, ns()); + wuow.commit(); } - }; - - // Decreasing indices merged ok. Basically the test above but decreasing. - class QueryStageMergeSortPrefixIndexReverse : public QueryStageMergeSortTestBase { - public: - void run() { - OldClientWriteContext ctx(&_txn, ns()); - Database* db = ctx.db(); - Collection* coll = db->getCollection(ns()); - if (!coll) { - WriteUnitOfWork wuow(&_txn); - coll = db->createCollection(&_txn, ns()); - wuow.commit(); - } - const int N = 50; + const int N = 50; - for (int i = 0; i < N; ++i) { - // We insert a:1 c:i for i=0..49 but in reverse order for the heck of it. - insert(BSON("a" << 1 << "c" << N - i - 1)); - insert(BSON("b" << 1 << "c" << i)); - } + for (int i = 0; i < N; ++i) { + // We insert a:1 c:i for i=0..49 but in reverse order for the heck of it. + insert(BSON("a" << 1 << "c" << N - i - 1)); + insert(BSON("b" << 1 << "c" << i)); + } - BSONObj firstIndex = BSON("a" << 1 << "c" << -1); - BSONObj secondIndex = BSON("b" << 1 << "c" << -1); - - addIndex(firstIndex); - addIndex(secondIndex); - - WorkingSet* ws = new WorkingSet(); - // Sort by c:-1 - MergeSortStageParams msparams; - msparams.pattern = BSON("c" << -1); - MergeSortStage* ms = new MergeSortStage(msparams, ws, coll); - - // a:1 - IndexScanParams params; - params.descriptor = getIndex(firstIndex, coll); - params.bounds.isSimpleRange = true; - params.bounds.startKey = objWithMaxKey(1); - params.bounds.endKey = objWithMinKey(1); - params.bounds.endKeyInclusive = true; - // This is the direction along the index. - params.direction = 1; - ms->addChild(new IndexScan(&_txn, params, ws, NULL)); + BSONObj firstIndex = BSON("a" << 1 << "c" << -1); + BSONObj secondIndex = BSON("b" << 1 << "c" << -1); + + addIndex(firstIndex); + addIndex(secondIndex); + + WorkingSet* ws = new WorkingSet(); + // Sort by c:-1 + MergeSortStageParams msparams; + msparams.pattern = BSON("c" << -1); + MergeSortStage* ms = new MergeSortStage(msparams, ws, coll); + + // a:1 + IndexScanParams params; + params.descriptor = getIndex(firstIndex, coll); + params.bounds.isSimpleRange = true; + params.bounds.startKey = objWithMaxKey(1); + params.bounds.endKey = objWithMinKey(1); + params.bounds.endKeyInclusive = true; + // This is the direction along the index. + params.direction = 1; + ms->addChild(new IndexScan(&_txn, params, ws, NULL)); + + // b:1 + params.descriptor = getIndex(secondIndex, coll); + ms->addChild(new IndexScan(&_txn, params, ws, NULL)); + + PlanExecutor* rawExec; + Status status = PlanExecutor::make(&_txn, + ws, + new FetchStage(&_txn, ws, ms, NULL, coll), + coll, + PlanExecutor::YIELD_MANUAL, + &rawExec); + ASSERT_OK(status); + std::unique_ptr<PlanExecutor> exec(rawExec); + + for (int i = 0; i < N; ++i) { + BSONObj first, second; + ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&first, NULL)); + ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&second, NULL)); + ASSERT_EQUALS(first["c"].numberInt(), second["c"].numberInt()); + ASSERT_EQUALS(N - i - 1, first["c"].numberInt()); + ASSERT((first.hasField("a") && second.hasField("b")) || + (first.hasField("b") && second.hasField("a"))); + } - // b:1 - params.descriptor = getIndex(secondIndex, coll); - ms->addChild(new IndexScan(&_txn, params, ws, NULL)); + // Should be done now. + BSONObj foo; + ASSERT_EQUALS(PlanExecutor::IS_EOF, exec->getNext(&foo, NULL)); + } +}; + +// One stage EOF immediately +class QueryStageMergeSortOneStageEOF : public QueryStageMergeSortTestBase { +public: + void run() { + OldClientWriteContext ctx(&_txn, ns()); + Database* db = ctx.db(); + Collection* coll = db->getCollection(ns()); + if (!coll) { + WriteUnitOfWork wuow(&_txn); + coll = db->createCollection(&_txn, ns()); + wuow.commit(); + } - PlanExecutor* rawExec; - Status status = PlanExecutor::make(&_txn, ws, new FetchStage(&_txn, ws, ms, NULL, coll), - coll, PlanExecutor::YIELD_MANUAL, &rawExec); - ASSERT_OK(status); - std::unique_ptr<PlanExecutor> exec(rawExec); - - for (int i = 0; i < N; ++i) { - BSONObj first, second; - ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&first, NULL)); - ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&second, NULL)); - ASSERT_EQUALS(first["c"].numberInt(), second["c"].numberInt()); - ASSERT_EQUALS(N - i - 1, first["c"].numberInt()); - ASSERT((first.hasField("a") && second.hasField("b")) - || (first.hasField("b") && second.hasField("a"))); - } + const int N = 50; - // Should be done now. - BSONObj foo; - ASSERT_EQUALS(PlanExecutor::IS_EOF, exec->getNext(&foo, NULL)); + for (int i = 0; i < N; ++i) { + insert(BSON("a" << 1 << "c" << i)); + insert(BSON("b" << 1 << "c" << i)); } - }; - - // One stage EOF immediately - class QueryStageMergeSortOneStageEOF : public QueryStageMergeSortTestBase { - public: - void run() { - OldClientWriteContext ctx(&_txn, ns()); - Database* db = ctx.db(); - Collection* coll = db->getCollection(ns()); - if (!coll) { - WriteUnitOfWork wuow(&_txn); - coll = db->createCollection(&_txn, ns()); - wuow.commit(); - } - - const int N = 50; - for (int i = 0; i < N; ++i) { - insert(BSON("a" << 1 << "c" << i)); - insert(BSON("b" << 1 << "c" << i)); - } + BSONObj firstIndex = BSON("a" << 1 << "c" << 1); + BSONObj secondIndex = BSON("b" << 1 << "c" << 1); + + addIndex(firstIndex); + addIndex(secondIndex); + + WorkingSet* ws = new WorkingSet(); + // Sort by c:1 + MergeSortStageParams msparams; + msparams.pattern = BSON("c" << 1); + MergeSortStage* ms = new MergeSortStage(msparams, ws, coll); + + // a:1 + IndexScanParams params; + params.descriptor = getIndex(firstIndex, coll); + params.bounds.isSimpleRange = true; + params.bounds.startKey = objWithMinKey(1); + params.bounds.endKey = objWithMaxKey(1); + params.bounds.endKeyInclusive = true; + params.direction = 1; + ms->addChild(new IndexScan(&_txn, params, ws, NULL)); + + // b:51 (EOF) + params.descriptor = getIndex(secondIndex, coll); + params.bounds.startKey = BSON("" << 51 << "" << MinKey); + params.bounds.endKey = BSON("" << 51 << "" << MaxKey); + ms->addChild(new IndexScan(&_txn, params, ws, NULL)); + + PlanExecutor* rawExec; + Status status = PlanExecutor::make(&_txn, + ws, + new FetchStage(&_txn, ws, ms, NULL, coll), + coll, + PlanExecutor::YIELD_MANUAL, + &rawExec); + ASSERT_OK(status); + std::unique_ptr<PlanExecutor> exec(rawExec); + + // Only getting results from the a:1 index scan. + for (int i = 0; i < N; ++i) { + BSONObj obj; + ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&obj, NULL)); + ASSERT_EQUALS(i, obj["c"].numberInt()); + ASSERT_EQUALS(1, obj["a"].numberInt()); + } - BSONObj firstIndex = BSON("a" << 1 << "c" << 1); - BSONObj secondIndex = BSON("b" << 1 << "c" << 1); - - addIndex(firstIndex); - addIndex(secondIndex); - - WorkingSet* ws = new WorkingSet(); - // Sort by c:1 - MergeSortStageParams msparams; - msparams.pattern = BSON("c" << 1); - MergeSortStage* ms = new MergeSortStage(msparams, ws, coll); - - // a:1 - IndexScanParams params; - params.descriptor = getIndex(firstIndex, coll); - params.bounds.isSimpleRange = true; - params.bounds.startKey = objWithMinKey(1); - params.bounds.endKey = objWithMaxKey(1); - params.bounds.endKeyInclusive = true; - params.direction = 1; - ms->addChild(new IndexScan(&_txn, params, ws, NULL)); + // Should be done now. + BSONObj foo; + ASSERT_EQUALS(PlanExecutor::IS_EOF, exec->getNext(&foo, NULL)); + } +}; + +// N stages each have 1 result +class QueryStageMergeSortManyShort : public QueryStageMergeSortTestBase { +public: + void run() { + OldClientWriteContext ctx(&_txn, ns()); + Database* db = ctx.db(); + Collection* coll = db->getCollection(ns()); + if (!coll) { + WriteUnitOfWork wuow(&_txn); + coll = db->createCollection(&_txn, ns()); + wuow.commit(); + } - // b:51 (EOF) - params.descriptor = getIndex(secondIndex, coll); - params.bounds.startKey = BSON("" << 51 << "" << MinKey); - params.bounds.endKey = BSON("" << 51 << "" << MaxKey); + WorkingSet* ws = new WorkingSet(); + // Sort by foo:1 + MergeSortStageParams msparams; + msparams.pattern = BSON("foo" << 1); + MergeSortStage* ms = new MergeSortStage(msparams, ws, coll); + + IndexScanParams params; + params.bounds.isSimpleRange = true; + params.bounds.startKey = objWithMinKey(1); + params.bounds.endKey = objWithMaxKey(1); + params.bounds.endKeyInclusive = true; + params.direction = 1; + + int numIndices = 20; + for (int i = 0; i < numIndices; ++i) { + // 'a', 'b', ... + string index(1, 'a' + i); + insert(BSON(index << 1 << "foo" << i)); + + BSONObj indexSpec = BSON(index << 1 << "foo" << 1); + addIndex(indexSpec); + params.descriptor = getIndex(indexSpec, coll); ms->addChild(new IndexScan(&_txn, params, ws, NULL)); - - PlanExecutor* rawExec; - Status status = PlanExecutor::make(&_txn, ws, new FetchStage(&_txn, ws, ms, NULL, coll), - coll, PlanExecutor::YIELD_MANUAL, &rawExec); - ASSERT_OK(status); - std::unique_ptr<PlanExecutor> exec(rawExec); - - // Only getting results from the a:1 index scan. - for (int i = 0; i < N; ++i) { - BSONObj obj; - ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&obj, NULL)); - ASSERT_EQUALS(i, obj["c"].numberInt()); - ASSERT_EQUALS(1, obj["a"].numberInt()); - } - - // Should be done now. - BSONObj foo; - ASSERT_EQUALS(PlanExecutor::IS_EOF, exec->getNext(&foo, NULL)); } - }; - - // N stages each have 1 result - class QueryStageMergeSortManyShort : public QueryStageMergeSortTestBase { - public: - void run() { - OldClientWriteContext ctx(&_txn, ns()); - Database* db = ctx.db(); - Collection* coll = db->getCollection(ns()); - if (!coll) { - WriteUnitOfWork wuow(&_txn); - coll = db->createCollection(&_txn, ns()); - wuow.commit(); - } - WorkingSet* ws = new WorkingSet(); - // Sort by foo:1 - MergeSortStageParams msparams; - msparams.pattern = BSON("foo" << 1); - MergeSortStage* ms = new MergeSortStage(msparams, ws, coll); - - IndexScanParams params; - params.bounds.isSimpleRange = true; - params.bounds.startKey = objWithMinKey(1); - params.bounds.endKey = objWithMaxKey(1); - params.bounds.endKeyInclusive = true; - params.direction = 1; - - int numIndices = 20; - for (int i = 0; i < numIndices; ++i) { - // 'a', 'b', ... - string index(1, 'a' + i); - insert(BSON(index << 1 << "foo" << i)); - - BSONObj indexSpec = BSON(index << 1 << "foo" << 1); - addIndex(indexSpec); - params.descriptor = getIndex(indexSpec, coll); - ms->addChild(new IndexScan(&_txn, params, ws, NULL)); - } - - PlanExecutor* rawExec; - Status status = PlanExecutor::make(&_txn, ws, new FetchStage(&_txn, ws, ms, NULL, coll), - coll, PlanExecutor::YIELD_MANUAL, &rawExec); - ASSERT_OK(status); - std::unique_ptr<PlanExecutor> exec(rawExec); - - for (int i = 0; i < numIndices; ++i) { - BSONObj obj; - ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&obj, NULL)); - ASSERT_EQUALS(i, obj["foo"].numberInt()); - string index(1, 'a' + i); - ASSERT_EQUALS(1, obj[index].numberInt()); - } - - // Should be done now. - BSONObj foo; - ASSERT_EQUALS(PlanExecutor::IS_EOF, exec->getNext(&foo, NULL)); + PlanExecutor* rawExec; + Status status = PlanExecutor::make(&_txn, + ws, + new FetchStage(&_txn, ws, ms, NULL, coll), + coll, + PlanExecutor::YIELD_MANUAL, + &rawExec); + ASSERT_OK(status); + std::unique_ptr<PlanExecutor> exec(rawExec); + + for (int i = 0; i < numIndices; ++i) { + BSONObj obj; + ASSERT_EQUALS(PlanExecutor::ADVANCED, exec->getNext(&obj, NULL)); + ASSERT_EQUALS(i, obj["foo"].numberInt()); + string index(1, 'a' + i); + ASSERT_EQUALS(1, obj[index].numberInt()); } - }; - - // Invalidation mid-run - class QueryStageMergeSortInvalidation : public QueryStageMergeSortTestBase { - public: - void run() { - OldClientWriteContext ctx(&_txn, ns()); - Database* db = ctx.db(); - Collection* coll = db->getCollection(ns()); - if (!coll) { - WriteUnitOfWork wuow(&_txn); - coll = db->createCollection(&_txn, ns()); - wuow.commit(); - } - WorkingSet ws; - // Sort by foo:1 - MergeSortStageParams msparams; - msparams.pattern = BSON("foo" << 1); - unique_ptr<MergeSortStage> ms(new MergeSortStage(msparams, &ws, coll)); - - IndexScanParams params; - params.bounds.isSimpleRange = true; - params.bounds.startKey = objWithMinKey(1); - params.bounds.endKey = objWithMaxKey(1); - params.bounds.endKeyInclusive = true; - params.direction = 1; - - // Index 'a'+i has foo equal to 'i'. - - int numIndices = 20; - for (int i = 0; i < numIndices; ++i) { - // 'a', 'b', ... - string index(1, 'a' + i); - insert(BSON(index << 1 << "foo" << i)); - - BSONObj indexSpec = BSON(index << 1 << "foo" << 1); - addIndex(indexSpec); - params.descriptor = getIndex(indexSpec, coll); - ms->addChild(new IndexScan(&_txn, params, &ws, NULL)); - } + // Should be done now. + BSONObj foo; + ASSERT_EQUALS(PlanExecutor::IS_EOF, exec->getNext(&foo, NULL)); + } +}; + +// Invalidation mid-run +class QueryStageMergeSortInvalidation : public QueryStageMergeSortTestBase { +public: + void run() { + OldClientWriteContext ctx(&_txn, ns()); + Database* db = ctx.db(); + Collection* coll = db->getCollection(ns()); + if (!coll) { + WriteUnitOfWork wuow(&_txn); + coll = db->createCollection(&_txn, ns()); + wuow.commit(); + } - set<RecordId> locs; - getLocs(&locs, coll); + WorkingSet ws; + // Sort by foo:1 + MergeSortStageParams msparams; + msparams.pattern = BSON("foo" << 1); + unique_ptr<MergeSortStage> ms(new MergeSortStage(msparams, &ws, coll)); + + IndexScanParams params; + params.bounds.isSimpleRange = true; + params.bounds.startKey = objWithMinKey(1); + params.bounds.endKey = objWithMaxKey(1); + params.bounds.endKeyInclusive = true; + params.direction = 1; + + // Index 'a'+i has foo equal to 'i'. + + int numIndices = 20; + for (int i = 0; i < numIndices; ++i) { + // 'a', 'b', ... + string index(1, 'a' + i); + insert(BSON(index << 1 << "foo" << i)); + + BSONObj indexSpec = BSON(index << 1 << "foo" << 1); + addIndex(indexSpec); + params.descriptor = getIndex(indexSpec, coll); + ms->addChild(new IndexScan(&_txn, params, &ws, NULL)); + } - set<RecordId>::iterator it = locs.begin(); + set<RecordId> locs; + getLocs(&locs, coll); - // Get 10 results. Should be getting results in order of 'locs'. - int count = 0; - while (!ms->isEOF() && count < 10) { - WorkingSetID id = WorkingSet::INVALID_ID; - PlanStage::StageState status = ms->work(&id); - if (PlanStage::ADVANCED != status) { continue; } + set<RecordId>::iterator it = locs.begin(); - WorkingSetMember* member = ws.get(id); - ASSERT_EQUALS(member->loc, *it); - BSONElement elt; - string index(1, 'a' + count); - ASSERT(member->getFieldDotted(index, &elt)); - ASSERT_EQUALS(1, elt.numberInt()); - ASSERT(member->getFieldDotted("foo", &elt)); - ASSERT_EQUALS(count, elt.numberInt()); - ++count; - ++it; + // Get 10 results. Should be getting results in order of 'locs'. + int count = 0; + while (!ms->isEOF() && count < 10) { + WorkingSetID id = WorkingSet::INVALID_ID; + PlanStage::StageState status = ms->work(&id); + if (PlanStage::ADVANCED != status) { + continue; } - // Invalidate locs[11]. Should force a fetch. We don't get it back. - ms->saveState(); - ms->invalidate(&_txn, *it, INVALIDATION_DELETION); - ms->restoreState(&_txn); + WorkingSetMember* member = ws.get(id); + ASSERT_EQUALS(member->loc, *it); + BSONElement elt; + string index(1, 'a' + count); + ASSERT(member->getFieldDotted(index, &elt)); + ASSERT_EQUALS(1, elt.numberInt()); + ASSERT(member->getFieldDotted("foo", &elt)); + ASSERT_EQUALS(count, elt.numberInt()); + ++count; + ++it; + } + + // Invalidate locs[11]. Should force a fetch. We don't get it back. + ms->saveState(); + ms->invalidate(&_txn, *it, INVALIDATION_DELETION); + ms->restoreState(&_txn); - // Make sure locs[11] was fetched for us. - { + // Make sure locs[11] was fetched for us. + { // TODO: If we have "return upon invalidation" ever triggerable, do the following test. /* WorkingSetID id = WorkingSet::INVALID_ID; @@ -600,46 +626,47 @@ namespace QueryStageMergeSortTests { ASSERT_EQUALS(count, elt.numberInt()); */ - ++it; - ++count; - } - - // And get the rest. - while (!ms->isEOF()) { - WorkingSetID id = WorkingSet::INVALID_ID; - PlanStage::StageState status = ms->work(&id); - if (PlanStage::ADVANCED != status) { continue; } + ++it; + ++count; + } - WorkingSetMember* member = ws.get(id); - ASSERT_EQUALS(member->loc, *it); - BSONElement elt; - string index(1, 'a' + count); - ASSERT_TRUE(member->getFieldDotted(index, &elt)); - ASSERT_EQUALS(1, elt.numberInt()); - ASSERT(member->getFieldDotted("foo", &elt)); - ASSERT_EQUALS(count, elt.numberInt()); - ++count; - ++it; + // And get the rest. + while (!ms->isEOF()) { + WorkingSetID id = WorkingSet::INVALID_ID; + PlanStage::StageState status = ms->work(&id); + if (PlanStage::ADVANCED != status) { + continue; } - } - }; - - class All : public Suite { - public: - All() : Suite( "query_stage_merge_sort_test" ) { } - - void setupTests() { - add<QueryStageMergeSortPrefixIndex>(); - add<QueryStageMergeSortDups>(); - add<QueryStageMergeSortDupsNoDedup>(); - add<QueryStageMergeSortPrefixIndexReverse>(); - add<QueryStageMergeSortOneStageEOF>(); - add<QueryStageMergeSortManyShort>(); - add<QueryStageMergeSortInvalidation>(); - } - }; - SuiteInstance<All> queryStageMergeSortTest; + WorkingSetMember* member = ws.get(id); + ASSERT_EQUALS(member->loc, *it); + BSONElement elt; + string index(1, 'a' + count); + ASSERT_TRUE(member->getFieldDotted(index, &elt)); + ASSERT_EQUALS(1, elt.numberInt()); + ASSERT(member->getFieldDotted("foo", &elt)); + ASSERT_EQUALS(count, elt.numberInt()); + ++count; + ++it; + } + } +}; + +class All : public Suite { +public: + All() : Suite("query_stage_merge_sort_test") {} + + void setupTests() { + add<QueryStageMergeSortPrefixIndex>(); + add<QueryStageMergeSortDups>(); + add<QueryStageMergeSortDupsNoDedup>(); + add<QueryStageMergeSortPrefixIndexReverse>(); + add<QueryStageMergeSortOneStageEOF>(); + add<QueryStageMergeSortManyShort>(); + add<QueryStageMergeSortInvalidation>(); + } +}; + +SuiteInstance<All> queryStageMergeSortTest; } // namespace - |