diff --git a/src/query/interpreter.hpp b/src/query/interpreter.hpp index efc955097..d78e2c677 100644 --- a/src/query/interpreter.hpp +++ b/src/query/interpreter.hpp @@ -117,7 +117,7 @@ class Interpreter { ast_storage, ctx.symbol_table_, vertex_counts); double min_cost = std::numeric_limits::max(); for (auto &plan : plans) { - auto cost = EstimatePlanCost(vertex_counts, *plan); + auto cost = EstimatePlanCost(vertex_counts, ctx.parameters_, *plan); if (!logical_plan || cost < min_cost) { // We won't be iterating over plans anymore, so it's ok to invalidate // unique_ptrs inside. @@ -130,7 +130,7 @@ class Interpreter { logical_plan = plan::MakeLogicalPlan( ast_storage, ctx.symbol_table_, vertex_counts); query_plan_cost_estimation = - EstimatePlanCost(vertex_counts, *logical_plan); + EstimatePlanCost(vertex_counts, ctx.parameters_, *logical_plan); } // generate frame based on symbol table max_position diff --git a/src/query/plan/cost_estimator.hpp b/src/query/plan/cost_estimator.hpp index a5fc48fca..30e197b7d 100644 --- a/src/query/plan/cost_estimator.hpp +++ b/src/query/plan/cost_estimator.hpp @@ -1,4 +1,5 @@ #include "query/frontend/ast/ast.hpp" +#include "query/parameters.hpp" #include "query/plan/operator.hpp" #include "query/typed_value.hpp" @@ -64,7 +65,8 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor { using HierarchicalLogicalOperatorVisitor::PreVisit; using HierarchicalLogicalOperatorVisitor::PostVisit; - CostEstimator(const TDbAccessor &db_accessor) : db_accessor_(db_accessor) {} + CostEstimator(const TDbAccessor &db_accessor, const Parameters ¶meters) + : db_accessor_(db_accessor), parameters(parameters) {} bool PostVisit(ScanAll &) override { cardinality_ *= db_accessor_.VerticesCount(); @@ -81,17 +83,10 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor { } bool PostVisit(ScanAllByLabelPropertyValue &logical_op) override { - // this cardinality estimation depends on the property value (expression). - // if it's a literal (const) we can evaluate cardinality exactly, otherwise + // This cardinality estimation depends on the property value (expression). + // If it's a constant, we can evaluate cardinality exactly, otherwise // we estimate - std::experimental::optional property_value = - std::experimental::nullopt; - if (auto *literal = - dynamic_cast(logical_op.expression())) - if (literal->value_.IsPropertyValue()) - property_value = - std::experimental::optional(literal->value_); - + auto property_value = ConstPropertyValue(logical_op.expression()); double factor = 1.0; if (property_value) // get the exact influence based on ScanAll(label, property, value) @@ -206,27 +201,43 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor { // accessor used for cardinality estimates in ScanAll and ScanAllByLabel const TDbAccessor &db_accessor_; + const Parameters ¶meters; void IncrementCost(double param) { cost_ += param * cardinality_; } // converts an optional ScanAll range bound into a property value - // if the bound is present and is a literal expression convertible to + // if the bound is present and is a constant expression convertible to // a property value. otherwise returns nullopt - static std::experimental::optional> - BoundToPropertyValue( + std::experimental::optional> BoundToPropertyValue( std::experimental::optional bound) { - if (bound) - if (auto *literal = dynamic_cast(bound->value())) - return std::experimental::make_optional( - utils::Bound(literal->value_, bound->type())); + if (bound) { + auto property_value = ConstPropertyValue(bound->value()); + if (property_value) + return utils::Bound(*property_value, bound->type()); + } + return std::experimental::nullopt; + } + + // If the expression is a constant property value, it is returned. Otherwise, + // return nullopt. + std::experimental::optional ConstPropertyValue( + const Expression *expression) { + if (auto *literal = dynamic_cast(expression)) { + if (literal->value_.IsPropertyValue()) return literal->value_; + } else if (auto *param_lookup = + dynamic_cast(expression)) { + auto value = parameters.AtTokenPosition(param_lookup->token_position_); + if (value.IsPropertyValue()) return value; + } return std::experimental::nullopt; } }; /** Returns the estimated cost of the given plan. */ template -double EstimatePlanCost(const TDbAccessor &db, LogicalOperator &plan) { - CostEstimator estimator(db); +double EstimatePlanCost(const TDbAccessor &db, const Parameters ¶meters, + LogicalOperator &plan) { + CostEstimator estimator(db, parameters); plan.Accept(estimator); return estimator.cost(); } diff --git a/tests/benchmark/query/planner.cpp b/tests/benchmark/query/planner.cpp index 3c21cb8b5..bc85eaa2d 100644 --- a/tests/benchmark/query/planner.cpp +++ b/tests/benchmark/query/planner.cpp @@ -95,6 +95,7 @@ static void BM_PlanAndEstimateIndexedMatching(benchmark::State &state) { std::tie(label, prop) = CreateIndexedVertices(index_count, vertex_count, dbms); auto dba = dbms.active(); + Parameters parameters; while (state.KeepRunning()) { state.PauseTiming(); query::AstTreeStorage storage; @@ -108,7 +109,7 @@ static void BM_PlanAndEstimateIndexedMatching(benchmark::State &state) { query::plan::MakeLogicalPlan( storage, symbol_table, *dba); for (auto &plan : plans) { - query::plan::EstimatePlanCost(*dba, *plan); + query::plan::EstimatePlanCost(*dba, parameters, *plan); } } } @@ -124,6 +125,7 @@ static void BM_PlanAndEstimateIndexedMatchingWithCachedCounts( CreateIndexedVertices(index_count, vertex_count, dbms); auto dba = dbms.active(); auto vertex_counts = query::plan::MakeVertexCountCache(*dba); + Parameters parameters; while (state.KeepRunning()) { state.PauseTiming(); query::AstTreeStorage storage; @@ -137,7 +139,7 @@ static void BM_PlanAndEstimateIndexedMatchingWithCachedCounts( query::plan::MakeLogicalPlan( storage, symbol_table, vertex_counts); for (auto &plan : plans) { - query::plan::EstimatePlanCost(vertex_counts, *plan); + query::plan::EstimatePlanCost(vertex_counts, parameters, *plan); } } } diff --git a/tests/manual/query_planner.cpp b/tests/manual/query_planner.cpp index a5428f4a2..49bb471a6 100644 --- a/tests/manual/query_planner.cpp +++ b/tests/manual/query_planner.cpp @@ -613,8 +613,10 @@ auto MakeLogicalPlans(query::AstTreeStorage &ast, plans_with_cost; auto plans = query::plan::MakeLogicalPlan( ast, symbol_table, dba); + Parameters parameters; for (auto &plan : plans) { - query::plan::CostEstimator estimator(dba); + query::plan::CostEstimator estimator(dba, + parameters); plan->Accept(estimator); plans_with_cost.emplace_back(std::move(plan), estimator.cost()); } diff --git a/tests/unit/query_cost_estimator.cpp b/tests/unit/query_cost_estimator.cpp index 64466e867..3f2bc988a 100644 --- a/tests/unit/query_cost_estimator.cpp +++ b/tests/unit/query_cost_estimator.cpp @@ -33,6 +33,7 @@ class QueryCostEstimator : public ::testing::Test { AstTreeStorage storage_; SymbolTable symbol_table_; + Parameters parameters_; int symbol_count = 0; void SetUp() { @@ -61,7 +62,7 @@ class QueryCostEstimator : public ::testing::Test { } auto Cost() { - CostEstimator cost_estimator(*dba); + CostEstimator cost_estimator(*dba, parameters_); last_op_->Accept(cost_estimator); return cost_estimator.cost(); } @@ -76,9 +77,15 @@ class QueryCostEstimator : public ::testing::Test { return storage_.Create(value); } - auto InclusiveBound(int bound) { + Expression *Parameter(const TypedValue &value) { + int token_position = parameters_.size(); + parameters_.Add(token_position, value); + return storage_.Create(token_position); + } + + auto InclusiveBound(Expression *expression) { return std::experimental::make_optional( - utils::MakeBoundInclusive(Literal(bound))); + utils::MakeBoundInclusive(expression)); }; const std::experimental::nullopt_t nullopt = std::experimental::nullopt; @@ -101,48 +108,58 @@ TEST_F(QueryCostEstimator, ScanAllByLabelCardinality) { EXPECT_COST(30 * CostParam::kScanAllByLabel); } -TEST_F(QueryCostEstimator, ScanAllByLabelPropertyValueLiteral) { +TEST_F(QueryCostEstimator, ScanAllByLabelPropertyValueConstant) { AddVertices(100, 30, 20); - MakeOp(last_op_, NextSymbol(), label, property, - Literal(12)); - EXPECT_COST(1 * CostParam::MakeScanAllByLabelPropertyValue); + for (auto const_val : {Literal(12), Parameter(12)}) { + MakeOp(nullptr, NextSymbol(), label, property, + const_val); + EXPECT_COST(1 * CostParam::MakeScanAllByLabelPropertyValue); + } } -TEST_F(QueryCostEstimator, ScanAllByLabelPropertyValueExpr) { +TEST_F(QueryCostEstimator, ScanAllByLabelPropertyValueConstExpr) { AddVertices(100, 30, 20); - MakeOp( - last_op_, NextSymbol(), label, property, - // once we make expression const-folding this test case will fail - storage_.Create(Literal(12))); - EXPECT_COST(20 * CardParam::kFilter * - CostParam::MakeScanAllByLabelPropertyValue); + for (auto const_val : {Literal(12), Parameter(12)}) { + MakeOp( + nullptr, NextSymbol(), label, property, + // once we make expression const-folding this test case will fail + storage_.Create(const_val)); + EXPECT_COST(20 * CardParam::kFilter * + CostParam::MakeScanAllByLabelPropertyValue); + } } -TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeUpper) { +TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeUpperConstant) { AddVertices(100, 30, 20); - MakeOp(last_op_, NextSymbol(), label, property, - nullopt, InclusiveBound(12)); - // cardinality estimation is exact for very small indexes - EXPECT_COST(13 * CostParam::MakeScanAllByLabelPropertyRange); + for (auto const_val : {Literal(12), Parameter(12)}) { + MakeOp(nullptr, NextSymbol(), label, property, + nullopt, InclusiveBound(const_val)); + // cardinality estimation is exact for very small indexes + EXPECT_COST(13 * CostParam::MakeScanAllByLabelPropertyRange); + } } -TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeLower) { +TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeLowerConstant) { AddVertices(100, 30, 20); - MakeOp(last_op_, NextSymbol(), label, property, - InclusiveBound(17), nullopt); - // cardinality estimation is exact for very small indexes - EXPECT_COST(3 * CostParam::MakeScanAllByLabelPropertyRange); + for (auto const_val : {Literal(17), Parameter(17)}) { + MakeOp(nullptr, NextSymbol(), label, property, + InclusiveBound(const_val), nullopt); + // cardinality estimation is exact for very small indexes + EXPECT_COST(3 * CostParam::MakeScanAllByLabelPropertyRange); + } } -TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeNonLiteral) { +TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeConstExpr) { AddVertices(100, 30, 20); - auto bound = std::experimental::make_optional( - utils::MakeBoundInclusive(static_cast( - storage_.Create(Literal(12))))); - MakeOp(last_op_, NextSymbol(), label, property, - bound, nullopt); - EXPECT_COST(20 * CardParam::kFilter * - CostParam::MakeScanAllByLabelPropertyRange); + for (auto const_val : {Literal(12), Parameter(12)}) { + auto bound = std::experimental::make_optional( + utils::MakeBoundInclusive(static_cast( + storage_.Create(const_val)))); + MakeOp(nullptr, NextSymbol(), label, property, + bound, nullopt); + EXPECT_COST(20 * CardParam::kFilter * + CostParam::MakeScanAllByLabelPropertyRange); + } } TEST_F(QueryCostEstimator, Expand) {