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

254 lines
9.5 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
////////////////////////////////////////////////////////////////////////////////
#include "RowFetcherHelper.h"
#include "gtest/gtest.h"
#include "Aql/ExecutorInfos.h"
#include "Aql/OutputAqlItemRow.h"
#include "Aql/Stats.h"
#include "Aql/SubqueryStartExecutor.h"
#include <velocypack/Builder.h>
#include <velocypack/Parser.h>
#include <velocypack/velocypack-aliases.h>
using namespace arangodb;
using namespace arangodb::aql;
using namespace arangodb::tests;
using namespace arangodb::tests::aql;
namespace {
ExecutorInfos MakeBaseInfos(RegisterId numRegs) {
auto emptyRegisterList = std::make_shared<std::unordered_set<RegisterId>>(
std::initializer_list<RegisterId>{});
std::unordered_set<RegisterId> toKeep;
for (RegisterId r = 0; r < numRegs; ++r) {
toKeep.emplace(r);
}
return ExecutorInfos(emptyRegisterList, emptyRegisterList, numRegs, numRegs, {}, toKeep);
}
void TestShadowRow(SharedAqlItemBlockPtr const& block, size_t row, bool isRelevant) {
EXPECT_TRUE(block->isShadowRow(row));
// We do this additional if, in order to allow the outer test loop to continue
// even if we do not have a shadow row.
if (block->isShadowRow(row)) {
ShadowAqlItemRow shadow{block, row};
EXPECT_EQ(shadow.isRelevant(), isRelevant) << "Testing row " << row;
}
}
} // namespace
class SubqueryStartExecutorTest : public ::testing::Test {
protected:
ResourceMonitor monitor;
AqlItemBlockManager itemBlockManager{&monitor, SerializationFormat::SHADOWROWS};
};
TEST_F(SubqueryStartExecutorTest, check_properties) {
EXPECT_TRUE(SubqueryStartExecutor::Properties::preservesOrder)
<< "The block has no effect on ordering of elements, it adds additional "
"rows only.";
EXPECT_EQ(SubqueryStartExecutor::Properties::allowsBlockPassthrough, ::arangodb::aql::BlockPassthrough::Disable)
<< "The block cannot be passThrough, as it increases the number of rows.";
EXPECT_TRUE(SubqueryStartExecutor::Properties::inputSizeRestrictsOutputSize)
<< "The block is restricted by input, it will atMost produce 2 times the "
"input. (Might be less if input contains shadowRows";
}
TEST_F(SubqueryStartExecutorTest, empty_input_does_not_add_shadow_rows) {
SharedAqlItemBlockPtr block{new AqlItemBlock(itemBlockManager, 1000, 1)};
VPackBuilder input;
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, input.steal(), false);
auto infos = MakeBaseInfos(1);
SubqueryStartExecutor testee(fetcher, infos);
NoStats stats{};
ExecutionState state{ExecutionState::HASMORE};
OutputAqlItemRow output{std::move(block), infos.getOutputRegisters(),
infos.registersToKeep(), infos.registersToClear()};
std::tie(state, stats) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_FALSE(output.produced());
EXPECT_EQ(output.numRowsWritten(), 0);
}
TEST_F(SubqueryStartExecutorTest, adds_a_shadowrow_after_single_input) {
SharedAqlItemBlockPtr block{new AqlItemBlock(itemBlockManager, 1000, 1)};
auto input = VPackParser::fromJson(R"([
["a"]
])");
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, input->steal(), false);
auto infos = MakeBaseInfos(1);
SubqueryStartExecutor testee(fetcher, infos);
NoStats stats{};
ExecutionState state{ExecutionState::HASMORE};
OutputAqlItemRow output{std::move(block), infos.getOutputRegisters(),
infos.registersToKeep(), infos.registersToClear()};
std::tie(state, stats) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_FALSE(output.produced());
EXPECT_EQ(output.numRowsWritten(), 2);
block = output.stealBlock();
EXPECT_FALSE(block->isShadowRow(0));
TestShadowRow(block, 1, true);
}
TEST_F(SubqueryStartExecutorTest, adds_a_shadowrow_after_every_input_line_in_single_pass) {
SharedAqlItemBlockPtr block{new AqlItemBlock(itemBlockManager, 1000, 1)};
auto input = VPackParser::fromJson(R"([
["a"],
["b"],
["c"]
])");
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, input->steal(), false);
auto infos = MakeBaseInfos(1);
SubqueryStartExecutor testee(fetcher, infos);
NoStats stats{};
ExecutionState state{ExecutionState::HASMORE};
OutputAqlItemRow output{std::move(block), infos.getOutputRegisters(),
infos.registersToKeep(), infos.registersToClear()};
std::tie(state, stats) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_FALSE(output.produced());
EXPECT_EQ(output.numRowsWritten(), 6);
block = output.stealBlock();
EXPECT_FALSE(block->isShadowRow(0));
TestShadowRow(block, 1, true);
EXPECT_FALSE(block->isShadowRow(2));
TestShadowRow(block, 3, true);
EXPECT_FALSE(block->isShadowRow(4));
TestShadowRow(block, 5, true);
}
TEST_F(SubqueryStartExecutorTest, shadow_row_does_not_fit_in_current_block) {
auto input = VPackParser::fromJson(R"([
["a"],
["b"],
["c"]
])");
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, input->steal(), false);
auto infos = MakeBaseInfos(1);
SubqueryStartExecutor testee(fetcher, infos);
NoStats stats{};
ExecutionState state{ExecutionState::HASMORE};
{
SharedAqlItemBlockPtr block{new AqlItemBlock(itemBlockManager, 3, 1)};
OutputAqlItemRow output{std::move(block), infos.getOutputRegisters(),
infos.registersToKeep(), infos.registersToClear()};
std::tie(state, stats) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::HASMORE);
EXPECT_FALSE(output.produced());
EXPECT_EQ(output.numRowsWritten(), 3);
block = output.stealBlock();
EXPECT_FALSE(block->isShadowRow(0));
TestShadowRow(block, 1, true);
EXPECT_FALSE(block->isShadowRow(2));
}
{
SharedAqlItemBlockPtr block{new AqlItemBlock(itemBlockManager, 3, 1)};
OutputAqlItemRow output{std::move(block), infos.getOutputRegisters(),
infos.registersToKeep(), infos.registersToClear()};
std::tie(state, stats) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_FALSE(output.produced());
EXPECT_EQ(output.numRowsWritten(), 3);
block = output.stealBlock();
TestShadowRow(block, 0, true);
EXPECT_FALSE(block->isShadowRow(1));
TestShadowRow(block, 2, true);
}
}
// TODO:
// This test can be enabled and should work as soon as the Fetcher skips non-relevant Subqueries
TEST_F(SubqueryStartExecutorTest, does_only_add_shadowrows_on_data_rows) {
SharedAqlItemBlockPtr block{new AqlItemBlock(itemBlockManager, 1000, 1)};
auto input = VPackParser::fromJson(R"([
["a"],
["b"],
["c"]
])");
auto infos = MakeBaseInfos(1);
{
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(
itemBlockManager, input->steal(), false);
SubqueryStartExecutor testee(fetcher, infos);
NoStats stats{};
ExecutionState state{ExecutionState::HASMORE};
OutputAqlItemRow output{std::move(block), infos.getOutputRegisters(),
infos.registersToKeep(), infos.registersToClear()};
std::tie(state, stats) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_FALSE(output.produced());
ASSERT_EQ(output.numRowsWritten(), 6);
block = output.stealBlock();
EXPECT_FALSE(block->isShadowRow(0));
TestShadowRow(block, 1, true);
EXPECT_FALSE(block->isShadowRow(2));
TestShadowRow(block, 3, true);
EXPECT_FALSE(block->isShadowRow(4));
TestShadowRow(block, 5, true);
// Taken from test above. We now have produced a block
// having 3 data rows alternating with 3 shadow rows
}
{
SingleRowFetcherHelper<::arangodb::aql::BlockPassthrough::Disable> fetcher(itemBlockManager,
6, false, block);
SubqueryStartExecutor testee(fetcher, infos);
block.reset(new AqlItemBlock(itemBlockManager, 1000, 1));
NoStats stats{};
ExecutionState state{ExecutionState::HASMORE};
OutputAqlItemRow output{std::move(block), infos.getOutputRegisters(),
infos.registersToKeep(), infos.registersToClear()};
std::tie(state, stats) = testee.produceRows(output);
EXPECT_EQ(state, ExecutionState::DONE);
EXPECT_FALSE(output.produced());
ASSERT_EQ(output.numRowsWritten(), 9);
block = output.stealBlock();
EXPECT_FALSE(block->isShadowRow(0));
TestShadowRow(block, 1, true);
TestShadowRow(block, 2, false);
EXPECT_FALSE(block->isShadowRow(3));
TestShadowRow(block, 4, true);
TestShadowRow(block, 5, false);
EXPECT_FALSE(block->isShadowRow(6));
TestShadowRow(block, 7, true);
TestShadowRow(block, 8, false);
}
}