157 lines
6.6 KiB
C++
157 lines
6.6 KiB
C++
// Copyright 2024 Memgraph Ltd.
|
|
//
|
|
// Use of this software is governed by the Business Source License
|
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
// License, and you may not use this file except in compliance with the Business Source License.
|
|
//
|
|
// As of the Change Date specified in that file, in accordance with
|
|
// the Business Source License, use of this software will be governed
|
|
// by the Apache License, Version 2.0, included in the file
|
|
// licenses/APL.txt.
|
|
|
|
/// @file
|
|
/// This file is an entry point for invoking various planners via the following
|
|
/// API:
|
|
/// * `MakeLogicalPlanForSingleQuery`
|
|
/// * `MakeLogicalPlan`
|
|
|
|
#pragma once
|
|
|
|
#include <utility>
|
|
|
|
#include "query/plan/cost_estimator.hpp"
|
|
#include "query/plan/operator.hpp"
|
|
#include "query/plan/preprocess.hpp"
|
|
#include "query/plan/pretty_print.hpp"
|
|
#include "query/plan/rewrite/edge_type_index_lookup.hpp"
|
|
#include "query/plan/rewrite/index_lookup.hpp"
|
|
#include "query/plan/rewrite/join.hpp"
|
|
#include "query/plan/rule_based_planner.hpp"
|
|
#include "query/plan/variable_start_planner.hpp"
|
|
#include "query/plan/vertex_count_cache.hpp"
|
|
|
|
namespace memgraph::query {
|
|
|
|
class AstStorage;
|
|
class SymbolTable;
|
|
|
|
namespace plan {
|
|
|
|
class PostProcessor final {
|
|
Parameters parameters_;
|
|
|
|
public:
|
|
IndexHints index_hints_{};
|
|
|
|
using ProcessedPlan = std::unique_ptr<LogicalOperator>;
|
|
|
|
explicit PostProcessor(Parameters parameters) : parameters_(std::move(parameters)) {}
|
|
|
|
template <class TDbAccessor>
|
|
PostProcessor(Parameters parameters, std::vector<IndexHint> index_hints, TDbAccessor *db)
|
|
: parameters_(std::move(parameters)), index_hints_(IndexHints(index_hints, db)) {}
|
|
|
|
template <class TPlanningContext>
|
|
std::unique_ptr<LogicalOperator> Rewrite(std::unique_ptr<LogicalOperator> plan, TPlanningContext *context) {
|
|
auto index_lookup_plan =
|
|
RewriteWithIndexLookup(std::move(plan), context->symbol_table, context->ast_storage, context->db, index_hints_);
|
|
auto join_plan =
|
|
RewriteWithJoinRewriter(std::move(index_lookup_plan), context->symbol_table, context->ast_storage, context->db);
|
|
auto edge_index_plan = RewriteWithEdgeTypeIndexRewriter(std::move(join_plan), context->symbol_table,
|
|
context->ast_storage, context->db);
|
|
return edge_index_plan;
|
|
}
|
|
|
|
template <class TVertexCounts>
|
|
PlanCost EstimatePlanCost(const std::unique_ptr<LogicalOperator> &plan, TVertexCounts *vertex_counts,
|
|
const SymbolTable &table) {
|
|
return query::plan::EstimatePlanCost(vertex_counts, table, parameters_, *plan, index_hints_);
|
|
}
|
|
};
|
|
|
|
/// @brief Generates the LogicalOperator tree for a single query and returns the
|
|
/// resulting plan.
|
|
///
|
|
/// @tparam TPlanner Type of the planner used for generation.
|
|
/// @tparam TDbAccessor Type of the database accessor used for generation.
|
|
/// @param vector of @c SingleQueryPart from the single query
|
|
/// @param context PlanningContext used for generating plans.
|
|
/// @return @c PlanResult which depends on the @c TPlanner used.
|
|
///
|
|
/// @sa PlanningContext
|
|
/// @sa RuleBasedPlanner
|
|
/// @sa VariableStartPlanner
|
|
template <template <class> class TPlanner, class TDbAccessor>
|
|
auto MakeLogicalPlanForSingleQuery(QueryParts query_parts, PlanningContext<TDbAccessor> *context) {
|
|
context->bound_symbols.clear();
|
|
return TPlanner<PlanningContext<TDbAccessor>>(context).Plan(query_parts);
|
|
}
|
|
|
|
/// Generates the LogicalOperator tree and returns the resulting plan.
|
|
///
|
|
/// @tparam TPlanningContext Type of the context used.
|
|
/// @tparam TPlanPostProcess Type of the plan post processor used.
|
|
///
|
|
/// @param context PlanningContext used for generating plans.
|
|
/// @param post_process performs plan rewrites and cost estimation.
|
|
/// @param use_variable_planner boolean flag to choose which planner to use.
|
|
///
|
|
/// @return pair consisting of the final `TPlanPostProcess::ProcessedPlan` and
|
|
/// the estimated cost of that plan as a `double`.
|
|
template <class TPlanningContext, class TPlanPostProcess>
|
|
auto MakeLogicalPlan(TPlanningContext *context, TPlanPostProcess *post_process, bool use_variable_planner) {
|
|
auto query_parts = CollectQueryParts(*context->symbol_table, *context->ast_storage, context->query);
|
|
auto &vertex_counts = *context->db;
|
|
double total_cost = std::numeric_limits<double>::max();
|
|
bool curr_uses_index_hint = false;
|
|
|
|
using ProcessedPlan = typename TPlanPostProcess::ProcessedPlan;
|
|
ProcessedPlan plan_with_least_cost;
|
|
|
|
std::optional<ProcessedPlan> curr_plan;
|
|
if (use_variable_planner) {
|
|
auto plans = MakeLogicalPlanForSingleQuery<VariableStartPlanner>(query_parts, context);
|
|
for (auto plan : plans) {
|
|
// Plans are generated lazily and the current plan will disappear, so
|
|
// it's ok to move it.
|
|
auto rewritten_plan = post_process->Rewrite(std::move(plan), context);
|
|
auto plan_cost = post_process->EstimatePlanCost(rewritten_plan, &vertex_counts, *context->symbol_table);
|
|
// if we have a plan that uses index hints, we reject all the plans that don't use index hinting because we want
|
|
// to force the plan using the index hints to be executed
|
|
if (curr_uses_index_hint && !plan_cost.use_index_hints) continue;
|
|
// if a plan uses index hints, and there is currently not yet a plan that utilizes it, we will take it regardless
|
|
if (plan_cost.use_index_hints && !curr_uses_index_hint) {
|
|
curr_uses_index_hint = plan_cost.use_index_hints;
|
|
curr_plan.emplace(std::move(rewritten_plan));
|
|
total_cost = plan_cost.cost;
|
|
continue;
|
|
}
|
|
// if both plans either use or don't use index hints, we want to use the one with the least cost
|
|
if (!curr_plan || plan_cost.cost < total_cost) {
|
|
curr_uses_index_hint = plan_cost.use_index_hints;
|
|
curr_plan.emplace(std::move(rewritten_plan));
|
|
total_cost = plan_cost.cost;
|
|
}
|
|
}
|
|
} else {
|
|
auto plan = MakeLogicalPlanForSingleQuery<RuleBasedPlanner>(query_parts, context);
|
|
auto rewritten_plan = post_process->Rewrite(std::move(plan), context);
|
|
total_cost = post_process->EstimatePlanCost(rewritten_plan, &vertex_counts, *context->symbol_table).cost;
|
|
curr_plan.emplace(std::move(rewritten_plan));
|
|
}
|
|
|
|
plan_with_least_cost = std::move(*curr_plan);
|
|
|
|
return std::make_pair(std::move(plan_with_least_cost), total_cost);
|
|
}
|
|
|
|
template <class TPlanningContext>
|
|
auto MakeLogicalPlan(TPlanningContext *context, const Parameters ¶meters, bool use_variable_planner) {
|
|
PostProcessor post_processor(parameters, context->query->index_hints_, context->db);
|
|
return MakeLogicalPlan(context, &post_processor, use_variable_planner);
|
|
}
|
|
|
|
} // namespace plan
|
|
|
|
} // namespace memgraph::query
|