std::ofstream is used, but there is no direct include for it. This fails the build with libstdc++ 11. Closes #8050
247 lines
8.6 KiB
C++
247 lines
8.6 KiB
C++
/*
|
|
* Copyright (C) 2019 ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* This file is part of Scylla.
|
|
*
|
|
* Scylla is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Scylla is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
// use boost::regex instead of std::regex due
|
|
// to stack overflow in debug mode
|
|
#include <boost/regex.hpp>
|
|
|
|
#include "test/lib/cql_test_env.hh"
|
|
#include "test/lib/cql_assertions.hh"
|
|
|
|
#include <seastar/core/future-util.hh>
|
|
#include <seastar/core/sleep.hh>
|
|
#include <seastar/core/app-template.hh>
|
|
#include <seastar/core/seastar.hh>
|
|
#include "transport/messages/result_message.hh"
|
|
#include "types/user.hh"
|
|
#include "types/map.hh"
|
|
#include "types/list.hh"
|
|
#include "types/set.hh"
|
|
#include "db/config.hh"
|
|
#include "db/paxos_grace_seconds_extension.hh"
|
|
#include "cql3/cql_config.hh"
|
|
#include "cql3/type_json.hh"
|
|
#include "test/lib/exception_utils.hh"
|
|
#include "alternator/tags_extension.hh"
|
|
#include "cdc/cdc_extension.hh"
|
|
#include <json/json.h>
|
|
|
|
static std::ofstream std_cout;
|
|
|
|
//
|
|
// A helper class to serialize result set output to a formatted JSON
|
|
//
|
|
class json_visitor final : public cql_transport::messages::result_message::visitor {
|
|
Json::Value& _root;
|
|
public:
|
|
json_visitor(Json::Value& root)
|
|
: _root(root)
|
|
{
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::void_message&) override {
|
|
_root["status"] = "ok";
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::set_keyspace& m) override {
|
|
_root["status"] = "ok";
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::prepared::cql& m) override {
|
|
_root["status"] = "ok";
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::prepared::thrift& m) override {
|
|
assert(false);
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::schema_change& m) override {
|
|
_root["status"] = "ok";
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::bounce_to_shard& m) override {
|
|
assert(false);
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::rows& m) override {
|
|
Json::Value& output_rows = _root["rows"];
|
|
const auto input_rows = m.rs().result_set().rows();
|
|
const auto& meta = m.rs().result_set().get_metadata().get_names();
|
|
for (auto&& in_row: input_rows) {
|
|
Json::Value out_row;
|
|
for (unsigned i = 0; i < meta.size(); ++i) {
|
|
const cql3::column_specification& col = *meta[i];
|
|
const bytes_opt& cell = in_row[i];
|
|
if (cell.has_value()) {
|
|
out_row[col.name->text()] = fmt::format("{}", to_json_string(*col.type, cell));
|
|
}
|
|
}
|
|
output_rows.append(out_row);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Prepare query_options with serial consistency
|
|
std::unique_ptr<cql3::query_options> repl_options() {
|
|
const auto& so = cql3::query_options::specific_options::DEFAULT;
|
|
auto qo = std::make_unique<cql3::query_options>(
|
|
db::consistency_level::ONE,
|
|
infinite_timeout_config,
|
|
std::vector<cql3::raw_value>{},
|
|
// Ensure (optional) serial consistency is always specified.
|
|
cql3::query_options::specific_options{
|
|
so.page_size,
|
|
so.state,
|
|
db::consistency_level::SERIAL,
|
|
so.timestamp,
|
|
}
|
|
);
|
|
return qo;
|
|
}
|
|
|
|
// Read-evaluate-print-loop for CQL
|
|
void repl(seastar::app_template& app) {
|
|
auto ext = std::make_shared<db::extensions>();
|
|
ext->add_schema_extension<alternator::tags_extension>(alternator::tags_extension::NAME);
|
|
ext->add_schema_extension<cdc::cdc_extension>(cdc::cdc_extension::NAME);
|
|
ext->add_schema_extension<db::paxos_grace_seconds_extension>(db::paxos_grace_seconds_extension::NAME);
|
|
auto db_cfg = ::make_shared<db::config>(std::move(ext));
|
|
db_cfg->enable_user_defined_functions({true}, db::config::config_source::CommandLine);
|
|
db_cfg->experimental_features(db::experimental_features_t::all(), db::config::config_source::CommandLine);
|
|
do_with_cql_env_thread([] (cql_test_env& e) {
|
|
|
|
// Comments allowed by CQL - -- and //
|
|
const boost::regex comment_re("^[[:space:]]*((--|//).*)?$");
|
|
// A comment is not a delimiter even if ends with one
|
|
const boost::regex delimiter_re("^(?![[:space:]]*(--|//)).*;[[:space:]]*$");
|
|
|
|
while (std::cin) {
|
|
std::string line;
|
|
std::ostringstream stmt;
|
|
if (!std::getline(std::cin, line)) {
|
|
break;
|
|
}
|
|
// Handle multiline input and comments
|
|
if (boost::regex_match(line.begin(), line.end(), comment_re)) {
|
|
std_cout << line << std::endl;
|
|
continue;
|
|
}
|
|
stmt << line << std::endl;
|
|
while (!boost::regex_match(line.begin(), line.end(), delimiter_re)) {
|
|
// Read the rest of input until delimiter or EOF
|
|
if (!std::getline(std::cin, line)) {
|
|
break;
|
|
}
|
|
stmt << line << std::endl;
|
|
}
|
|
// Print the statement
|
|
std_cout << stmt.str();
|
|
Json::Value json;
|
|
|
|
auto execute = [&json, &stmt, &e] () mutable {
|
|
return seastar::async([&] () mutable -> std::optional<unsigned> {
|
|
auto qo = repl_options();
|
|
auto msg = e.execute_cql(stmt.str(), std::move(qo)).get0();
|
|
if (msg->move_to_shard()) {
|
|
return *msg->move_to_shard();
|
|
}
|
|
json_visitor visitor(json);
|
|
msg->accept(visitor);
|
|
return std::nullopt;
|
|
});
|
|
};
|
|
|
|
try {
|
|
auto shard = execute().get0();
|
|
if (shard) {
|
|
smp::submit_to(*shard, std::move(execute)).get();
|
|
}
|
|
} catch (std::exception& e) {
|
|
json["status"] = "error";
|
|
json["message"] = fmt::format("{}", e);
|
|
}
|
|
std_cout << json << std::endl;
|
|
}
|
|
}, db_cfg).get0();
|
|
}
|
|
|
|
// Reset stdin/stdout/log streams to locations pointed
|
|
// on the command line.
|
|
void apply_configuration(const boost::program_options::variables_map& cfg) {
|
|
|
|
if (cfg.contains("input")) {
|
|
static std::ifstream input(cfg["input"].as<std::string>());
|
|
std::cin.rdbuf(input.rdbuf());
|
|
}
|
|
static std::ofstream log(cfg["log"].as<std::string>());
|
|
// Seastar always logs to std::cout, hack this around
|
|
// by redirecting std::cout to a file and capturing
|
|
// the old std::cout in std_cout
|
|
auto save_filebuf = std::cout.rdbuf(log.rdbuf());
|
|
if (cfg.contains("output")) {
|
|
std_cout.open(cfg["output"].as<std::string>());
|
|
} else {
|
|
std_cout.std::ios::rdbuf(save_filebuf);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
|
|
namespace bpo = boost::program_options;
|
|
namespace fs = std::filesystem;
|
|
|
|
seastar::app_template::config cfg;
|
|
cfg.name = fmt::format(R"({} - An embedded single-node version of Scylla.
|
|
|
|
Runs read-evaluate-print loop, reading commands from stdin,
|
|
evaluating them and printing output, formatted as JSON, to stdout.
|
|
Creates a temporary database in /tmp and deletes it at exit.
|
|
Pre-configures a default keyspace, naturally, with replication
|
|
factor 1.
|
|
|
|
Used in unit tests as a test driver for .test.cql files.
|
|
|
|
Available )", argv[0]);
|
|
|
|
seastar::app_template app(cfg);
|
|
|
|
/* Define options for input, output and log file. */
|
|
app.add_options()
|
|
("input", bpo::value<std::string>(),
|
|
"Input file with CQL, defaults to stdin")
|
|
("output", bpo::value<std::string>(),
|
|
"Output file for data, defaults to stdout")
|
|
("log", bpo::value<std::string>()->default_value(
|
|
fmt::format("{}.log", fs::path(argv[0]).stem().string())),
|
|
"Output file for Scylla log");
|
|
|
|
return app.run(argc, argv, [&app] {
|
|
|
|
apply_configuration(app.configuration());
|
|
|
|
return seastar::async([&app] {
|
|
return repl(app);
|
|
});
|
|
});
|
|
}
|
|
|