New client features: non-interactive shell, shell commands, csv format
Summary: If the `STDIN_FILENO` is a `TTY`, then an interactive command prompt is shown. Otherwise, client runs in non-interactive mode. Shell commands start with a colon sign, and end at the end of line. Csv and tabular output formats are supported. Reviewers: teon.banek, mferencevic Reviewed By: teon.banek, mferencevic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1633
This commit is contained in:
parent
abef91616d
commit
c553a309d2
@ -1,6 +1,7 @@
|
||||
#include "communication/bolt/v1/value.hpp"
|
||||
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace communication::bolt {
|
||||
|
||||
@ -330,7 +331,7 @@ std::ostream &operator<<(std::ostream &os, const Value &value) {
|
||||
case Value::Type::Double:
|
||||
return os << value.ValueDouble();
|
||||
case Value::Type::String:
|
||||
return os << value.ValueString();
|
||||
return os << utils::Escape(value.ValueString());
|
||||
case Value::Type::List:
|
||||
os << "[";
|
||||
utils::PrintIterable(os, value.ValueList());
|
||||
|
@ -335,4 +335,32 @@ inline std::string RandomString(size_t length) {
|
||||
str[i] = charset[rand_dist(pseudo_rand_gen)];
|
||||
return str;
|
||||
}
|
||||
|
||||
/// Escapes all whitespace and quotation characters to produce a string
|
||||
/// which can be used as a string literal.
|
||||
inline std::string Escape(const std::string &src) {
|
||||
std::string ret;
|
||||
ret.reserve(src.size() + 2);
|
||||
ret.append(1, '"');
|
||||
for (auto c : src) {
|
||||
if (c == '\\' || c == '\'' || c == '"') {
|
||||
ret.append(1, '\\');
|
||||
ret.append(1, c);
|
||||
} else if (c == '\b') {
|
||||
ret.append("\\b");
|
||||
} else if (c == '\f') {
|
||||
ret.append("\\f");
|
||||
} else if (c == '\n') {
|
||||
ret.append("\\n");
|
||||
} else if (c == '\r') {
|
||||
ret.append("\\r");
|
||||
} else if (c == '\t') {
|
||||
ret.append("\\t");
|
||||
} else {
|
||||
ret.append(1, c);
|
||||
}
|
||||
}
|
||||
ret.append(1, '"');
|
||||
return ret;
|
||||
}
|
||||
} // namespace utils
|
||||
|
@ -6,6 +6,11 @@ target_link_libraries(mg_import_csv mg-single-node kvstore_dummy_lib)
|
||||
add_executable(mg_statsd mg_statsd/main.cpp)
|
||||
target_link_libraries(mg_statsd mg-communication mg-io mg-utils mg-stats)
|
||||
|
||||
# Generate a version.hpp file
|
||||
set(VERSION_STRING ${memgraph_VERSION})
|
||||
configure_file(../../src/version.hpp.in version.hpp @ONLY)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# Memgraph Client Target
|
||||
add_executable(mg_client mg_client/main.cpp)
|
||||
set(CLIENT_LIBS mg-communication mg-io mg-utils)
|
||||
@ -14,6 +19,7 @@ if (READLINE_FOUND)
|
||||
endif()
|
||||
target_link_libraries(mg_client ${CLIENT_LIBS})
|
||||
|
||||
|
||||
# Strip the executable in release build.
|
||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||
if (lower_build_type STREQUAL "release")
|
||||
|
@ -2,7 +2,9 @@
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include <pwd.h>
|
||||
#include <signal.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <algorithm>
|
||||
#include <experimental/filesystem>
|
||||
@ -12,18 +14,82 @@
|
||||
#include "communication/bolt/client.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "io/network/utils.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/flag_validation.hpp"
|
||||
#include "utils/signals.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/terminate_handler.hpp"
|
||||
#include "utils/timer.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
volatile sig_atomic_t is_shutting_down = 0;
|
||||
|
||||
// Usage strings.
|
||||
static const std::string kUsage =
|
||||
"Memgraph bolt client.\n"
|
||||
"The client can be run in interactive or non-interactive mode.\n";
|
||||
static const std::string kInteractiveUsage =
|
||||
"In interactive mode, user can enter cypher queries and supported "
|
||||
"commands.\n\n"
|
||||
"Cypher queries can span through multiple lines and conclude with a\n"
|
||||
"semi-colon (;). Each query is executed in the database and the results\n"
|
||||
"are printed out.\n\n"
|
||||
"The following interactive commands are supported:\n\n"
|
||||
"\t:help\t Print out usage for interactive mode\n"
|
||||
"\t:quit\t Exit the shell\n";
|
||||
|
||||
// Supported commands.
|
||||
// Maybe also add reconnect?
|
||||
static const std::string kCommandQuit = ":quit";
|
||||
static const std::string kCommandHelp = ":help";
|
||||
|
||||
// Supported formats.
|
||||
static const std::string kCsvFormat = "csv";
|
||||
static const std::string kTabularFormat = "tabular";
|
||||
|
||||
DEFINE_string(host, "127.0.0.1",
|
||||
"Server address. It can be a DNS resolveable hostname.");
|
||||
"Server address. It can be a DNS resolvable hostname.");
|
||||
DEFINE_int32(port, 7687, "Server port");
|
||||
DEFINE_string(username, "", "Username for the database");
|
||||
DEFINE_string(password, "", "Password for the database");
|
||||
DEFINE_bool(use_ssl, true, "Set to true to connect with SSL to the server.");
|
||||
DEFINE_bool(use_ssl, true, "Use SSL when connecting to the server.");
|
||||
DEFINE_bool(fit_to_screen, false, "Fit output width to screen width.");
|
||||
DEFINE_VALIDATED_string(
|
||||
output_format, "tabular",
|
||||
"Query output format. Can be csv/tabular. If output format is "
|
||||
"other than tabular `fit-to-screen` flag is ignored.",
|
||||
{
|
||||
if (value == kCsvFormat || value == kTabularFormat) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
DEFINE_VALIDATED_string(csv_delimiter, ",",
|
||||
"Character used to separate fields.", {
|
||||
if (value.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
DEFINE_string(
|
||||
csv_escapechar, "",
|
||||
"Character used to escape the quotechar(\") if csv-doublequote is false.");
|
||||
DEFINE_bool(
|
||||
csv_doublequote, true,
|
||||
"Controls how instances of quotechar(\") appearing inside a field should "
|
||||
"themselves be quoted. When true, the character is doubled. When false, "
|
||||
"the escapechar is used as a prefix to the quotechar. "
|
||||
"If csv-doublequote is false 'csv-escapechar' must be set.");
|
||||
|
||||
static bool ValidateCsvDoubleQuote() {
|
||||
if (!FLAGS_csv_doublequote && FLAGS_csv_escapechar.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef HAS_READLINE
|
||||
DEFINE_string(history, "~/.memgraph",
|
||||
@ -42,14 +108,47 @@ DECLARE_int32(min_log_level);
|
||||
// Unfinished query text from previous input.
|
||||
// e.g. Previous input was MATCH(n) RETURN n; MATCH
|
||||
// then default_text would be set to MATCH for next query.
|
||||
std::string default_text;
|
||||
static std::string default_text;
|
||||
|
||||
static const std::string kPrompt = "memgraph> ";
|
||||
static const std::string kMultilinePrompt = " -> ";
|
||||
|
||||
static void PrintHelp() { std::cout << kInteractiveUsage << std::endl; }
|
||||
|
||||
static void EchoFailure(const std::string &failure_msg,
|
||||
const std::string &explanation) {
|
||||
if (isatty(STDIN_FILENO)) {
|
||||
std::cout << "\033[1;31m" << failure_msg << ": \033[0m";
|
||||
std::cout << explanation << std::endl;
|
||||
} else {
|
||||
std::cerr << failure_msg << ": ";
|
||||
std::cerr << explanation << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
static void EchoInfo(const std::string &message) {
|
||||
if (isatty(STDIN_FILENO)) {
|
||||
std::cout << message << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
static void SetStdinEcho(bool enable = true) {
|
||||
struct termios tty;
|
||||
tcgetattr(STDIN_FILENO, &tty);
|
||||
if (!enable) {
|
||||
tty.c_lflag &= ~ECHO;
|
||||
} else {
|
||||
tty.c_lflag |= ECHO;
|
||||
}
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &tty);
|
||||
}
|
||||
|
||||
#ifdef HAS_READLINE
|
||||
|
||||
#include "readline/history.h"
|
||||
#include "readline/readline.h"
|
||||
|
||||
/** Helper function that sets default input for 'readline'*/
|
||||
/// Helper function that sets default input for 'readline'
|
||||
static int SetDefaultText() {
|
||||
rl_insert_text(default_text.c_str());
|
||||
default_text = "";
|
||||
@ -57,9 +156,9 @@ static int SetDefaultText() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Memgraph and OpenCypher keywords.
|
||||
* Copied from src/query/frontend/opencypher/grammar/Cypher.g4
|
||||
* and src/query/frontend/grammar/MemgraphCypher.g4 */
|
||||
/// Memgraph and OpenCypher keywords.
|
||||
/// Copied from src/query/frontend/opencypher/grammar/Cypher.g4
|
||||
/// and src/query/frontend/grammar/MemgraphCypher.g4
|
||||
static const std::vector<std::string> kMemgraphKeywords{
|
||||
"ALTER", "AUTH", "BATCH", "BATCHES", "CLEAR", "DATA",
|
||||
"DENY", "DROP", "FOR", "FROM", "GRANT", "IDENTIFIED",
|
||||
@ -125,14 +224,12 @@ static char **Completer(const char *text, int start, int end) {
|
||||
return rl_completion_matches(text, CompletionGenerator);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 User input line, or nullopt on EOF.
|
||||
*/
|
||||
/// 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 User input line, or nullopt on EOF.
|
||||
static std::experimental::optional<std::string> ReadLine(
|
||||
const std::string &prompt) {
|
||||
if (default_text.size() > 0) {
|
||||
@ -150,35 +247,38 @@ static std::experimental::optional<std::string> ReadLine(
|
||||
|
||||
#else
|
||||
|
||||
/** Helper function that reads a line from the standard input
|
||||
* using getline.
|
||||
* @param prompt The prompt to display.
|
||||
* @return User input line, or nullopt on EOF.
|
||||
*/
|
||||
/// Helper function that reads a line from the standard input
|
||||
/// using getline.
|
||||
/// @param prompt The prompt to display.
|
||||
/// @return User input line, or nullopt on EOF.
|
||||
static std::experimental::optional<std::string> ReadLine(
|
||||
const std::string &prompt) {
|
||||
std::cout << prompt << default_text;
|
||||
std::string line;
|
||||
std::getline(std::cin, line);
|
||||
if (!isatty(STDIN_FILENO)) {
|
||||
// Stupid hack to have same output as readline if stdin is redirected.
|
||||
std::cout << line << std::endl;
|
||||
}
|
||||
if (std::cin.eof()) return std::experimental::nullopt;
|
||||
line = default_text + line;
|
||||
default_text = "";
|
||||
if (std::cin.eof()) return std::experimental::nullopt;
|
||||
return line;
|
||||
}
|
||||
|
||||
#endif // HAS_READLINE
|
||||
|
||||
/** Helper function that parses user line input.
|
||||
* @param line user input line.
|
||||
* @param quote quote character or '\0'; if set line is inside quotation.
|
||||
* @param escaped if set, next character should be escaped.
|
||||
* @return pair of string and bool. string is parsed line and bool marks
|
||||
* if query finished(Query finishes with ';') with this line.
|
||||
*/
|
||||
static std::experimental::optional<std::string> GetLine() {
|
||||
std::string line;
|
||||
std::getline(std::cin, line);
|
||||
if (std::cin.eof()) return std::experimental::nullopt;
|
||||
line = default_text + line;
|
||||
default_text = "";
|
||||
return line;
|
||||
}
|
||||
|
||||
/// Helper function that parses user line input.
|
||||
/// @param line user input line.
|
||||
/// @param quote quote character or '\0'; if set line is inside quotation.
|
||||
/// @param escaped if set, next character should be escaped.
|
||||
/// @return pair of string and bool. string is parsed line and bool marks
|
||||
/// if query finished(Query finishes with ';') with this line.
|
||||
static std::pair<std::string, bool> ParseLine(const std::string &line,
|
||||
char *quote, bool *escaped) {
|
||||
// Parse line.
|
||||
@ -204,8 +304,7 @@ static std::pair<std::string, bool> ParseLine(const std::string &line,
|
||||
return std::make_pair(parsed_line.str(), is_done);
|
||||
}
|
||||
|
||||
static std::experimental::optional<std::string> GetQuery(
|
||||
const std::string &prompt) {
|
||||
static std::experimental::optional<std::string> GetQuery() {
|
||||
char quote = '\0';
|
||||
bool escaped = false;
|
||||
auto ret = ParseLine(default_text, "e, &escaped);
|
||||
@ -215,19 +314,39 @@ static std::experimental::optional<std::string> GetQuery(
|
||||
return ret.first;
|
||||
}
|
||||
std::stringstream query;
|
||||
std::string multiline_prompt(" -> ");
|
||||
std::experimental::optional<std::string> line;
|
||||
int line_cnt = 0;
|
||||
auto is_done = false;
|
||||
while (!is_done) {
|
||||
auto line = ReadLine(line_cnt == 0 ? prompt : multiline_prompt);
|
||||
if (!isatty(STDIN_FILENO)) {
|
||||
line = GetLine();
|
||||
} else {
|
||||
line = ReadLine(line_cnt == 0 ? kPrompt : kMultilinePrompt);
|
||||
if (line_cnt == 0 && line && line->size() > 0 && (*line)[0] == ':') {
|
||||
auto trimmed_line = utils::Trim(*line);
|
||||
if (trimmed_line == kCommandQuit) {
|
||||
return std::experimental::nullopt;
|
||||
} else if (trimmed_line == kCommandHelp) {
|
||||
PrintHelp();
|
||||
return "";
|
||||
} else {
|
||||
EchoFailure("Unsupported command", trimmed_line);
|
||||
PrintHelp();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!line) return std::experimental::nullopt;
|
||||
if (line->empty()) continue;
|
||||
auto ret = ParseLine(*line, "e, &escaped);
|
||||
query << ret.first << " ";
|
||||
query << ret.first;
|
||||
auto char_count = ret.first.size();
|
||||
if (ret.second) {
|
||||
is_done = true;
|
||||
char_count += 1;
|
||||
char_count += 1; // ';' sign
|
||||
} else {
|
||||
// Query is multiline so append whitespace.
|
||||
query << " ";
|
||||
}
|
||||
if (char_count < line->size()) {
|
||||
default_text = utils::Trim(line->substr(char_count));
|
||||
@ -238,9 +357,9 @@ static std::experimental::optional<std::string> GetQuery(
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void PrettyPrintData(const std::vector<T> &data, int total_width,
|
||||
static void PrintRowTabular(const std::vector<T> &data, int total_width,
|
||||
int column_width, int num_columns,
|
||||
bool all_columns_fit) {
|
||||
bool all_columns_fit, int margin = 1) {
|
||||
if (!all_columns_fit) num_columns -= 1;
|
||||
std::string data_output = std::string(total_width, ' ');
|
||||
for (auto i = 0; i < total_width; i += column_width) {
|
||||
@ -250,11 +369,11 @@ static void PrettyPrintData(const std::vector<T> &data, int total_width,
|
||||
std::stringstream field;
|
||||
field << data[idx]; // convert Value to string
|
||||
std::string field_str(field.str());
|
||||
if (field_str.size() > column_width - 1) {
|
||||
field_str.erase(column_width - 1, std::string::npos);
|
||||
if (field_str.size() > column_width - 2 * margin - 1) {
|
||||
field_str.erase(column_width - 2 * margin - 1, std::string::npos);
|
||||
field_str.replace(field_str.size() - 3, 3, "...");
|
||||
}
|
||||
data_output.replace(i + 1, field_str.size(), field_str);
|
||||
data_output.replace(i + 1 + margin, field_str.size(), field_str);
|
||||
}
|
||||
}
|
||||
if (!all_columns_fit) {
|
||||
@ -264,25 +383,24 @@ static void PrettyPrintData(const std::vector<T> &data, int total_width,
|
||||
std::cout << data_output << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for determining maximum length of data.
|
||||
* @param data Vector of string representable elements. Elements should have
|
||||
* operator '<<' implemented.
|
||||
* @return length needed for representing max size element in vector. Plus
|
||||
* one is added because of column start character '|'.
|
||||
*/
|
||||
/// Helper function for determining maximum length of data.
|
||||
/// @param data Vector of string representable elements. Elements should have
|
||||
/// operator '<<' implemented.
|
||||
/// @param margin Column margin width.
|
||||
/// @return length needed for representing max size element in vector. Plus
|
||||
/// one is added because of column start character '|'.
|
||||
template <typename T>
|
||||
static uint64_t GetMaxColumnWidth(const std::vector<T> &data) {
|
||||
static uint64_t GetMaxColumnWidth(const std::vector<T> &data, int margin = 1) {
|
||||
uint64_t column_width = 0;
|
||||
for (auto &elem : data) {
|
||||
std::stringstream field;
|
||||
field << elem;
|
||||
column_width = std::max(column_width, field.str().size());
|
||||
column_width = std::max(column_width, field.str().size() + 2 * margin);
|
||||
}
|
||||
return column_width + 1;
|
||||
}
|
||||
|
||||
static void PrettyPrint(
|
||||
static void PrintTabular(
|
||||
const std::vector<std::string> &header,
|
||||
const std::vector<std::vector<communication::bolt::Value>> &records) {
|
||||
struct winsize w;
|
||||
@ -330,19 +448,91 @@ static void PrettyPrint(
|
||||
line_fill[total_width - 1] = '+';
|
||||
std::cout << line_fill << std::endl;
|
||||
// Print Header.
|
||||
PrettyPrintData(header, total_width, column_width, num_columns,
|
||||
PrintRowTabular(header, total_width, column_width, num_columns,
|
||||
all_columns_fit);
|
||||
std::cout << line_fill << std::endl;
|
||||
// Print Records.
|
||||
for (size_t i = 0; i < records.size(); ++i) {
|
||||
PrettyPrintData(records[i], total_width, column_width, num_columns,
|
||||
PrintRowTabular(records[i], total_width, column_width, num_columns,
|
||||
all_columns_fit);
|
||||
}
|
||||
std::cout << line_fill << std::endl;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::vector<std::string> FormatCsvFields(const std::vector<T> &fields) {
|
||||
std::vector<std::string> formatted;
|
||||
formatted.reserve(fields.size());
|
||||
for (auto &field : fields) {
|
||||
std::stringstream field_stream;
|
||||
field_stream << field;
|
||||
std::string formatted_field(field_stream.str());
|
||||
if (FLAGS_csv_doublequote) {
|
||||
formatted_field = utils::Replace(formatted_field, "\"", "\"\"");
|
||||
} else {
|
||||
formatted_field =
|
||||
utils::Replace(formatted_field, "\"", FLAGS_csv_escapechar + "\"");
|
||||
}
|
||||
formatted_field.insert(0, 1, '"');
|
||||
formatted_field.append(1, '"');
|
||||
formatted.push_back(formatted_field);
|
||||
}
|
||||
return formatted;
|
||||
}
|
||||
|
||||
static void PrintCsv(
|
||||
const std::vector<std::string> &header,
|
||||
const std::vector<std::vector<communication::bolt::Value>> &records) {
|
||||
// Print Header.
|
||||
auto formatted_header = FormatCsvFields(header);
|
||||
utils::PrintIterable(std::cout, formatted_header, FLAGS_csv_delimiter);
|
||||
std::cout << std::endl;
|
||||
// Print Records.
|
||||
for (size_t i = 0; i < records.size(); ++i) {
|
||||
auto formatted_row = FormatCsvFields(records[i]);
|
||||
utils::PrintIterable(std::cout, formatted_row, FLAGS_csv_delimiter);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
static void Output(
|
||||
const std::vector<std::string> &header,
|
||||
const std::vector<std::vector<communication::bolt::Value>> &records) {
|
||||
if (FLAGS_output_format == kTabularFormat) {
|
||||
PrintTabular(header, records);
|
||||
} else if (FLAGS_output_format == kCsvFormat) {
|
||||
PrintCsv(header, records);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
gflags::SetVersionString(version_string);
|
||||
gflags::SetUsageMessage(kUsage);
|
||||
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
if (FLAGS_output_format == kCsvFormat && !ValidateCsvDoubleQuote()) {
|
||||
EchoFailure(
|
||||
"Unsupported combination of 'csv-doublequote' and 'csv-escapechar'\n"
|
||||
"flags",
|
||||
"Run './mg_client --help' for usage.");
|
||||
return 1;
|
||||
}
|
||||
auto password = FLAGS_password;
|
||||
if (isatty(STDIN_FILENO) && FLAGS_username.size() > 0 &&
|
||||
password.size() == 0) {
|
||||
SetStdinEcho(false);
|
||||
auto password_optional = ReadLine("Password: ");
|
||||
std::cout << std::endl;
|
||||
if (password_optional) {
|
||||
password = *password_optional;
|
||||
} else {
|
||||
EchoFailure(
|
||||
"Password not submitted",
|
||||
fmt::format("Requested password for username {}", FLAGS_username));
|
||||
return 1;
|
||||
}
|
||||
SetStdinEcho(true);
|
||||
}
|
||||
FLAGS_min_log_level = google::ERROR;
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
|
||||
@ -360,8 +550,7 @@ int main(int argc, char **argv) {
|
||||
history_dir = fs::path(pw->pw_dir) / kDefaultHistoryMemgraphDir;
|
||||
}
|
||||
if (!utils::EnsureDir(history_dir)) {
|
||||
std::cout << fmt::format("History directory {} doesn't exist", history_dir)
|
||||
<< std::endl;
|
||||
EchoFailure("History directory doesn't exist", history_dir);
|
||||
// Should program exit here or just continue with warning message?
|
||||
return 1;
|
||||
}
|
||||
@ -370,14 +559,53 @@ int main(int argc, char **argv) {
|
||||
if (fs::exists(history_file)) {
|
||||
auto ret = read_history(history_file.string().c_str());
|
||||
if (ret != 0) {
|
||||
std::cout << fmt::format("Unable to read history file: {}", history_file);
|
||||
EchoFailure("Unable to read history file", history_file);
|
||||
// Should program exit here or just continue with warning message?
|
||||
return 1;
|
||||
}
|
||||
history_len = history_length;
|
||||
}
|
||||
|
||||
// Save history function. Used to save readline history after each query.
|
||||
auto save_history = [&history_len, history_file] {
|
||||
if (!FLAGS_no_history) {
|
||||
int ret = 0;
|
||||
// If there was no history, create history file.
|
||||
// Otherwise, append to existing history.
|
||||
if (history_len == 0) {
|
||||
ret = write_history(history_file.string().c_str());
|
||||
} else {
|
||||
ret = append_history(1, history_file.string().c_str());
|
||||
}
|
||||
if (ret != 0) {
|
||||
EchoFailure("Unable to save history to file", history_file);
|
||||
return 1;
|
||||
}
|
||||
++history_len;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
#endif
|
||||
|
||||
// Prevent handling shutdown inside a shutdown. For example, SIGINT handler
|
||||
// being interrupted by SIGTERM before is_shutting_down is set, thus causing
|
||||
// double shutdown.
|
||||
sigset_t block_shutdown_signals;
|
||||
sigemptyset(&block_shutdown_signals);
|
||||
sigaddset(&block_shutdown_signals, SIGTERM);
|
||||
sigaddset(&block_shutdown_signals, SIGINT);
|
||||
|
||||
auto shutdown = [](int exit_code = 0) {
|
||||
if (is_shutting_down) return;
|
||||
is_shutting_down = 1;
|
||||
std::quick_exit(exit_code);
|
||||
};
|
||||
|
||||
utils::SignalHandler::RegisterHandler(utils::Signal::Terminate, shutdown,
|
||||
block_shutdown_signals);
|
||||
utils::SignalHandler::RegisterHandler(utils::Signal::Interupt, shutdown,
|
||||
block_shutdown_signals);
|
||||
|
||||
// TODO handle endpoint exception.
|
||||
// It has CHECK in constructor if address is not valid.
|
||||
io::network::Endpoint endpoint(io::network::ResolveHostname(FLAGS_host),
|
||||
@ -385,27 +613,46 @@ int main(int argc, char **argv) {
|
||||
communication::ClientContext context(FLAGS_use_ssl);
|
||||
communication::bolt::Client client(&context);
|
||||
|
||||
if (!client.Connect(endpoint, FLAGS_username, FLAGS_password)) {
|
||||
if (!client.Connect(endpoint, FLAGS_username, password)) {
|
||||
// Error message is logged in client.Connect method
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "mg-client" << std::endl;
|
||||
std::cout << fmt::format("Connected to 'memgraph://{}'", endpoint)
|
||||
<< std::endl;
|
||||
EchoInfo("mg-client");
|
||||
EchoInfo("Type :help for shell usage");
|
||||
EchoInfo("Quit the shell by typing Ctrl-D(eof) or :quit");
|
||||
EchoInfo(fmt::format("Connected to 'memgraph://{}'", endpoint));
|
||||
int num_retries = 3;
|
||||
while (true) {
|
||||
auto query = GetQuery("memgraph> ");
|
||||
auto query = GetQuery();
|
||||
if (!query) break;
|
||||
if (query->empty()) continue;
|
||||
try {
|
||||
utils::Timer t;
|
||||
auto ret = client.Execute(*query, {});
|
||||
PrettyPrint(ret.fields, ret.records);
|
||||
auto elapsed = t.Elapsed().count();
|
||||
if (ret.records.size() > 0) Output(ret.fields, ret.records);
|
||||
if (isatty(STDIN_FILENO)) {
|
||||
std::string summary;
|
||||
if (ret.records.size() == 0) {
|
||||
summary = "Empty set";
|
||||
} else if (ret.records.size() == 1) {
|
||||
summary = std::to_string(ret.records.size()) + " row in set";
|
||||
} else {
|
||||
summary = std::to_string(ret.records.size()) + " rows in set";
|
||||
}
|
||||
std::cout << summary << " (" << fmt::format("{:.2f}", elapsed)
|
||||
<< " sec)" << std::endl;
|
||||
#ifdef HAS_READLINE
|
||||
auto history_ret = save_history();
|
||||
if (history_ret != 0) return history_ret;
|
||||
#endif
|
||||
}
|
||||
} catch (const communication::bolt::ClientQueryException &e) {
|
||||
std::cout << "Client received exception: " << e.what() << std::endl;
|
||||
EchoFailure("Client received exception", e.what());
|
||||
} catch (const communication::bolt::ClientFatalException &e) {
|
||||
std::cout << "Client received exception: " << e.what() << std::endl;
|
||||
std::cout << "Trying to reconnect" << std::endl;
|
||||
EchoFailure("Client received exception", e.what());
|
||||
EchoInfo("Trying to reconnect");
|
||||
bool is_connected = false;
|
||||
client.Close();
|
||||
while (num_retries > 0) {
|
||||
@ -418,34 +665,13 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
if (is_connected) {
|
||||
num_retries = 3;
|
||||
std::cout << fmt::format("Connected to 'memgraph://{}'", endpoint)
|
||||
<< std::endl;
|
||||
EchoInfo(fmt::format("Connected to 'memgraph://{}'", endpoint));
|
||||
} else {
|
||||
std::cout << fmt::format("Couldn't connect to 'memgraph://{}'",
|
||||
endpoint)
|
||||
<< std::endl;
|
||||
EchoFailure("Couldn't connect to",
|
||||
fmt::format("'memgraph://{}'", endpoint));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if HAS_READLINE
|
||||
int curr_len = history_length;
|
||||
if (!FLAGS_no_history && curr_len - history_len > 0) {
|
||||
int ret = 0;
|
||||
// If there was no history, create history file.
|
||||
// Otherwise, append to existing history.
|
||||
if (history_len == 0) {
|
||||
ret = write_history(history_file.string().c_str());
|
||||
} else {
|
||||
ret =
|
||||
append_history(curr_len - history_len, history_file.string().c_str());
|
||||
}
|
||||
if (ret != 0) {
|
||||
std::cout << "Unable to save history" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
1
tools/tests/client/input/multiple_columns.txt
Normal file
1
tools/tests/client/input/multiple_columns.txt
Normal file
@ -0,0 +1 @@
|
||||
CREATE (n:Node)-[e:Edge]->(m:Vertex) RETURN n,e,m;
|
2
tools/tests/client/output_csv/escaping.txt
Normal file
2
tools/tests/client/output_csv/escaping.txt
Normal file
@ -0,0 +1,2 @@
|
||||
"n"
|
||||
"(:Node {tmp: ""\""\\;\\""})"
|
12
tools/tests/client/output_csv/multiline_query.txt
Normal file
12
tools/tests/client/output_csv/multiline_query.txt
Normal file
@ -0,0 +1,12 @@
|
||||
"n"
|
||||
"(:Constantine {quote: ""In hoc signo vinces""})"
|
||||
"n"
|
||||
"(:Constantine {quote: ""In hoc signo vinces""})"
|
||||
"n"
|
||||
"(:Erdody {quote: ""Regnum regno non praescribit leges""})"
|
||||
"n"
|
||||
"(:Erdody {quote: ""Regnum regno non praescribit leges""})"
|
||||
"n"
|
||||
"(:Caesar {quote: ""Alea iacta est""})"
|
||||
"n"
|
||||
"(:Caesar {quote: ""Alea iacta est""})"
|
2
tools/tests/client/output_csv/multiple_columns.txt
Normal file
2
tools/tests/client/output_csv/multiple_columns.txt
Normal file
@ -0,0 +1,2 @@
|
||||
"n","e","m"
|
||||
"(:Node)","[Edge]","(:Vertex)"
|
@ -0,0 +1,6 @@
|
||||
"n"
|
||||
"(:Node)"
|
||||
"n"
|
||||
"(:Vertex)"
|
||||
"n"
|
||||
"(:Vertex)"
|
8
tools/tests/client/output_csv/query_per_line.txt
Normal file
8
tools/tests/client/output_csv/query_per_line.txt
Normal file
@ -0,0 +1,8 @@
|
||||
"n"
|
||||
"(:Node)"
|
||||
"n"
|
||||
"(:Node)"
|
||||
"n"
|
||||
"(:Vertex)"
|
||||
"n"
|
||||
"(:Vertex)"
|
10
tools/tests/client/output_csv/quote.txt
Normal file
10
tools/tests/client/output_csv/quote.txt
Normal file
@ -0,0 +1,10 @@
|
||||
"n"
|
||||
"(:Ciceron {quote: ""o tempora o mores""})"
|
||||
"n"
|
||||
"(:Ciceron {quote: ""o tempora o mores!""})"
|
||||
"n"
|
||||
"(:Ciceron {quote: ""o tempora \'o mores\'""})"
|
||||
"n"
|
||||
"(:Ciceron {quote: ""o tempora \""o mores\""""})"
|
||||
"n"
|
||||
"(:Ciceron {quote: ""o tempora \""o mores\""""})"
|
12
tools/tests/client/output_csv/unfinished_query.txt
Normal file
12
tools/tests/client/output_csv/unfinished_query.txt
Normal file
@ -0,0 +1,12 @@
|
||||
"n"
|
||||
"(:Ovid {quote: ""Exitus Acta Probat""})"
|
||||
"n"
|
||||
"(:Ovid {quote: ""Exitus Acta Probat""})"
|
||||
"n"
|
||||
"(:Ovid {quote: ""Exitus Acta Probat""})"
|
||||
"n"
|
||||
"(:Bible {quote: ""Fiat Lux""})"
|
||||
"n"
|
||||
"(:Plinius {quote: ""In vino veritas""})"
|
||||
"n"
|
||||
"(:Plinius {quote: ""In vino veritas""})"
|
@ -1,9 +0,0 @@
|
||||
mg-client
|
||||
Connected to 'memgraph://127.0.0.1:7687'
|
||||
memgraph> CREATE (n:Node{tmp:"\"\\;\\"}) RETURN n;
|
||||
+-------------------+
|
||||
|n |
|
||||
+-------------------+
|
||||
|(:Node {tmp: "\;\})|
|
||||
+-------------------+
|
||||
memgraph>
|
@ -1,49 +0,0 @@
|
||||
mg-client
|
||||
Connected to 'memgraph://127.0.0.1:7687'
|
||||
memgraph> CREATE (n:Constantine{quote:"In hoc signo vinces"})
|
||||
-> RETURN n;
|
||||
+-------------------------------------------+
|
||||
|n |
|
||||
+-------------------------------------------+
|
||||
|(:Constantine {quote: In hoc signo vinces})|
|
||||
+-------------------------------------------+
|
||||
memgraph> MATCH
|
||||
-> (n)
|
||||
-> RETURN
|
||||
-> n;
|
||||
+-------------------------------------------+
|
||||
|n |
|
||||
+-------------------------------------------+
|
||||
|(:Constantine {quote: In hoc signo vinces})|
|
||||
+-------------------------------------------+
|
||||
memgraph> CREATE (n:Erdody{quote:
|
||||
-> "Regnum regno non praescribit leges"})
|
||||
-> RETURN
|
||||
-> n;
|
||||
+-----------------------------------------------------+
|
||||
|n |
|
||||
+-----------------------------------------------------+
|
||||
|(:Erdody {quote: Regnum regno non praescribit leges})|
|
||||
+-----------------------------------------------------+
|
||||
memgraph> MATCH (n:Erdody) RETURN
|
||||
-> n;
|
||||
+-----------------------------------------------------+
|
||||
|n |
|
||||
+-----------------------------------------------------+
|
||||
|(:Erdody {quote: Regnum regno non praescribit leges})|
|
||||
+-----------------------------------------------------+
|
||||
memgraph> CREATE (n:Caesar{quote:"Alea iacta
|
||||
-> est"}) RETURN n;
|
||||
+---------------------------------+
|
||||
|n |
|
||||
+---------------------------------+
|
||||
|(:Caesar {quote: Alea iacta est})|
|
||||
+---------------------------------+
|
||||
memgraph> MATCH (n:Caesar)
|
||||
-> RETURN n;
|
||||
+---------------------------------+
|
||||
|n |
|
||||
+---------------------------------+
|
||||
|(:Caesar {quote: Alea iacta est})|
|
||||
+---------------------------------+
|
||||
memgraph>
|
@ -1,19 +0,0 @@
|
||||
mg-client
|
||||
Connected to 'memgraph://127.0.0.1:7687'
|
||||
memgraph> CREATE (n:Node) RETURN n; CREATE (n:Vertex) RETURN n; MATCH (n:Vertex) RETURN n;
|
||||
+-------+
|
||||
|n |
|
||||
+-------+
|
||||
|(:Node)|
|
||||
+-------+
|
||||
+---------+
|
||||
|n |
|
||||
+---------+
|
||||
|(:Vertex)|
|
||||
+---------+
|
||||
+---------+
|
||||
|n |
|
||||
+---------+
|
||||
|(:Vertex)|
|
||||
+---------+
|
||||
memgraph>
|
@ -1,27 +0,0 @@
|
||||
mg-client
|
||||
Connected to 'memgraph://127.0.0.1:7687'
|
||||
memgraph> CREATE (n:Node) RETURN n;
|
||||
+-------+
|
||||
|n |
|
||||
+-------+
|
||||
|(:Node)|
|
||||
+-------+
|
||||
memgraph> MATCH (n:Node) RETURN n;
|
||||
+-------+
|
||||
|n |
|
||||
+-------+
|
||||
|(:Node)|
|
||||
+-------+
|
||||
memgraph> CREATE (n:Vertex) RETURN n;
|
||||
+---------+
|
||||
|n |
|
||||
+---------+
|
||||
|(:Vertex)|
|
||||
+---------+
|
||||
memgraph> MATCH (n:Vertex) RETURN n;
|
||||
+---------+
|
||||
|n |
|
||||
+---------+
|
||||
|(:Vertex)|
|
||||
+---------+
|
||||
memgraph>
|
@ -1,33 +0,0 @@
|
||||
mg-client
|
||||
Connected to 'memgraph://127.0.0.1:7687'
|
||||
memgraph> CREATE (n:Ciceron{quote:"o tempora o mores"}) RETURN n;
|
||||
+-------------------------------------+
|
||||
|n |
|
||||
+-------------------------------------+
|
||||
|(:Ciceron {quote: o tempora o mores})|
|
||||
+-------------------------------------+
|
||||
memgraph> CREATE (n:Ciceron{quote:'o tempora o mores!'}) RETURN n;
|
||||
+--------------------------------------+
|
||||
|n |
|
||||
+--------------------------------------+
|
||||
|(:Ciceron {quote: o tempora o mores!})|
|
||||
+--------------------------------------+
|
||||
memgraph> CREATE (n:Ciceron{quote:"o tempora 'o mores'"}) RETURN n;
|
||||
+---------------------------------------+
|
||||
|n |
|
||||
+---------------------------------------+
|
||||
|(:Ciceron {quote: o tempora 'o mores'})|
|
||||
+---------------------------------------+
|
||||
memgraph> CREATE (n:Ciceron{quote:'o tempora "o mores"'}) RETURN n;
|
||||
+---------------------------------------+
|
||||
|n |
|
||||
+---------------------------------------+
|
||||
|(:Ciceron {quote: o tempora "o mores"})|
|
||||
+---------------------------------------+
|
||||
memgraph> CREATE (n:Ciceron{quote:"o tempora \"o mores\""}) RETURN n;
|
||||
+---------------------------------------+
|
||||
|n |
|
||||
+---------------------------------------+
|
||||
|(:Ciceron {quote: o tempora "o mores"})|
|
||||
+---------------------------------------+
|
||||
memgraph>
|
5
tools/tests/client/output_tabular/escaping.txt
Normal file
5
tools/tests/client/output_tabular/escaping.txt
Normal file
@ -0,0 +1,5 @@
|
||||
+------------------------+
|
||||
| n |
|
||||
+------------------------+
|
||||
|(:Node {tmp: "\"\\;\\"})|
|
||||
+------------------------+
|
30
tools/tests/client/output_tabular/multiline_query.txt
Normal file
30
tools/tests/client/output_tabular/multiline_query.txt
Normal file
@ -0,0 +1,30 @@
|
||||
+---------------------------------------------+
|
||||
| n |
|
||||
+---------------------------------------------+
|
||||
|(:Constantine {quote: "In hoc signo vinces"})|
|
||||
+---------------------------------------------+
|
||||
+---------------------------------------------+
|
||||
| n |
|
||||
+---------------------------------------------+
|
||||
|(:Constantine {quote: "In hoc signo vinces"})|
|
||||
+---------------------------------------------+
|
||||
+-------------------------------------------------------+
|
||||
| n |
|
||||
+-------------------------------------------------------+
|
||||
|(:Erdody {quote: "Regnum regno non praescribit leges"})|
|
||||
+-------------------------------------------------------+
|
||||
+-------------------------------------------------------+
|
||||
| n |
|
||||
+-------------------------------------------------------+
|
||||
|(:Erdody {quote: "Regnum regno non praescribit leges"})|
|
||||
+-------------------------------------------------------+
|
||||
+-----------------------------------+
|
||||
| n |
|
||||
+-----------------------------------+
|
||||
|(:Caesar {quote: "Alea iacta est"})|
|
||||
+-----------------------------------+
|
||||
+-----------------------------------+
|
||||
| n |
|
||||
+-----------------------------------+
|
||||
|(:Caesar {quote: "Alea iacta est"})|
|
||||
+-----------------------------------+
|
5
tools/tests/client/output_tabular/multiple_columns.txt
Normal file
5
tools/tests/client/output_tabular/multiple_columns.txt
Normal file
@ -0,0 +1,5 @@
|
||||
+---------+---------+---------+
|
||||
| n | e | m |
|
||||
+---------+---------+---------+
|
||||
|(:Node) |[Edge] |(:Vertex)|
|
||||
+---------+---------+---------+
|
@ -0,0 +1,15 @@
|
||||
+-------+
|
||||
| n |
|
||||
+-------+
|
||||
|(:Node)|
|
||||
+-------+
|
||||
+---------+
|
||||
| n |
|
||||
+---------+
|
||||
|(:Vertex)|
|
||||
+---------+
|
||||
+---------+
|
||||
| n |
|
||||
+---------+
|
||||
|(:Vertex)|
|
||||
+---------+
|
20
tools/tests/client/output_tabular/query_per_line.txt
Normal file
20
tools/tests/client/output_tabular/query_per_line.txt
Normal file
@ -0,0 +1,20 @@
|
||||
+-------+
|
||||
| n |
|
||||
+-------+
|
||||
|(:Node)|
|
||||
+-------+
|
||||
+-------+
|
||||
| n |
|
||||
+-------+
|
||||
|(:Node)|
|
||||
+-------+
|
||||
+---------+
|
||||
| n |
|
||||
+---------+
|
||||
|(:Vertex)|
|
||||
+---------+
|
||||
+---------+
|
||||
| n |
|
||||
+---------+
|
||||
|(:Vertex)|
|
||||
+---------+
|
25
tools/tests/client/output_tabular/quote.txt
Normal file
25
tools/tests/client/output_tabular/quote.txt
Normal file
@ -0,0 +1,25 @@
|
||||
+---------------------------------------+
|
||||
| n |
|
||||
+---------------------------------------+
|
||||
|(:Ciceron {quote: "o tempora o mores"})|
|
||||
+---------------------------------------+
|
||||
+----------------------------------------+
|
||||
| n |
|
||||
+----------------------------------------+
|
||||
|(:Ciceron {quote: "o tempora o mores!"})|
|
||||
+----------------------------------------+
|
||||
+-------------------------------------------+
|
||||
| n |
|
||||
+-------------------------------------------+
|
||||
|(:Ciceron {quote: "o tempora \'o mores\'"})|
|
||||
+-------------------------------------------+
|
||||
+-------------------------------------------+
|
||||
| n |
|
||||
+-------------------------------------------+
|
||||
|(:Ciceron {quote: "o tempora \"o mores\""})|
|
||||
+-------------------------------------------+
|
||||
+-------------------------------------------+
|
||||
| n |
|
||||
+-------------------------------------------+
|
||||
|(:Ciceron {quote: "o tempora \"o mores\""})|
|
||||
+-------------------------------------------+
|
30
tools/tests/client/output_tabular/unfinished_query.txt
Normal file
30
tools/tests/client/output_tabular/unfinished_query.txt
Normal file
@ -0,0 +1,30 @@
|
||||
+-------------------------------------+
|
||||
| n |
|
||||
+-------------------------------------+
|
||||
|(:Ovid {quote: "Exitus Acta Probat"})|
|
||||
+-------------------------------------+
|
||||
+-------------------------------------+
|
||||
| n |
|
||||
+-------------------------------------+
|
||||
|(:Ovid {quote: "Exitus Acta Probat"})|
|
||||
+-------------------------------------+
|
||||
+-------------------------------------+
|
||||
| n |
|
||||
+-------------------------------------+
|
||||
|(:Ovid {quote: "Exitus Acta Probat"})|
|
||||
+-------------------------------------+
|
||||
+----------------------------+
|
||||
| n |
|
||||
+----------------------------+
|
||||
|(:Bible {quote: "Fiat Lux"})|
|
||||
+----------------------------+
|
||||
+-------------------------------------+
|
||||
| n |
|
||||
+-------------------------------------+
|
||||
|(:Plinius {quote: "In vino veritas"})|
|
||||
+-------------------------------------+
|
||||
+-------------------------------------+
|
||||
| n |
|
||||
+-------------------------------------+
|
||||
|(:Plinius {quote: "In vino veritas"})|
|
||||
+-------------------------------------+
|
@ -1,39 +0,0 @@
|
||||
mg-client
|
||||
Connected to 'memgraph://127.0.0.1:7687'
|
||||
memgraph> CREATE (n:Ovid{quote:"Exitus Acta Probat"}) RETURN n; MATCH (n)
|
||||
+-----------------------------------+
|
||||
|n |
|
||||
+-----------------------------------+
|
||||
|(:Ovid {quote: Exitus Acta Probat})|
|
||||
+-----------------------------------+
|
||||
memgraph> MATCH (n)RETURN n;
|
||||
+-----------------------------------+
|
||||
|n |
|
||||
+-----------------------------------+
|
||||
|(:Ovid {quote: Exitus Acta Probat})|
|
||||
+-----------------------------------+
|
||||
memgraph> MATCH (n) RETURN n; CREATE (n:Bible{quote:"Fiat Lux"}) RETURN n; CREATE (n:Plinius{quote:"In vino veritas"}) RETURN n; MATCH
|
||||
+-----------------------------------+
|
||||
|n |
|
||||
+-----------------------------------+
|
||||
|(:Ovid {quote: Exitus Acta Probat})|
|
||||
+-----------------------------------+
|
||||
+--------------------------+
|
||||
|n |
|
||||
+--------------------------+
|
||||
|(:Bible {quote: Fiat Lux})|
|
||||
+--------------------------+
|
||||
+-----------------------------------+
|
||||
|n |
|
||||
+-----------------------------------+
|
||||
|(:Plinius {quote: In vino veritas})|
|
||||
+-----------------------------------+
|
||||
memgraph> MATCH(n:Plinius)
|
||||
-> RETURN
|
||||
-> n;
|
||||
+-----------------------------------+
|
||||
|n |
|
||||
+-----------------------------------+
|
||||
|(:Plinius {quote: In vino veritas})|
|
||||
+-----------------------------------+
|
||||
memgraph>
|
@ -40,10 +40,17 @@ if [ ! -d $client_dir ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find tests input files.
|
||||
# Find tests dir.
|
||||
tests_dir="$DIR/client"
|
||||
if [ ! -d $tests_dir ]; then
|
||||
echo_failure "Directory with tests input not found"
|
||||
echo_failure "Directory with tests not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find tests input files.
|
||||
input_dir="$tests_dir/input"
|
||||
if [ ! -d $input_dir ]; then
|
||||
echo_failure "Directory with tests input files not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -65,26 +72,34 @@ echo # Blank line
|
||||
|
||||
client_flags="--use-ssl=false"
|
||||
test_code=0
|
||||
for filename in $tests_dir/input_*.txt; do
|
||||
test_name=$(basename $filename)
|
||||
test_name=${test_name#*_}
|
||||
test_name=${test_name%.*}
|
||||
output_name="output_$test_name.txt"
|
||||
for output_dir in $tests_dir/output_*; do
|
||||
for filename in $input_dir/*; do
|
||||
test_name=$(basename $filename)
|
||||
test_name=${test_name%.*}
|
||||
output_name="$test_name.txt"
|
||||
|
||||
echo_info "Running test $test_name"
|
||||
$client_dir/mg_client $client_flags < $filename > $tmpdir/$test_name
|
||||
diff -b $tmpdir/$test_name $tests_dir/$output_name
|
||||
test_code=$?
|
||||
output_format=$(basename $output_dir)
|
||||
output_format=${output_format#*_}
|
||||
run_flags="$client_flags --output-format=$output_format"
|
||||
|
||||
echo_info "Running test '$test_name' with $output_format output"
|
||||
$client_dir/mg_client $run_flags < $filename > $tmpdir/$test_name
|
||||
diff -b $tmpdir/$test_name $output_dir/$output_name
|
||||
test_code=$?
|
||||
if [ $test_code -ne 0 ]; then
|
||||
echo_failure "Test '$test_name' with $output_format output failed"
|
||||
break
|
||||
else
|
||||
echo_success "Test '$test_name' with $output_format output passed"
|
||||
fi
|
||||
|
||||
# Clear database for each test.
|
||||
$client_dir/mg_client $client_flags <<< "MATCH (n) DETACH DELETE n;" \
|
||||
&> /dev/null || exit 1
|
||||
done
|
||||
if [ $test_code -ne 0 ]; then
|
||||
echo_failure "Test $test_name failed"
|
||||
break
|
||||
else
|
||||
echo_success "Test $test_name passed"
|
||||
fi
|
||||
|
||||
# Clear database for each test.
|
||||
$client_dir/mg_client $client_flags <<< "MATCH (n) DETACH DELETE n;" \
|
||||
&> /dev/null || exit 1
|
||||
done
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user