Query Console implemented
Summary: buda, teon.banek, mislav.bradac Reviewers: mislav.bradac, buda, teon.banek Reviewed By: buda Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D163
This commit is contained in:
parent
e9357ea13b
commit
f304dfef28
@ -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
|
||||
|
172
src/query/console.cpp
Normal file
172
src/query/console.cpp
Normal file
@ -0,0 +1,172 @@
|
||||
//
|
||||
// Copyright 2017 Memgraph
|
||||
// Created by Florijan Stamenkovic on 23.03.17.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#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 <typename TStream, typename TIterable, typename TConverter>
|
||||
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<VertexAccessor>();
|
||||
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<EdgeAccessor>();
|
||||
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<std::string> &header = results.GetHeader();
|
||||
std::vector<int> 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<std::vector<std::string>> result_strings(
|
||||
results_data.size(), std::vector<std::string>(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<std::string> 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;
|
||||
}
|
||||
}
|
||||
}
|
22
src/query/console.hpp
Normal file
22
src/query/console.hpp
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// Copyright 2017 Memgraph
|
||||
// Created by Florijan Stamenkovic on 23.03.17.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
|
||||
#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);
|
||||
}
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -1,175 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#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<std::string> &) { logger.info("header"); }
|
||||
|
||||
void Result(std::vector<TypedValue> &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<VertexAccessor>();
|
||||
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<EdgeAccessor>();
|
||||
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<std::string, TypedValue> &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<Stdout>());
|
||||
|
||||
// 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;
|
||||
}
|
69
tests/manual/console_test.cpp
Normal file
69
tests/manual/console_test.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
#include <iostream>
|
||||
|
||||
#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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user