/** * Copyright (C) 2018-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * 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 * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * 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 Server Side 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/platform/basic.h" #include #include "mongo/db/exec/ensure_sorted.h" #include "mongo/db/exec/queued_data_stage.h" #include "mongo/db/exec/sort_key_generator.h" #include "mongo/db/json.h" #include "mongo/db/query/collation/collator_interface_mock.h" #include "mongo/db/query/query_test_service_context.h" #include "mongo/unittest/unittest.h" namespace mongo { namespace { class QueryStageEnsureSortedTest : public unittest::Test { public: /** * Test function to verify the EnsureSortedStage. * patternStr is the JSON representation of the sort pattern BSONObj. * inputStr represents the input data set in a BSONObj. * {input: [doc1, doc2, doc3, ...]} * expectedStr represents the expected output data set. * {output: [docA, docB, docC, ...]} * collator is passed to EnsureSortedStage() for string comparisons. */ void testWork(const char* patternStr, const char* inputStr, const char* expectedStr, CollatorInterface* collator = nullptr) { auto opCtx = _serviceContext.makeOperationContext(); WorkingSet ws; auto queuedDataStage = std::make_unique(opCtx.get(), &ws); BSONObj inputObj = fromjson(inputStr); BSONElement inputElt = inputObj["input"]; ASSERT(inputElt.isABSONObj()); for (auto&& elt : inputElt.embeddedObject()) { ASSERT(elt.isABSONObj()); BSONObj obj = elt.embeddedObject().getOwned(); // Insert obj from input array into working set. WorkingSetID id = ws.allocate(); WorkingSetMember* wsm = ws.get(id); wsm->obj = Snapshotted(SnapshotId(), obj); wsm->transitionToOwnedObj(); queuedDataStage->pushBack(id); } // Initialization. BSONObj pattern = fromjson(patternStr); auto sortKeyGen = std::make_unique( opCtx.get(), queuedDataStage.release(), &ws, pattern, collator); EnsureSortedStage ess(opCtx.get(), pattern, &ws, sortKeyGen.release()); WorkingSetID id = WorkingSet::INVALID_ID; PlanStage::StageState state = PlanStage::NEED_TIME; // Documents are inserted into BSON document in this format: // {output: [docA, docB, docC, ...]} BSONObjBuilder bob; BSONArrayBuilder arr(bob.subarrayStart("output")); while (state != PlanStage::IS_EOF) { state = ess.work(&id); ASSERT_NE(state, PlanStage::FAILURE); if (state == PlanStage::ADVANCED) { WorkingSetMember* member = ws.get(id); const BSONObj& obj = member->obj.value(); arr.append(obj); } } ASSERT_TRUE(ess.isEOF()); arr.doneFast(); BSONObj outputObj = bob.obj(); // Compare the results against what we expect. BSONObj expectedObj = fromjson(expectedStr); ASSERT_BSONOBJ_EQ(outputObj, expectedObj); } protected: QueryTestServiceContext _serviceContext; }; TEST_F(QueryStageEnsureSortedTest, EnsureSortedEmptyWorkingSet) { auto opCtx = _serviceContext.makeOperationContext(); WorkingSet ws; auto queuedDataStage = std::make_unique(opCtx.get(), &ws); auto sortKeyGen = std::make_unique( opCtx.get(), queuedDataStage.release(), &ws, BSONObj(), nullptr); EnsureSortedStage ess(opCtx.get(), BSONObj(), &ws, sortKeyGen.release()); WorkingSetID id = WorkingSet::INVALID_ID; PlanStage::StageState state = PlanStage::NEED_TIME; while (state == PlanStage::NEED_TIME || state == PlanStage::NEED_YIELD) { state = ess.work(&id); ASSERT_NE(state, PlanStage::ADVANCED); } ASSERT_EQ(state, PlanStage::IS_EOF); } // // EnsureSorted on already sorted order should make no change. // TEST_F(QueryStageEnsureSortedTest, EnsureAlreadySortedAscending) { testWork("{a: 1}", "{input: [{a: 1}, {a: 2}, {a: 3}]}", "{output: [{a: 1}, {a: 2}, {a: 3}]}"); } TEST_F(QueryStageEnsureSortedTest, EnsureAlreadySortedDescending) { testWork("{a: -1}", "{input: [{a: 3}, {a: 2}, {a: 1}]}", "{output: [{a: 3}, {a: 2}, {a: 1}]}"); } TEST_F(QueryStageEnsureSortedTest, EnsureIrrelevantSortKey) { testWork("{b: 1}", "{input: [{a: 2}, {a: 1}, {a: 3}]}", "{output: [{a: 2}, {a: 1}, {a: 3}]}"); } // // EnsureSorted should drop unsorted results. // TEST_F(QueryStageEnsureSortedTest, EnsureSortedOnAscending) { testWork("{a: 1}", "{input: [{a: 1}, {a: 2}, {a: 0}, {a: 4}, {a: 6}]}", "{output: [{a: 1}, {a: 2}, {a: 4}, {a: 6}]}"); } TEST_F(QueryStageEnsureSortedTest, EnsureSortedOnDescending) { testWork("{a: -1}", "{input: [{a: 6}, {a: 4}, {a: 3}, {a: 9}, {a: 8}]}", "{output: [{a: 6}, {a: 4}, {a: 3}]}"); } TEST_F(QueryStageEnsureSortedTest, EnsureSortedCompoundKey) { testWork("{a: -1, b: 1}", "{input: [{a: 6, b: 10}, {a: 6, b: 8}, {a: 6, b: 12}, {a: 9, b: 13}, {a: 5, b: 1}]}", "{output: [{a: 6, b: 10}, {a: 6, b: 12}, {a: 5, b: 1}]}"); } TEST_F(QueryStageEnsureSortedTest, EnsureSortedStringsNullCollator) { testWork("{a: 1}", "{input: [{a: 'abc'}, {a: 'cba'}]}", "{output: [{a: 'abc'}, {a: 'cba'}]}"); } TEST_F(QueryStageEnsureSortedTest, EnsureSortedStringsCollator) { CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kReverseString); testWork("{a: 1}", "{input: [{a: 'abc'}, {a: 'cba'}]}", "{output: [{a: 'abc'}]}", &collator); } } // namespace } // namespace mongo