diff --git a/CMakeLists.txt b/CMakeLists.txt index e47ecabbf..870a28f02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,7 @@ add_custom_target(clean_all # threading find_package(Threads REQUIRED) +# find_package(readline REQUIRED) # ----------------------------------------------------------------------------- # c++14 @@ -352,6 +353,7 @@ set(memgraph_src_files ${src_dir}/database/graph_db.cpp ${src_dir}/database/graph_db_accessor.cpp ${src_dir}/query/stripper.cpp + ${src_dir}/query/console.cpp ${src_dir}/query/frontend/ast/cypher_main_visitor.cpp ${src_dir}/query/backend/cpp/typed_value.cpp ${src_dir}/query/frontend/logical/planner.cpp @@ -421,6 +423,7 @@ if (MEMGRAPH) target_link_libraries(${MEMGRAPH_BUILD_NAME} yaml-cpp) target_link_libraries(${MEMGRAPH_BUILD_NAME} antlr_opencypher_parser_lib) target_link_libraries(${MEMGRAPH_BUILD_NAME} dl) + target_link_libraries(${MEMGRAPH_BUILD_NAME} readline) endif() # utility target to copy hardcoded queries diff --git a/src/query/console.cpp b/src/query/console.cpp new file mode 100644 index 000000000..e30278134 --- /dev/null +++ b/src/query/console.cpp @@ -0,0 +1,172 @@ +// +// Copyright 2017 Memgraph +// Created by Florijan Stamenkovic on 23.03.17. +// + +#include +#include + +#include "console.hpp" +#include "query/exceptions.hpp" +#include "query/interpreter.hpp" + +#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. + * Possibly empty. + */ +std::string ReadLine(const char *prompt) { + char *line_read = readline(prompt); + if (line_read && *line_read) add_history(line_read); + std::string r_val(line_read); + if (line_read) free(line_read); + return r_val; +} + +/** + * Helper function that outputs a collection of items to + * the given stream, separating them with the given delimiter. + */ +template +void PrintIterable(TStream &stream, const TIterable &iterable, + const std::string &delim, TConverter converter = {}) { + bool first = true; + for (const auto &item : iterable) { + if (first) + first = false; + else + stream << delim; + stream << converter(item); + } +} + +/** + * Converts the given TypedValue into a string (single line). + */ +std::string TypedValueToString(const TypedValue &value) { + std::stringstream ss; + switch (value.type()) { + case TypedValue::Type::Vertex: { + auto va = value.Value(); + ss << "Vertex("; + PrintIterable(ss, va.labels(), ":", [&](auto label) { + return va.db_accessor().label_name(label); + }); + ss << "{"; + PrintIterable(ss, va.Properties(), ", ", [&](const auto kv) { + return va.db_accessor().property_name(kv.first) + ": " + + TypedValueToString(kv.second); + }); + ss << "})"; + break; + } + case TypedValue::Type::Edge: { + auto ea = value.Value(); + ss << "Edge[" << ea.db_accessor().edge_type_name(ea.edge_type()); + ss << "{"; + PrintIterable(ss, ea.Properties(), ", ", [&](const auto kv) { + return ea.db_accessor().property_name(kv.first) + ": " + + TypedValueToString(kv.second); + }); + ss << "}]"; + break; + } + case TypedValue::Type::List: + break; + case TypedValue::Type::Map: + break; + case TypedValue::Type::Path: + break; + default: + ss << value; + } + return ss.str(); +} + +/** + * Prints out all the given results to standard out. + */ +void PrintResults(ResultStreamFaker results) { + const std::vector &header = results.GetHeader(); + std::vector column_widths(header.size()); + for (int col_ind = 0; col_ind < header.size(); ++col_ind) + column_widths[col_ind] = (int)header[col_ind].size(); + + // convert all the results into strings, and track max column width + auto &results_data = results.GetResults(); + std::vector> result_strings( + results_data.size(), std::vector(column_widths.size())); + for (int row_ind = 0; row_ind < results_data.size(); ++row_ind) { + for (int col_ind = 0; col_ind < column_widths.size(); ++col_ind) { + std::string string_val = + TypedValueToString(results_data[row_ind][col_ind]); + column_widths[col_ind] = + std::max(column_widths[col_ind], (int)string_val.size()); + result_strings[row_ind][col_ind] = string_val; + } + } + + // output a results table + // first define some helper functions + auto emit_horizontal_line = [&]() { + std::cout << "+"; + for (auto col_width : column_widths) + std::cout << std::string((unsigned long)col_width + 2, '-') << "+"; + std::cout << std::endl; + }; + + auto emit_result_vec = [&](const std::vector result_vec) { + std::cout << "| "; + for (int col_ind = 0; col_ind < column_widths.size(); ++col_ind) { + const std::string &res = result_vec[col_ind]; + std::cout << res << std::string(column_widths[col_ind] - res.size(), ' '); + std::cout << " | "; + } + std::cout << std::endl; + }; + + // final output of results + emit_horizontal_line(); + emit_result_vec(results.GetHeader()); + emit_horizontal_line(); + for (const auto &result_vec : result_strings) emit_result_vec(result_vec); + emit_horizontal_line(); + + // output the summary + std::cout << "Query summary: {"; + PrintIterable(std::cout, results.GetSummary(), ", ", [&](const auto kv) { + return kv.first + ": " + TypedValueToString(kv.second); + }); + std::cout << "}" << std::endl; +} + +void query::Console(Dbms &dbms) { + std::cout << "Welcome to *Awesome* Memgraph Console (AMC)" << std::endl; + while (true) { + std::string command = ReadLine(">"); + if (command.size() == 0) continue; + + // special commands + if (command == "quit") break; + + // regular cypher queries + try { + auto dba = dbms.active(); + ResultStreamFaker results; + query::Interpret(command, *dba, results); + PrintResults(results); + dba->commit(); + } catch (const query::SyntaxException &e) { + std::cout << "SYNTAX EXCEPTION: " << e.what() << std::endl; + } catch (const query::SemanticException &e) { + std::cout << "SEMANTIC EXCEPTION: " << e.what() << std::endl; + } + } +} diff --git a/src/query/console.hpp b/src/query/console.hpp new file mode 100644 index 000000000..3a216e318 --- /dev/null +++ b/src/query/console.hpp @@ -0,0 +1,22 @@ +// +// Copyright 2017 Memgraph +// Created by Florijan Stamenkovic on 23.03.17. +// + +#pragma once + +#include + +#include "communication/result_stream_faker.hpp" +#include "dbms/dbms.hpp" + +namespace query { + +/** + * Console for interacting with a database + * (the active database in the given DBMS). + * Immediately starts the user-input loop + * and interprets the entered queries. + */ +void Console(Dbms &dbms); +} diff --git a/src/query/frontend/opencypher/parser.hpp b/src/query/frontend/opencypher/parser.hpp index 81a527938..919964202 100644 --- a/src/query/frontend/opencypher/parser.hpp +++ b/src/query/frontend/opencypher/parser.hpp @@ -21,10 +21,12 @@ using namespace antlr4; class Parser { public: /** - * @param query incomming query that has to be compiled into query plan + * @param query incoming query that has to be compiled into query plan * the first step is to generate AST */ Parser(const std::string query) : query_(std::move(query)) { + parser_.removeErrorListeners(); + tree_ = parser_.cypher(); if (parser_.getNumberOfSyntaxErrors()) { throw query::SyntaxException(); } @@ -39,8 +41,8 @@ class Parser { CommonTokenStream tokens_{&lexer_}; // generate ast - CypherParser parser_{&tokens_}; - tree::ParseTree *tree_{parser_.cypher()}; +CypherParser parser_{&tokens_}; +tree::ParseTree *tree_ = nullptr; }; } } diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt index c17f8add7..d07669988 100644 --- a/tests/manual/CMakeLists.txt +++ b/tests/manual/CMakeLists.txt @@ -1,4 +1,5 @@ find_package(Threads REQUIRED) +# find_package(readline REQUIRED) # set current directory name as a test type get_filename_component(test_type ${CMAKE_CURRENT_SOURCE_DIR} NAME) @@ -40,5 +41,7 @@ foreach(test_cpp ${test_type_cpps}) target_link_libraries(${target_name} antlr_opencypher_parser_lib) # dynamic lib target_link_libraries(${target_name} dl) + # readline lib + target_link_libraries(${target_name} readline) endforeach() diff --git a/tests/manual/compiler_prototype.cpp b/tests/manual/compiler_prototype.cpp deleted file mode 100644 index 6eab6fcdd..000000000 --- a/tests/manual/compiler_prototype.cpp +++ /dev/null @@ -1,175 +0,0 @@ -#include -#include - -#include "dbms/dbms.hpp" -#include "logging/default.hpp" -#include "logging/streams/stdout.cpp" -#include "query/interpreter.hpp" -#include "query/backend/cpp/typed_value.hpp" -#include "query/frontend/logical/operator.hpp" - -using std::cout; -using std::cin; -using std::endl; - -/** - * A Stream implementation that writes out to the - * console (for testing and debugging only). - */ -// TODO move somewhere to /test/manual or so -class ConsoleResultStream : public Loggable { -public: - ConsoleResultStream() : Loggable("ConsoleResultStream") {} - - void Header(const std::vector &) { logger.info("header"); } - - void Result(std::vector &values) { - std::stringstream ss; - bool first = true; - for (auto value : values) { - if (first) { - ss << "\t"; - first = false; - } - else - ss << " | "; - switch (value.type()) { - case TypedValue::Type::Vertex: { - auto va = value.Value(); - ss << "Vertex("; - for (auto label : va.labels()) - ss << ":" << va.db_accessor().label_name(label) << " "; - ss << "{"; - for (auto kv : va.Properties()) { - ss << va.db_accessor().property_name(kv.first) << ": "; - ss << kv.second << ", "; - } - ss << "}"; - ss << ")"; - break; - } - case TypedValue::Type::Edge: { - auto ea = value.Value(); - ss << "Edge[" << ea.db_accessor().edge_type_name(ea.edge_type()) << "}"; - ss << "{"; - for (auto kv : ea.Properties()) { - ss << ea.db_accessor().property_name(kv.first) << ": "; - ss << kv.second << ", "; - } - ss << "}"; - ss << "]"; - break; - } - - case TypedValue::Type::List:break; - case TypedValue::Type::Map:break; - case TypedValue::Type::Path:break; - default: - ss << value; - } - } - logger.info("{}", ss.str()); - } - - void Summary(const std::map &summary) { - std::stringstream ss; - ss << "Summary {"; - bool first = true; - for (auto kv : summary) - ss << kv.first << " : " << kv.second << ", "; - ss << "}"; - logger.info("{}", ss.str()); - } -}; - - -int main(int argc, char* argv[]) { - // init arguments - REGISTER_ARGS(argc, argv); - - // init logger - logging::init_sync(); - logging::log->pipe(std::make_unique()); - - // init db context - Dbms dbms; - ConsoleResultStream stream; - - // initialize the database - auto dba = dbms.active(); - - // labels - auto company = dba->label("Company"); - auto person = dba->label("Person"); - auto device = dba->label("Device"); - - // props - auto name = dba->property("name"); - auto age = dba->property("age"); - auto type = dba->property("type"); - - // vertices - auto memgraph = dba->insert_vertex(); - memgraph.PropsSet(name, "Memgraph"); - memgraph.add_label(company); - auto teon = dba->insert_vertex(); - teon.PropsSet(name, "Teon"); - teon.PropsSet(age, 26); - teon.add_label(person); - auto mislav = dba->insert_vertex(); - mislav.PropsSet(name, "Mislav"); - mislav.PropsSet(age, 22); - mislav.add_label(person); - auto florijan = dba->insert_vertex(); - florijan.PropsSet(name, "Florijan"); - florijan.PropsSet(age, 31); - florijan.add_label(person); - auto xps_15 = dba->insert_vertex(); - xps_15.PropsSet(type, "PC"); - xps_15.PropsSet(name, "Dell XPS 15"); - xps_15.add_label(device); - - // edges - dba->insert_edge(teon, memgraph, dba->edge_type("MEMBER_OF")); - dba->insert_edge(mislav, memgraph, dba->edge_type("MEMBER_OF")); - dba->insert_edge(florijan, memgraph, dba->edge_type("MEMBER_OF")); - - dba->insert_edge(teon, mislav, dba->edge_type("FRIEND_OF")); - dba->insert_edge(mislav, teon, dba->edge_type("FRIEND_OF")); - dba->insert_edge(florijan, mislav, dba->edge_type("FRIEND_OF")); - dba->insert_edge(mislav, florijan, dba->edge_type("FRIEND_OF")); - dba->insert_edge(florijan, teon, dba->edge_type("FRIEND_OF")); - dba->insert_edge(teon, florijan, dba->edge_type("FRIEND_OF")); - - dba->insert_edge(memgraph, xps_15, dba->edge_type("OWNS")); - - dba->insert_edge(teon, xps_15, dba->edge_type("USES")); - dba->insert_edge(mislav, xps_15, dba->edge_type("USES")); - dba->insert_edge(florijan, xps_15, dba->edge_type("USES")); - - dba->commit(); - - cout << "-- Memgraph Query Engine --" << endl; - - while (true) { - auto inner_dba = dbms.active(); - // read command - cout << "> "; - std::string command; - std::getline(cin, command); - if (command == "quit") break; - // execute command / query - // try { - // auto db_accessor = dbms.active(); - query::Interpret(command, *inner_dba, stream); - inner_dba->commit(); - // } catch (const std::exception& e) { - // cout << e.what() << endl; - // } catch (...) { - // // pass - // } -// - } - - return 0; -} diff --git a/tests/manual/console_test.cpp b/tests/manual/console_test.cpp new file mode 100644 index 000000000..bd244e753 --- /dev/null +++ b/tests/manual/console_test.cpp @@ -0,0 +1,69 @@ +#include + +#include "dbms/dbms.hpp" +#include "query/console.hpp" +#include "query/interpreter.hpp" + +void fill_db(Dbms &dbms) { + auto dba = dbms.active(); + + // labels + auto company = dba->label("Company"); + auto person = dba->label("Person"); + auto device = dba->label("Device"); + + // props + auto name = dba->property("name"); + auto age = dba->property("age"); + auto type = dba->property("type"); + + // vertices + auto memgraph = dba->insert_vertex(); + memgraph.PropsSet(name, "Memgraph"); + memgraph.add_label(company); + auto teon = dba->insert_vertex(); + teon.PropsSet(name, "Teon"); + teon.PropsSet(age, 26); + teon.add_label(person); + auto mislav = dba->insert_vertex(); + mislav.PropsSet(name, "Mislav"); + mislav.PropsSet(age, 22); + mislav.add_label(person); + auto florijan = dba->insert_vertex(); + florijan.PropsSet(name, "Florijan"); + florijan.PropsSet(age, 31); + florijan.add_label(person); + auto xps_15 = dba->insert_vertex(); + xps_15.PropsSet(type, "PC"); + xps_15.PropsSet(name, "Dell XPS 15"); + xps_15.add_label(device); + + // edges + dba->insert_edge(teon, memgraph, dba->edge_type("MEMBER_OF")); + dba->insert_edge(mislav, memgraph, dba->edge_type("MEMBER_OF")); + dba->insert_edge(florijan, memgraph, dba->edge_type("MEMBER_OF")); + + dba->insert_edge(teon, mislav, dba->edge_type("FRIEND_OF")); + dba->insert_edge(mislav, teon, dba->edge_type("FRIEND_OF")); + dba->insert_edge(florijan, mislav, dba->edge_type("FRIEND_OF")); + dba->insert_edge(mislav, florijan, dba->edge_type("FRIEND_OF")); + dba->insert_edge(florijan, teon, dba->edge_type("FRIEND_OF")); + dba->insert_edge(teon, florijan, dba->edge_type("FRIEND_OF")); + + dba->insert_edge(memgraph, xps_15, dba->edge_type("OWNS")); + + dba->insert_edge(teon, xps_15, dba->edge_type("USES")); + dba->insert_edge(mislav, xps_15, dba->edge_type("USES")); + dba->insert_edge(florijan, xps_15, dba->edge_type("USES")); + + dba->commit(); +} + +int main(int argc, char *argv[]) { + REGISTER_ARGS(argc, argv); + + Dbms dbms; + fill_db(dbms); + query::Console(dbms); + return 0; +}