1
0
Fork 0
arangodb/tests/Aql/SubqueryEndExecutorTest.cpp

346 lines
14 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2019 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
/// @author Markus Pfeiffer
////////////////////////////////////////////////////////////////////////////////
#include "AqlItemBlockHelper.h"
#include "RowFetcherHelper.h"
#include "gtest/gtest.h"
#include "Mocks/Death_Test.h"
#include "Aql/OutputAqlItemRow.h"
#include "Aql/RegisterPlan.h"
#include "Aql/SubqueryEndExecutor.h"
#include "Logger/LogMacros.h"
#include "Basics/VelocyPackHelper.h"
using namespace arangodb;
using namespace arangodb::aql;
using namespace arangodb::tests;
using namespace arangodb::tests::aql;
using namespace arangodb::basics;
using RegisterSet = std::unordered_set<RegisterId>;
class SubqueryEndExecutorTest : public ::testing::Test {
public:
SubqueryEndExecutorTest()
: _infos(std::make_shared<RegisterSet>(std::initializer_list<RegisterId>({0})),
std::make_shared<RegisterSet>(std::initializer_list<RegisterId>({0})),
1, 1, {}, {}, nullptr, RegisterId{0}, RegisterId{0}) {}
protected:
ResourceMonitor monitor;
AqlItemBlockManager itemBlockManager{&monitor, SerializationFormat::SHADOWROWS};
SubqueryEndExecutorInfos _infos;
void ExpectedValues(OutputAqlItemRow& itemRow,
std::vector<std::vector<std::string>> const& expectedStrings,
std::unordered_map<size_t, uint64_t> const& shadowRows) const {
auto block = itemRow.stealBlock();
ASSERT_EQ(expectedStrings.size(), block->size());
for (size_t rowIdx = 0; rowIdx < block->size(); rowIdx++) {
if (block->isShadowRow(rowIdx)) {
ShadowAqlItemRow shadow{block, rowIdx};
auto depth = shadowRows.find(rowIdx);
if (depth != shadowRows.end()) {
EXPECT_EQ(depth->second, shadow.getDepth());
} else {
FAIL() << "did not expect row " << rowIdx << " to be a shadow row";
}
} else {
EXPECT_EQ(shadowRows.find(rowIdx), shadowRows.end())
<< "expected row " << rowIdx << " to be a shadow row";
InputAqlItemRow input{block, rowIdx};
for (unsigned int colIdx = 0; colIdx < block->getNrRegs(); colIdx++) {
auto expected = VPackParser::fromJson(expectedStrings.at(rowIdx).at(colIdx));
auto value = input.getValue(RegisterId{colIdx}).slice();
EXPECT_TRUE(VelocyPackHelper::equal(value, expected->slice(), false))
<< value.toJson() << " != " << expected->toJson();
}
}
}
}
};
TEST_F(SubqueryEndExecutorTest, check_properties) {
EXPECT_TRUE(SubqueryEndExecutor::Properties::preservesOrder)
<< "The block has no effect on ordering of elements, it adds additional "
"rows only.";
EXPECT_EQ(SubqueryEndExecutor::Properties::allowsBlockPassthrough, ::arangodb::aql::BlockPassthrough::Disable)
<< "The block cannot be passThrough, as it increases the number of rows.";
EXPECT_TRUE(SubqueryEndExecutor::Properties::inputSizeRestrictsOutputSize)
<< "The block produces one output row per input row plus potentially a "
"shadow rows which is bounded by the structure of the query";
};
// If the input to a spliced subquery is empty, there should be no output
TEST_F(SubqueryEndExecutorTest, empty_input_expects_no_shadow_rows) {
SharedAqlItemBlockPtr outputBlock;
SharedAqlItemBlockPtr inputBlock = buildBlock<1>(itemBlockManager, {{{}}});
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, 1, false, inputBlock);
SubqueryEndExecutor testee(fetcher, _infos);
// I don't seem to be able to make an empty inputBlock above,
// so we just fetch the one row that's in the block.
fetcher.fetchRow();
ExecutionState state{ExecutionState::HASMORE};
outputBlock.reset(new AqlItemBlock(itemBlockManager, inputBlock->size(), 1));
OutputAqlItemRow output{std::move(outputBlock), _infos.getOutputRegisters(),
_infos.registersToKeep(), _infos.registersToClear()};
std::tie(state, std::ignore) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_EQ(output.numRowsWritten(), 0);
}
TEST_F(SubqueryEndExecutorTest, single_input_expects_shadow_rows) {
SharedAqlItemBlockPtr outputBlock;
SharedAqlItemBlockPtr inputBlock =
buildBlock<1>(itemBlockManager, {{{1}}, {{1}}}, {{1, 0}});
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, inputBlock->size(), false, inputBlock);
SubqueryEndExecutor testee(fetcher, _infos);
ExecutionState state{ExecutionState::HASMORE};
outputBlock.reset(new AqlItemBlock(itemBlockManager, inputBlock->size(), 1));
OutputAqlItemRow output{std::move(outputBlock), _infos.getOutputRegisters(),
_infos.registersToKeep(), _infos.registersToClear()};
std::tie(state, std::ignore) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_EQ(output.numRowsWritten(), 1);
ExpectedValues(output, {{"[1]"}}, {});
}
TEST_F(SubqueryEndExecutorTest, two_inputs_one_shadowrow) {
SharedAqlItemBlockPtr outputBlock;
SharedAqlItemBlockPtr inputBlock =
buildBlock<1>(itemBlockManager, {{{42}}, {{34}}, {{1}}}, {{2, 0}});
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, inputBlock->size(), false, inputBlock);
SubqueryEndExecutor testee(fetcher, _infos);
ExecutionState state{ExecutionState::HASMORE};
outputBlock.reset(new AqlItemBlock(itemBlockManager, inputBlock->size(), 1));
OutputAqlItemRow output{std::move(outputBlock), _infos.getOutputRegisters(),
_infos.registersToKeep(), _infos.registersToClear()};
std::tie(state, std::ignore) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_EQ(output.numRowsWritten(), 1);
ExpectedValues(output, {{"[42,34]"}}, {});
}
TEST_F(SubqueryEndExecutorTest, two_inputs_two_shadowrows) {
SharedAqlItemBlockPtr outputBlock;
SharedAqlItemBlockPtr inputBlock =
buildBlock<1>(itemBlockManager, {{{42}}, {{1}}, {{34}}, {{1}}}, {{1, 0}, {3, 0}});
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, inputBlock->size(), false, inputBlock);
SubqueryEndExecutor testee(fetcher, _infos);
ExecutionState state{ExecutionState::HASMORE};
outputBlock.reset(new AqlItemBlock(itemBlockManager, inputBlock->size(), 1));
OutputAqlItemRow output{std::move(outputBlock), _infos.getOutputRegisters(),
_infos.registersToKeep(), _infos.registersToClear()};
std::tie(state, std::ignore) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_EQ(output.numRowsWritten(), 2);
ExpectedValues(output, {{"[42]"}, {"[34]"}}, {});
}
TEST_F(SubqueryEndExecutorTest, two_input_one_shadowrow_two_irrelevant) {
SharedAqlItemBlockPtr outputBlock;
SharedAqlItemBlockPtr inputBlock =
buildBlock<1>(itemBlockManager, {{{42}}, {{42}}, {{42}}, {{42}}, {{42}}},
{{2, 0}, {3, 1}, {4, 2}});
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, inputBlock->size(), false, inputBlock);
SubqueryEndExecutor testee(fetcher, _infos);
ExecutionState state{ExecutionState::HASMORE};
outputBlock.reset(new AqlItemBlock(itemBlockManager, inputBlock->size(), 1));
OutputAqlItemRow output{std::move(outputBlock), _infos.getOutputRegisters(),
_infos.registersToKeep(), _infos.registersToClear()};
std::tie(state, std::ignore) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_EQ(output.numRowsWritten(), 3);
ExpectedValues(output, {{{"[42, 42]"}}, {{""}}, {{""}}}, {{1, 0}, {2, 1}});
}
TEST_F(SubqueryEndExecutorTest, consume_output_of_subquery_end_executor) {
ExecutionState state{ExecutionState::HASMORE};
SharedAqlItemBlockPtr outputBlock;
SharedAqlItemBlockPtr inputBlock =
buildBlock<1>(itemBlockManager, {{{42}}, {{42}}, {{42}}, {{42}}, {{42}}},
{{2, 0}, {3, 1}, {4, 2}});
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, inputBlock->size(), false, inputBlock);
SubqueryEndExecutor testee(fetcher, _infos);
outputBlock.reset(new AqlItemBlock(itemBlockManager, inputBlock->size(), 1));
OutputAqlItemRow output{std::move(outputBlock), _infos.getOutputRegisters(),
_infos.registersToKeep(), _infos.registersToClear()};
std::tie(state, std::ignore) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_EQ(output.numRowsWritten(), 3);
// ExpectedValues(output, { "[42, 42]", "", "" });
outputBlock = output.stealBlock();
inputBlock.swap(outputBlock);
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher2(
itemBlockManager, inputBlock->size(), false, inputBlock);
SubqueryEndExecutor testee2(fetcher2, _infos);
outputBlock.reset(new AqlItemBlock(itemBlockManager, inputBlock->size(), 1));
OutputAqlItemRow output2{std::move(outputBlock), _infos.getOutputRegisters(),
_infos.registersToKeep(), _infos.registersToClear()};
std::tie(state, std::ignore) = testee2.produceRows(output2);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_EQ(output2.numRowsWritten(), 2);
ExpectedValues(output2, {{"[ [42, 42] ]"}, {""}}, {{1, 0}});
}
TEST_F(SubqueryEndExecutorTest, write_to_register_outside) {
auto infos = SubqueryEndExecutorInfos(
std::make_shared<RegisterSet>(std::initializer_list<RegisterId>{0}),
std::make_shared<RegisterSet>(std::initializer_list<RegisterId>{1}), 1, 2,
{}, RegisterSet{0}, nullptr, RegisterId{0}, RegisterId{1});
ExecutionState state{ExecutionState::HASMORE};
SharedAqlItemBlockPtr outputBlock;
SharedAqlItemBlockPtr inputBlock =
buildBlock<1>(itemBlockManager, {{{42}}, {{23}}}, {{1, 0}});
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, inputBlock->size(), false, inputBlock);
SubqueryEndExecutor testee(fetcher, infos);
outputBlock.reset(new AqlItemBlock(itemBlockManager, inputBlock->size(), 2));
OutputAqlItemRow output{std::move(outputBlock), infos.getOutputRegisters(),
infos.registersToKeep(), infos.registersToClear()};
std::tie(state, std::ignore) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_EQ(output.numRowsWritten(), 1);
ExpectedValues(output, {{"23", "[42]"}}, {{1, 0}});
}
TEST_F(SubqueryEndExecutorTest, no_input_register) {
auto infos = SubqueryEndExecutorInfos(
std::make_shared<RegisterSet>(std::initializer_list<RegisterId>{0}),
std::make_shared<RegisterSet>(std::initializer_list<RegisterId>{1}), 1, 2, {},
RegisterSet{0}, nullptr, RegisterId{RegisterPlan::MaxRegisterId}, RegisterId{1});
ExecutionState state{ExecutionState::HASMORE};
SharedAqlItemBlockPtr outputBlock;
SharedAqlItemBlockPtr inputBlock =
buildBlock<1>(itemBlockManager, {{{42}}, {{23}}}, {{1, 0}});
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, inputBlock->size(), false, inputBlock);
SubqueryEndExecutor testee(fetcher, infos);
outputBlock.reset(new AqlItemBlock(itemBlockManager, inputBlock->size(), 2));
OutputAqlItemRow output{std::move(outputBlock), infos.getOutputRegisters(),
infos.registersToKeep(), infos.registersToClear()};
std::tie(state, std::ignore) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_EQ(output.numRowsWritten(), 1);
ExpectedValues(output, {{"23", "[]"}}, {{1, 0}});
}
// TODO: This is a "death test" with malformed shadow row layout (an irrelevant shadow row before any other row)
// See https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#death-tests-and-threads
using SubqueryEndExecutorTest_DeathTest = SubqueryEndExecutorTest;
TEST_F(SubqueryEndExecutorTest_DeathTest, no_shadow_row) {
SharedAqlItemBlockPtr outputBlock;
SharedAqlItemBlockPtr inputBlock = buildBlock<1>(itemBlockManager, {{1}});
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, inputBlock->size(), false, inputBlock);
SubqueryEndExecutor testee(fetcher, _infos);
ExecutionState state{ExecutionState::HASMORE};
outputBlock.reset(new AqlItemBlock(itemBlockManager, inputBlock->size(), 1));
OutputAqlItemRow output{std::move(outputBlock), _infos.getOutputRegisters(),
_infos.registersToKeep(), _infos.registersToClear()};
EXPECT_DEATH_CORE_FREE(std::tie(state, std::ignore) = testee.produceRows(output),
".*");
}
TEST_F(SubqueryEndExecutorTest_DeathTest, misplaced_irrelevant_shadowrow) {
SharedAqlItemBlockPtr outputBlock;
SharedAqlItemBlockPtr inputBlock =
buildBlock<1>(itemBlockManager, {{42}, {42}, {42}}, {{1, 1}, {2, 1}});
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, inputBlock->size(), false, inputBlock);
SubqueryEndExecutor testee(fetcher, _infos);
ExecutionState state{ExecutionState::HASMORE};
outputBlock.reset(new AqlItemBlock(itemBlockManager, inputBlock->size(), 1));
OutputAqlItemRow output{std::move(outputBlock), _infos.getOutputRegisters(),
_infos.registersToKeep(), _infos.registersToClear()};
EXPECT_DEATH_CORE_FREE(std::tie(state, std::ignore) = testee.produceRows(output),
".*");
}