In order to eventually switch to a single JSON library, most of the libjsoncpp usage is dropped in favor of rjson. Unfortunately, one usage still remains: test/utils/test_repl utility heavily depends on the *exact textual* format of its output JSON files, so replacing a library results in all tests failing because of differences in formatting. It is possible to force rjson to print its documents in the exact matching format, but that's left for later, since the issue is not critical. It would be nice though if our test suite compared JSON documents with a real JSON parser, since there are more differences - e.g. libjsoncpp keeps children of the object sorted, while rapidjson uses an unordered data structure. This change should cause no change in semantics, it strives just to replace all usage of libjsoncpp with rjson.
242 lines
8.3 KiB
C++
242 lines
8.3 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 <regex>
|
|
|
|
#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 "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);
|
|
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 std::regex comment_re("^[[:space:]]*((--|//).*)?$");
|
|
// A comment is not a delimiter even if ends with one
|
|
const std::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 (std::regex_match(line.begin(), line.end(), comment_re)) {
|
|
std_cout << line << std::endl;
|
|
continue;
|
|
}
|
|
stmt << line << std::endl;
|
|
while (!std::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.count("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.count("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);
|
|
});
|
|
});
|
|
}
|
|
|