2022-07-19 18:28:19 +08:00
|
|
|
// Copyright 2022 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.
|
|
|
|
|
|
|
|
#include "query/v2/cypher_query_interpreter.hpp"
|
2022-09-07 23:15:32 +08:00
|
|
|
#include "query/v2/bindings/symbol_generator.hpp"
|
2022-11-28 20:38:38 +08:00
|
|
|
#include "query/v2/request_router.hpp"
|
2022-07-19 18:28:19 +08:00
|
|
|
|
|
|
|
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
|
|
|
DEFINE_HIDDEN_bool(query_cost_planner, true, "Use the cost-estimating query planner.");
|
|
|
|
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
|
|
|
DEFINE_VALIDATED_int32(query_plan_cache_ttl, 60, "Time to live for cached query plans, in seconds.",
|
|
|
|
FLAG_IN_RANGE(0, std::numeric_limits<int32_t>::max()));
|
|
|
|
|
|
|
|
namespace memgraph::query::v2 {
|
|
|
|
CachedPlan::CachedPlan(std::unique_ptr<LogicalPlan> plan) : plan_(std::move(plan)) {}
|
|
|
|
|
|
|
|
ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::string, storage::v3::PropertyValue> ¶ms,
|
2022-07-28 21:36:17 +08:00
|
|
|
utils::SkipList<QueryCacheEntry> *cache, const InterpreterConfig::Query &query_config) {
|
2022-07-19 18:28:19 +08:00
|
|
|
// Strip the query for caching purposes. The process of stripping a query
|
|
|
|
// "normalizes" it by replacing any literals with new parameters. This
|
|
|
|
// results in just the *structure* of the query being taken into account for
|
|
|
|
// caching.
|
|
|
|
frontend::StrippedQuery stripped_query{query_string};
|
|
|
|
|
|
|
|
// Copy over the parameters that were introduced during stripping.
|
|
|
|
Parameters parameters{stripped_query.literals()};
|
|
|
|
|
|
|
|
// Check that all user-specified parameters are provided.
|
|
|
|
for (const auto ¶m_pair : stripped_query.parameters()) {
|
|
|
|
auto it = params.find(param_pair.second);
|
|
|
|
|
|
|
|
if (it == params.end()) {
|
|
|
|
throw query::v2::UnprovidedParameterError("Parameter ${} not provided.", param_pair.second);
|
|
|
|
}
|
|
|
|
|
|
|
|
parameters.Add(param_pair.first, it->second);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cache the query's AST if it isn't already.
|
|
|
|
auto hash = stripped_query.hash();
|
|
|
|
auto accessor = cache->access();
|
|
|
|
auto it = accessor.find(hash);
|
2022-09-07 23:15:32 +08:00
|
|
|
std::unique_ptr<memgraph::frontend::opencypher::Parser<>> parser;
|
2022-07-19 18:28:19 +08:00
|
|
|
|
|
|
|
// Return a copy of both the AST storage and the query.
|
|
|
|
CachedQuery result;
|
|
|
|
bool is_cacheable = true;
|
|
|
|
|
|
|
|
auto get_information_from_cache = [&](const auto &cached_query) {
|
|
|
|
result.ast_storage.properties_ = cached_query.ast_storage.properties_;
|
|
|
|
result.ast_storage.labels_ = cached_query.ast_storage.labels_;
|
|
|
|
result.ast_storage.edge_types_ = cached_query.ast_storage.edge_types_;
|
|
|
|
|
|
|
|
result.query = cached_query.query->Clone(&result.ast_storage);
|
|
|
|
result.required_privileges = cached_query.required_privileges;
|
|
|
|
};
|
|
|
|
|
|
|
|
if (it == accessor.end()) {
|
2022-07-28 21:36:17 +08:00
|
|
|
try {
|
2022-09-07 23:15:32 +08:00
|
|
|
parser = std::make_unique<memgraph::frontend::opencypher::Parser<>>(stripped_query.query());
|
2022-07-28 21:36:17 +08:00
|
|
|
} catch (const SyntaxException &e) {
|
|
|
|
// There is a syntax exception in the stripped query. Re-run the parser
|
|
|
|
// on the original query to get an appropriate error messsage.
|
2022-09-07 23:15:32 +08:00
|
|
|
parser = std::make_unique<memgraph::frontend::opencypher::Parser<>>(query_string);
|
2022-07-28 21:36:17 +08:00
|
|
|
|
|
|
|
// If an exception was not thrown here, the stripper messed something
|
|
|
|
// up.
|
|
|
|
LOG_FATAL("The stripped query can't be parsed, but the original can.");
|
2022-07-19 18:28:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Convert the ANTLR4 parse tree into an AST.
|
|
|
|
AstStorage ast_storage;
|
2022-09-07 23:15:32 +08:00
|
|
|
expr::ParsingContext context{true};
|
|
|
|
memgraph::expr::CypherMainVisitor visitor(context, &ast_storage);
|
2022-07-19 18:28:19 +08:00
|
|
|
|
|
|
|
visitor.visit(parser->tree());
|
|
|
|
|
|
|
|
if (visitor.GetQueryInfo().has_load_csv && !query_config.allow_load_csv) {
|
|
|
|
throw utils::BasicException("Load CSV not allowed on this instance because it was disabled by a config.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (visitor.GetQueryInfo().is_cacheable) {
|
|
|
|
CachedQuery cached_query{std::move(ast_storage), visitor.query(),
|
|
|
|
query::v2::GetRequiredPrivileges(visitor.query())};
|
|
|
|
it = accessor.insert({hash, std::move(cached_query)}).first;
|
|
|
|
|
|
|
|
get_information_from_cache(it->second);
|
|
|
|
} else {
|
|
|
|
result.ast_storage.properties_ = ast_storage.properties_;
|
|
|
|
result.ast_storage.labels_ = ast_storage.labels_;
|
|
|
|
result.ast_storage.edge_types_ = ast_storage.edge_types_;
|
|
|
|
|
|
|
|
result.query = visitor.query()->Clone(&result.ast_storage);
|
|
|
|
result.required_privileges = query::v2::GetRequiredPrivileges(visitor.query());
|
|
|
|
|
|
|
|
is_cacheable = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
get_information_from_cache(it->second);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ParsedQuery{query_string,
|
|
|
|
params,
|
|
|
|
std::move(parameters),
|
|
|
|
std::move(stripped_query),
|
|
|
|
std::move(result.ast_storage),
|
|
|
|
result.query,
|
|
|
|
std::move(result.required_privileges),
|
|
|
|
is_cacheable};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<LogicalPlan> MakeLogicalPlan(AstStorage ast_storage, CypherQuery *query, const Parameters ¶meters,
|
2022-11-28 21:03:07 +08:00
|
|
|
RequestRouterInterface *request_router,
|
2022-07-19 18:28:19 +08:00
|
|
|
const std::vector<Identifier *> &predefined_identifiers) {
|
2022-11-28 21:03:07 +08:00
|
|
|
auto vertex_counts = plan::MakeVertexCountCache(request_router);
|
2022-09-07 23:15:32 +08:00
|
|
|
auto symbol_table = expr::MakeSymbolTable(query, predefined_identifiers);
|
2022-07-19 18:28:19 +08:00
|
|
|
auto planning_context = plan::MakePlanningContext(&ast_storage, &symbol_table, query, &vertex_counts);
|
|
|
|
auto [root, cost] = plan::MakeLogicalPlan(&planning_context, parameters, FLAGS_query_cost_planner);
|
|
|
|
return std::make_unique<SingleNodeLogicalPlan>(std::move(root), cost, std::move(ast_storage),
|
|
|
|
std::move(symbol_table));
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<CachedPlan> CypherQueryToPlan(uint64_t hash, AstStorage ast_storage, CypherQuery *query,
|
|
|
|
const Parameters ¶meters, utils::SkipList<PlanCacheEntry> *plan_cache,
|
2022-11-28 21:03:07 +08:00
|
|
|
RequestRouterInterface *request_router,
|
2022-07-19 18:28:19 +08:00
|
|
|
const std::vector<Identifier *> &predefined_identifiers) {
|
|
|
|
std::optional<utils::SkipList<PlanCacheEntry>::Accessor> plan_cache_access;
|
|
|
|
if (plan_cache) {
|
|
|
|
plan_cache_access.emplace(plan_cache->access());
|
|
|
|
auto it = plan_cache_access->find(hash);
|
|
|
|
if (it != plan_cache_access->end()) {
|
|
|
|
if (it->second->IsExpired()) {
|
|
|
|
plan_cache_access->remove(hash);
|
|
|
|
} else {
|
|
|
|
return it->second;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto plan = std::make_shared<CachedPlan>(
|
2022-11-28 21:03:07 +08:00
|
|
|
MakeLogicalPlan(std::move(ast_storage), query, parameters, request_router, predefined_identifiers));
|
2022-07-19 18:28:19 +08:00
|
|
|
if (plan_cache_access) {
|
|
|
|
plan_cache_access->insert({hash, plan});
|
|
|
|
}
|
|
|
|
return plan;
|
|
|
|
}
|
|
|
|
} // namespace memgraph::query::v2
|