2017-06-01 18:09:18 +08:00
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <memory>
|
|
|
|
|
2017-07-12 18:17:15 +08:00
|
|
|
#include "database/dbms.hpp"
|
2017-06-01 18:09:18 +08:00
|
|
|
#include "query/frontend/ast/ast.hpp"
|
|
|
|
#include "query/frontend/semantic/symbol_table.hpp"
|
|
|
|
#include "query/plan/cost_estimator.hpp"
|
|
|
|
#include "query/plan/operator.hpp"
|
|
|
|
#include "storage/vertex_accessor.hpp"
|
|
|
|
|
|
|
|
using namespace query;
|
|
|
|
using namespace query::plan;
|
|
|
|
|
2017-08-24 20:24:17 +08:00
|
|
|
using CardParam = CostEstimator<GraphDbAccessor>::CardParam;
|
|
|
|
using CostParam = CostEstimator<GraphDbAccessor>::CostParam;
|
|
|
|
using MiscParam = CostEstimator<GraphDbAccessor>::MiscParam;
|
2017-06-01 18:09:18 +08:00
|
|
|
|
|
|
|
/** A fixture for cost estimation. Sets up the database
|
|
|
|
* and accessor (adds some vertices). Provides convenience
|
|
|
|
* functions for creating the logical plan. Note that the
|
|
|
|
* resulting plan is NOT fit for execution, only for cost
|
|
|
|
* estimation testing. */
|
|
|
|
class QueryCostEstimator : public ::testing::Test {
|
|
|
|
protected:
|
|
|
|
Dbms dbms;
|
|
|
|
std::unique_ptr<GraphDbAccessor> dba = dbms.active();
|
2017-08-09 21:36:01 +08:00
|
|
|
GraphDbTypes::Label label = dba->Label("label");
|
|
|
|
GraphDbTypes::Property property = dba->Property("property");
|
2017-06-01 18:09:18 +08:00
|
|
|
|
|
|
|
// we incrementally build the logical operator plan
|
|
|
|
// start it off with Once
|
|
|
|
std::shared_ptr<LogicalOperator> last_op_ = std::make_shared<Once>();
|
|
|
|
|
|
|
|
AstTreeStorage storage_;
|
|
|
|
SymbolTable symbol_table_;
|
|
|
|
int symbol_count = 0;
|
|
|
|
|
2017-08-03 20:18:19 +08:00
|
|
|
void SetUp() {
|
|
|
|
// create the index in the current db accessor and then swap it to a new one
|
|
|
|
dba->BuildIndex(label, property);
|
|
|
|
auto new_dba = dbms.active();
|
|
|
|
dba.swap(new_dba);
|
|
|
|
}
|
|
|
|
|
2017-06-01 18:09:18 +08:00
|
|
|
Symbol NextSymbol() {
|
|
|
|
return symbol_table_.CreateSymbol("Symbol" + std::to_string(symbol_count++),
|
|
|
|
true);
|
|
|
|
}
|
|
|
|
|
2017-08-03 20:18:19 +08:00
|
|
|
/** Adds the given number of vertices to the DB, of which
|
|
|
|
* the given numbers are labeled and have a property set. */
|
|
|
|
void AddVertices(int vertex_count, int labeled_count,
|
|
|
|
int property_count = 0) {
|
2017-06-01 18:09:18 +08:00
|
|
|
for (int i = 0; i < vertex_count; i++) {
|
2017-08-09 21:36:01 +08:00
|
|
|
auto vertex = dba->InsertVertex();
|
2017-06-01 18:09:18 +08:00
|
|
|
if (i < labeled_count) vertex.add_label(label);
|
2017-08-03 20:18:19 +08:00
|
|
|
if (i < property_count) vertex.PropsSet(property, i);
|
2017-06-01 18:09:18 +08:00
|
|
|
}
|
|
|
|
|
2017-08-09 21:36:01 +08:00
|
|
|
dba->AdvanceCommand();
|
2017-06-01 18:09:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
auto Cost() {
|
2017-08-24 20:24:17 +08:00
|
|
|
CostEstimator<GraphDbAccessor> cost_estimator(*dba);
|
2017-06-01 18:09:18 +08:00
|
|
|
last_op_->Accept(cost_estimator);
|
|
|
|
return cost_estimator.cost();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename TLogicalOperator, typename... TArgs>
|
|
|
|
void MakeOp(TArgs... args) {
|
|
|
|
last_op_ = std::make_shared<TLogicalOperator>(args...);
|
|
|
|
}
|
2017-08-03 20:18:19 +08:00
|
|
|
|
|
|
|
template <typename TValue>
|
|
|
|
Expression *Literal(TValue value) {
|
|
|
|
return storage_.Create<PrimitiveLiteral>(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto InclusiveBound(int bound) {
|
|
|
|
return std::experimental::make_optional(
|
|
|
|
utils::MakeBoundInclusive(Literal(bound)));
|
|
|
|
};
|
|
|
|
|
|
|
|
const std::experimental::nullopt_t nullopt = std::experimental::nullopt;
|
2017-06-01 18:09:18 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
// multiply with 1 to avoid linker error (possibly fixed in CLang >= 3.81)
|
|
|
|
#define EXPECT_COST(COST) EXPECT_FLOAT_EQ(Cost(), 1 * COST)
|
|
|
|
|
|
|
|
TEST_F(QueryCostEstimator, Once) { EXPECT_COST(0); }
|
|
|
|
|
|
|
|
TEST_F(QueryCostEstimator, ScanAll) {
|
2017-08-03 20:18:19 +08:00
|
|
|
AddVertices(100, 30, 20);
|
2017-06-01 18:09:18 +08:00
|
|
|
MakeOp<ScanAll>(last_op_, NextSymbol());
|
|
|
|
EXPECT_COST(100 * CostParam::kScanAll);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(QueryCostEstimator, ScanAllByLabelCardinality) {
|
2017-08-03 20:18:19 +08:00
|
|
|
AddVertices(100, 30, 20);
|
2017-06-01 18:09:18 +08:00
|
|
|
MakeOp<ScanAllByLabel>(last_op_, NextSymbol(), label);
|
|
|
|
EXPECT_COST(30 * CostParam::kScanAllByLabel);
|
|
|
|
}
|
|
|
|
|
2017-08-03 20:18:19 +08:00
|
|
|
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyValueLiteral) {
|
|
|
|
AddVertices(100, 30, 20);
|
|
|
|
MakeOp<ScanAllByLabelPropertyValue>(last_op_, NextSymbol(), label, property,
|
|
|
|
Literal(12));
|
|
|
|
EXPECT_COST(1 * CostParam::MakeScanAllByLabelPropertyValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyValueExpr) {
|
|
|
|
AddVertices(100, 30, 20);
|
|
|
|
MakeOp<ScanAllByLabelPropertyValue>(
|
|
|
|
last_op_, NextSymbol(), label, property,
|
|
|
|
// once we make expression const-folding this test case will fail
|
|
|
|
storage_.Create<UnaryPlusOperator>(Literal(12)));
|
|
|
|
EXPECT_COST(20 * CardParam::kFilter *
|
|
|
|
CostParam::MakeScanAllByLabelPropertyValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeUpper) {
|
|
|
|
AddVertices(100, 30, 20);
|
|
|
|
MakeOp<ScanAllByLabelPropertyRange>(last_op_, NextSymbol(), label, property,
|
|
|
|
nullopt, InclusiveBound(12));
|
|
|
|
// cardinality estimation is exact for very small indexes
|
|
|
|
EXPECT_COST(13 * CostParam::MakeScanAllByLabelPropertyRange);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeLower) {
|
|
|
|
AddVertices(100, 30, 20);
|
|
|
|
MakeOp<ScanAllByLabelPropertyRange>(last_op_, NextSymbol(), label, property,
|
|
|
|
InclusiveBound(17), nullopt);
|
|
|
|
// cardinality estimation is exact for very small indexes
|
|
|
|
EXPECT_COST(3 * CostParam::MakeScanAllByLabelPropertyRange);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeNonLiteral) {
|
|
|
|
AddVertices(100, 30, 20);
|
|
|
|
auto bound = std::experimental::make_optional(
|
|
|
|
utils::MakeBoundInclusive(static_cast<Expression *>(
|
|
|
|
storage_.Create<UnaryPlusOperator>(Literal(12)))));
|
|
|
|
MakeOp<ScanAllByLabelPropertyRange>(last_op_, NextSymbol(), label, property,
|
|
|
|
bound, nullopt);
|
|
|
|
EXPECT_COST(20 * CardParam::kFilter *
|
|
|
|
CostParam::MakeScanAllByLabelPropertyRange);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(QueryCostEstimator, Expand) {
|
2017-09-11 14:51:33 +08:00
|
|
|
MakeOp<Expand>(NextSymbol(), NextSymbol(), EdgeAtom::Direction::IN, nullptr,
|
|
|
|
last_op_, NextSymbol(), false, false);
|
2017-06-01 18:09:18 +08:00
|
|
|
EXPECT_COST(CardParam::kExpand * CostParam::kExpand);
|
|
|
|
}
|
|
|
|
|
2017-08-03 20:18:19 +08:00
|
|
|
TEST_F(QueryCostEstimator, ExpandVariable) {
|
|
|
|
MakeOp<ExpandVariable>(NextSymbol(), NextSymbol(), EdgeAtom::Direction::IN,
|
2017-09-11 14:51:33 +08:00
|
|
|
nullptr, false, nullptr, nullptr, last_op_,
|
|
|
|
NextSymbol(), false, false);
|
2017-08-03 20:18:19 +08:00
|
|
|
EXPECT_COST(CardParam::kExpandVariable * CostParam::kExpandVariable);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(QueryCostEstimator, ExpandBreadthFirst) {
|
|
|
|
MakeOp<ExpandBreadthFirst>(
|
2017-09-12 16:40:38 +08:00
|
|
|
NextSymbol(), NextSymbol(), EdgeAtom::Direction::IN, nullptr, Literal(3),
|
2017-08-03 20:18:19 +08:00
|
|
|
NextSymbol(), NextSymbol(), Literal(true), last_op_, NextSymbol(), false);
|
|
|
|
EXPECT_COST(CardParam::kExpandBreadthFirst * CostParam::kExpandBreadthFirst);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper for testing an operations cost and cardinality.
|
|
|
|
// Only for operations that first increment cost, then modify cardinality.
|
|
|
|
// Intentially a macro (instead of function) for better test feedback.
|
2017-06-01 18:09:18 +08:00
|
|
|
#define TEST_OP(OP, OP_COST_PARAM, OP_CARD_PARAM) \
|
|
|
|
OP; \
|
|
|
|
EXPECT_COST(OP_COST_PARAM); \
|
|
|
|
OP; \
|
|
|
|
EXPECT_COST(OP_COST_PARAM + OP_CARD_PARAM * OP_COST_PARAM);
|
|
|
|
|
|
|
|
TEST_F(QueryCostEstimator, Filter) {
|
2017-08-03 20:18:19 +08:00
|
|
|
TEST_OP(MakeOp<Filter>(last_op_, Literal(true)), CostParam::kFilter,
|
|
|
|
CardParam::kFilter);
|
2017-06-01 18:09:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(QueryCostEstimator, ExpandUniquenessFilter) {
|
|
|
|
TEST_OP(MakeOp<ExpandUniquenessFilter<VertexAccessor>>(last_op_, NextSymbol(),
|
|
|
|
std::vector<Symbol>()),
|
|
|
|
CostParam::kExpandUniquenessFilter,
|
|
|
|
CardParam::kExpandUniquenessFilter);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(QueryCostEstimator, UnwindLiteral) {
|
2017-08-24 20:24:17 +08:00
|
|
|
TEST_OP(
|
|
|
|
MakeOp<query::plan::Unwind>(
|
|
|
|
last_op_,
|
|
|
|
storage_.Create<ListLiteral>(std::vector<Expression *>(7, nullptr)),
|
|
|
|
NextSymbol()),
|
|
|
|
CostParam::kUnwind, 7);
|
2017-06-01 18:09:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(QueryCostEstimator, UnwindNoLiteral) {
|
|
|
|
TEST_OP(MakeOp<query::plan::Unwind>(last_op_, nullptr, NextSymbol()),
|
|
|
|
CostParam::kUnwind, MiscParam::kUnwindNoLiteral);
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef TEST_OP
|
|
|
|
#undef EXPECT_COST
|
|
|
|
//
|
|
|
|
// TODO test cost when ScanAll, Expand, Accumulate, Limit
|
|
|
|
// vs cost for SA, Expand, Limit
|