//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2018 ArangoDB GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Michael Hackstein //////////////////////////////////////////////////////////////////////////////// #include "Aql/AqlItemBlockInputRange.h" #include "gtest/gtest.h" #include "Aql/AqlItemBlockManager.h" #include "Aql/InputAqlItemRow.h" #include "Aql/ShadowAqlItemRow.h" #include "AqlItemBlockHelper.h" #include "Basics/VelocyPackHelper.h" #include #include #include using namespace arangodb; using namespace arangodb::aql; using namespace arangodb::basics; namespace arangodb { namespace tests { namespace aql { // Test empty constructor class InputRangeTest : public ::testing::TestWithParam { protected: ResourceMonitor monitor; AqlItemBlockManager itemBlockManager{&monitor, SerializationFormat::SHADOWROWS}; AqlItemBlockInputRange createEmpty() { return AqlItemBlockInputRange{GetParam()}; } AqlItemBlockInputRange createFromBlock(arangodb::aql::SharedAqlItemBlockPtr& block) { auto const [start, end] = block->getRelevantRange(); return AqlItemBlockInputRange(GetParam(), block, start, end); } void validateEndReached(AqlItemBlockInputRange& testee) { EXPECT_EQ(GetParam(), testee.state()); // Test Data rows EXPECT_FALSE(testee.hasMore()); { auto const [state, row] = testee.peek(); EXPECT_EQ(GetParam(), state); EXPECT_FALSE(row.isInitialized()); } { auto const [state, row] = testee.next(); EXPECT_EQ(GetParam(), state); EXPECT_FALSE(row.isInitialized()); } // Test Shadow Rows EXPECT_FALSE(testee.hasShadowRow()); { auto const [state, row] = testee.peekShadowRow(); EXPECT_EQ(GetParam(), state); EXPECT_FALSE(row.isInitialized()); } { auto const [state, row] = testee.nextShadowRow(); EXPECT_EQ(GetParam(), state); EXPECT_FALSE(row.isInitialized()); } } void validateNextIsDataRow(AqlItemBlockInputRange& testee, ExecutorState expectedState, int64_t value) { EXPECT_TRUE(testee.hasMore()); EXPECT_FALSE(testee.hasShadowRow()); // We have the next row EXPECT_EQ(testee.state(), ExecutorState::HASMORE); auto rowIndexBefore = testee.getRowIndex(); // Validate that shadowRowAPI does not move on { auto [state, row] = testee.peekShadowRow(); EXPECT_EQ(state, ExecutorState::DONE); EXPECT_FALSE(row.isInitialized()); ASSERT_EQ(rowIndexBefore, testee.getRowIndex()) << "Skipped a non processed row."; } { auto [state, row] = testee.nextShadowRow(); EXPECT_EQ(state, ExecutorState::DONE); EXPECT_FALSE(row.isInitialized()); ASSERT_EQ(rowIndexBefore, testee.getRowIndex()) << "Skipped a non processed row."; } // Validate Data Row API { auto [state, row] = testee.peek(); EXPECT_EQ(state, expectedState); EXPECT_TRUE(row.isInitialized()); auto val = row.getValue(0); ASSERT_TRUE(val.isNumber()); EXPECT_EQ(val.toInt64(), value); ASSERT_EQ(rowIndexBefore, testee.getRowIndex()) << "Skipped a non processed row."; } { auto [state, row] = testee.next(); EXPECT_EQ(state, expectedState); EXPECT_TRUE(row.isInitialized()); auto val = row.getValue(0); ASSERT_TRUE(val.isNumber()); EXPECT_EQ(val.toInt64(), value); ASSERT_NE(rowIndexBefore, testee.getRowIndex()) << "Did not go to next row."; } EXPECT_EQ(expectedState, testee.state()); } void validateNextIsShadowRow(AqlItemBlockInputRange& testee, ExecutorState expectedState, int64_t value, uint64_t depth) { EXPECT_TRUE(testee.hasShadowRow()); // The next is a ShadowRow, the state shall be done EXPECT_EQ(testee.state(), ExecutorState::DONE); auto rowIndexBefore = testee.getRowIndex(); // Validate that inputRowAPI does not move on { auto [state, row] = testee.peek(); EXPECT_EQ(state, ExecutorState::DONE); EXPECT_FALSE(row.isInitialized()); ASSERT_EQ(rowIndexBefore, testee.getRowIndex()) << "Skipped a non processed row."; } { auto [state, row] = testee.next(); EXPECT_EQ(state, ExecutorState::DONE); EXPECT_FALSE(row.isInitialized()); ASSERT_EQ(rowIndexBefore, testee.getRowIndex()) << "Skipped a non processed row."; } // Validate ShadowRow API { auto [state, row] = testee.peekShadowRow(); EXPECT_EQ(state, expectedState); EXPECT_TRUE(row.isInitialized()); auto val = row.getValue(0); ASSERT_TRUE(val.isNumber()); EXPECT_EQ(val.toInt64(), value); EXPECT_EQ(row.getDepth(), depth); ASSERT_EQ(rowIndexBefore, testee.getRowIndex()) << "Skipped a non processed row."; } { auto [state, row] = testee.nextShadowRow(); EXPECT_EQ(state, expectedState); EXPECT_TRUE(row.isInitialized()); auto val = row.getValue(0); ASSERT_TRUE(val.isNumber()); EXPECT_EQ(val.toInt64(), value); EXPECT_EQ(row.getDepth(), depth); ASSERT_NE(rowIndexBefore, testee.getRowIndex()) << "Did not go to next row."; } } }; TEST_P(InputRangeTest, empty_returns_given_state) { auto testee = createEmpty(); EXPECT_EQ(GetParam(), testee.state()); } TEST_P(InputRangeTest, empty_does_not_have_more) { auto testee = createEmpty(); EXPECT_FALSE(testee.hasMore()); } TEST_P(InputRangeTest, empty_peek_is_empty) { auto testee = createEmpty(); auto const [state, row] = testee.peek(); EXPECT_EQ(GetParam(), state); EXPECT_FALSE(row.isInitialized()); } TEST_P(InputRangeTest, empty_next_is_empty) { auto testee = createEmpty(); auto const [state, row] = testee.next(); EXPECT_EQ(GetParam(), state); EXPECT_FALSE(row.isInitialized()); } TEST_P(InputRangeTest, empty_does_not_have_more_shadow_rows) { auto testee = createEmpty(); EXPECT_FALSE(testee.hasShadowRow()); } TEST_P(InputRangeTest, empty_peek_shadow_is_empty) { auto testee = createEmpty(); auto const [state, row] = testee.peekShadowRow(); EXPECT_EQ(GetParam(), state); EXPECT_FALSE(row.isInitialized()); } TEST_P(InputRangeTest, empty_next_shadow_is_empty) { auto testee = createEmpty(); auto const [state, row] = testee.nextShadowRow(); EXPECT_EQ(GetParam(), state); EXPECT_FALSE(row.isInitialized()); } TEST_P(InputRangeTest, no_shadow_rows_in_block) { SharedAqlItemBlockPtr inputBlock = buildBlock<1>(itemBlockManager, {{{1}}, {{2}}, {{3}}, {{4}}}, {}); auto testee = createFromBlock(inputBlock); // We have 3 internal rows validateNextIsDataRow(testee, ExecutorState::HASMORE, 1); validateNextIsDataRow(testee, ExecutorState::HASMORE, 2); validateNextIsDataRow(testee, ExecutorState::HASMORE, 3); // Last Row needs to return upstream State validateNextIsDataRow(testee, GetParam(), 4); validateEndReached(testee); } TEST_P(InputRangeTest, level_0_shadow_rows_in_block) { SharedAqlItemBlockPtr inputBlock = buildBlock<1>(itemBlockManager, {{{1}}, {{2}}, {{3}}, {{4}}}, {{1, 0}, {3, 0}}); auto testee = createFromBlock(inputBlock); validateNextIsDataRow(testee, ExecutorState::DONE, 1); validateNextIsShadowRow(testee, ExecutorState::DONE, 2, 0); validateNextIsDataRow(testee, ExecutorState::DONE, 3); // Last Row needs to return upstream State validateNextIsShadowRow(testee, GetParam(), 4, 0); validateEndReached(testee); } TEST_P(InputRangeTest, multi_level_shadow_rows_in_block) { SharedAqlItemBlockPtr inputBlock = buildBlock<1>(itemBlockManager, {{{1}}, {{2}}, {{3}}, {{4}}, {{5}}, {{6}}, {{7}}}, {{3, 0}, {4, 1}, {5, 2}}); auto testee = createFromBlock(inputBlock); validateNextIsDataRow(testee, ExecutorState::HASMORE, 1); validateNextIsDataRow(testee, ExecutorState::HASMORE, 2); validateNextIsDataRow(testee, ExecutorState::DONE, 3); validateNextIsShadowRow(testee, ExecutorState::HASMORE, 4, 0); validateNextIsShadowRow(testee, ExecutorState::HASMORE, 5, 1); validateNextIsShadowRow(testee, ExecutorState::DONE, 6, 2); // Last Row needs to return upstream State validateNextIsDataRow(testee, GetParam(), 7); validateEndReached(testee); } TEST_P(InputRangeTest, multi_shadow_rows_batches_in_block) { SharedAqlItemBlockPtr inputBlock = buildBlock<1>(itemBlockManager, {{{1}}, {{2}}, {{3}}, {{4}}, {{5}}, {{6}}, {{7}}}, {{3, 0}, {4, 1}, {5, 0}, {6, 1}}); auto testee = createFromBlock(inputBlock); validateNextIsDataRow(testee, ExecutorState::HASMORE, 1); validateNextIsDataRow(testee, ExecutorState::HASMORE, 2); validateNextIsDataRow(testee, ExecutorState::DONE, 3); validateNextIsShadowRow(testee, ExecutorState::HASMORE, 4, 0); validateNextIsShadowRow(testee, ExecutorState::DONE, 5, 1); validateNextIsShadowRow(testee, ExecutorState::HASMORE, 6, 0); // Last Row needs to return upstream State validateNextIsShadowRow(testee, GetParam(), 7, 1); validateEndReached(testee); } INSTANTIATE_TEST_CASE_P(AqlItemBlockInputRangeTest, InputRangeTest, ::testing::Values(ExecutorState::DONE, ExecutorState::HASMORE)); } // namespace aql } // namespace tests } // namespace arangodb