Decouple interactive planning manual test (#585)
This commit is contained in:
parent
e5437080c5
commit
1c3bb969e9
@ -35,12 +35,18 @@ target_link_libraries(${test_prefix}kvstore_console mg-kvstore gflags mg-utils)
|
||||
add_manual_test(query_hash.cpp)
|
||||
target_link_libraries(${test_prefix}query_hash mg-query)
|
||||
|
||||
add_manual_test(query_planner.cpp interactive_planning.cpp)
|
||||
add_manual_test(query_planner.cpp interactive/planning.cpp)
|
||||
target_link_libraries(${test_prefix}query_planner mg-query)
|
||||
if (READLINE_FOUND)
|
||||
target_link_libraries(${test_prefix}query_planner readline)
|
||||
endif()
|
||||
|
||||
add_manual_test(query_execution_dummy.cpp)
|
||||
target_link_libraries(${test_prefix}query_execution_dummy mg-query)
|
||||
if (READLINE_FOUND)
|
||||
target_link_libraries(${test_prefix}query_execution_dummy readline)
|
||||
endif()
|
||||
|
||||
add_manual_test(expression_pretty_printer.cpp)
|
||||
target_link_libraries(${test_prefix}expression_pretty_printer mg-query)
|
||||
|
||||
|
@ -9,82 +9,13 @@
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "interactive_planning.hpp"
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "query/context.hpp"
|
||||
#include "query/db_accessor.hpp"
|
||||
#include "query/frontend/ast/cypher_main_visitor.hpp"
|
||||
#include "query/frontend/opencypher/parser.hpp"
|
||||
#include "query/frontend/semantic/symbol_generator.hpp"
|
||||
#include "query/plan/operator.hpp"
|
||||
#include "query/plan/planner.hpp"
|
||||
#include "query/plan/pretty_print.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "readline.hpp"
|
||||
#include "timer.hpp"
|
||||
|
||||
DEFINE_string(save_mock_db_file, "", "File where the mock database should be saved (on exit)");
|
||||
|
||||
DEFINE_string(load_mock_db_file, "", "File from which the mock database should be loaded");
|
||||
|
||||
#ifdef HAS_READLINE
|
||||
// TODO: This should probably be moved to some utils file.
|
||||
|
||||
#include "readline/history.h"
|
||||
#include "readline/readline.h"
|
||||
|
||||
/**
|
||||
* Helper function that reads a line from the
|
||||
* standard input using the 'readline' lib.
|
||||
* Adds support for history and reverse-search.
|
||||
*
|
||||
* @param prompt The prompt to display.
|
||||
* @return A single command the user entered, or nullopt on EOF.
|
||||
*/
|
||||
std::optional<std::string> ReadLine(const std::string &prompt) {
|
||||
char *line = readline(prompt.c_str());
|
||||
if (!line) return std::nullopt;
|
||||
|
||||
if (*line) add_history(line);
|
||||
std::string r_val(line);
|
||||
free(line);
|
||||
return r_val;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::optional<std::string> ReadLine(const std::string &prompt) {
|
||||
std::cout << prompt;
|
||||
std::string line;
|
||||
std::getline(std::cin, line);
|
||||
if (std::cin.eof()) return std::nullopt;
|
||||
return line;
|
||||
}
|
||||
|
||||
#endif // HAS_READLINE
|
||||
|
||||
// Repeats the prompt untile the user inputs an integer.
|
||||
int64_t ReadInt(const std::string &prompt) {
|
||||
int64_t val = 0;
|
||||
std::stringstream ss;
|
||||
do {
|
||||
auto line = ReadLine(prompt);
|
||||
if (!line) continue;
|
||||
ss.str(*line);
|
||||
ss.clear();
|
||||
ss >> val;
|
||||
} while (ss.fail() || !ss.eof());
|
||||
return val;
|
||||
}
|
||||
|
||||
bool AskYesNo(const std::string &prompt) {
|
||||
inline bool AskYesNo(const std::string &prompt) {
|
||||
while (auto line = ReadLine(prompt + " (y/n) ")) {
|
||||
if (*line == "y" || *line == "Y") return true;
|
||||
if (*line == "n" || *line == "N") return false;
|
||||
@ -92,48 +23,6 @@ bool AskYesNo(const std::string &prompt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
void Start() {
|
||||
duration_ = duration_.zero();
|
||||
start_time_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Pause() {
|
||||
if (pause_ == 0) {
|
||||
duration_ += std::chrono::steady_clock::now() - start_time_;
|
||||
}
|
||||
++pause_;
|
||||
}
|
||||
|
||||
void Resume() {
|
||||
if (pause_ == 1) {
|
||||
start_time_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
pause_ = std::max(0, pause_ - 1);
|
||||
}
|
||||
|
||||
template <class TFun>
|
||||
auto WithPause(const TFun &fun) {
|
||||
Pause();
|
||||
auto ret = fun();
|
||||
Resume();
|
||||
return std::move(ret);
|
||||
}
|
||||
|
||||
std::chrono::duration<double> Elapsed() {
|
||||
if (pause_ == 0) {
|
||||
return duration_ + (std::chrono::steady_clock::now() - start_time_);
|
||||
}
|
||||
return duration_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::duration<double> duration_;
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time_;
|
||||
int pause_ = 0;
|
||||
};
|
||||
|
||||
// Dummy DbAccessor which forwards user input for various vertex counts.
|
||||
class InteractiveDbAccessor {
|
||||
public:
|
||||
@ -343,164 +232,3 @@ class InteractiveDbAccessor {
|
||||
return memgraph::storage::PropertyValue(val);
|
||||
}
|
||||
};
|
||||
|
||||
DEFCOMMAND(Top) {
|
||||
int64_t n_plans = 0;
|
||||
std::stringstream ss(args[0]);
|
||||
ss >> n_plans;
|
||||
if (ss.fail() || !ss.eof()) return;
|
||||
n_plans = std::min(static_cast<int64_t>(plans.size()), n_plans);
|
||||
for (int64_t i = 0; i < n_plans; ++i) {
|
||||
std::cout << "---- Plan #" << i << " ---- " << std::endl;
|
||||
std::cout << "cost: " << plans[i].cost << std::endl;
|
||||
memgraph::query::plan::PrettyPrint(dba, plans[i].final_plan.get());
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
DEFCOMMAND(Show) {
|
||||
int64_t plan_ix = 0;
|
||||
std::stringstream ss(args[0]);
|
||||
ss >> plan_ix;
|
||||
if (ss.fail() || !ss.eof() || plan_ix >= plans.size()) return;
|
||||
const auto &plan = plans[plan_ix].final_plan;
|
||||
auto cost = plans[plan_ix].cost;
|
||||
std::cout << "Plan cost: " << cost << std::endl;
|
||||
memgraph::query::plan::PrettyPrint(dba, plan.get());
|
||||
}
|
||||
|
||||
DEFCOMMAND(ShowUnoptimized) {
|
||||
int64_t plan_ix = 0;
|
||||
std::stringstream ss(args[0]);
|
||||
ss >> plan_ix;
|
||||
if (ss.fail() || !ss.eof() || plan_ix >= plans.size()) return;
|
||||
const auto &plan = plans[plan_ix].unoptimized_plan;
|
||||
memgraph::query::plan::PrettyPrint(dba, plan.get());
|
||||
}
|
||||
|
||||
DEFCOMMAND(Help);
|
||||
|
||||
std::map<std::string, Command> commands = {
|
||||
{"top", {TopCommand, 1, "Show top N plans"}},
|
||||
{"show", {ShowCommand, 1, "Show the Nth plan"}},
|
||||
{"show-unoptimized", {ShowUnoptimizedCommand, 1, "Show the Nth plan in its original, unoptimized form"}},
|
||||
{"help", {HelpCommand, 0, "Show available commands"}},
|
||||
};
|
||||
|
||||
void AddCommand(const std::string &name, const Command &command) { commands[name] = command; }
|
||||
|
||||
DEFCOMMAND(Help) {
|
||||
std::cout << "Available commands:" << std::endl;
|
||||
for (const auto &command : commands) {
|
||||
std::cout << command.first;
|
||||
for (int i = 1; i <= command.second.arg_count; ++i) {
|
||||
std::cout << " arg" << i;
|
||||
}
|
||||
std::cout << " -- " << command.second.documentation << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ExaminePlans(memgraph::query::DbAccessor *dba, const memgraph::query::SymbolTable &symbol_table,
|
||||
std::vector<InteractivePlan> &plans, const memgraph::query::AstStorage &ast) {
|
||||
while (true) {
|
||||
auto line = ReadLine("plan? ");
|
||||
if (!line || *line == "quit") break;
|
||||
auto words = memgraph::utils::Split(memgraph::utils::ToLowerCase(*line));
|
||||
if (words.empty()) continue;
|
||||
auto command_name = words[0];
|
||||
std::vector<std::string> args(words.begin() + 1, words.end());
|
||||
auto command_it = commands.find(command_name);
|
||||
if (command_it == commands.end()) {
|
||||
std::cout << "Undefined command: '" << command_name << "'. Try 'help'." << std::endl;
|
||||
continue;
|
||||
}
|
||||
const auto &command = command_it->second;
|
||||
if (args.size() < command.arg_count) {
|
||||
std::cout << command_name << " expects " << command.arg_count << " arguments" << std::endl;
|
||||
continue;
|
||||
}
|
||||
command.function(*dba, symbol_table, plans, args, ast);
|
||||
}
|
||||
}
|
||||
|
||||
memgraph::query::Query *MakeAst(const std::string &query, memgraph::query::AstStorage *storage) {
|
||||
memgraph::query::frontend::ParsingContext parsing_context;
|
||||
parsing_context.is_query_cached = false;
|
||||
// query -> AST
|
||||
auto parser = std::make_unique<memgraph::query::frontend::opencypher::Parser>(query);
|
||||
// AST -> high level tree
|
||||
memgraph::query::frontend::CypherMainVisitor visitor(parsing_context, storage);
|
||||
visitor.visit(parser->tree());
|
||||
return visitor.query();
|
||||
}
|
||||
|
||||
// Returns a list of InteractivePlan instances, sorted in the ascending order by
|
||||
// cost.
|
||||
auto MakeLogicalPlans(memgraph::query::CypherQuery *query, memgraph::query::AstStorage &ast,
|
||||
memgraph::query::SymbolTable &symbol_table, InteractiveDbAccessor *dba) {
|
||||
auto query_parts = memgraph::query::plan::CollectQueryParts(symbol_table, ast, query);
|
||||
std::vector<InteractivePlan> interactive_plans;
|
||||
auto ctx = memgraph::query::plan::MakePlanningContext(&ast, &symbol_table, query, dba);
|
||||
if (query_parts.query_parts.size() <= 0) {
|
||||
std::cerr << "Failed to extract query parts" << std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
memgraph::query::Parameters parameters;
|
||||
memgraph::query::plan::PostProcessor post_process(parameters);
|
||||
auto plans = memgraph::query::plan::MakeLogicalPlanForSingleQuery<memgraph::query::plan::VariableStartPlanner>(
|
||||
query_parts.query_parts.at(0).single_query_parts, &ctx);
|
||||
for (auto plan : plans) {
|
||||
memgraph::query::AstStorage ast_copy;
|
||||
auto unoptimized_plan = plan->Clone(&ast_copy);
|
||||
auto rewritten_plan = post_process.Rewrite(std::move(plan), &ctx);
|
||||
double cost = post_process.EstimatePlanCost(rewritten_plan, dba);
|
||||
interactive_plans.push_back(
|
||||
InteractivePlan{std::move(unoptimized_plan), std::move(ast_copy), std::move(rewritten_plan), cost});
|
||||
}
|
||||
std::stable_sort(interactive_plans.begin(), interactive_plans.end(),
|
||||
[](const auto &a, const auto &b) { return a.cost < b.cost; });
|
||||
return interactive_plans;
|
||||
}
|
||||
|
||||
void RunInteractivePlanning(memgraph::query::DbAccessor *dba) {
|
||||
std::string in_db_filename(memgraph::utils::Trim(FLAGS_load_mock_db_file));
|
||||
if (!in_db_filename.empty() && !std::filesystem::exists(in_db_filename)) {
|
||||
std::cerr << "File '" << in_db_filename << "' does not exist!" << std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
Timer planning_timer;
|
||||
InteractiveDbAccessor interactive_db(dba, in_db_filename.empty() ? ReadInt("Vertices in DB: ") : 0, planning_timer);
|
||||
if (!in_db_filename.empty()) {
|
||||
std::ifstream db_file(in_db_filename);
|
||||
interactive_db.Load(db_file);
|
||||
}
|
||||
while (true) {
|
||||
auto line = ReadLine("query? ");
|
||||
if (!line || *line == "quit") break;
|
||||
if (line->empty()) continue;
|
||||
try {
|
||||
memgraph::query::AstStorage ast;
|
||||
auto *query = dynamic_cast<memgraph::query::CypherQuery *>(MakeAst(*line, &ast));
|
||||
if (!query) {
|
||||
throw memgraph::utils::BasicException(
|
||||
"Interactive planning is only avaialable for regular openCypher "
|
||||
"queries.");
|
||||
}
|
||||
auto symbol_table = memgraph::query::MakeSymbolTable(query);
|
||||
planning_timer.Start();
|
||||
auto plans = MakeLogicalPlans(query, ast, symbol_table, &interactive_db);
|
||||
auto planning_time = planning_timer.Elapsed();
|
||||
std::cout << "Planning took " << std::chrono::duration<double, std::milli>(planning_time).count() << "ms"
|
||||
<< std::endl;
|
||||
std::cout << "Generated " << plans.size() << " plans" << std::endl;
|
||||
ExaminePlans(dba, symbol_table, plans, ast);
|
||||
} catch (const memgraph::utils::BasicException &e) {
|
||||
std::cout << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
std::string db_filename(memgraph::utils::Trim(FLAGS_save_mock_db_file));
|
||||
if (!db_filename.empty()) {
|
||||
std::ofstream db_file(db_filename);
|
||||
interactive_db.Save(db_file);
|
||||
}
|
||||
}
|
70
tests/manual/interactive/plan.hpp
Normal file
70
tests/manual/interactive/plan.hpp
Normal file
@ -0,0 +1,70 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "query/frontend/ast/cypher_main_visitor.hpp"
|
||||
#include "query/frontend/opencypher/parser.hpp"
|
||||
#include "query/plan/planner.hpp"
|
||||
|
||||
#include "db_accessor.hpp"
|
||||
|
||||
struct InteractivePlan {
|
||||
// Original plan after going only through the RuleBasedPlanner.
|
||||
std::unique_ptr<memgraph::query::plan::LogicalOperator> unoptimized_plan;
|
||||
// Storage for the AST used in unoptimized_plan
|
||||
memgraph::query::AstStorage ast_storage;
|
||||
// Final plan after being rewritten and optimized.
|
||||
std::unique_ptr<memgraph::query::plan::LogicalOperator> final_plan;
|
||||
// Cost of the final plan.
|
||||
double cost;
|
||||
};
|
||||
|
||||
inline memgraph::query::Query *MakeAst(const std::string &query, memgraph::query::AstStorage *storage) {
|
||||
memgraph::query::frontend::ParsingContext parsing_context;
|
||||
parsing_context.is_query_cached = false;
|
||||
// query -> AST
|
||||
auto parser = std::make_unique<memgraph::query::frontend::opencypher::Parser>(query);
|
||||
// AST -> high level tree
|
||||
memgraph::query::frontend::CypherMainVisitor visitor(parsing_context, storage);
|
||||
visitor.visit(parser->tree());
|
||||
return visitor.query();
|
||||
}
|
||||
|
||||
// Returns a list of InteractivePlan instances, sorted in the ascending order by
|
||||
// cost.
|
||||
inline auto MakeLogicalPlans(memgraph::query::CypherQuery *query, memgraph::query::AstStorage &ast,
|
||||
memgraph::query::SymbolTable &symbol_table, InteractiveDbAccessor *dba) {
|
||||
auto query_parts = memgraph::query::plan::CollectQueryParts(symbol_table, ast, query);
|
||||
std::vector<InteractivePlan> interactive_plans;
|
||||
auto ctx = memgraph::query::plan::MakePlanningContext(&ast, &symbol_table, query, dba);
|
||||
if (query_parts.query_parts.size() <= 0) {
|
||||
std::cerr << "Failed to extract query parts" << std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
memgraph::query::Parameters parameters;
|
||||
memgraph::query::plan::PostProcessor post_process(parameters);
|
||||
auto plans = memgraph::query::plan::MakeLogicalPlanForSingleQuery<memgraph::query::plan::VariableStartPlanner>(
|
||||
query_parts.query_parts.at(0).single_query_parts, &ctx);
|
||||
for (auto plan : plans) {
|
||||
memgraph::query::AstStorage ast_copy;
|
||||
auto unoptimized_plan = plan->Clone(&ast_copy);
|
||||
auto rewritten_plan = post_process.Rewrite(std::move(plan), &ctx);
|
||||
double cost = post_process.EstimatePlanCost(rewritten_plan, dba);
|
||||
interactive_plans.push_back(
|
||||
InteractivePlan{std::move(unoptimized_plan), std::move(ast_copy), std::move(rewritten_plan), cost});
|
||||
}
|
||||
std::stable_sort(interactive_plans.begin(), interactive_plans.end(),
|
||||
[](const auto &a, const auto &b) { return a.cost < b.cost; });
|
||||
return interactive_plans;
|
||||
}
|
158
tests/manual/interactive/planning.cpp
Normal file
158
tests/manual/interactive/planning.cpp
Normal file
@ -0,0 +1,158 @@
|
||||
// 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 "planning.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "db_accessor.hpp"
|
||||
#include "plan.hpp"
|
||||
#include "query/context.hpp"
|
||||
#include "query/db_accessor.hpp"
|
||||
#include "query/frontend/ast/cypher_main_visitor.hpp"
|
||||
#include "query/frontend/opencypher/parser.hpp"
|
||||
#include "query/frontend/semantic/symbol_generator.hpp"
|
||||
#include "query/plan/operator.hpp"
|
||||
#include "query/plan/planner.hpp"
|
||||
#include "query/plan/pretty_print.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
DEFINE_string(save_mock_db_file, "", "File where the mock database should be saved (on exit)");
|
||||
DEFINE_string(load_mock_db_file, "", "File from which the mock database should be loaded");
|
||||
|
||||
DEFCOMMAND(Top) {
|
||||
int64_t n_plans = 0;
|
||||
std::stringstream ss(args[0]);
|
||||
ss >> n_plans;
|
||||
if (ss.fail() || !ss.eof()) return;
|
||||
n_plans = std::min(static_cast<int64_t>(plans.size()), n_plans);
|
||||
for (int64_t i = 0; i < n_plans; ++i) {
|
||||
std::cout << "---- Plan #" << i << " ---- " << std::endl;
|
||||
std::cout << "cost: " << plans[i].cost << std::endl;
|
||||
memgraph::query::plan::PrettyPrint(dba, plans[i].final_plan.get());
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
DEFCOMMAND(Show) {
|
||||
int64_t plan_ix = 0;
|
||||
std::stringstream ss(args[0]);
|
||||
ss >> plan_ix;
|
||||
if (ss.fail() || !ss.eof() || plan_ix >= plans.size()) return;
|
||||
const auto &plan = plans[plan_ix].final_plan;
|
||||
auto cost = plans[plan_ix].cost;
|
||||
std::cout << "Plan cost: " << cost << std::endl;
|
||||
memgraph::query::plan::PrettyPrint(dba, plan.get());
|
||||
}
|
||||
|
||||
DEFCOMMAND(ShowUnoptimized) {
|
||||
int64_t plan_ix = 0;
|
||||
std::stringstream ss(args[0]);
|
||||
ss >> plan_ix;
|
||||
if (ss.fail() || !ss.eof() || plan_ix >= plans.size()) return;
|
||||
const auto &plan = plans[plan_ix].unoptimized_plan;
|
||||
memgraph::query::plan::PrettyPrint(dba, plan.get());
|
||||
}
|
||||
|
||||
DEFCOMMAND(Help);
|
||||
|
||||
std::map<std::string, Command> commands = {
|
||||
{"top", {TopCommand, 1, "Show top N plans"}},
|
||||
{"show", {ShowCommand, 1, "Show the Nth plan"}},
|
||||
{"show-unoptimized", {ShowUnoptimizedCommand, 1, "Show the Nth plan in its original, unoptimized form"}},
|
||||
{"help", {HelpCommand, 0, "Show available commands"}},
|
||||
};
|
||||
|
||||
void AddCommand(const std::string &name, const Command &command) { commands[name] = command; }
|
||||
|
||||
DEFCOMMAND(Help) {
|
||||
std::cout << "Available commands:" << std::endl;
|
||||
for (const auto &command : commands) {
|
||||
std::cout << command.first;
|
||||
for (int i = 1; i <= command.second.arg_count; ++i) {
|
||||
std::cout << " arg" << i;
|
||||
}
|
||||
std::cout << " -- " << command.second.documentation << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ExaminePlans(memgraph::query::DbAccessor *dba, const memgraph::query::SymbolTable &symbol_table,
|
||||
std::vector<InteractivePlan> &plans, const memgraph::query::AstStorage &ast) {
|
||||
while (true) {
|
||||
auto line = ReadLine("plan? ");
|
||||
if (!line || *line == "quit") break;
|
||||
auto words = memgraph::utils::Split(memgraph::utils::ToLowerCase(*line));
|
||||
if (words.empty()) continue;
|
||||
auto command_name = words[0];
|
||||
std::vector<std::string> args(words.begin() + 1, words.end());
|
||||
auto command_it = commands.find(command_name);
|
||||
if (command_it == commands.end()) {
|
||||
std::cout << "Undefined command: '" << command_name << "'. Try 'help'." << std::endl;
|
||||
continue;
|
||||
}
|
||||
const auto &command = command_it->second;
|
||||
if (args.size() < command.arg_count) {
|
||||
std::cout << command_name << " expects " << command.arg_count << " arguments" << std::endl;
|
||||
continue;
|
||||
}
|
||||
command.function(*dba, symbol_table, plans, args, ast);
|
||||
}
|
||||
}
|
||||
|
||||
void RunInteractivePlanning(memgraph::query::DbAccessor *dba) {
|
||||
std::string in_db_filename(memgraph::utils::Trim(FLAGS_load_mock_db_file));
|
||||
if (!in_db_filename.empty() && !std::filesystem::exists(in_db_filename)) {
|
||||
std::cerr << "File '" << in_db_filename << "' does not exist!" << std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
Timer planning_timer;
|
||||
InteractiveDbAccessor interactive_db(dba, in_db_filename.empty() ? ReadInt("Vertices in DB: ") : 0, planning_timer);
|
||||
if (!in_db_filename.empty()) {
|
||||
std::ifstream db_file(in_db_filename);
|
||||
interactive_db.Load(db_file);
|
||||
}
|
||||
while (true) {
|
||||
auto line = ReadLine("query? ");
|
||||
if (!line || *line == "quit") break;
|
||||
if (line->empty()) continue;
|
||||
try {
|
||||
memgraph::query::AstStorage ast;
|
||||
auto *query = dynamic_cast<memgraph::query::CypherQuery *>(MakeAst(*line, &ast));
|
||||
if (!query) {
|
||||
throw memgraph::utils::BasicException(
|
||||
"Interactive planning is only avaialable for regular openCypher "
|
||||
"queries.");
|
||||
}
|
||||
auto symbol_table = memgraph::query::MakeSymbolTable(query);
|
||||
planning_timer.Start();
|
||||
auto plans = MakeLogicalPlans(query, ast, symbol_table, &interactive_db);
|
||||
auto planning_time = planning_timer.Elapsed();
|
||||
std::cout << "Planning took " << std::chrono::duration<double, std::milli>(planning_time).count() << "ms"
|
||||
<< std::endl;
|
||||
std::cout << "Generated " << plans.size() << " plans" << std::endl;
|
||||
ExaminePlans(dba, symbol_table, plans, ast);
|
||||
} catch (const memgraph::utils::BasicException &e) {
|
||||
std::cout << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
std::string db_filename(memgraph::utils::Trim(FLAGS_save_mock_db_file));
|
||||
if (!db_filename.empty()) {
|
||||
std::ofstream db_file(db_filename);
|
||||
interactive_db.Save(db_file);
|
||||
}
|
||||
}
|
@ -21,21 +21,12 @@
|
||||
#include "query/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/plan/operator.hpp"
|
||||
|
||||
#include "plan.hpp"
|
||||
|
||||
namespace database {
|
||||
class GraphDbAccessor;
|
||||
}
|
||||
|
||||
struct InteractivePlan {
|
||||
// Original plan after going only through the RuleBasedPlanner.
|
||||
std::unique_ptr<memgraph::query::plan::LogicalOperator> unoptimized_plan;
|
||||
// Storage for the AST used in unoptimized_plan
|
||||
memgraph::query::AstStorage ast_storage;
|
||||
// Final plan after being rewritten and optimized.
|
||||
std::unique_ptr<memgraph::query::plan::LogicalOperator> final_plan;
|
||||
// Cost of the final plan.
|
||||
double cost;
|
||||
};
|
||||
|
||||
typedef std::vector<InteractivePlan> PlansWithCost;
|
||||
|
||||
// Encapsulates a consoles command function.
|
65
tests/manual/interactive/readline.hpp
Normal file
65
tests/manual/interactive/readline.hpp
Normal file
@ -0,0 +1,65 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#ifdef HAS_READLINE
|
||||
// TODO: This should probably be moved to some utils file.
|
||||
|
||||
#include "readline/history.h"
|
||||
#include "readline/readline.h"
|
||||
|
||||
/**
|
||||
* Helper function that reads a line from the
|
||||
* standard input using the 'readline' lib.
|
||||
* Adds support for history and reverse-search.
|
||||
*
|
||||
* @param prompt The prompt to display.
|
||||
* @return A single command the user entered, or nullopt on EOF.
|
||||
*/
|
||||
inline std::optional<std::string> ReadLine(const std::string &prompt) {
|
||||
char *line = readline(prompt.c_str());
|
||||
if (!line) return std::nullopt;
|
||||
|
||||
if (*line) add_history(line);
|
||||
std::string r_val(line);
|
||||
free(line);
|
||||
return r_val;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
inline std::optional<std::string> ReadLine(const std::string &prompt) {
|
||||
std::cout << prompt;
|
||||
std::string line;
|
||||
std::getline(std::cin, line);
|
||||
if (std::cin.eof()) return std::nullopt;
|
||||
return line;
|
||||
}
|
||||
|
||||
#endif // HAS_READLINE
|
||||
|
||||
// Repeats the prompt untile the user inputs an integer.
|
||||
inline int64_t ReadInt(const std::string &prompt) {
|
||||
int64_t val = 0;
|
||||
std::stringstream ss;
|
||||
do {
|
||||
auto line = ReadLine(prompt);
|
||||
if (!line) continue;
|
||||
ss.str(*line);
|
||||
ss.clear();
|
||||
ss >> val;
|
||||
} while (ss.fail() || !ss.eof());
|
||||
return val;
|
||||
}
|
57
tests/manual/interactive/timer.hpp
Normal file
57
tests/manual/interactive/timer.hpp
Normal file
@ -0,0 +1,57 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
void Start() {
|
||||
duration_ = duration_.zero();
|
||||
start_time_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Pause() {
|
||||
if (pause_ == 0) {
|
||||
duration_ += std::chrono::steady_clock::now() - start_time_;
|
||||
}
|
||||
++pause_;
|
||||
}
|
||||
|
||||
void Resume() {
|
||||
if (pause_ == 1) {
|
||||
start_time_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
pause_ = std::max(0, pause_ - 1);
|
||||
}
|
||||
|
||||
template <class TFun>
|
||||
auto WithPause(const TFun &fun) {
|
||||
Pause();
|
||||
auto ret = fun();
|
||||
Resume();
|
||||
return std::move(ret);
|
||||
}
|
||||
|
||||
std::chrono::duration<double> Elapsed() {
|
||||
if (pause_ == 0) {
|
||||
return duration_ + (std::chrono::steady_clock::now() - start_time_);
|
||||
}
|
||||
return duration_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::duration<double> duration_;
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time_;
|
||||
int pause_ = 0;
|
||||
};
|
183
tests/manual/query_execution_dummy.cpp
Normal file
183
tests/manual/query_execution_dummy.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
// 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 "interactive/planning.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "interactive/db_accessor.hpp"
|
||||
#include "interactive/plan.hpp"
|
||||
#include "query/frontend/semantic/symbol_generator.hpp"
|
||||
#include "storage/v2/storage.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace memgraph::query::plan {
|
||||
|
||||
class TestLogicalOperatorVisitor final : public HierarchicalLogicalOperatorVisitor {
|
||||
public:
|
||||
TestLogicalOperatorVisitor() {}
|
||||
|
||||
using HierarchicalLogicalOperatorVisitor::PostVisit;
|
||||
using HierarchicalLogicalOperatorVisitor::PreVisit;
|
||||
using HierarchicalLogicalOperatorVisitor::Visit;
|
||||
|
||||
void Start() {}
|
||||
|
||||
bool IsDone() { return true; }
|
||||
|
||||
bool Visit(Once &) override {
|
||||
std::cout << "Visit Once" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(Filter &op) override { return true; }
|
||||
bool PostVisit(Filter &op) override { return true; }
|
||||
|
||||
bool PreVisit(ScanAll &op) override {
|
||||
std::cout << "PreVisit ScanAll, output " << op.output_symbol_.name_ << std::endl;
|
||||
return true;
|
||||
}
|
||||
bool PostVisit(ScanAll &scan) override { return true; }
|
||||
|
||||
bool PreVisit(Expand &op) override { return true; }
|
||||
bool PostVisit(Expand &expand) override { return true; }
|
||||
|
||||
bool PreVisit(ExpandVariable &op) override { return true; }
|
||||
bool PostVisit(ExpandVariable &expand) override { return true; }
|
||||
|
||||
bool PreVisit(Merge &op) override { return false; }
|
||||
bool PostVisit(Merge &) override { return true; }
|
||||
|
||||
bool PreVisit(Optional &op) override { return false; }
|
||||
bool PostVisit(Optional &) override { return true; }
|
||||
|
||||
bool PreVisit(Cartesian &op) override { return true; }
|
||||
bool PostVisit(Cartesian &) override { return true; }
|
||||
|
||||
bool PreVisit(Union &op) override { return false; }
|
||||
bool PostVisit(Union &) override { return true; }
|
||||
|
||||
bool PreVisit(CreateNode &op) override { return true; }
|
||||
bool PostVisit(CreateNode &) override { return true; }
|
||||
|
||||
bool PreVisit(CreateExpand &op) override { return true; }
|
||||
bool PostVisit(CreateExpand &) override { return true; }
|
||||
|
||||
bool PreVisit(ScanAllByLabel &op) override { return true; }
|
||||
bool PostVisit(ScanAllByLabel &) override { return true; }
|
||||
|
||||
bool PreVisit(ScanAllByLabelPropertyRange &op) override { return true; }
|
||||
bool PostVisit(ScanAllByLabelPropertyRange &) override { return true; }
|
||||
|
||||
bool PreVisit(ScanAllByLabelPropertyValue &op) override { return true; }
|
||||
bool PostVisit(ScanAllByLabelPropertyValue &) override { return true; }
|
||||
|
||||
bool PreVisit(ScanAllByLabelProperty &op) override { return true; }
|
||||
bool PostVisit(ScanAllByLabelProperty &) override { return true; }
|
||||
|
||||
bool PreVisit(ScanAllById &op) override { return true; }
|
||||
bool PostVisit(ScanAllById &) override { return true; }
|
||||
|
||||
bool PreVisit(ConstructNamedPath &op) override { return true; }
|
||||
bool PostVisit(ConstructNamedPath &) override { return true; }
|
||||
|
||||
bool PreVisit(Produce &op) override {
|
||||
std::cout << "PreVisit Produce, named expressions: ";
|
||||
for (const auto &name_expr : op.named_expressions_) {
|
||||
std::cout << name_expr->name_ << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
return true;
|
||||
}
|
||||
bool PostVisit(Produce &) override { return true; }
|
||||
|
||||
bool PreVisit(Delete &op) override { return true; }
|
||||
bool PostVisit(Delete &) override { return true; }
|
||||
|
||||
bool PreVisit(SetProperty &op) override { return true; }
|
||||
bool PostVisit(SetProperty &) override { return true; }
|
||||
|
||||
bool PreVisit(SetProperties &op) override { return true; }
|
||||
bool PostVisit(SetProperties &) override { return true; }
|
||||
|
||||
bool PreVisit(SetLabels &op) override { return true; }
|
||||
bool PostVisit(SetLabels &) override { return true; }
|
||||
|
||||
bool PreVisit(RemoveProperty &op) override { return true; }
|
||||
bool PostVisit(RemoveProperty &) override { return true; }
|
||||
|
||||
bool PreVisit(RemoveLabels &op) override { return true; }
|
||||
bool PostVisit(RemoveLabels &) override { return true; }
|
||||
|
||||
bool PreVisit(EdgeUniquenessFilter &op) override { return true; }
|
||||
bool PostVisit(EdgeUniquenessFilter &) override { return true; }
|
||||
|
||||
bool PreVisit(Accumulate &op) override { return true; }
|
||||
bool PostVisit(Accumulate &) override { return true; }
|
||||
|
||||
bool PreVisit(Aggregate &op) override { return true; }
|
||||
bool PostVisit(Aggregate &) override { return true; }
|
||||
|
||||
bool PreVisit(Skip &op) override { return true; }
|
||||
bool PostVisit(Skip &) override { return true; }
|
||||
|
||||
bool PreVisit(Limit &op) override { return true; }
|
||||
bool PostVisit(Limit &) override { return true; }
|
||||
|
||||
bool PreVisit(OrderBy &op) override { return true; }
|
||||
bool PostVisit(OrderBy &) override { return true; }
|
||||
|
||||
bool PreVisit(Unwind &op) override { return true; }
|
||||
bool PostVisit(Unwind &) override { return true; }
|
||||
|
||||
bool PreVisit(Distinct &op) override { return true; }
|
||||
bool PostVisit(Distinct &) override { return true; }
|
||||
|
||||
bool PreVisit(CallProcedure &op) override { return true; }
|
||||
bool PostVisit(CallProcedure &) override { return true; }
|
||||
};
|
||||
} // namespace memgraph::query::plan
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
spdlog::set_level(spdlog::level::info);
|
||||
|
||||
memgraph::storage::Storage db;
|
||||
auto storage_dba = db.Access();
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
|
||||
Timer planning_timer;
|
||||
InteractiveDbAccessor interactive_db(&dba, 10, planning_timer);
|
||||
std::string input_query = "MATCH (n) RETURN n;";
|
||||
memgraph::query::AstStorage ast;
|
||||
auto *query = dynamic_cast<memgraph::query::CypherQuery *>(MakeAst(input_query, &ast));
|
||||
if (!query) {
|
||||
throw memgraph::utils::BasicException("Create CypherQuery failed");
|
||||
}
|
||||
auto symbol_table = memgraph::query::MakeSymbolTable(query);
|
||||
planning_timer.Start();
|
||||
auto plans = MakeLogicalPlans(query, ast, symbol_table, &interactive_db);
|
||||
if (plans.size() == 0) {
|
||||
throw memgraph::utils::BasicException("No plans");
|
||||
}
|
||||
|
||||
memgraph::query::plan::TestLogicalOperatorVisitor executor;
|
||||
plans[0].unoptimized_plan->Accept(executor);
|
||||
executor.Start();
|
||||
while (!executor.IsDone()) {
|
||||
std::cout << "Executor NOT done yet" << std::endl;
|
||||
}
|
||||
std::cout << "Executor done" << std::endl;
|
||||
return 0;
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "interactive_planning.hpp"
|
||||
#include "interactive/planning.hpp"
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user