2017-03-22 23:38:43 +08:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include <ctime>
|
2017-06-05 18:44:18 +08:00
|
|
|
#include <limits>
|
2017-03-22 23:38:43 +08:00
|
|
|
|
2017-06-21 17:29:13 +08:00
|
|
|
#include <gflags/gflags.h>
|
|
|
|
#include <glog/logging.h>
|
|
|
|
|
2017-03-22 23:38:43 +08:00
|
|
|
#include "database/graph_db_accessor.hpp"
|
|
|
|
#include "query/context.hpp"
|
2017-07-20 00:14:59 +08:00
|
|
|
#include "query/exceptions.hpp"
|
2017-03-22 23:38:43 +08:00
|
|
|
#include "query/frontend/ast/cypher_main_visitor.hpp"
|
|
|
|
#include "query/frontend/opencypher/parser.hpp"
|
|
|
|
#include "query/frontend/semantic/symbol_generator.hpp"
|
2017-06-15 00:53:02 +08:00
|
|
|
#include "query/frontend/stripped.hpp"
|
2017-04-13 16:01:16 +08:00
|
|
|
#include "query/interpret/frame.hpp"
|
2017-06-01 18:09:18 +08:00
|
|
|
#include "query/plan/cost_estimator.hpp"
|
2017-06-08 00:28:31 +08:00
|
|
|
#include "query/plan/planner.hpp"
|
2017-08-04 20:12:06 +08:00
|
|
|
#include "threading/sync/spinlock.hpp"
|
2017-07-15 01:33:45 +08:00
|
|
|
#include "utils/timer.hpp"
|
2017-03-22 23:38:43 +08:00
|
|
|
|
2017-06-16 15:22:30 +08:00
|
|
|
// TODO: Remove ast_cache flag and add flag that limits cache size.
|
2017-06-15 00:53:02 +08:00
|
|
|
DECLARE_bool(ast_cache);
|
2017-06-16 15:22:30 +08:00
|
|
|
DECLARE_bool(query_cost_planner);
|
2017-06-15 00:53:02 +08:00
|
|
|
|
2017-03-22 23:38:43 +08:00
|
|
|
namespace query {
|
|
|
|
|
2017-06-21 17:29:13 +08:00
|
|
|
class Interpreter {
|
2017-06-08 00:28:31 +08:00
|
|
|
public:
|
2017-06-21 17:29:13 +08:00
|
|
|
Interpreter() {}
|
2017-06-08 00:28:31 +08:00
|
|
|
template <typename Stream>
|
|
|
|
void Interpret(const std::string &query, GraphDbAccessor &db_accessor,
|
2017-07-20 00:14:59 +08:00
|
|
|
Stream &stream,
|
|
|
|
const std::map<std::string, TypedValue> ¶ms) {
|
2017-07-15 01:33:45 +08:00
|
|
|
utils::Timer frontend_timer;
|
2017-06-08 00:28:31 +08:00
|
|
|
Config config;
|
|
|
|
Context ctx(config, db_accessor);
|
|
|
|
std::map<std::string, TypedValue> summary;
|
|
|
|
|
2017-06-15 00:53:02 +08:00
|
|
|
// stripped query -> high level tree
|
|
|
|
AstTreeStorage ast_storage = [&]() {
|
|
|
|
if (!FLAGS_ast_cache) {
|
2017-07-20 00:14:59 +08:00
|
|
|
// This is totally fine, since we don't really expect anyone to turn off
|
|
|
|
// the cache.
|
|
|
|
if (!params.empty()) {
|
|
|
|
throw utils::NotYetImplemented(
|
|
|
|
"Params not implemented if ast cache is turned off");
|
|
|
|
}
|
|
|
|
|
2017-06-15 00:53:02 +08:00
|
|
|
// stripped query -> AST
|
2017-08-03 19:28:50 +08:00
|
|
|
auto parser = [&] {
|
|
|
|
// Be careful about unlocking since parser can throw.
|
2017-08-04 20:12:06 +08:00
|
|
|
std::unique_lock<SpinLock> guard(antlr_lock_);
|
2017-08-03 19:28:50 +08:00
|
|
|
return std::make_unique<frontend::opencypher::Parser>(query);
|
|
|
|
}();
|
|
|
|
auto low_level_tree = parser->tree();
|
2017-06-08 00:28:31 +08:00
|
|
|
|
2017-06-15 00:53:02 +08:00
|
|
|
// AST -> high level tree
|
|
|
|
frontend::CypherMainVisitor visitor(ctx);
|
|
|
|
visitor.visit(low_level_tree);
|
|
|
|
return std::move(visitor.storage());
|
|
|
|
}
|
2017-06-08 00:28:31 +08:00
|
|
|
|
2017-06-16 19:40:42 +08:00
|
|
|
// query -> stripped query
|
|
|
|
StrippedQuery stripped(query);
|
|
|
|
|
2017-06-15 00:53:02 +08:00
|
|
|
auto ast_cache_accessor = ast_cache_.access();
|
2017-06-16 19:40:42 +08:00
|
|
|
auto it = ast_cache_accessor.find(stripped.hash());
|
2017-06-15 00:53:02 +08:00
|
|
|
if (it == ast_cache_accessor.end()) {
|
|
|
|
// stripped query -> AST
|
2017-08-03 19:28:50 +08:00
|
|
|
auto parser = [&] {
|
|
|
|
// Be careful about unlocking since parser can throw.
|
2017-08-04 20:12:06 +08:00
|
|
|
std::unique_lock<SpinLock> guard(antlr_lock_);
|
2017-08-03 19:28:50 +08:00
|
|
|
return std::make_unique<frontend::opencypher::Parser>(
|
|
|
|
stripped.query());
|
|
|
|
}();
|
|
|
|
auto low_level_tree = parser->tree();
|
2017-06-15 00:53:02 +08:00
|
|
|
|
|
|
|
// AST -> high level tree
|
|
|
|
frontend::CypherMainVisitor visitor(ctx);
|
|
|
|
visitor.visit(low_level_tree);
|
|
|
|
|
|
|
|
// Cache it.
|
|
|
|
it = ast_cache_accessor
|
|
|
|
.insert(stripped.hash(),
|
|
|
|
CachedAst(std::move(visitor.storage())))
|
|
|
|
.first;
|
|
|
|
}
|
2017-07-20 00:14:59 +08:00
|
|
|
|
|
|
|
// Update literals map with provided parameters.
|
|
|
|
auto literals = stripped.literals();
|
|
|
|
for (const auto ¶m_pair : stripped.parameters()) {
|
|
|
|
auto param_it = params.find(param_pair.second);
|
|
|
|
if (param_it == params.end()) {
|
|
|
|
throw query::UnprovidedParameterError(
|
|
|
|
fmt::format("Parameter$ {} not provided", param_pair.second));
|
|
|
|
}
|
|
|
|
literals.Add(param_pair.first, param_it->second);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Plug literals, parameters and named expressions.
|
|
|
|
return it->second.Plug(literals, stripped.named_expressions());
|
2017-06-15 00:53:02 +08:00
|
|
|
}();
|
2017-07-15 01:33:45 +08:00
|
|
|
auto frontend_time = frontend_timer.Elapsed();
|
2017-06-08 00:28:31 +08:00
|
|
|
|
2017-07-15 01:33:45 +08:00
|
|
|
utils::Timer planning_timer;
|
2017-06-08 00:28:31 +08:00
|
|
|
// symbol table fill
|
|
|
|
SymbolTable symbol_table;
|
|
|
|
SymbolGenerator symbol_generator(symbol_table);
|
2017-06-15 00:53:02 +08:00
|
|
|
ast_storage.query()->Accept(symbol_generator);
|
2017-06-08 00:28:31 +08:00
|
|
|
|
|
|
|
// high level tree -> logical plan
|
|
|
|
std::unique_ptr<plan::LogicalOperator> logical_plan;
|
|
|
|
double query_plan_cost_estimation = 0.0;
|
|
|
|
if (FLAGS_query_cost_planner) {
|
|
|
|
auto plans = plan::MakeLogicalPlan<plan::VariableStartPlanner>(
|
2017-07-07 19:03:01 +08:00
|
|
|
ast_storage, symbol_table, db_accessor);
|
2017-06-08 00:28:31 +08:00
|
|
|
double min_cost = std::numeric_limits<double>::max();
|
|
|
|
for (auto &plan : plans) {
|
2017-08-24 20:24:17 +08:00
|
|
|
plan::CostEstimator<GraphDbAccessor> estimator(db_accessor);
|
2017-06-08 00:28:31 +08:00
|
|
|
plan->Accept(estimator);
|
|
|
|
auto cost = estimator.cost();
|
|
|
|
if (!logical_plan || cost < min_cost) {
|
|
|
|
// We won't be iterating over plans anymore, so it's ok to invalidate
|
|
|
|
// unique_ptrs inside.
|
|
|
|
logical_plan = std::move(plan);
|
|
|
|
min_cost = cost;
|
|
|
|
}
|
2017-06-05 18:44:18 +08:00
|
|
|
}
|
2017-06-08 00:28:31 +08:00
|
|
|
query_plan_cost_estimation = min_cost;
|
|
|
|
} else {
|
|
|
|
logical_plan = plan::MakeLogicalPlan<plan::RuleBasedPlanner>(
|
2017-07-07 19:03:01 +08:00
|
|
|
ast_storage, symbol_table, db_accessor);
|
2017-08-24 20:24:17 +08:00
|
|
|
plan::CostEstimator<GraphDbAccessor> cost_estimator(db_accessor);
|
2017-06-08 00:28:31 +08:00
|
|
|
logical_plan->Accept(cost_estimator);
|
|
|
|
query_plan_cost_estimation = cost_estimator.cost();
|
2017-06-05 18:44:18 +08:00
|
|
|
}
|
2017-05-23 17:28:41 +08:00
|
|
|
|
2017-06-08 00:28:31 +08:00
|
|
|
// generate frame based on symbol table max_position
|
|
|
|
Frame frame(symbol_table.max_position());
|
2017-07-15 01:33:45 +08:00
|
|
|
auto planning_time = planning_timer.Elapsed();
|
2017-06-08 00:28:31 +08:00
|
|
|
|
2017-07-15 01:33:45 +08:00
|
|
|
utils::Timer execution_timer;
|
2017-06-08 00:28:31 +08:00
|
|
|
std::vector<std::string> header;
|
|
|
|
std::vector<Symbol> output_symbols(
|
|
|
|
logical_plan->OutputSymbols(symbol_table));
|
|
|
|
if (!output_symbols.empty()) {
|
|
|
|
// Since we have output symbols, this means that the query contains RETURN
|
|
|
|
// clause, so stream out the results.
|
|
|
|
|
|
|
|
// generate header
|
|
|
|
for (const auto &symbol : output_symbols) header.push_back(symbol.name());
|
|
|
|
stream.Header(header);
|
|
|
|
|
|
|
|
// stream out results
|
|
|
|
auto cursor = logical_plan->MakeCursor(db_accessor);
|
|
|
|
while (cursor->Pull(frame, symbol_table)) {
|
|
|
|
std::vector<TypedValue> values;
|
|
|
|
for (const auto &symbol : output_symbols)
|
|
|
|
values.emplace_back(frame[symbol]);
|
|
|
|
stream.Result(values);
|
|
|
|
}
|
|
|
|
} else if (dynamic_cast<plan::CreateNode *>(logical_plan.get()) ||
|
|
|
|
dynamic_cast<plan::CreateExpand *>(logical_plan.get()) ||
|
|
|
|
dynamic_cast<plan::SetProperty *>(logical_plan.get()) ||
|
|
|
|
dynamic_cast<plan::SetProperties *>(logical_plan.get()) ||
|
|
|
|
dynamic_cast<plan::SetLabels *>(logical_plan.get()) ||
|
|
|
|
dynamic_cast<plan::RemoveProperty *>(logical_plan.get()) ||
|
|
|
|
dynamic_cast<plan::RemoveLabels *>(logical_plan.get()) ||
|
|
|
|
dynamic_cast<plan::Delete *>(logical_plan.get()) ||
|
2017-07-03 16:38:58 +08:00
|
|
|
dynamic_cast<plan::Merge *>(logical_plan.get()) ||
|
|
|
|
dynamic_cast<plan::CreateIndex *>(logical_plan.get())) {
|
2017-06-08 00:28:31 +08:00
|
|
|
stream.Header(header);
|
|
|
|
auto cursor = logical_plan->MakeCursor(db_accessor);
|
|
|
|
while (cursor->Pull(frame, symbol_table)) continue;
|
|
|
|
} else {
|
|
|
|
throw QueryRuntimeException("Unknown top level LogicalOperator");
|
2017-03-22 23:38:43 +08:00
|
|
|
}
|
2017-07-15 01:33:45 +08:00
|
|
|
auto execution_time = execution_timer.Elapsed();
|
2017-06-08 00:28:31 +08:00
|
|
|
|
2017-09-04 20:16:12 +08:00
|
|
|
summary["parsing_time"] = frontend_time.count();
|
|
|
|
summary["planning_time"] = planning_time.count();
|
|
|
|
summary["plan_execution_time"] = execution_time.count();
|
|
|
|
summary["cost_estimate"] = query_plan_cost_estimation;
|
2017-06-08 00:28:31 +08:00
|
|
|
|
|
|
|
// TODO: set summary['type'] based on transaction metadata
|
|
|
|
// the type can't be determined based only on top level LogicalOp
|
|
|
|
// (for example MATCH DELETE RETURN will have Produce as it's top)
|
|
|
|
// for now always use "rw" because something must be set, but it doesn't
|
|
|
|
// have to be correct (for Bolt clients)
|
|
|
|
summary["type"] = "rw";
|
|
|
|
stream.Summary(summary);
|
2017-09-04 19:37:01 +08:00
|
|
|
DLOG(INFO) << "Executed " << query << ", " << summary;
|
2017-03-22 23:38:43 +08:00
|
|
|
}
|
|
|
|
|
2017-06-08 00:28:31 +08:00
|
|
|
private:
|
2017-06-15 00:53:02 +08:00
|
|
|
ConcurrentMap<HashType, CachedAst> ast_cache_;
|
2017-08-03 19:28:50 +08:00
|
|
|
// Antlr has singleton instance that is shared between threads. It is
|
|
|
|
// protected by locks inside of antlr. Unfortunately, they are not protected
|
|
|
|
// in a very good way. Once we have antlr version without race conditions we
|
|
|
|
// can remove this lock. This will probably never happen since antlr
|
|
|
|
// developers introduce more bugs in each version. Fortunately, we have cache
|
|
|
|
// so this lock probably won't impact performance much...
|
|
|
|
SpinLock antlr_lock_;
|
2017-06-08 00:28:31 +08:00
|
|
|
};
|
2017-04-26 22:12:39 +08:00
|
|
|
|
|
|
|
} // namespace query
|