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:
florijan 2017-03-23 14:45:51 +01:00
parent e9357ea13b
commit f304dfef28
7 changed files with 274 additions and 178 deletions

View File

@ -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
View 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
View 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);
}

View File

@ -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;
};
}
}

View File

@ -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()

View File

@ -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;
}

View 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;
}