Merge branch 'dev/pdziepak/types-from-string/v2'

This commit is contained in:
Tomasz Grabiec
2015-07-07 11:33:23 +03:00
12 changed files with 357 additions and 55 deletions

View File

@@ -353,7 +353,7 @@ boost_test_lib = [
]
defines = []
libs = '-laio -lboost_program_options -lboost_system -lboost_filesystem -lstdc++ -lm -lboost_unit_test_framework -lboost_thread -lcryptopp -lrt -lyaml-cpp'
libs = '-laio -lboost_program_options -lboost_system -lboost_filesystem -lstdc++ -lm -lboost_unit_test_framework -lboost_thread -lcryptopp -lrt -lyaml-cpp -lboost_date_time'
hwloc_libs = '-lhwloc -lnuma -lpciaccess -lxml2 -lz'
urchin_libs = '-llz4 -lsnappy -lz'

View File

@@ -1327,9 +1327,7 @@ native_type returns [shared_ptr<cql3_type> t]
#endif
| K_DOUBLE { $t = cql3_type::double_; }
| K_FLOAT { $t = cql3_type::float_; }
#if 0
| K_INET { $t = CQL3Type.Native.INET;}
#endif
| K_INET { $t = cql3_type::inet; }
| K_INT { $t = cql3_type::int_; }
| K_TEXT { $t = cql3_type::text; }
| K_TIMESTAMP { $t = cql3_type::timestamp; }

View File

@@ -75,7 +75,7 @@ public:
try {
data_type_for<int64_t>()->validate(*tval);
} catch (exceptions::marshal_exception e) {
} catch (marshal_exception e) {
throw exceptions::invalid_request_exception("Invalid timestamp value");
}
return boost::any_cast<int64_t>(data_type_for<int64_t>()->deserialize(*tval));
@@ -93,7 +93,7 @@ public:
try {
data_type_for<int32_t>()->validate(*tval);
}
catch (exceptions::marshal_exception e) {
catch (marshal_exception e) {
throw exceptions::invalid_request_exception("Invalid TTL value");
}

View File

@@ -57,7 +57,7 @@ constants::literal::parsed_value(data_type validator)
return long_type->from_string(_text);
}
return validator->from_string(_text);
} catch (const exceptions::marshal_exception& e) {
} catch (const marshal_exception& e) {
throw exceptions::invalid_request_exception(e.what());
}
}

View File

@@ -153,7 +153,7 @@ public:
_receiver->type->validate(value.value());
}
return value;
} catch (const exceptions::marshal_exception& e) {
} catch (const marshal_exception& e) {
throw exceptions::invalid_request_exception(e.what());
}
}

View File

@@ -32,27 +32,6 @@
namespace exceptions {
class invalid_request_exception : public std::logic_error {
public:
invalid_request_exception(std::string cause)
: logic_error(cause)
{ }
};
class keyspace_not_defined_exception : public invalid_request_exception {
public:
keyspace_not_defined_exception(std::string cause)
: invalid_request_exception(cause)
{ }
};
class marshal_exception : public std::logic_error {
public:
marshal_exception(std::string cause)
: logic_error(cause)
{ }
};
enum class exception_code : int32_t {
SERVER_ERROR = 0x0000,
PROTOCOL_ERROR = 0x000A,
@@ -101,6 +80,20 @@ public:
using cassandra_exception::cassandra_exception;
};
class invalid_request_exception : public request_validation_exception {
public:
invalid_request_exception(sstring cause)
: request_validation_exception(exception_code::INVALID, cause)
{ }
};
class keyspace_not_defined_exception : public invalid_request_exception {
public:
keyspace_not_defined_exception(std::string cause)
: invalid_request_exception(cause)
{ }
};
class prepared_query_not_found_exception : public request_validation_exception {
public:
prepared_query_not_found_exception(bytes id)

View File

@@ -998,3 +998,67 @@ SEASTAR_TEST_CASE(test_ttl) {
});
}
SEASTAR_TEST_CASE(test_types) {
return do_with_cql_env([] (cql_test_env& e) {
return make_ready_future<>().then([&e] {
return e.execute_cql(
"CREATE TABLE all_types ("
" a ascii PRIMARY KEY,"
" b bigint,"
" c blob,"
" d boolean,"
" e double,"
" f float,"
" g inet,"
" h int,"
" i text,"
" j timestamp,"
" k timeuuid,"
" l uuid,"
" m varchar,"
");").discard_result();
}).then([&e] {
e.require_table_exists("ks", "all_types");
return e.execute_cql(
"INSERT INTO all_types (a, b, c, d, e, f, g, h, i, j, k, l, m) VALUES ("
" 'ascii',"
" 123456789,"
" 0xdeadbeef,"
" true,"
" 3.14,"
" 3.14,"
" '127.0.0.1',"
" 3,"
" 'zażółć gęślą jaźń',"
" '2001-10-18 14:15:55.134+0000',"
" d2177dd0-eaa2-11de-a572-001b779c76e3,"
" d2177dd0-eaa2-11de-a572-001b779c76e3,"
" 'varchar'"
");").discard_result();
}).then([&e] {
return e.execute_cql("SELECT * FROM all_types WHERE a = 'ascii'");
}).then([&e] (auto msg) {
struct tm t = { 0 };
t.tm_year = 2001 - 1900;
t.tm_mon = 10 - 1;
t.tm_mday = 18;
t.tm_hour = 14;
t.tm_min = 15;
t.tm_sec = 55;
auto tp = db_clock::from_time_t(timegm(&t)) + std::chrono::milliseconds(134);
assert_that(msg).is_rows().with_rows({
{
ascii_type->decompose(sstring("ascii")), long_type->decompose(123456789l),
from_hex("deadbeef"), boolean_type->decompose(true),
double_type->decompose(3.14), float_type->decompose(3.14f),
inet_addr_type->decompose(net::ipv4_address("127.0.0.1")),
int32_type->decompose(3), utf8_type->decompose(sstring("zażółć gęślą jaźń")),
timestamp_type->decompose(tp),
timeuuid_type->decompose(utils::UUID(sstring("d2177dd0-eaa2-11de-a572-001b779c76e3"))),
uuid_type->decompose(utils::UUID(sstring("d2177dd0-eaa2-11de-a572-001b779c76e3"))),
utf8_type->decompose(sstring("varchar"))
}
});
});
});
}

View File

@@ -6,9 +6,24 @@
#define BOOST_TEST_MODULE core
#include <boost/test/unit_test.hpp>
#include <utils/UUID_gen.hh>
#include <boost/asio/ip/address_v4.hpp>
#include <net/ip.hh>
#include "types.hh"
#include "compound.hh"
using namespace std::literals::chrono_literals;
void test_parsing_fails(const shared_ptr<const abstract_type>& type, sstring str)
{
try {
type->from_string(str);
BOOST_FAIL(sprint("Parsing of '%s' should have failed", str));
} catch (const marshal_exception& e) {
// expected
}
}
BOOST_AUTO_TEST_CASE(test_bytes_type_string_conversions) {
BOOST_REQUIRE(bytes_type->equal(bytes_type->from_string("616263646566"), bytes_type->decompose(bytes{"abcdef"})));
}
@@ -35,23 +50,104 @@ BOOST_AUTO_TEST_CASE(test_int32_type_string_conversions) {
BOOST_REQUIRE(int32_type->equal(int32_type->from_string("2147483647"), int32_type->decompose((int32_t)2147483647)));
BOOST_REQUIRE_EQUAL(int32_type->to_string(int32_type->decompose((int32_t)-2147483647)), "-2147483647");
auto test_parsing_fails = [] (sstring text) {
try {
int32_type->from_string(text);
BOOST_FAIL(sprint("Parsing of '%s' should have failed", text));
} catch (const marshal_exception& e) {
// expected
}
};
test_parsing_fails("asd");
test_parsing_fails("-2147483649");
test_parsing_fails("2147483648");
test_parsing_fails("2147483648123");
test_parsing_fails(int32_type, "asd");
test_parsing_fails(int32_type, "-2147483649");
test_parsing_fails(int32_type, "2147483648");
test_parsing_fails(int32_type, "2147483648123");
BOOST_REQUIRE_EQUAL(int32_type->to_string(bytes()), "");
}
BOOST_AUTO_TEST_CASE(test_timeuuid_type_string_conversions) {
auto now = utils::UUID_gen::get_time_UUID();
BOOST_REQUIRE(timeuuid_type->equal(timeuuid_type->from_string(now.to_sstring()), timeuuid_type->decompose(now)));
auto uuid = utils::UUID(sstring("d2177dd0-eaa2-11de-a572-001b779c76e3"));
BOOST_REQUIRE(timeuuid_type->equal(timeuuid_type->from_string("D2177dD0-EAa2-11de-a572-001B779C76e3"), timeuuid_type->decompose(uuid)));
test_parsing_fails(timeuuid_type, "something");
test_parsing_fails(timeuuid_type, "D2177dD0-EAa2-11de-a572-001B779C76e3a");
test_parsing_fails(timeuuid_type, "D2177dD0-EAa2-11de-a572001-B779C76e3");
test_parsing_fails(timeuuid_type, "D2177dD0EAa211dea572001B779C76e3");
test_parsing_fails(timeuuid_type, utils::make_random_uuid().to_sstring());
}
BOOST_AUTO_TEST_CASE(test_uuid_type_string_conversions) {
auto now = utils::UUID_gen::get_time_UUID();
BOOST_REQUIRE(uuid_type->equal(uuid_type->from_string(now.to_sstring()), uuid_type->decompose(now)));
auto random = utils::make_random_uuid();
BOOST_REQUIRE(uuid_type->equal(uuid_type->from_string(random.to_sstring()), uuid_type->decompose(random)));
auto uuid = utils::UUID(sstring("d2177dd0-eaa2-11de-a572-001b779c76e3"));
BOOST_REQUIRE(uuid_type->equal(uuid_type->from_string("D2177dD0-EAa2-11de-a572-001B779C76e3"), uuid_type->decompose(uuid)));
test_parsing_fails(uuid_type, "something");
test_parsing_fails(uuid_type, "D2177dD0-EAa2-11de-a572-001B779C76e3a");
test_parsing_fails(uuid_type, "D2177dD0-EAa2-11de-a572001-B779C76e3");
test_parsing_fails(uuid_type, "D2177dD0EAa211dea572001B779C76e3");
}
BOOST_AUTO_TEST_CASE(test_inet_type_string_conversions) {
net::ipv4_address addr("127.0.0.1");
BOOST_REQUIRE(inet_addr_type->equal(inet_addr_type->from_string("127.0.0.1"), inet_addr_type->decompose(addr)));
test_parsing_fails(inet_addr_type, "something");
test_parsing_fails(inet_addr_type, "300.127.127.127");
test_parsing_fails(inet_addr_type, "127-127.127.127");
test_parsing_fails(inet_addr_type, "127.127.127.127.127");
}
BOOST_AUTO_TEST_CASE(test_timestamp_type_string_conversions) {
timestamp_type->from_string("now");
db_clock::time_point tp(db_clock::duration(1435881600000));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("1435881600000"), timestamp_type->decompose(tp)));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("2015-07-03+0000"), timestamp_type->decompose(tp)));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("2015-07-03-00"), timestamp_type->decompose(tp)));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("2015-07-03 00:00+0000"), timestamp_type->decompose(tp)));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("2015-07-03 01:00:00+0000"), timestamp_type->decompose(tp + 1h)));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("2015-07-03 01:02:03.123+0000"), timestamp_type->decompose(tp + 123ms + 1h + 2min + 3s)));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("2015-07-03 12:30:00+1230"), timestamp_type->decompose(tp)));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("2015-07-03 12:00:00+12"), timestamp_type->decompose(tp)));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("2015-07-03 12:30:00+12:30"), timestamp_type->decompose(tp)));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("2015-07-02 23:00-0100"), timestamp_type->decompose(tp)));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("2015-07-03T00:00+0000"), timestamp_type->decompose(tp)));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("2015-07-03T01:00:00+0000"), timestamp_type->decompose(tp + 1h)));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("2015-07-03T00:00:00.123+0000"), timestamp_type->decompose(tp + 123ms)));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("2015-07-03T12:30:00+1230"), timestamp_type->decompose(tp)));
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string("2015-07-02T23:00-0100"), timestamp_type->decompose(tp)));
auto now = time(nullptr);
auto local_now = *localtime(&now);
char buf[100];
db_clock::time_point now_tp(db_clock::duration(now * 1000));
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S%z", &local_now);
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string(buf), timestamp_type->decompose(now_tp)));
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &local_now);
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string(buf), timestamp_type->decompose(now_tp)));
struct tm dst = { 0 };
dst.tm_isdst = -1;
dst.tm_year = 2015 - 1900;
dst.tm_mon = 1 - 1;
dst.tm_mday = 2;
dst.tm_hour = 3;
dst.tm_min = 4;
dst.tm_sec = 5;
auto dst_jan = db_clock::from_time_t(mktime(&dst));
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &dst);
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string(buf), timestamp_type->decompose(dst_jan)));
dst.tm_isdst = -1;
dst.tm_mon = 6 - 1;
auto dst_jun = db_clock::from_time_t(mktime(&dst));
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &dst);
BOOST_REQUIRE(timestamp_type->equal(timestamp_type->from_string(buf), timestamp_type->decompose(dst_jun)));
test_parsing_fails(timestamp_type, "something");
test_parsing_fails(timestamp_type, "2001-99-01");
test_parsing_fails(timestamp_type, "2001-01-01 12:00:00.0a");
test_parsing_fails(timestamp_type, "2001-01-01 12:00p0000");
test_parsing_fails(timestamp_type, "2001-01-01 12:00+1200a");
}
BOOST_AUTO_TEST_CASE(test_boolean_type_string_conversions) {
BOOST_REQUIRE(boolean_type->equal(boolean_type->from_string(""), boolean_type->decompose(false)));
BOOST_REQUIRE(boolean_type->equal(boolean_type->from_string("false"), boolean_type->decompose(false)));

View File

@@ -358,6 +358,8 @@ future<> cql_server::connection::process_request() {
}).then_wrapped([stream = f.stream, this] (future<> f) {
try {
f.get();
} catch (const exceptions::cassandra_exception& ex) {
write_error(stream, ex.code(), ex.what());
} catch (std::exception& ex) {
write_error(stream, exceptions::exception_code::SERVER_ERROR, ex.what());
} catch (...) {

164
types.cc
View File

@@ -13,9 +13,12 @@
#include "combine.hh"
#include <cmath>
#include <sstream>
#include <regex>
#include <boost/iterator/transform_iterator.hpp>
#include <boost/range/adaptor/filtered.hpp>
#include <boost/range/numeric.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/c_local_time_adjustor.hpp>
template<typename T>
struct simple_type_traits {
@@ -337,10 +340,25 @@ struct timeuuid_type_impl : public abstract_type {
return std::hash<bytes_view>()(v);
}
virtual bytes from_string(sstring_view s) const override {
throw std::runtime_error("not implemented");
if (s.empty()) {
return bytes();
}
static const std::regex re("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$");
if (!std::regex_match(s.data(), re)) {
throw marshal_exception();
}
utils::UUID v(s);
if (v.version() != 1) {
throw marshal_exception();
}
return v.to_bytes();
}
virtual sstring to_string(const bytes& b) const override {
throw std::runtime_error("not implemented");
auto v = deserialize(b);
if (v.empty()) {
return "";
}
return boost::any_cast<const utils::UUID&>(v).to_sstring();
}
virtual ::shared_ptr<cql3::cql3_type> as_cql3_type() const override {
return cql3::cql3_type::timeuuid;
@@ -381,8 +399,90 @@ struct timestamp_type_impl : simple_type_impl<db_clock::time_point> {
return boost::any(db_clock::time_point(db_clock::duration(v)));
}
// FIXME: isCompatibleWith(timestampuuid)
boost::posix_time::ptime get_time(const std::string& s) const {
// Apparently, the code below doesn't leak the input facet.
// std::locale::facet has some internal, custom reference counting
// and deletes the object when it's no longer used.
auto tif = new boost::posix_time::time_input_facet("%Y-%m-%d %H:%M:%S%F");
std::istringstream ss(s);
ss.imbue(std::locale(ss.getloc(), tif));
boost::posix_time::ptime t;
ss >> t;
if (ss.fail() || ss.peek() != std::istringstream::traits_type::eof()) {
throw marshal_exception();
}
return t;
}
boost::posix_time::time_duration get_utc_offset(const std::string& s) const {
static constexpr const char* formats[] = {
"%H:%M",
"%H%M",
};
for (auto&& f : formats) {
auto tif = new boost::posix_time::time_input_facet(f);
std::istringstream ss(s);
ss.imbue(std::locale(ss.getloc(), tif));
auto sign = ss.get();
boost::posix_time::ptime p;
ss >> p;
if (ss.good() && ss.peek() == std::istringstream::traits_type::eof()) {
return p.time_of_day() * (sign == '-' ? -1 : 1);
}
}
throw marshal_exception();
}
int64_t timestamp_from_string(sstring_view s) const {
std::string str;
str.resize(s.size());
std::transform(s.begin(), s.end(), str.begin(), ::tolower);
if (str == "now") {
return db_clock::now().time_since_epoch().count();
}
char* end;
auto v = std::strtoll(s.begin(), &end, 10);
if (end == s.begin() + s.size()) {
return v;
}
std::regex date_re("^\\d{4}-\\d{2}-\\d{2}([ t]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?)?");
std::smatch dsm;
if (!std::regex_search(str, dsm, date_re)) {
throw marshal_exception();
}
auto t = get_time(dsm.str());
auto tz = dsm.suffix().str();
std::regex tz_re("([\\+-]\\d{2}:?(\\d{2})?)");
std::smatch tsm;
if (std::regex_match(tz, tsm, tz_re)) {
t -= get_utc_offset(tsm.str());
} else if (tz.empty()) {
typedef boost::date_time::c_local_adjustor<boost::posix_time::ptime> local_tz;
// local_tz::local_to_utc(), where are you?
auto t1 = local_tz::utc_to_local(t);
auto tz_offset = t1 - t;
auto t2 = local_tz::utc_to_local(t - tz_offset);
auto dst_offset = t2 - t;
t -= tz_offset + dst_offset;
} else {
throw marshal_exception();
}
return (t - boost::posix_time::from_time_t(0)).total_milliseconds();
}
virtual bytes from_string(sstring_view s) const override {
throw std::runtime_error("not implemented");
if (s.empty()) {
return bytes();
}
int64_t ts;
try {
ts = timestamp_from_string(s);
} catch (...) {
throw marshal_exception(sprint("unable to parse date '%s'", s));
}
bytes b(bytes::initialized_later(), sizeof(int64_t));
*unaligned_cast<int64_t*>(b.begin()) = net::hton(ts);
return b;
}
virtual sstring to_string(const bytes& b) const override {
throw std::runtime_error("not implemented");
@@ -447,10 +547,22 @@ struct uuid_type_impl : abstract_type {
return std::hash<bytes_view>()(v);
}
virtual bytes from_string(sstring_view s) const override {
throw std::runtime_error("not implemented");
if (s.empty()) {
return bytes();
}
static const std::regex re("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$");
if (!std::regex_match(s.data(), re)) {
throw marshal_exception();
}
utils::UUID v(s);
return v.to_bytes();
}
virtual sstring to_string(const bytes& b) const override {
throw std::runtime_error("not implemented");
auto v = deserialize(b);
if (v.empty()) {
return "";
}
return boost::any_cast<const utils::UUID&>(v).to_sstring();
}
virtual ::shared_ptr<cql3::cql3_type> as_cql3_type() const override {
return cql3::cql3_type::uuid;
@@ -497,10 +609,28 @@ struct inet_addr_type_impl : abstract_type {
return std::hash<bytes_view>()(v);
}
virtual bytes from_string(sstring_view s) const override {
throw std::runtime_error("not implemented");
// FIXME: support host names
if (s.empty()) {
return bytes();
}
net::ipv4_address ipv4;
try {
ipv4 = net::ipv4_address(s.data());
} catch (...) {
throw marshal_exception();
}
bytes b(bytes::initialized_later(), sizeof(uint32_t));
auto out = b.begin();
serialize(boost::any(ipv4), out);
return b;
}
virtual sstring to_string(const bytes& b) const override {
throw std::runtime_error("not implemented");
auto v = deserialize(b);
if (v.empty()) {
return "";
}
boost::asio::ip::address_v4 ipv4(boost::any_cast<const net::ipv4_address&>(v).ip);
return ipv4.to_string();
}
virtual ::shared_ptr<cql3::cql3_type> as_cql3_type() const override {
return cql3::cql3_type::inet;
@@ -573,10 +703,26 @@ struct floating_type_impl : public simple_type_impl<T> {
return boost::any(x.d);
}
virtual bytes from_string(sstring_view s) const override {
throw std::runtime_error("not implemented");
if (s.empty()) {
return bytes();
}
try {
auto d = boost::lexical_cast<T>(s.begin(), s.size());
bytes b(bytes::initialized_later(), sizeof(T));
auto out = b.begin();
serialize(boost::any(d), out);
return b;
}
catch(const boost::bad_lexical_cast& e) {
throw marshal_exception(sprint("Invalid number format '%s'", s));
}
}
virtual sstring to_string(const bytes& b) const override {
throw std::runtime_error("not implemented");
auto v = deserialize(b);
if (v.empty()) {
return "";
}
return to_sstring(boost::any_cast<T>(v));
}
};

View File

@@ -27,7 +27,8 @@ public:
UUID() : most_sig_bits(0), least_sig_bits(0) {}
UUID(int64_t most_sig_bits, int64_t least_sig_bits)
: most_sig_bits(most_sig_bits), least_sig_bits(least_sig_bits) {}
explicit UUID(const sstring& uuid_string);
explicit UUID(const sstring& uuid_string) : UUID(sstring_view(uuid_string)) { }
explicit UUID(sstring_view uuid_string);
int64_t get_most_significant_bits() const {
return most_sig_bits;

View File

@@ -40,11 +40,13 @@ std::ostream& operator<<(std::ostream& out, const UUID& uuid) {
return out << uuid.to_sstring();
}
UUID::UUID(const sstring& uuid) {
auto uuid_string = uuid;
UUID::UUID(sstring_view uuid) {
sstring uuid_string(uuid.begin(), uuid.end());
boost::erase_all(uuid_string, "-");
auto size = uuid_string.size() / 2;
assert(size == 16);
if (size != 16) {
throw marshal_exception();
}
sstring most = sstring(uuid_string.begin(), uuid_string.begin() + size);
sstring least = sstring(uuid_string.begin() + size, uuid_string.end());
int base = 16;