Files
scylladb/test/tools/cql_repl.cc
Avi Kivity 2d25705db0 cql3: deinline non-trivial methods in selection.hh
This allows us to forward-declare raw_selector, which in turn reduces
indirect inclusions of expression.hh from 147 to 58, reducing rebuilds
when anything in that area changes.

Includes that were lost due to the change are restored in individual
translation units.

Closes #9434
2021-10-05 12:58:55 +02:00

247 lines
8.6 KiB
C++

/*
* Copyright (C) 2019-present 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 "cql3/column_identifier.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,
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);
});
});
}