Merge "Support duration type" from Jesse
"This patch series adds support for the `duration` type in CQL, which was added to Cassandra in 3.10. As part of this work, it was necessary also to add support for the `vint` and `unsigned vint` types to the native protocol implementation, which are part of v5 of the specification. To test interactively, it is necessary to use cqlsh distributed with Cassandra, as the version we distribute does not yet support the duration type." * 'jhk/duration_protocol/v5' of https://github.com/hakuch/scylla: Support `duration` CQL native type CQL native protocol: Add support for `vint` serialization duration_test.cc: Add test for printing zero duration duration.cc: Remove nop `const` qualifier on return type Change `const` qualifier declaration order for `duration` duration.cc: Simplify range checking Rename `duration` to `cql_duration`
This commit is contained in:
@@ -241,6 +241,7 @@ scylla_tests = [
|
||||
'tests/cell_locker_test',
|
||||
'tests/streaming_histogram_test',
|
||||
'tests/duration_test',
|
||||
'tests/vint_serialization_test',
|
||||
]
|
||||
|
||||
apps = [
|
||||
@@ -526,6 +527,7 @@ scylla_core = (['database.cc',
|
||||
'range_tombstone_list.cc',
|
||||
'disk-error-handler.cc',
|
||||
'duration.cc',
|
||||
'vint-serialization.cc',
|
||||
]
|
||||
+ [Antlr3Grammar('cql3/Cql.g')]
|
||||
+ [Thrift('interface/cassandra.thrift', 'Cassandra')]
|
||||
@@ -623,6 +625,7 @@ pure_boost_tests = set([
|
||||
'tests/cartesian_product_test',
|
||||
'tests/streaming_histogram_test',
|
||||
'tests/duration_test',
|
||||
'tests/vint_serialization_test',
|
||||
])
|
||||
|
||||
tests_not_using_seastar_test_framework = set([
|
||||
|
||||
24
cql3/Cql.g
24
cql3/Cql.g
@@ -1167,6 +1167,7 @@ constant returns [shared_ptr<cql3::constants::literal> constant]
|
||||
| t=INTEGER { $constant = cql3::constants::literal::integer(sstring{$t.text}); }
|
||||
| t=FLOAT { $constant = cql3::constants::literal::floating_point(sstring{$t.text}); }
|
||||
| t=BOOLEAN { $constant = cql3::constants::literal::bool_(sstring{$t.text}); }
|
||||
| t=DURATION { $constant = cql3::constants::literal::duration(sstring{$t.text}); }
|
||||
| t=UUID { $constant = cql3::constants::literal::uuid(sstring{$t.text}); }
|
||||
| t=HEXNUMBER { $constant = cql3::constants::literal::hex(sstring{$t.text}); }
|
||||
| { sign=""; } ('-' {sign = "-"; } )? t=(K_NAN | K_INFINITY) { $constant = cql3::constants::literal::floating_point(sstring{sign + $t.text}); }
|
||||
@@ -1464,6 +1465,7 @@ native_type returns [shared_ptr<cql3_type> t]
|
||||
| K_COUNTER { $t = cql3_type::counter; }
|
||||
| K_DECIMAL { $t = cql3_type::decimal; }
|
||||
| K_DOUBLE { $t = cql3_type::double_; }
|
||||
| K_DURATION { $t = cql3_type::duration; }
|
||||
| K_FLOAT { $t = cql3_type::float_; }
|
||||
| K_INET { $t = cql3_type::inet; }
|
||||
| K_INT { $t = cql3_type::int_; }
|
||||
@@ -1649,6 +1651,7 @@ K_BOOLEAN: B O O L E A N;
|
||||
K_COUNTER: C O U N T E R;
|
||||
K_DECIMAL: D E C I M A L;
|
||||
K_DOUBLE: D O U B L E;
|
||||
K_DURATION: D U R A T I O N;
|
||||
K_FLOAT: F L O A T;
|
||||
K_INET: I N E T;
|
||||
K_INT: I N T;
|
||||
@@ -1778,6 +1781,20 @@ fragment EXPONENT
|
||||
: E ('+' | '-')? DIGIT+
|
||||
;
|
||||
|
||||
fragment DURATION_UNIT
|
||||
: Y
|
||||
| M O
|
||||
| W
|
||||
| D
|
||||
| H
|
||||
| M
|
||||
| S
|
||||
| M S
|
||||
| U S
|
||||
| '\u00B5' S
|
||||
| N S
|
||||
;
|
||||
|
||||
INTEGER
|
||||
: '-'? DIGIT+
|
||||
;
|
||||
@@ -1802,6 +1819,13 @@ BOOLEAN
|
||||
: T R U E | F A L S E
|
||||
;
|
||||
|
||||
DURATION
|
||||
: '-'? DIGIT+ DURATION_UNIT (DIGIT+ DURATION_UNIT)*
|
||||
| '-'? 'P' (DIGIT+ 'Y')? (DIGIT+ 'M')? (DIGIT+ 'D')? ('T' (DIGIT+ 'H')? (DIGIT+ 'M')? (DIGIT+ 'S')?)? // ISO 8601 "format with designators"
|
||||
| '-'? 'P' DIGIT+ 'W'
|
||||
| '-'? 'P' DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT 'T' DIGIT DIGIT ':' DIGIT DIGIT ':' DIGIT DIGIT // ISO 8601 "alternative format"
|
||||
;
|
||||
|
||||
IDENT
|
||||
: LETTER (LETTER | DIGIT | '_')*
|
||||
;
|
||||
|
||||
@@ -40,11 +40,29 @@
|
||||
*/
|
||||
|
||||
#include "cql3/column_condition.hh"
|
||||
#include "statements/request_validations.hh"
|
||||
#include "unimplemented.hh"
|
||||
#include "lists.hh"
|
||||
#include "maps.hh"
|
||||
#include <boost/range/algorithm_ext/push_back.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
void validate_operation_on_durations(const abstract_type& type, const cql3::operator_type& op) {
|
||||
using cql3::statements::request_validations::check_false;
|
||||
|
||||
if (op.is_slice() && type.references_duration()) {
|
||||
check_false(type.is_collection(), "Slice conditions are not supported on collections containing durations");
|
||||
check_false(type.is_tuple(), "Slice conditions are not supported on tuples containing durations");
|
||||
check_false(type.is_user_type(), "Slice conditions are not supported on UDTs containing durations");
|
||||
|
||||
// We're a duration.
|
||||
throw exceptions::invalid_request_exception(sprint("Slice conditions are not supported on durations"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
bool
|
||||
@@ -95,6 +113,7 @@ column_condition::raw::prepare(database& db, const sstring& keyspace, const colu
|
||||
}
|
||||
return column_condition::in_condition(receiver, std::move(terms));
|
||||
} else {
|
||||
validate_operation_on_durations(*receiver.type, _op);
|
||||
return column_condition::condition(receiver, _value->prepare(db, keyspace, receiver.column_specification), _op);
|
||||
}
|
||||
}
|
||||
@@ -129,6 +148,8 @@ column_condition::raw::prepare(database& db, const sstring& keyspace, const colu
|
||||
| boost::adaptors::transformed(std::bind(&term::raw::prepare, std::placeholders::_1, std::ref(db), std::ref(keyspace), value_spec)));
|
||||
return column_condition::in_condition(receiver, _collection_element->prepare(db, keyspace, element_spec), terms);
|
||||
} else {
|
||||
validate_operation_on_durations(*receiver.type, _op);
|
||||
|
||||
return column_condition::condition(receiver,
|
||||
_collection_element->prepare(db, keyspace, element_spec),
|
||||
_value->prepare(db, keyspace, value_spec),
|
||||
|
||||
@@ -52,12 +52,13 @@ std::ostream&
|
||||
operator<<(std::ostream&out, constants::type t)
|
||||
{
|
||||
switch (t) {
|
||||
case constants::type::STRING: return out << "STRING";
|
||||
case constants::type::INTEGER: return out << "INTEGER";
|
||||
case constants::type::UUID: return out << "UUID";
|
||||
case constants::type::FLOAT: return out << "FLOAT";
|
||||
case constants::type::BOOLEAN: return out << "BOOLEAN";
|
||||
case constants::type::HEX: return out << "HEX";
|
||||
case constants::type::STRING: return out << "STRING";
|
||||
case constants::type::INTEGER: return out << "INTEGER";
|
||||
case constants::type::UUID: return out << "UUID";
|
||||
case constants::type::FLOAT: return out << "FLOAT";
|
||||
case constants::type::BOOLEAN: return out << "BOOLEAN";
|
||||
case constants::type::HEX: return out << "HEX";
|
||||
case constants::type::DURATION: return out << "DURATION";
|
||||
};
|
||||
assert(0);
|
||||
}
|
||||
@@ -145,6 +146,11 @@ constants::literal::test_assignment(database& db, const sstring& keyspace, ::sha
|
||||
return assignment_testable::test_result::WEAKLY_ASSIGNABLE;
|
||||
}
|
||||
break;
|
||||
case type::DURATION:
|
||||
if (kind == cql3_type::kind_enum_set::prepare<cql3_type::kind::DURATION>()) {
|
||||
return assignment_testable::test_result::EXACT_MATCH;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return assignment_testable::test_result::NOT_ASSIGNABLE;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public:
|
||||
#endif
|
||||
public:
|
||||
enum class type {
|
||||
STRING, INTEGER, UUID, FLOAT, BOOLEAN, HEX
|
||||
STRING, INTEGER, UUID, FLOAT, BOOLEAN, HEX, DURATION
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -149,6 +149,10 @@ public:
|
||||
return ::make_shared<literal>(type::HEX, text);
|
||||
}
|
||||
|
||||
static ::shared_ptr<literal> duration(sstring text) {
|
||||
return ::make_shared<literal>(type::DURATION, text);
|
||||
}
|
||||
|
||||
virtual ::shared_ptr<term> prepare(database& db, const sstring& keyspace, ::shared_ptr<column_specification> receiver);
|
||||
private:
|
||||
bytes parsed_value(data_type validator);
|
||||
|
||||
@@ -48,6 +48,10 @@ shared_ptr<cql3_type> cql3_type::raw::prepare(database& db, const sstring& keysp
|
||||
}
|
||||
}
|
||||
|
||||
bool cql3_type::raw::is_duration() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cql3_type::raw::references_user_type(const sstring& name) const {
|
||||
return false;
|
||||
}
|
||||
@@ -78,6 +82,10 @@ public:
|
||||
virtual sstring to_string() const {
|
||||
return _type->to_string();
|
||||
}
|
||||
|
||||
virtual bool is_duration() const override {
|
||||
return _type->get_type()->equals(duration_type);
|
||||
}
|
||||
};
|
||||
|
||||
class cql3_type::raw_collection : public raw {
|
||||
@@ -126,9 +134,15 @@ public:
|
||||
if (_kind == &collection_type_impl::kind::list) {
|
||||
return make_shared(cql3_type(to_string(), list_type_impl::get_instance(_values->prepare_internal(keyspace, user_types)->get_type(), !_frozen), false));
|
||||
} else if (_kind == &collection_type_impl::kind::set) {
|
||||
if (_values->is_duration()) {
|
||||
throw exceptions::invalid_request_exception(sprint("Durations are not allowed inside sets: %s", *this));
|
||||
}
|
||||
return make_shared(cql3_type(to_string(), set_type_impl::get_instance(_values->prepare_internal(keyspace, user_types)->get_type(), !_frozen), false));
|
||||
} else if (_kind == &collection_type_impl::kind::map) {
|
||||
assert(_keys); // "Got null keys type for a collection";
|
||||
if (_keys->is_duration()) {
|
||||
throw exceptions::invalid_request_exception(sprint("Durations are not allowed as map keys: %s", *this));
|
||||
}
|
||||
return make_shared(cql3_type(to_string(), map_type_impl::get_instance(_keys->prepare_internal(keyspace, user_types)->get_type(), _values->prepare_internal(keyspace, user_types)->get_type(), !_frozen), false));
|
||||
}
|
||||
abort();
|
||||
@@ -138,6 +152,10 @@ public:
|
||||
return (_keys && _keys->references_user_type(name)) || _values->references_user_type(name);
|
||||
}
|
||||
|
||||
bool is_duration() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual sstring to_string() const override {
|
||||
sstring start = _frozen ? "frozen<" : "";
|
||||
sstring end = _frozen ? ">" : "";
|
||||
@@ -329,6 +347,7 @@ thread_local shared_ptr<cql3_type> cql3_type::inet = make("inet", inet_addr_type
|
||||
thread_local shared_ptr<cql3_type> cql3_type::varint = make("varint", varint_type, cql3_type::kind::VARINT);
|
||||
thread_local shared_ptr<cql3_type> cql3_type::decimal = make("decimal", decimal_type, cql3_type::kind::DECIMAL);
|
||||
thread_local shared_ptr<cql3_type> cql3_type::counter = make("counter", counter_type, cql3_type::kind::COUNTER);
|
||||
thread_local shared_ptr<cql3_type> cql3_type::duration = make("duration", duration_type, cql3_type::kind::DURATION);
|
||||
|
||||
const std::vector<shared_ptr<cql3_type>>&
|
||||
cql3_type::values() {
|
||||
@@ -354,6 +373,7 @@ cql3_type::values() {
|
||||
cql3_type::timeuuid,
|
||||
cql3_type::date,
|
||||
cql3_type::time,
|
||||
cql3_type::duration,
|
||||
};
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ public:
|
||||
virtual bool supports_freezing() const = 0;
|
||||
virtual bool is_collection() const;
|
||||
virtual bool is_counter() const;
|
||||
virtual bool is_duration() const;
|
||||
virtual bool references_user_type(const sstring&) const;
|
||||
virtual std::experimental::optional<sstring> keyspace() const;
|
||||
virtual void freeze();
|
||||
@@ -102,7 +103,7 @@ private:
|
||||
|
||||
public:
|
||||
enum class kind : int8_t {
|
||||
ASCII, BIGINT, BLOB, BOOLEAN, COUNTER, DECIMAL, DOUBLE, EMPTY, FLOAT, INT, SMALLINT, TINYINT, INET, TEXT, TIMESTAMP, UUID, VARCHAR, VARINT, TIMEUUID, DATE, TIME
|
||||
ASCII, BIGINT, BLOB, BOOLEAN, COUNTER, DECIMAL, DOUBLE, EMPTY, FLOAT, INT, SMALLINT, TINYINT, INET, TEXT, TIMESTAMP, UUID, VARCHAR, VARINT, TIMEUUID, DATE, TIME, DURATION
|
||||
};
|
||||
using kind_enum = super_enum<kind,
|
||||
kind::ASCII,
|
||||
@@ -125,7 +126,8 @@ public:
|
||||
kind::VARINT,
|
||||
kind::TIMEUUID,
|
||||
kind::DATE,
|
||||
kind::TIME>;
|
||||
kind::TIME,
|
||||
kind::DURATION>;
|
||||
using kind_enum_set = enum_set<kind_enum>;
|
||||
private:
|
||||
std::experimental::optional<kind_enum_set::prepared> _kind;
|
||||
@@ -154,6 +156,7 @@ public:
|
||||
static thread_local shared_ptr<cql3_type> varint;
|
||||
static thread_local shared_ptr<cql3_type> decimal;
|
||||
static thread_local shared_ptr<cql3_type> counter;
|
||||
static thread_local shared_ptr<cql3_type> duration;
|
||||
|
||||
static const std::vector<shared_ptr<cql3_type>>& values();
|
||||
public:
|
||||
|
||||
@@ -72,6 +72,9 @@ private:
|
||||
{}
|
||||
public:
|
||||
const operator_type& reverse() const { return _reverse; }
|
||||
bool is_slice() const {
|
||||
return (*this == LT) || (*this == LTE) || (*this == GT) || (*this == GTE);
|
||||
}
|
||||
sstring to_string() const { return _text; }
|
||||
bool operator==(const operator_type& other) const { return this == &other; }
|
||||
bool operator!=(const operator_type& other) const { return this != &other; }
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include "cql3/restrictions/single_column_restriction.hh"
|
||||
#include "statements/request_validations.hh"
|
||||
|
||||
#include "core/shared_ptr.hh"
|
||||
#include "to_string.hh"
|
||||
@@ -157,6 +158,19 @@ protected:
|
||||
statements::bound bound,
|
||||
bool inclusive) override {
|
||||
auto&& column_def = to_column_definition(schema, _entity);
|
||||
|
||||
if (column_def.type->references_duration()) {
|
||||
using statements::request_validations::check_false;
|
||||
const auto& ty = *column_def.type;
|
||||
|
||||
check_false(ty.is_collection(), "Slice restrictions are not supported on collections containing durations");
|
||||
check_false(ty.is_tuple(), "Slice restrictions are not supported on tuples containing durations");
|
||||
check_false(ty.is_user_type(), "Slice restrictions are not supported on UDTs containing durations");
|
||||
|
||||
// We're a duration.
|
||||
throw exceptions::invalid_request_exception("Slice restrictions are not supported on duration columns");
|
||||
}
|
||||
|
||||
auto term = to_term(to_receivers(schema, column_def), _value, db, schema->ks_name(), std::move(bound_names));
|
||||
return ::make_shared<restrictions::single_column_restriction::slice>(column_def, bound, inclusive, std::move(term));
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include "service/storage_service.hh"
|
||||
#include "schema.hh"
|
||||
#include "schema_builder.hh"
|
||||
#include "request_validations.hh"
|
||||
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
@@ -108,6 +109,18 @@ create_index_statement::validate(distributed<service::storage_proxy>& proxy, con
|
||||
sprint("No column definition found for column %s", *target->column));
|
||||
}
|
||||
|
||||
if (cd->type->references_duration()) {
|
||||
using request_validations::check_false;
|
||||
const auto& ty = *cd->type;
|
||||
|
||||
check_false(ty.is_collection(), "Secondary indexes are not supported on collections containing durations");
|
||||
check_false(ty.is_tuple(), "Secondary indexes are not supported on tuples containing durations");
|
||||
check_false(ty.is_user_type(), "Secondary indexes are not supported on UDTs containing durations");
|
||||
|
||||
// We're a duration.
|
||||
throw exceptions::invalid_request_exception("Secondary indexes are not supported on duration columns");
|
||||
}
|
||||
|
||||
// Origin TODO: we could lift that limitation
|
||||
if ((schema->is_dense() || !schema->thrift().has_compound_comparator()) &&
|
||||
cd->kind != column_kind::regular_column) {
|
||||
|
||||
@@ -219,6 +219,9 @@ std::unique_ptr<prepared_statement> create_table_statement::raw_statement::prepa
|
||||
if (t->is_counter()) {
|
||||
throw exceptions::invalid_request_exception(sprint("counter type is not supported for PRIMARY KEY part %s", alias->text()));
|
||||
}
|
||||
if (t->references_duration()) {
|
||||
throw exceptions::invalid_request_exception(sprint("duration type is not supported for PRIMARY KEY part %s", alias->text()));
|
||||
}
|
||||
if (_static_columns.count(alias) > 0) {
|
||||
throw exceptions::invalid_request_exception(sprint("Static column %s cannot be part of the PRIMARY KEY", alias->text()));
|
||||
}
|
||||
@@ -254,6 +257,9 @@ std::unique_ptr<prepared_statement> create_table_statement::raw_statement::prepa
|
||||
if (at->is_counter()) {
|
||||
throw exceptions::invalid_request_exception(sprint("counter type is not supported for PRIMARY KEY part %s", stmt->_column_aliases[0]));
|
||||
}
|
||||
if (at->references_duration()) {
|
||||
throw exceptions::invalid_request_exception(sprint("duration type is not supported for PRIMARY KEY part %s", stmt->_column_aliases[0]));
|
||||
}
|
||||
stmt->_clustering_key_types.emplace_back(at);
|
||||
} else {
|
||||
std::vector<data_type> types;
|
||||
@@ -263,6 +269,9 @@ std::unique_ptr<prepared_statement> create_table_statement::raw_statement::prepa
|
||||
if (type->is_counter()) {
|
||||
throw exceptions::invalid_request_exception(sprint("counter type is not supported for PRIMARY KEY part %s", t->text()));
|
||||
}
|
||||
if (type->references_duration()) {
|
||||
throw exceptions::invalid_request_exception(sprint("duration type is not supported for PRIMARY KEY part %s", t->text()));
|
||||
}
|
||||
if (_static_columns.count(t) > 0) {
|
||||
throw exceptions::invalid_request_exception(sprint("Static column %s cannot be part of the PRIMARY KEY", t->text()));
|
||||
}
|
||||
|
||||
@@ -116,6 +116,12 @@ static bool validate_primary_key(
|
||||
throw exceptions::invalid_request_exception(sprint(
|
||||
"Cannot use MultiCell column '%s' in PRIMARY KEY of materialized view", def->name_as_text()));
|
||||
}
|
||||
|
||||
if (def->type->references_duration()) {
|
||||
throw exceptions::invalid_request_exception(sprint(
|
||||
"Cannot use Duration column '%s' in PRIMARY KEY of materialized view", def->name_as_text()));
|
||||
}
|
||||
|
||||
if (def->is_static()) {
|
||||
throw exceptions::invalid_request_exception(sprint(
|
||||
"Cannot use Static column '%s' in PRIMARY KEY of materialized view", def->name_as_text()));
|
||||
|
||||
162
duration.cc
162
duration.cc
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "stdx.hh"
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <seastar/core/print.hh>
|
||||
|
||||
#include <cctype>
|
||||
@@ -39,20 +40,20 @@ namespace {
|
||||
// Helper for retrieving the counter based on knowing its type.
|
||||
//
|
||||
template<class Counter>
|
||||
constexpr typename Counter::value_type& counter_ref(duration &) noexcept;
|
||||
constexpr typename Counter::value_type& counter_ref(cql_duration &) noexcept;
|
||||
|
||||
template<>
|
||||
constexpr months_counter::value_type& counter_ref<months_counter>(duration &d) noexcept {
|
||||
constexpr months_counter::value_type& counter_ref<months_counter>(cql_duration &d) noexcept {
|
||||
return d.months;
|
||||
}
|
||||
|
||||
template<>
|
||||
constexpr days_counter::value_type& counter_ref<days_counter>(duration &d) noexcept {
|
||||
constexpr days_counter::value_type& counter_ref<days_counter>(cql_duration &d) noexcept {
|
||||
return d.days;
|
||||
}
|
||||
|
||||
template<>
|
||||
constexpr nanoseconds_counter::value_type& counter_ref<nanoseconds_counter>(duration &d) noexcept {
|
||||
constexpr nanoseconds_counter::value_type& counter_ref<nanoseconds_counter>(cql_duration &d) noexcept {
|
||||
return d.nanoseconds;
|
||||
}
|
||||
|
||||
@@ -60,28 +61,28 @@ constexpr nanoseconds_counter::value_type& counter_ref<nanoseconds_counter>(dura
|
||||
class duration_unit {
|
||||
public:
|
||||
using index_type = uint8_t;
|
||||
using common_counter_type = duration::common_counter_type;
|
||||
using common_counter_type = cql_duration::common_counter_type;
|
||||
|
||||
virtual ~duration_unit() = default;
|
||||
|
||||
// Units with larger indicies are greater. For example, "months" have a greater index than "days".
|
||||
virtual index_type index() const noexcept = 0;
|
||||
|
||||
virtual char const* const short_name() const noexcept = 0;
|
||||
virtual const char* short_name() const noexcept = 0;
|
||||
|
||||
virtual char const* const long_name() const noexcept = 0;
|
||||
virtual const char* long_name() const noexcept = 0;
|
||||
|
||||
// Increment the appropriate counter in the duration instance based on a count of this unit.
|
||||
virtual void increment_count(duration &, common_counter_type) const noexcept = 0;
|
||||
virtual void increment_count(cql_duration&, common_counter_type) const noexcept = 0;
|
||||
|
||||
// The remaining capacity (in terms of this unit) of the appropriate counter in the duration instance.
|
||||
virtual common_counter_type available_count(duration const&) const noexcept = 0;
|
||||
virtual common_counter_type available_count(const cql_duration&) const noexcept = 0;
|
||||
};
|
||||
|
||||
// `_index` is the assigned index of this unit.
|
||||
// `Counter` is the counter type in the `duration` instance that is used to store this unit.
|
||||
// `Counter` is the counter type in the `cql_duration` instance that is used to store this unit.
|
||||
// `_factor` is the conversion factor of one count of this unit to the corresponding count in `Counter`.
|
||||
template <uint8_t _index, class Counter, duration::common_counter_type _factor>
|
||||
template <uint8_t _index, class Counter, cql_duration::common_counter_type _factor>
|
||||
class duration_unit_impl : public duration_unit {
|
||||
public:
|
||||
static constexpr auto factor = _factor;
|
||||
@@ -92,67 +93,67 @@ public:
|
||||
return _index;
|
||||
}
|
||||
|
||||
void increment_count(duration &d, common_counter_type c) const noexcept override {
|
||||
void increment_count(cql_duration &d, common_counter_type c) const noexcept override {
|
||||
counter_ref<Counter>(d) += (c * factor);
|
||||
}
|
||||
|
||||
common_counter_type available_count(duration const& d) const noexcept override {
|
||||
auto const limit = std::numeric_limits<typename Counter::value_type>::max();
|
||||
return {(limit - counter_ref<Counter>(const_cast<duration&>(d))) / factor};
|
||||
common_counter_type available_count(const cql_duration& d) const noexcept override {
|
||||
const auto limit = std::numeric_limits<typename Counter::value_type>::max();
|
||||
return {(limit - counter_ref<Counter>(const_cast<cql_duration&>(d))) / factor};
|
||||
}
|
||||
};
|
||||
|
||||
struct nanosecond_unit final : public duration_unit_impl<0, nanoseconds_counter , 1> {
|
||||
char const* const short_name() const noexcept override { return "ns"; }
|
||||
char const* const long_name() const noexcept override { return "nanoseconds"; }
|
||||
const char* short_name() const noexcept override { return "ns"; }
|
||||
const char* long_name() const noexcept override { return "nanoseconds"; }
|
||||
} const nanosecond{};
|
||||
|
||||
struct microsecond_unit final : public duration_unit_impl<1, nanoseconds_counter, 1000> {
|
||||
char const* const short_name() const noexcept override { return "us"; }
|
||||
char const* const long_name() const noexcept override { return "microseconds"; }
|
||||
const char* short_name() const noexcept override { return "us"; }
|
||||
const char* long_name() const noexcept override { return "microseconds"; }
|
||||
} const microsecond{};
|
||||
|
||||
struct millisecond_unit final : public duration_unit_impl<2, nanoseconds_counter, microsecond_unit::factor * 1000> {
|
||||
char const* const short_name() const noexcept override { return "ms"; }
|
||||
char const* const long_name() const noexcept override { return "milliseconds"; }
|
||||
const char* short_name() const noexcept override { return "ms"; }
|
||||
const char* long_name() const noexcept override { return "milliseconds"; }
|
||||
} const millisecond{};
|
||||
|
||||
struct second_unit final : public duration_unit_impl<3, nanoseconds_counter, millisecond_unit::factor * 1000> {
|
||||
char const* const short_name() const noexcept override { return "s"; }
|
||||
char const* const long_name() const noexcept override { return "seconds"; }
|
||||
const char* short_name() const noexcept override { return "s"; }
|
||||
const char* long_name() const noexcept override { return "seconds"; }
|
||||
} const second{};
|
||||
|
||||
struct minute_unit final : public duration_unit_impl<4, nanoseconds_counter, second_unit::factor * 60> {
|
||||
char const* const short_name() const noexcept override { return "m"; }
|
||||
char const* const long_name() const noexcept override { return "minutes"; }
|
||||
const char* short_name() const noexcept override { return "m"; }
|
||||
const char* long_name() const noexcept override { return "minutes"; }
|
||||
} const minute{};
|
||||
|
||||
struct hour_unit final : public duration_unit_impl<5, nanoseconds_counter, minute_unit::factor * 60> {
|
||||
char const* const short_name() const noexcept override { return "h"; }
|
||||
char const* const long_name() const noexcept override { return "hours"; }
|
||||
const char* short_name() const noexcept override { return "h"; }
|
||||
const char* long_name() const noexcept override { return "hours"; }
|
||||
} const hour{};
|
||||
|
||||
struct day_unit final : public duration_unit_impl<6, days_counter, 1> {
|
||||
char const* const short_name() const noexcept override { return "d"; }
|
||||
char const* const long_name() const noexcept override { return "days"; }
|
||||
const char* short_name() const noexcept override { return "d"; }
|
||||
const char* long_name() const noexcept override { return "days"; }
|
||||
} const day{};
|
||||
|
||||
struct week_unit final : public duration_unit_impl<7, days_counter, 7> {
|
||||
char const* const short_name() const noexcept override { return "w"; }
|
||||
char const* const long_name() const noexcept override { return "weeks"; }
|
||||
const char* short_name() const noexcept override { return "w"; }
|
||||
const char* long_name() const noexcept override { return "weeks"; }
|
||||
} const week{};
|
||||
|
||||
struct month_unit final : public duration_unit_impl<8, months_counter, 1> {
|
||||
char const* const short_name() const noexcept override { return "mo"; }
|
||||
char const* const long_name() const noexcept override { return "months"; }
|
||||
const char* short_name() const noexcept override { return "mo"; }
|
||||
const char* long_name() const noexcept override { return "months"; }
|
||||
} const month{};
|
||||
|
||||
struct year_unit final : public duration_unit_impl<9, months_counter, 12> {
|
||||
char const* const short_name() const noexcept override { return "y"; }
|
||||
char const* const long_name() const noexcept override { return "years"; }
|
||||
const char* short_name() const noexcept override { return "y"; }
|
||||
const char* long_name() const noexcept override { return "years"; }
|
||||
} const year{};
|
||||
|
||||
auto const unit_table = std::unordered_map<stdx::string_view, std::reference_wrapper<duration_unit const>>{
|
||||
const auto unit_table = std::unordered_map<stdx::string_view, std::reference_wrapper<const duration_unit>>{
|
||||
{year.short_name(), year},
|
||||
{month.short_name(), month},
|
||||
{week.short_name(), week},
|
||||
@@ -171,9 +172,12 @@ auto const unit_table = std::unordered_map<stdx::string_view, std::reference_wra
|
||||
// Throws `std::out_of_range` if a counter is out of range.
|
||||
//
|
||||
template <class Match, class Index = typename Match::size_type>
|
||||
duration::common_counter_type parse_count(Match const& m, Index group_index) {
|
||||
static_assert(sizeof(duration::common_counter_type) <= sizeof(long long), "must be same");
|
||||
return std::stoll(m[group_index].str());
|
||||
cql_duration::common_counter_type parse_count(const Match& m, Index group_index) {
|
||||
try {
|
||||
return boost::lexical_cast<cql_duration::common_counter_type>(m[group_index].str());
|
||||
} catch (const boost::bad_lexical_cast&) {
|
||||
throw std::out_of_range("duration counter");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -181,12 +185,12 @@ duration::common_counter_type parse_count(Match const& m, Index group_index) {
|
||||
//
|
||||
// We support overflow detection on construction for convenience and compatibility with Cassandra.
|
||||
//
|
||||
// We maintain some additional state over a `duration` in order to track the order in which components are added when
|
||||
// We maintain some additional state over a `cql_duration` in order to track the order in which components are added when
|
||||
// parsing the standard format.
|
||||
//
|
||||
class duration_builder final {
|
||||
public:
|
||||
duration_builder& add(duration::common_counter_type count, duration_unit const& unit) {
|
||||
duration_builder& add(cql_duration::common_counter_type count, const duration_unit& unit) {
|
||||
validate_addition(count, unit);
|
||||
validate_and_update_order(unit);
|
||||
|
||||
@@ -195,36 +199,36 @@ public:
|
||||
}
|
||||
|
||||
template <class Match, class Index = typename Match::size_type>
|
||||
duration_builder& add_parsed_count(Match const& m, Index group_index, duration_unit const& unit) {
|
||||
duration::common_counter_type count;
|
||||
duration_builder& add_parsed_count(const Match& m, Index group_index, const duration_unit& unit) {
|
||||
cql_duration::common_counter_type count;
|
||||
|
||||
try {
|
||||
count = parse_count(m, group_index);
|
||||
} catch (std::out_of_range const&) {
|
||||
throw duration_error(sprint("Invalid duration. The count for the %s is out of range", unit.long_name()));
|
||||
} catch (const std::out_of_range&) {
|
||||
throw cql_duration_error(sprint("Invalid duration. The count for the %s is out of range", unit.long_name()));
|
||||
}
|
||||
|
||||
return add(count, unit);
|
||||
}
|
||||
|
||||
duration build() const noexcept {
|
||||
cql_duration build() const noexcept {
|
||||
return _duration;
|
||||
}
|
||||
|
||||
private:
|
||||
duration_unit const* _current_unit{nullptr};
|
||||
const duration_unit* _current_unit{nullptr};
|
||||
|
||||
duration _duration{};
|
||||
cql_duration _duration{};
|
||||
|
||||
//
|
||||
// Throws `duration_error` if the addition of a quantity of the designated unit would overflow one of the
|
||||
// Throws `cql_duration_error` if the addition of a quantity of the designated unit would overflow one of the
|
||||
// counters.
|
||||
//
|
||||
void validate_addition(typename duration::common_counter_type count, duration_unit const& unit) const {
|
||||
auto const available = unit.available_count(_duration);
|
||||
void validate_addition(typename cql_duration::common_counter_type count, const duration_unit& unit) const {
|
||||
const auto available = unit.available_count(_duration);
|
||||
|
||||
if (count > available) {
|
||||
throw duration_error(
|
||||
throw cql_duration_error(
|
||||
sprint("Invalid duration. The number of %s must be less than or equal to %s",
|
||||
unit.long_name(),
|
||||
available));
|
||||
@@ -237,16 +241,16 @@ private:
|
||||
//
|
||||
// This function also updates the last-observed unit for the next invocation.
|
||||
//
|
||||
// Throws `duration_error` for order violations.
|
||||
// Throws `cql_duration_error` for order violations.
|
||||
//
|
||||
void validate_and_update_order(duration_unit const& unit) {
|
||||
auto const index = unit.index();
|
||||
void validate_and_update_order(const duration_unit& unit) {
|
||||
const auto index = unit.index();
|
||||
|
||||
if (_current_unit != nullptr) {
|
||||
if (index == _current_unit->index()) {
|
||||
throw duration_error(sprint("Invalid duration. The %s are specified multiple times", unit.long_name()));
|
||||
throw cql_duration_error(sprint("Invalid duration. The %s are specified multiple times", unit.long_name()));
|
||||
} else if (index > _current_unit->index()) {
|
||||
throw duration_error(
|
||||
throw cql_duration_error(
|
||||
sprint("Invalid duration. The %s should be after %s",
|
||||
_current_unit->long_name(),
|
||||
unit.long_name()));
|
||||
@@ -258,10 +262,10 @@ private:
|
||||
};
|
||||
|
||||
//
|
||||
// These functions assume no sign information ('-). That is left to the `duration` constructor.
|
||||
// These functions assume no sign information ('-). That is left to the `cql_duration` constructor.
|
||||
//
|
||||
|
||||
stdx::optional<duration> parse_duration_standard_format(stdx::string_view s) {
|
||||
stdx::optional<cql_duration> parse_duration_standard_format(stdx::string_view s) {
|
||||
|
||||
//
|
||||
// We parse one component (pair of a count and unit) at a time in order to give more precise error messages when
|
||||
@@ -270,7 +274,7 @@ stdx::optional<duration> parse_duration_standard_format(stdx::string_view s) {
|
||||
// The other formats are more strict and complain less helpfully.
|
||||
//
|
||||
|
||||
static auto const pattern =
|
||||
static const auto pattern =
|
||||
std::regex(u8"(\\d+)(y|Y|mo|MO|mO|Mo|w|W|d|D|h|H|s|S|ms|MS|mS|Ms|us|US|uS|Us|µs|µS|ns|NS|nS|Ns|m|M)");
|
||||
|
||||
auto iter = s.cbegin();
|
||||
@@ -308,8 +312,8 @@ stdx::optional<duration> parse_duration_standard_format(stdx::string_view s) {
|
||||
return b.build();
|
||||
}
|
||||
|
||||
stdx::optional<duration> parse_duration_iso8601_format(stdx::string_view s) {
|
||||
static auto const pattern = std::regex("P((\\d+)Y)?((\\d+)M)?((\\d+)D)?(T((\\d+)H)?((\\d+)M)?((\\d+)S)?)?");
|
||||
stdx::optional<cql_duration> parse_duration_iso8601_format(stdx::string_view s) {
|
||||
static const auto pattern = std::regex("P((\\d+)Y)?((\\d+)M)?((\\d+)D)?(T((\\d+)H)?((\\d+)M)?((\\d+)S)?)?");
|
||||
|
||||
std::cmatch match;
|
||||
if (!std::regex_match(s.data(), match, pattern)) {
|
||||
@@ -348,8 +352,8 @@ stdx::optional<duration> parse_duration_iso8601_format(stdx::string_view s) {
|
||||
return b.build();
|
||||
}
|
||||
|
||||
stdx::optional<duration> parse_duration_iso8601_alternative_format(stdx::string_view s) {
|
||||
static auto const pattern = std::regex("P(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})");
|
||||
stdx::optional<cql_duration> parse_duration_iso8601_alternative_format(stdx::string_view s) {
|
||||
static const auto pattern = std::regex("P(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})");
|
||||
|
||||
std::cmatch match;
|
||||
if (!std::regex_match(s.data(), match, pattern)) {
|
||||
@@ -366,8 +370,8 @@ stdx::optional<duration> parse_duration_iso8601_alternative_format(stdx::string_
|
||||
.build();
|
||||
}
|
||||
|
||||
stdx::optional<duration> parse_duration_iso8601_week_format(stdx::string_view s) {
|
||||
static auto const pattern = std::regex("P(\\d+)W");
|
||||
stdx::optional<cql_duration> parse_duration_iso8601_week_format(stdx::string_view s) {
|
||||
static const auto pattern = std::regex("P(\\d+)W");
|
||||
|
||||
std::cmatch match;
|
||||
if (!std::regex_match(s.data(), match, pattern)) {
|
||||
@@ -380,7 +384,7 @@ stdx::optional<duration> parse_duration_iso8601_week_format(stdx::string_view s)
|
||||
}
|
||||
|
||||
// Parse a duration string without sign information assuming one of the supported formats.
|
||||
stdx::optional<duration> parse_duration(stdx::string_view s) {
|
||||
stdx::optional<cql_duration> parse_duration(stdx::string_view s) {
|
||||
if (s.length() == 0u) {
|
||||
return {};
|
||||
}
|
||||
@@ -402,15 +406,15 @@ stdx::optional<duration> parse_duration(stdx::string_view s) {
|
||||
|
||||
}
|
||||
|
||||
duration::duration(stdx::string_view s) {
|
||||
bool const is_negative = (s.length() != 0) && (s[0] == '-');
|
||||
cql_duration::cql_duration(stdx::string_view s) {
|
||||
const bool is_negative = (s.length() != 0) && (s[0] == '-');
|
||||
|
||||
// Without any sign indicator ('-').
|
||||
auto const ps = (is_negative ? s.cbegin() + 1 : s.cbegin());
|
||||
const auto ps = (is_negative ? s.cbegin() + 1 : s.cbegin());
|
||||
|
||||
auto const d = parse_duration(ps);
|
||||
const auto d = parse_duration(ps);
|
||||
if (!d) {
|
||||
throw duration_error(sprint("Unable to convert '%s' to a duration", s));
|
||||
throw cql_duration_error(sprint("Unable to convert '%s' to a duration", s));
|
||||
}
|
||||
|
||||
*this = *d;
|
||||
@@ -422,7 +426,7 @@ duration::duration(stdx::string_view s) {
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, duration const& d) {
|
||||
std::ostream& operator<<(std::ostream& os, const cql_duration& d) {
|
||||
if ((d.months < 0) || (d.days < 0) || (d.nanoseconds < 0)) {
|
||||
os << '-';
|
||||
}
|
||||
@@ -431,8 +435,8 @@ std::ostream& operator<<(std::ostream& os, duration const& d) {
|
||||
// unit.
|
||||
//
|
||||
// Returns the remaining count.
|
||||
auto const append = [&os](duration::common_counter_type count, auto&& unit) {
|
||||
auto const divider = unit.factor;
|
||||
const auto append = [&os](cql_duration::common_counter_type count, auto&& unit) {
|
||||
const auto divider = unit.factor;
|
||||
|
||||
if ((count == 0) || (count < divider)) {
|
||||
return count;
|
||||
@@ -442,7 +446,7 @@ std::ostream& operator<<(std::ostream& os, duration const& d) {
|
||||
return count % divider;
|
||||
};
|
||||
|
||||
auto const month_remainder = append(std::abs(d.months), year);
|
||||
const auto month_remainder = append(std::abs(d.months), year);
|
||||
append(month_remainder, month);
|
||||
|
||||
append(std::abs(d.days), day);
|
||||
@@ -457,16 +461,16 @@ std::ostream& operator<<(std::ostream& os, duration const& d) {
|
||||
return os;
|
||||
}
|
||||
|
||||
seastar::sstring to_string(duration const& d) {
|
||||
seastar::sstring to_string(const cql_duration& d) {
|
||||
std::ostringstream ss;
|
||||
ss << d;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool operator==(duration const& d1, duration const& d2) noexcept {
|
||||
bool operator==(const cql_duration& d1, const cql_duration& d2) noexcept {
|
||||
return (d1.months == d2.months) && (d1.days == d2.days) && (d1.nanoseconds == d2.nanoseconds);
|
||||
}
|
||||
|
||||
bool operator!=(duration const& d1, duration const& d2) noexcept {
|
||||
bool operator!=(const cql_duration& d1, const cql_duration& d2) noexcept {
|
||||
return !(d1 == d2);
|
||||
}
|
||||
|
||||
34
duration.hh
34
duration.hh
@@ -30,26 +30,26 @@
|
||||
|
||||
// Wrapper for a value with a type-tag for differentiating instances.
|
||||
template <class Value, class Tag>
|
||||
class duration_counter final {
|
||||
class cql_duration_counter final {
|
||||
public:
|
||||
using value_type = Value;
|
||||
|
||||
explicit constexpr duration_counter(value_type count) noexcept : _count(count) {}
|
||||
explicit constexpr cql_duration_counter(value_type count) noexcept : _count(count) {}
|
||||
|
||||
constexpr operator value_type() const noexcept { return _count; }
|
||||
private:
|
||||
value_type _count;
|
||||
};
|
||||
|
||||
using months_counter = duration_counter<int32_t, struct month_tag>;
|
||||
using days_counter = duration_counter<int32_t, struct day_tag>;
|
||||
using nanoseconds_counter = duration_counter<int64_t, struct nanosecond_tag>;
|
||||
using months_counter = cql_duration_counter<int32_t, struct month_tag>;
|
||||
using days_counter = cql_duration_counter<int32_t, struct day_tag>;
|
||||
using nanoseconds_counter = cql_duration_counter<int64_t, struct nanosecond_tag>;
|
||||
|
||||
class duration_error : public std::invalid_argument {
|
||||
class cql_duration_error : public std::invalid_argument {
|
||||
public:
|
||||
explicit duration_error(std::experimental::string_view what) : std::invalid_argument(what.data()) {}
|
||||
explicit cql_duration_error(std::experimental::string_view what) : std::invalid_argument(what.data()) {}
|
||||
|
||||
virtual ~duration_error() = default;
|
||||
virtual ~cql_duration_error() = default;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
// The primary use of this type is to manipulate absolute time-stamps with relative offsets. For example,
|
||||
// `"Jan. 31 2005 at 23:15" + 3mo5d`.
|
||||
//
|
||||
class duration final {
|
||||
class cql_duration final {
|
||||
public:
|
||||
using common_counter_type = int64_t;
|
||||
|
||||
@@ -76,10 +76,10 @@ public:
|
||||
"The common counter type is smaller than one of the component counter types.");
|
||||
|
||||
// A zero-valued duration.
|
||||
constexpr duration() noexcept = default;
|
||||
constexpr cql_duration() noexcept = default;
|
||||
|
||||
// Construct a duration with explicit values for its three counters.
|
||||
constexpr duration(months_counter m, days_counter d, nanoseconds_counter n) noexcept :
|
||||
constexpr cql_duration(months_counter m, days_counter d, nanoseconds_counter n) noexcept :
|
||||
months(m),
|
||||
days(d),
|
||||
nanoseconds(n) {}
|
||||
@@ -116,9 +116,9 @@ public:
|
||||
// For all formats, a negative duration is indicated by beginning the string with the '-' symbol. For example,
|
||||
// "-2y10ns".
|
||||
//
|
||||
// Throws `duration_error` in the event of a parsing error.
|
||||
// Throws `cql_duration_error` in the event of a parsing error.
|
||||
//
|
||||
explicit duration(std::experimental::string_view s);
|
||||
explicit cql_duration(std::experimental::string_view s);
|
||||
|
||||
months_counter::value_type months{0};
|
||||
days_counter::value_type days{0};
|
||||
@@ -130,15 +130,15 @@ public:
|
||||
//
|
||||
// Durations are simplified during printing so that `duration(24, 0, 0)` is printed as "2y".
|
||||
//
|
||||
std::ostream& operator<<(std::ostream& os, duration const& d);
|
||||
std::ostream& operator<<(std::ostream& os, const cql_duration& d);
|
||||
|
||||
// See above.
|
||||
seastar::sstring to_string(duration const&);
|
||||
seastar::sstring to_string(const cql_duration&);
|
||||
|
||||
//
|
||||
// Note that equality comparison is based on exact counter matches. It is not valid to expect equivalency across
|
||||
// counters like months and days. See the documentation for `duration` for more.
|
||||
//
|
||||
|
||||
bool operator==(duration const&, duration const&) noexcept;
|
||||
bool operator!=(duration const&, duration const&) noexcept;
|
||||
bool operator==(const cql_duration&, const cql_duration&) noexcept;
|
||||
bool operator!=(const cql_duration&, const cql_duration&) noexcept;
|
||||
|
||||
@@ -1142,6 +1142,102 @@ SEASTAR_TEST_CASE(test_user_type) {
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Since durations don't have a well-defined ordering on their semantic value, a number of restrictions exist on their
|
||||
// use.
|
||||
//
|
||||
SEASTAR_TEST_CASE(test_duration_restrictions) {
|
||||
static constexpr auto validate_request_failure = [](cql_test_env& env,
|
||||
const sstring& request,
|
||||
const sstring& expected_message) {
|
||||
BOOST_REQUIRE_EXCEPTION(env.execute_cql(request).get(),
|
||||
exceptions::invalid_request_exception,
|
||||
[&expected_message](auto &&ire) {
|
||||
BOOST_REQUIRE_EQUAL(expected_message, ire.what());
|
||||
return true;
|
||||
});
|
||||
|
||||
return make_ready_future<>();
|
||||
};
|
||||
|
||||
return do_with_cql_env([](cql_test_env& env) {
|
||||
return make_ready_future<>().then([&env] {
|
||||
// Disallow "direct" use of durations in ordered collection types to avoid user confusion when their
|
||||
// ordering doesn't match expectations.
|
||||
return make_ready_future<>().then([&env] {
|
||||
return validate_request_failure(
|
||||
env,
|
||||
"create type my_type (a set<duration>);",
|
||||
"Durations are not allowed inside sets: set<duration>");
|
||||
}).then([&env] {
|
||||
return validate_request_failure(
|
||||
env,
|
||||
"create type my_type (a map<duration, int>);",
|
||||
"Durations are not allowed as map keys: map<duration, int>");
|
||||
});
|
||||
}).then([&env] {
|
||||
// Disallow any type referring to a duration from being used in a primary key of a table or a materialized
|
||||
// view.
|
||||
return make_ready_future<>().then([&env] {
|
||||
return validate_request_failure(
|
||||
env,
|
||||
"create table my_table (direct_key duration PRIMARY KEY);",
|
||||
"duration type is not supported for PRIMARY KEY part direct_key");
|
||||
}).then([&env] {
|
||||
return validate_request_failure(
|
||||
env,
|
||||
"create table my_table (collection_key frozen<list<duration>> PRIMARY KEY);",
|
||||
"duration type is not supported for PRIMARY KEY part collection_key");
|
||||
}).then([&env] {
|
||||
return env.execute_cql("create type my_type0 (span duration);").discard_result().then([&env] {
|
||||
return validate_request_failure(
|
||||
env,
|
||||
"create table my_table (udt_key frozen<my_type0> PRIMARY KEY);",
|
||||
"duration type is not supported for PRIMARY KEY part udt_key");
|
||||
});
|
||||
}).then([&env] {
|
||||
return validate_request_failure(
|
||||
env,
|
||||
"create table my_table (tuple_key tuple<int, duration, int> PRIMARY KEY);",
|
||||
"duration type is not supported for PRIMARY KEY part tuple_key");
|
||||
}).then([&env] {
|
||||
return env.execute_cql("create table my_table0 (key int PRIMARY KEY, name text, span duration);")
|
||||
.discard_result().then([&env] {
|
||||
return validate_request_failure(
|
||||
env,
|
||||
"create materialized view my_mv as"
|
||||
" select * from my_table0 "
|
||||
" where name = 'abc' "
|
||||
" primary key (key, span);",
|
||||
"Cannot use Duration column 'span' in PRIMARY KEY of materialized view");
|
||||
});
|
||||
});
|
||||
}).then([&env] {
|
||||
// Disallow creating secondary indexes on durations.
|
||||
return validate_request_failure(
|
||||
env,
|
||||
"create index my_index on my_table0 (span);",
|
||||
"Secondary indexes are not supported on duration columns");
|
||||
}).then([&env] {
|
||||
// Disallow slice-based restrictions and conditions on durations.
|
||||
//
|
||||
// Note that multi-column restrictions are only supported on clustering columns (which cannot be `duration`)
|
||||
// and that multi-column conditions are not supported in the grammar.
|
||||
return make_ready_future<>().then([&env] {
|
||||
return validate_request_failure(
|
||||
env,
|
||||
"select * from my_table0 where key = 0 and span < 3d;",
|
||||
"Slice restrictions are not supported on duration columns");
|
||||
}).then([&env] {
|
||||
return validate_request_failure(
|
||||
env,
|
||||
"update my_table0 set name = 'joe' where key = 0 if span >= 5m",
|
||||
"Slice conditions are not supported on durations");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_select_multiple_ranges) {
|
||||
return do_with_cql_env([] (auto&& e) {
|
||||
return e.execute_cql("create table cf (p1 varchar, r1 int, PRIMARY KEY (p1));").discard_result().then([&e] {
|
||||
@@ -1397,11 +1493,12 @@ SEASTAR_TEST_CASE(test_types) {
|
||||
" q smallint,"
|
||||
" r date,"
|
||||
" s time,"
|
||||
" u duration,"
|
||||
");").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, n, o, p, q, r, s) VALUES ("
|
||||
"INSERT INTO all_types (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, u) VALUES ("
|
||||
" 'ascii',"
|
||||
" 123456789,"
|
||||
" 0xdeadbeef,"
|
||||
@@ -1420,7 +1517,8 @@ SEASTAR_TEST_CASE(test_types) {
|
||||
" 3,"
|
||||
" 3,"
|
||||
" '1970-01-02',"
|
||||
" '00:00:00.000000001'"
|
||||
" '00:00:00.000000001',"
|
||||
" 1y2mo3w4d5h6m7s8ms9us10ns"
|
||||
");").discard_result();
|
||||
}).then([&e] {
|
||||
return e.execute_cql("SELECT * FROM all_types WHERE a = 'ascii'");
|
||||
@@ -1449,10 +1547,11 @@ SEASTAR_TEST_CASE(test_types) {
|
||||
short_type->decompose(int16_t(3)),
|
||||
simple_date_type->decompose(int32_t(0x80000001)),
|
||||
time_type->decompose(int64_t(0x0000000000000001)),
|
||||
duration_type->decompose(cql_duration("1y2mo3w4d5h6m7s8ms9us10ns"))
|
||||
}
|
||||
});
|
||||
return e.execute_cql(
|
||||
"INSERT INTO all_types (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s) VALUES ("
|
||||
"INSERT INTO all_types (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, u) VALUES ("
|
||||
" blobAsAscii(asciiAsBlob('ascii2')),"
|
||||
" blobAsBigint(bigintAsBlob(123456789)),"
|
||||
" bigintAsBlob(12),"
|
||||
@@ -1470,7 +1569,8 @@ SEASTAR_TEST_CASE(test_types) {
|
||||
" blobAsTinyint(tinyintAsBlob(3)),"
|
||||
" blobAsSmallint(smallintAsBlob(3)),"
|
||||
" blobAsDate(dateAsBlob('1970-01-02')),"
|
||||
" blobAsTime(timeAsBlob('00:00:00.000000001'))"
|
||||
" blobAsTime(timeAsBlob('00:00:00.000000001')),"
|
||||
" blobAsDuration(durationAsBlob(10y9mo8w7d6h5m4s3ms2us1ns))"
|
||||
");").discard_result();
|
||||
}).then([&e] {
|
||||
return e.execute_cql("SELECT * FROM all_types WHERE a = 'ascii2'");
|
||||
@@ -1499,6 +1599,7 @@ SEASTAR_TEST_CASE(test_types) {
|
||||
short_type->decompose(int16_t(3)),
|
||||
simple_date_type->decompose(int32_t(0x80000001)),
|
||||
time_type->decompose(int64_t(0x0000000000000001)),
|
||||
duration_type->decompose(cql_duration("10y9mo8w7d6h5m4s3ms2us1ns"))
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ constexpr auto ns_per_m = ns_per_s * 60L;
|
||||
constexpr auto ns_per_h = ns_per_m * 60L;
|
||||
|
||||
// Normally we want to be explict, but brevity is nice for tests.
|
||||
constexpr duration make_duration(months_counter::value_type m,
|
||||
constexpr cql_duration make_duration(months_counter::value_type m,
|
||||
days_counter::value_type d,
|
||||
nanoseconds_counter::value_type ns) noexcept {
|
||||
return {months_counter(m), days_counter(d), nanoseconds_counter(ns)};
|
||||
@@ -56,120 +56,121 @@ constexpr duration make_duration(months_counter::value_type m,
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(parse_standard) {
|
||||
BOOST_REQUIRE_EQUAL(make_duration(14, 0, 0), duration("1y2mo"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(-14, 0, 0), duration("-1y2mo"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(14, 0, 0), duration("1Y2MO"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 14, 0), duration("2w"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 2, 10 * ns_per_h), duration("2d10h"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 2, 0), duration("2d"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 30 * ns_per_h), duration("30h"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, (30 * ns_per_h) + (20 * ns_per_m)), duration("30h20m"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 20 * ns_per_m), duration("20m"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 56 * ns_per_s), duration("56s"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 567 * ns_per_ms), duration("567ms"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 1950 * ns_per_us), duration("1950us"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 1950 * ns_per_us), duration("1950µs"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 1950000), duration("1950000ns"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 1950000), duration("1950000NS"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, -1950000), duration("-1950000ns"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(15, 0, 130 * ns_per_m), duration("1y3mo2h10m"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(14, 0, 0), cql_duration("1y2mo"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(-14, 0, 0), cql_duration("-1y2mo"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(14, 0, 0), cql_duration("1Y2MO"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 14, 0), cql_duration("2w"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 2, 10 * ns_per_h), cql_duration("2d10h"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 2, 0), cql_duration("2d"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 30 * ns_per_h), cql_duration("30h"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, (30 * ns_per_h) + (20 * ns_per_m)), cql_duration("30h20m"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 20 * ns_per_m), cql_duration("20m"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 56 * ns_per_s), cql_duration("56s"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 567 * ns_per_ms), cql_duration("567ms"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 1950 * ns_per_us), cql_duration("1950us"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 1950 * ns_per_us), cql_duration("1950µs"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 1950000), cql_duration("1950000ns"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 1950000), cql_duration("1950000NS"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, -1950000), cql_duration("-1950000ns"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(15, 0, 130 * ns_per_m), cql_duration("1y3mo2h10m"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(parse_standard_syntax_error) {
|
||||
// Read the entire input.
|
||||
BOOST_REQUIRE_EXCEPTION(duration("1y500"), duration_error, [](auto&& exn) {
|
||||
BOOST_REQUIRE_EXCEPTION(cql_duration("1y500"), cql_duration_error, [](auto&& exn) {
|
||||
return stdx::string_view(exn.what()) == "Unable to convert '1y500' to a duration";
|
||||
});
|
||||
|
||||
// Do not skip invalid characters in the middle.
|
||||
BOOST_REQUIRE_EXCEPTION(duration("1y xxx 500d"), duration_error, [](auto&& exn) {
|
||||
BOOST_REQUIRE_EXCEPTION(cql_duration("1y xxx 500d"), cql_duration_error, [](auto&& exn) {
|
||||
return stdx::string_view(exn.what()) == "Unable to convert '1y xxx 500d' to a duration";
|
||||
});
|
||||
|
||||
// Do not skip invalid characters at the beginning.
|
||||
BOOST_REQUIRE_EXCEPTION(duration("xxx1y500d"), duration_error, [](auto&& exn) {
|
||||
BOOST_REQUIRE_EXCEPTION(cql_duration("xxx1y500d"), cql_duration_error, [](auto&& exn) {
|
||||
return stdx::string_view(exn.what()) == "Unable to convert 'xxx1y500d' to a duration";
|
||||
});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(parse_standard_order_error) {
|
||||
BOOST_REQUIRE_EXCEPTION(duration("20s1h3m"), duration_error, [](auto &&exn) {
|
||||
BOOST_REQUIRE_EXCEPTION(cql_duration("20s1h3m"), cql_duration_error, [](auto &&exn) {
|
||||
return stdx::string_view(exn.what()) == "Invalid duration. The seconds should be after hours";
|
||||
});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(parse_standard_repeated_error) {
|
||||
BOOST_REQUIRE_EXCEPTION(duration("1h2h3m"), duration_error, [](auto &&exn) {
|
||||
BOOST_REQUIRE_EXCEPTION(cql_duration("1h2h3m"), cql_duration_error, [](auto &&exn) {
|
||||
return stdx::string_view(exn.what()) == "Invalid duration. The hours are specified multiple times";
|
||||
});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(parse_standard_overflow_error) {
|
||||
BOOST_REQUIRE_EXCEPTION(duration("178956971y"), duration_error, [](auto &&exn) {
|
||||
BOOST_REQUIRE_EXCEPTION(cql_duration("178956971y"), cql_duration_error, [](auto &&exn) {
|
||||
return stdx::string_view(exn.what())
|
||||
== "Invalid duration. The number of years must be less than or equal to 178956970";
|
||||
});
|
||||
|
||||
BOOST_REQUIRE_EXCEPTION(duration("178956970y14mo"), duration_error, [](auto &&exn) {
|
||||
BOOST_REQUIRE_EXCEPTION(cql_duration("178956970y14mo"), cql_duration_error, [](auto &&exn) {
|
||||
return stdx::string_view(exn.what())
|
||||
== "Invalid duration. The number of months must be less than or equal to 7";
|
||||
});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(parse_iso8601) {
|
||||
BOOST_REQUIRE_EQUAL(make_duration(12, 2, 0), duration("P1Y2D"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(14, 0, 0), duration("P1Y2M"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 14, 0), duration("P2W"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(12, 0, 2 * ns_per_h), duration("P1YT2H"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(-14, 0, 0), duration("-P1Y2M"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 2, 0), duration("P2D"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 30 * ns_per_h), duration("PT30H"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, (30 * ns_per_h) + (20 * ns_per_m)), duration("PT30H20M"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 20 * ns_per_m), duration("PT20M"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 56 * ns_per_s), duration("PT56S"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(15, 0, 130 * ns_per_m), duration("P1Y3MT2H10M"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(12, 2, 0), cql_duration("P1Y2D"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(14, 0, 0), cql_duration("P1Y2M"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 14, 0), cql_duration("P2W"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(12, 0, 2 * ns_per_h), cql_duration("P1YT2H"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(-14, 0, 0), cql_duration("-P1Y2M"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 2, 0), cql_duration("P2D"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 30 * ns_per_h), cql_duration("PT30H"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, (30 * ns_per_h) + (20 * ns_per_m)), cql_duration("PT30H20M"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 20 * ns_per_m), cql_duration("PT20M"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 56 * ns_per_s), cql_duration("PT56S"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(15, 0, 130 * ns_per_m), cql_duration("P1Y3MT2H10M"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(parse_iso8601_syntax_error) {
|
||||
BOOST_REQUIRE_EXCEPTION(duration("P2003T23s"), duration_error, [](auto&& exn) {
|
||||
BOOST_REQUIRE_EXCEPTION(cql_duration("P2003T23s"), cql_duration_error, [](auto&& exn) {
|
||||
return stdx::string_view(exn.what()) == "Unable to convert 'P2003T23s' to a duration";
|
||||
});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(parse_iso8601_alternative) {
|
||||
BOOST_REQUIRE_EQUAL(make_duration(12, 2, 0), duration("P0001-00-02T00:00:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(14, 0, 0), duration("P0001-02-00T00:00:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(12, 0, 2 * ns_per_h), duration("P0001-00-00T02:00:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(-14, 0, 0), duration("-P0001-02-00T00:00:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 2, 0), duration("P0000-00-02T00:00:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 30 * ns_per_h), duration("P0000-00-00T30:00:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, (30 * ns_per_h) + (20 * ns_per_m)), duration("P0000-00-00T30:20:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 20 * ns_per_m), duration("P0000-00-00T00:20:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 56 * ns_per_s), duration("P0000-00-00T00:00:56"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(15, 0, 130 * ns_per_m), duration("P0001-03-00T02:10:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(12, 2, 0), cql_duration("P0001-00-02T00:00:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(14, 0, 0), cql_duration("P0001-02-00T00:00:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(12, 0, 2 * ns_per_h), cql_duration("P0001-00-00T02:00:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(-14, 0, 0), cql_duration("-P0001-02-00T00:00:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 2, 0), cql_duration("P0000-00-02T00:00:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 30 * ns_per_h), cql_duration("P0000-00-00T30:00:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, (30 * ns_per_h) + (20 * ns_per_m)), cql_duration("P0000-00-00T30:20:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 20 * ns_per_m), cql_duration("P0000-00-00T00:20:00"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(0, 0, 56 * ns_per_s), cql_duration("P0000-00-00T00:00:56"));
|
||||
BOOST_REQUIRE_EQUAL(make_duration(15, 0, 130 * ns_per_m), cql_duration("P0001-03-00T02:10:00"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(parse_iso8601_alternative_syntax_error) {
|
||||
BOOST_REQUIRE_EXCEPTION(duration("P0001-00-02T000000"), duration_error, [](auto&& exn) {
|
||||
BOOST_REQUIRE_EXCEPTION(cql_duration("P0001-00-02T000000"), cql_duration_error, [](auto&& exn) {
|
||||
return stdx::string_view(exn.what()) == "Unable to convert 'P0001-00-02T000000' to a duration";
|
||||
});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(parse_component_overflow) {
|
||||
BOOST_REQUIRE_EXCEPTION(duration("10000000000000000000000000000000000m"), duration_error, [](auto&& exn) {
|
||||
BOOST_REQUIRE_EXCEPTION(cql_duration("10000000000000000000000000000000000m"), cql_duration_error, [](auto&& exn) {
|
||||
return stdx::string_view(exn.what()) == "Invalid duration. The count for the minutes is out of range";
|
||||
});
|
||||
|
||||
BOOST_REQUIRE_EXCEPTION(duration("P10000000000000000000000000000000000Y5D"), duration_error, [](auto&& exn) {
|
||||
BOOST_REQUIRE_EXCEPTION(cql_duration("P10000000000000000000000000000000000Y5D"), cql_duration_error, [](auto&& exn) {
|
||||
return stdx::string_view(exn.what()) == "Invalid duration. The count for the years is out of range";
|
||||
});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(pretty_print) {
|
||||
BOOST_REQUIRE_EQUAL(to_string(duration("1y3d")), "1y3d");
|
||||
BOOST_REQUIRE_EQUAL(to_string(duration("25mo")), "2y1mo");
|
||||
BOOST_REQUIRE_EQUAL(to_string(duration("1y2mo3w4d5h6m7s8ms9us10ns")), "1y2mo25d5h6m7s8ms9us10ns");
|
||||
BOOST_REQUIRE_EQUAL(to_string(duration("-1d5m")), "-1d5m");
|
||||
BOOST_REQUIRE_EQUAL(to_string(cql_duration("1y3d")), "1y3d");
|
||||
BOOST_REQUIRE_EQUAL(to_string(cql_duration("25mo")), "2y1mo");
|
||||
BOOST_REQUIRE_EQUAL(to_string(cql_duration("1y2mo3w4d5h6m7s8ms9us10ns")), "1y2mo25d5h6m7s8ms9us10ns");
|
||||
BOOST_REQUIRE_EQUAL(to_string(cql_duration("-1d5m")), "-1d5m");
|
||||
BOOST_REQUIRE_EQUAL(to_string(cql_duration("0d")), "");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(equality) {
|
||||
|
||||
@@ -195,6 +195,13 @@ BOOST_AUTO_TEST_CASE(test_time_type_string_conversions) {
|
||||
test_parsing_fails(time_type, "00:00:00.-0000000001");
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_duration_type_string_conversions) {
|
||||
// See `duration_test.cc` for more conversion tests.
|
||||
BOOST_REQUIRE(duration_type->equal(duration_type->from_string("1y3mo5m2s"),
|
||||
duration_type->decompose(cql_duration("1y3mo5m2s"))));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_uuid_type_comparison) {
|
||||
auto uuid1 = uuid_type->decompose(utils::UUID(sstring("ad4d3770-7a50-11e6-ac4d-000000000003")));
|
||||
auto uuid2 = uuid_type->decompose(utils::UUID(sstring("c512ba10-7a50-11e6-ac4d-000000000003")));
|
||||
@@ -331,6 +338,10 @@ BOOST_AUTO_TEST_CASE(test_floating_types_compare) {
|
||||
test_floating_type_compare<double>(double_type);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_duration_type_compare) {
|
||||
duration_type->equal(duration_type->from_string("3d5m"), duration_type->from_string("3d5m"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_varint) {
|
||||
BOOST_REQUIRE(varint_type->equal(varint_type->from_string("-1"), varint_type->decompose(boost::multiprecision::cpp_int(-1))));
|
||||
BOOST_REQUIRE(varint_type->equal(varint_type->from_string("255"), varint_type->decompose(boost::multiprecision::cpp_int(255))));
|
||||
@@ -553,6 +564,36 @@ BOOST_AUTO_TEST_CASE(test_uuid_type_validation) {
|
||||
test_validation_fails(uuid_type, from_hex("00"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_duration_type_validation) {
|
||||
duration_type->validate(duration_type->from_string("1m23us"));
|
||||
|
||||
BOOST_REQUIRE_EXCEPTION(duration_type->validate(from_hex("ff")), marshal_exception, [](auto&& exn) {
|
||||
BOOST_REQUIRE_EQUAL("marshaling error: Expected at least 3 bytes for a duration, got 1", exn.what());
|
||||
return true;
|
||||
});
|
||||
|
||||
BOOST_REQUIRE_EXCEPTION(duration_type->validate(from_hex("fffffffffffffffffe0202")),
|
||||
marshal_exception,
|
||||
[](auto&& exn) {
|
||||
BOOST_REQUIRE_EQUAL("marshaling error: The duration months (9223372036854775807) must be a 32 bit integer", exn.what());
|
||||
return true;
|
||||
});
|
||||
|
||||
BOOST_REQUIRE_EXCEPTION(duration_type->validate(from_hex("010201")), marshal_exception, [](auto&& exn) {
|
||||
BOOST_REQUIRE_EQUAL(
|
||||
"marshaling error: The duration months, days, and nanoseconds must be all of the same sign (-1, 1, -1)",
|
||||
exn.what());
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_duration_deserialization) {
|
||||
BOOST_REQUIRE_EQUAL(
|
||||
value_cast<cql_duration>(duration_type->deserialize(duration_type->from_string("1mo3d2m"))),
|
||||
cql_duration("1mo3d2m"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_parse_bad_hex) {
|
||||
auto parser = db::marshal::type_parser("636f6c75kd6h:org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.Int32Type)");
|
||||
BOOST_REQUIRE_THROW(parser.parse(), exceptions::syntax_exception);
|
||||
@@ -581,6 +622,12 @@ BOOST_AUTO_TEST_CASE(test_parse_valid_map) {
|
||||
BOOST_REQUIRE(type->as_cql3_type()->to_string() == "map<int, int>");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_parse_valid_duration) {
|
||||
auto parser = db::marshal::type_parser("org.apache.cassandra.db.marshal.DurationType");
|
||||
auto type = parser.parse();
|
||||
BOOST_REQUIRE(type->as_cql3_type()->to_string() == "duration");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_parse_valid_tuple) {
|
||||
auto parser = db::marshal::type_parser("org.apache.cassandra.db.marshal.TupleType(org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.Int32Type)");
|
||||
auto type = parser.parse();
|
||||
|
||||
147
tests/vint_serialization_test.cc
Normal file
147
tests/vint_serialization_test.cc
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
#define BOOST_TEST_MODULE core
|
||||
|
||||
#include "vint-serialization.hh"
|
||||
|
||||
#include "bytes.hh"
|
||||
#include "disk-error-handler.hh"
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <random>
|
||||
|
||||
thread_local disk_error_signal_type commit_error;
|
||||
thread_local disk_error_signal_type general_disk_error;
|
||||
|
||||
using namespace seastar;
|
||||
|
||||
namespace {
|
||||
|
||||
typename bytes::value_type operator "" _b(unsigned long long value) {
|
||||
return static_cast<bytes::value_type>(value);
|
||||
}
|
||||
|
||||
// Check that the encoded value decodes back to the value. Also allows inspecting the encoded bytes.
|
||||
template <class Vint, class BytesInspector>
|
||||
void check_bytes_and_roundtrip(typename Vint::value_type value, BytesInspector&& f) {
|
||||
static std::array<int8_t, 9> encoding_buffer({});
|
||||
|
||||
const auto size = Vint::serialize(value, encoding_buffer.begin());
|
||||
const auto view = bytes_view(encoding_buffer.data(), size);
|
||||
f(view);
|
||||
|
||||
const auto deserialized = Vint::deserialize(view);
|
||||
BOOST_REQUIRE_EQUAL(deserialized.value, value);
|
||||
BOOST_REQUIRE_EQUAL(deserialized.size, size);
|
||||
};
|
||||
|
||||
// Check that the encoded value decodes back to the value.
|
||||
template <class Vint>
|
||||
void check_roundtrip(typename Vint::value_type value) {
|
||||
check_bytes_and_roundtrip<Vint>(value, [](const bytes_view&) {});
|
||||
}
|
||||
|
||||
template <class Integer, class RandomEngine, class Callback>
|
||||
void with_random_samples(RandomEngine& rng, std::size_t count, Callback&& f) {
|
||||
std::uniform_int_distribution<Integer> distribution;
|
||||
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
f(distribution(rng));
|
||||
}
|
||||
}
|
||||
|
||||
template <class Vint, class RandomEngine>
|
||||
void check_roundtrip_sweep(std::size_t count, RandomEngine& rng) {
|
||||
with_random_samples<typename Vint::value_type>(rng, count, &check_roundtrip<Vint>);
|
||||
};
|
||||
|
||||
|
||||
auto& random_engine() {
|
||||
static std::random_device rd;
|
||||
static std::mt19937 rng(rd());
|
||||
return rng;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(sanity_unsigned_examples) {
|
||||
using vint = unsigned_vint;
|
||||
|
||||
check_bytes_and_roundtrip<vint>(0, [](bytes_view v) {
|
||||
BOOST_REQUIRE_EQUAL(v, (bytes{0_b}));
|
||||
});
|
||||
|
||||
check_bytes_and_roundtrip<vint>(5, [](bytes_view v) {
|
||||
BOOST_REQUIRE_EQUAL(v, (bytes{0b0000'0101_b}));
|
||||
});
|
||||
|
||||
check_bytes_and_roundtrip<vint>(1111, [](bytes_view v) {
|
||||
BOOST_REQUIRE_EQUAL(v, (bytes{0b1000'0100_b, 0b0101'0111_b}));
|
||||
});
|
||||
|
||||
check_bytes_and_roundtrip<vint>(256'000, [](bytes_view v) {
|
||||
BOOST_REQUIRE_EQUAL(v, (bytes{0b1100'0011_b, 0b1110'1000_b, 0b0000'0000_b}));
|
||||
});
|
||||
|
||||
check_bytes_and_roundtrip<vint>(0xff'ee'dd'cc'bb'aa'99'88, [](bytes_view v) {
|
||||
BOOST_REQUIRE_EQUAL(v, (bytes{0xff_b, 0xff_b, 0xee_b, 0xdd_b, 0xcc_b, 0xbb_b, 0xaa_b, 0x99_b, 0x88_b}));
|
||||
});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(sanity_unsigned_sweep) {
|
||||
check_roundtrip_sweep<unsigned_vint>(100'000, random_engine());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(sanity_signed_examples) {
|
||||
using vint = signed_vint;
|
||||
|
||||
check_bytes_and_roundtrip<vint>(0, [](bytes_view v) {
|
||||
BOOST_REQUIRE_EQUAL(v, (bytes{0_b}));
|
||||
});
|
||||
|
||||
check_bytes_and_roundtrip<vint>(-1, [](bytes_view v) {
|
||||
BOOST_REQUIRE_EQUAL(v, (bytes{0b0000'0001_b}));
|
||||
});
|
||||
|
||||
check_bytes_and_roundtrip<vint>(1, [](bytes_view v) {
|
||||
BOOST_REQUIRE_EQUAL(v, (bytes{0b0000'0010_b}));
|
||||
});
|
||||
|
||||
check_bytes_and_roundtrip<vint>(-2, [](bytes_view v) {
|
||||
BOOST_REQUIRE_EQUAL(v, (bytes{0b0000'0011_b}));
|
||||
});
|
||||
|
||||
check_bytes_and_roundtrip<vint>(2, [](bytes_view v) {
|
||||
BOOST_REQUIRE_EQUAL(v, (bytes{0b0000'0100_b}));
|
||||
});
|
||||
|
||||
check_bytes_and_roundtrip<vint>(-256'000, [](bytes_view v) {
|
||||
BOOST_REQUIRE_EQUAL(v, (bytes{0b1100'0111_b, 0b1100'1111_b, 0b1111'1111_b}));
|
||||
});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(sanity_signed_sweep) {
|
||||
check_roundtrip_sweep<signed_vint>(100'000, random_engine());
|
||||
}
|
||||
@@ -1740,6 +1740,7 @@ private:
|
||||
TIME = 0x0012,
|
||||
SMALLINT = 0x0013,
|
||||
TINYINT = 0x0014,
|
||||
DURATION = 0x0015,
|
||||
LIST = 0x0020,
|
||||
MAP = 0x0021,
|
||||
SET = 0x0022,
|
||||
@@ -1826,6 +1827,7 @@ thread_local const type_codec::type_id_to_type_type type_codec::type_id_to_type
|
||||
(type_id::FLOAT , float_type)
|
||||
(type_id::INT , int32_type)
|
||||
(type_id::TINYINT , byte_type)
|
||||
(type_id::DURATION , duration_type)
|
||||
(type_id::SMALLINT , short_type)
|
||||
(type_id::TIMESTAMP , timestamp_type)
|
||||
(type_id::UUID , uuid_type)
|
||||
|
||||
174
types.cc
174
types.cc
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include "cql3/cql3_type.hh"
|
||||
#include "cql3/lists.hh"
|
||||
#include "cql3/maps.hh"
|
||||
@@ -30,6 +31,7 @@
|
||||
#include "net/ip.hh"
|
||||
#include "database.hh"
|
||||
#include "utils/serialization.hh"
|
||||
#include "vint-serialization.hh"
|
||||
#include "combine.hh"
|
||||
#include <cmath>
|
||||
#include <chrono>
|
||||
@@ -43,6 +45,7 @@
|
||||
#include <boost/date_time/c_local_time_adjustor.hpp>
|
||||
#include <boost/locale/encoding_utf.hpp>
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
#include <boost/algorithm/cxx11/any_of.hpp>
|
||||
#include "utils/big_decimal.hh"
|
||||
#include "utils/date.h"
|
||||
#include "mutation_partition.hh"
|
||||
@@ -75,6 +78,7 @@ static const char* float_type_name = "org.apache.cassandra.db.marshal.FloatT
|
||||
static const char* varint_type_name = "org.apache.cassandra.db.marshal.IntegerType";
|
||||
static const char* decimal_type_name = "org.apache.cassandra.db.marshal.DecimalType";
|
||||
static const char* counter_type_name = "org.apache.cassandra.db.marshal.CounterColumnType";
|
||||
static const char* duration_type_name = "org.apache.cassandra.db.marshal.DurationType";
|
||||
static const char* empty_type_name = "org.apache.cassandra.db.marshal.EmptyType";
|
||||
|
||||
template<typename T>
|
||||
@@ -1542,6 +1546,155 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// TODO(jhaberku): Move this to Seastar.
|
||||
template <size_t... Ts, class Function>
|
||||
auto generate_tuple_from_index(std::index_sequence<Ts...>, Function&& f) {
|
||||
// To ensure that tuple is constructed in the correct order (because the evaluation order of the arguments to
|
||||
// `std::make_tuple` is unspecified), use braced initialization (which does define the order). However, we still
|
||||
// need to figure out the type.
|
||||
using result_type = decltype(std::make_tuple(f(Ts)...));
|
||||
return result_type{f(Ts)...};
|
||||
}
|
||||
|
||||
class duration_type_impl : public concrete_type<cql_duration> {
|
||||
public:
|
||||
duration_type_impl() : concrete_type(duration_type_name) {
|
||||
}
|
||||
virtual void serialize(const void* value, bytes::iterator& out) const override {
|
||||
if (value == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& m = from_value(value);
|
||||
if (m.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& d = m.get();
|
||||
|
||||
out += signed_vint::serialize(d.months, out);
|
||||
out += signed_vint::serialize(d.days, out);
|
||||
out += signed_vint::serialize(d.nanoseconds, out);
|
||||
}
|
||||
virtual size_t serialized_size(const void* value) const override {
|
||||
if (value == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& m = from_value(value);
|
||||
if (m.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& d = m.get();
|
||||
|
||||
return signed_vint::serialized_size(d.months) +
|
||||
signed_vint::serialized_size(d.days) +
|
||||
signed_vint::serialized_size(d.nanoseconds);
|
||||
}
|
||||
virtual void validate(bytes_view v) const override {
|
||||
if (v.size() < 3) {
|
||||
throw marshal_exception(sprint("Expected at least 3 bytes for a duration, got %d", v.size()));
|
||||
}
|
||||
|
||||
counter_type months, days, nanoseconds;
|
||||
std::tie(months, days, nanoseconds) = deserialize_counters(v);
|
||||
|
||||
static constexpr auto check_counter_range = [](counter_type value, auto counter_value_type_instance, stdx::string_view counter_name) {
|
||||
using counter_value_type = decltype(counter_value_type_instance);
|
||||
|
||||
if (static_cast<counter_value_type>(value) != value) {
|
||||
throw marshal_exception(sprint("The duration %s (%" PRId64 ") must be a %d bit integer",
|
||||
counter_name,
|
||||
value,
|
||||
std::numeric_limits<counter_value_type>::digits + 1));
|
||||
}
|
||||
};
|
||||
|
||||
check_counter_range(months, months_counter::value_type(), "months");
|
||||
check_counter_range(days, days_counter::value_type(), "days");
|
||||
check_counter_range(nanoseconds, nanoseconds_counter::value_type(), "nanoseconds");
|
||||
|
||||
if (!(((months <= 0) && (days <= 0) && (nanoseconds <= 0))
|
||||
|| ((months >= 0) && (days >= 0) && (nanoseconds >= 0)))) {
|
||||
throw marshal_exception(
|
||||
sprint("The duration months, days, and nanoseconds must be all of the same sign (%" PRId64 ", %" PRId64 ", %" PRId64 ")",
|
||||
months,
|
||||
days,
|
||||
nanoseconds));
|
||||
}
|
||||
}
|
||||
virtual data_value deserialize(bytes_view v) const override {
|
||||
if (v.empty()) {
|
||||
return make_empty();
|
||||
}
|
||||
|
||||
counter_type months, days, nanoseconds;
|
||||
std::tie(months, days, nanoseconds) = deserialize_counters(v);
|
||||
|
||||
return make_value(cql_duration(months_counter(months),
|
||||
days_counter(days),
|
||||
nanoseconds_counter(nanoseconds)));
|
||||
}
|
||||
virtual bytes from_string(sstring_view s) const override {
|
||||
if (s.empty()) {
|
||||
return bytes();
|
||||
}
|
||||
|
||||
try {
|
||||
native_type nd = cql_duration(s);
|
||||
|
||||
bytes b(bytes::initialized_later(), serialized_size(&nd));
|
||||
auto out = b.begin();
|
||||
serialize(&nd, out);
|
||||
|
||||
return b;
|
||||
} catch (cql_duration_error const& e) {
|
||||
throw marshal_exception(e.what());
|
||||
}
|
||||
}
|
||||
virtual sstring to_string(const bytes& b) const override {
|
||||
auto v = deserialize(b);
|
||||
if (v.is_null()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return ::to_string(from_value(v).get());
|
||||
}
|
||||
virtual size_t hash(bytes_view v) const override {
|
||||
return std::hash<bytes_view>()(v);
|
||||
}
|
||||
virtual bool is_byte_order_comparable() const override {
|
||||
return true;
|
||||
}
|
||||
virtual bool less(bytes_view v1, bytes_view v2) const override {
|
||||
return less_unsigned(v1, v2);
|
||||
}
|
||||
virtual shared_ptr<cql3::cql3_type> as_cql3_type() const override {
|
||||
return cql3::cql3_type::duration;
|
||||
}
|
||||
virtual bool references_duration() const override {
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
using counter_type = cql_duration::common_counter_type;
|
||||
|
||||
static std::tuple<counter_type, counter_type, counter_type> deserialize_counters(bytes_view v) {
|
||||
auto deserialize_and_advance = [&v](auto&& i) {
|
||||
const auto d = signed_vint::deserialize(v);
|
||||
v.remove_prefix(d.size);
|
||||
|
||||
if (v.empty() && (i != 2)) {
|
||||
throw marshal_exception();
|
||||
}
|
||||
|
||||
return static_cast<counter_type>(d.value);
|
||||
};
|
||||
|
||||
return generate_tuple_from_index(std::make_index_sequence<3>(), std::move(deserialize_and_advance));
|
||||
}
|
||||
};
|
||||
|
||||
struct empty_type_impl : abstract_type {
|
||||
empty_type_impl() : abstract_type(empty_type_name) {}
|
||||
virtual void serialize(const void* value, bytes::iterator& out) const override {
|
||||
@@ -2031,6 +2184,10 @@ map_type_impl::update_user_type(const shared_ptr<const user_type_impl> updated)
|
||||
get_instance(k ? *k : _keys, v ? *v : _values, _is_multi_cell)));
|
||||
}
|
||||
|
||||
bool map_type_impl::references_duration() const {
|
||||
return _keys->references_duration() || _values->references_duration();
|
||||
}
|
||||
|
||||
auto collection_type_impl::deserialize_mutation_form(collection_mutation_view cm) -> mutation_view {
|
||||
auto&& in = cm.data;
|
||||
mutation_view ret;
|
||||
@@ -2500,6 +2657,10 @@ set_type_impl::update_user_type(const shared_ptr<const user_type_impl> updated)
|
||||
return std::experimental::nullopt;
|
||||
}
|
||||
|
||||
bool set_type_impl::references_duration() const {
|
||||
return _elements->references_duration();
|
||||
}
|
||||
|
||||
list_type
|
||||
list_type_impl::get_instance(data_type elements, bool is_multi_cell) {
|
||||
return intern::get_instance(elements, is_multi_cell);
|
||||
@@ -2682,6 +2843,10 @@ list_type_impl::update_user_type(const shared_ptr<const user_type_impl> updated)
|
||||
return std::experimental::nullopt;
|
||||
}
|
||||
|
||||
bool list_type_impl::references_duration() const {
|
||||
return _elements->references_duration();
|
||||
}
|
||||
|
||||
tuple_type_impl::tuple_type_impl(sstring name, std::vector<data_type> types)
|
||||
: concrete_type(std::move(name)), _types(std::move(types)) {
|
||||
for (auto& t : _types) {
|
||||
@@ -2948,6 +3113,10 @@ tuple_type_impl::update_user_type(const shared_ptr<const user_type_impl> updated
|
||||
return std::experimental::nullopt;
|
||||
}
|
||||
|
||||
bool tuple_type_impl::references_duration() const {
|
||||
return boost::algorithm::any_of(_types, std::mem_fn(&abstract_type::references_duration));
|
||||
}
|
||||
|
||||
sstring
|
||||
user_type_impl::get_name_as_string() const {
|
||||
auto real_utf8_type = static_cast<const utf8_type_impl*>(utf8_type.get());
|
||||
@@ -3061,6 +3230,7 @@ thread_local const shared_ptr<const abstract_type> double_type(make_shared<doubl
|
||||
thread_local const shared_ptr<const abstract_type> varint_type(make_shared<varint_type_impl>());
|
||||
thread_local const shared_ptr<const abstract_type> decimal_type(make_shared<decimal_type_impl>());
|
||||
thread_local const shared_ptr<const abstract_type> counter_type(make_shared<counter_type_impl>());
|
||||
thread_local const shared_ptr<const abstract_type> duration_type(make_shared<duration_type_impl>());
|
||||
thread_local const data_type empty_type(make_shared<empty_type_impl>());
|
||||
|
||||
data_type abstract_type::parse_type(const sstring& name)
|
||||
@@ -3086,6 +3256,7 @@ data_type abstract_type::parse_type(const sstring& name)
|
||||
{ varint_type_name, varint_type },
|
||||
{ decimal_type_name, decimal_type },
|
||||
{ counter_type_name, counter_type },
|
||||
{ duration_type_name, duration_type },
|
||||
{ empty_type_name, empty_type },
|
||||
};
|
||||
auto it = types.find(name);
|
||||
@@ -3160,6 +3331,9 @@ data_value::data_value(boost::multiprecision::cpp_int v) : data_value(make_new(v
|
||||
data_value::data_value(big_decimal v) : data_value(make_new(decimal_type, v)) {
|
||||
}
|
||||
|
||||
data_value::data_value(cql_duration d) : data_value(make_new(duration_type, d)) {
|
||||
}
|
||||
|
||||
data_value
|
||||
make_list_value(data_type type, list_type_impl::native_type value) {
|
||||
return data_value::make_new(std::move(type), std::move(value));
|
||||
|
||||
10
types.hh
10
types.hh
@@ -37,6 +37,7 @@
|
||||
#include "cql_serialization_format.hh"
|
||||
#include "tombstone.hh"
|
||||
#include "to_string.hh"
|
||||
#include "duration.hh"
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
#include <boost/range/algorithm/for_each.hpp>
|
||||
#include <boost/range/numeric.hpp>
|
||||
@@ -363,6 +364,7 @@ public:
|
||||
data_value(db_clock::time_point);
|
||||
data_value(boost::multiprecision::cpp_int);
|
||||
data_value(big_decimal);
|
||||
data_value(cql_duration);
|
||||
explicit data_value(std::experimental::optional<bytes>);
|
||||
template <typename NativeType>
|
||||
data_value(std::experimental::optional<NativeType>);
|
||||
@@ -476,6 +478,9 @@ public:
|
||||
}
|
||||
virtual bool references_user_type(const sstring& keyspace, const bytes& name) const = 0;
|
||||
virtual std::experimental::optional<data_type> update_user_type(const shared_ptr<const user_type_impl> updated) const = 0;
|
||||
virtual bool references_duration() const {
|
||||
return false;
|
||||
}
|
||||
protected:
|
||||
virtual bool equals(const abstract_type& other) const {
|
||||
return this == &other;
|
||||
@@ -1030,6 +1035,7 @@ public:
|
||||
virtual bytes to_value(mutation_view mut, cql_serialization_format sf) const override;
|
||||
virtual bool references_user_type(const sstring& keyspace, const bytes& name) const override;
|
||||
virtual std::experimental::optional<data_type> update_user_type(const shared_ptr<const user_type_impl> updated) const override;
|
||||
virtual bool references_duration() const override;
|
||||
};
|
||||
|
||||
using map_type = shared_ptr<const map_type_impl>;
|
||||
@@ -1069,6 +1075,7 @@ public:
|
||||
const std::vector<bytes_view>& v, cql_serialization_format sf) const;
|
||||
virtual bool references_user_type(const sstring& keyspace, const bytes& name) const override;
|
||||
virtual std::experimental::optional<data_type> update_user_type(const shared_ptr<const user_type_impl> updated) const override;
|
||||
virtual bool references_duration() const override;
|
||||
};
|
||||
|
||||
using set_type = shared_ptr<const set_type_impl>;
|
||||
@@ -1106,6 +1113,7 @@ public:
|
||||
virtual bytes to_value(mutation_view mut, cql_serialization_format sf) const override;
|
||||
virtual bool references_user_type(const sstring& keyspace, const bytes& name) const override;
|
||||
virtual std::experimental::optional<data_type> update_user_type(const shared_ptr<const user_type_impl> updated) const override;
|
||||
virtual bool references_duration() const override;
|
||||
};
|
||||
|
||||
using list_type = shared_ptr<const list_type_impl>;
|
||||
@@ -1173,6 +1181,7 @@ extern thread_local const shared_ptr<const abstract_type> double_type;
|
||||
extern thread_local const shared_ptr<const abstract_type> varint_type;
|
||||
extern thread_local const shared_ptr<const abstract_type> decimal_type;
|
||||
extern thread_local const shared_ptr<const abstract_type> counter_type;
|
||||
extern thread_local const shared_ptr<const abstract_type> duration_type;
|
||||
extern thread_local const data_type empty_type;
|
||||
|
||||
template <>
|
||||
@@ -1565,6 +1574,7 @@ public:
|
||||
virtual bool is_tuple() const override { return true; }
|
||||
virtual bool references_user_type(const sstring& keyspace, const bytes& name) const override;
|
||||
virtual std::experimental::optional<data_type> update_user_type(const shared_ptr<const user_type_impl> updated) const override;
|
||||
virtual bool references_duration() const override;
|
||||
private:
|
||||
bool check_compatibility(const abstract_type& previous, bool (abstract_type::*predicate)(const abstract_type&) const) const;
|
||||
static sstring make_name(const std::vector<data_type>& types);
|
||||
|
||||
156
vint-serialization.cc
Normal file
156
vint-serialization.cc
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2017 ScyllaDB
|
||||
*
|
||||
* Modified by 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 "vint-serialization.hh"
|
||||
|
||||
#include "stdx.hh"
|
||||
|
||||
#include <seastar/core/bitops.hh>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
static_assert(-1 == ~0, "Not a twos-complement architecture");
|
||||
|
||||
// Accounts for the case that all bits are zero.
|
||||
static vint_size_type count_leading_zero_bits(uint64_t n) noexcept {
|
||||
if (n == 0) {
|
||||
return vint_size_type(std::numeric_limits<uint64_t>::digits);
|
||||
}
|
||||
|
||||
return vint_size_type(count_leading_zeros(n));
|
||||
}
|
||||
|
||||
static constexpr uint64_t encode_zigzag(int64_t n) noexcept {
|
||||
// The right shift has to be arithmetic and not logical.
|
||||
return (static_cast<uint64_t>(n) << 1) ^ static_cast<uint64_t>(n >> 63);
|
||||
}
|
||||
|
||||
static constexpr int64_t decode_zigzag(uint64_t n) noexcept {
|
||||
return static_cast<int64_t>((n >> 1) ^ -(n & 1));
|
||||
}
|
||||
|
||||
// Mask for extracting from the first byte the part that is not used for indicating the total number of bytes.
|
||||
static uint64_t first_byte_value_mask(vint_size_type extra_bytes_size) {
|
||||
// Include the sentinel zero bit in the mask.
|
||||
return uint64_t(0xff) >> extra_bytes_size;
|
||||
}
|
||||
|
||||
vint_size_type signed_vint::serialize(int64_t value, bytes::iterator out) {
|
||||
return unsigned_vint::serialize(encode_zigzag(value), out);
|
||||
}
|
||||
|
||||
vint_size_type signed_vint::serialized_size(int64_t value) noexcept {
|
||||
return unsigned_vint::serialized_size(encode_zigzag(value));
|
||||
}
|
||||
|
||||
signed_vint::deserialized_type signed_vint::deserialize(bytes_view v) {
|
||||
const auto un = unsigned_vint::deserialize(v);
|
||||
return deserialized_type{decode_zigzag(un.value), un.size};
|
||||
}
|
||||
|
||||
// The number of additional bytes that we need to read.
|
||||
static vint_size_type count_extra_bytes(int8_t first_byte) {
|
||||
// Sign extension.
|
||||
const int64_t v(first_byte);
|
||||
|
||||
return count_leading_zero_bits(static_cast<uint64_t>(~v)) - vint_size_type(64 - 8);
|
||||
}
|
||||
|
||||
static void encode(uint64_t value, vint_size_type size, bytes::iterator out) {
|
||||
std::array<int8_t, 9> buffer({});
|
||||
|
||||
// `size` is always in the range [1, 9].
|
||||
const auto extra_bytes_size = size - 1;
|
||||
|
||||
for (vint_size_type i = 0; i <= extra_bytes_size; ++i) {
|
||||
buffer[extra_bytes_size - i] = static_cast<int8_t>(value & 0xff);
|
||||
value >>= 8;
|
||||
}
|
||||
|
||||
buffer[0] |= ~first_byte_value_mask(extra_bytes_size);
|
||||
std::copy_n(buffer.cbegin(), size, out);
|
||||
}
|
||||
|
||||
vint_size_type unsigned_vint::serialize(uint64_t value, bytes::iterator out) {
|
||||
const auto size = serialized_size(value);
|
||||
|
||||
if (size == 1) {
|
||||
*out = static_cast<int8_t>(value & 0xff);
|
||||
return 1;
|
||||
}
|
||||
|
||||
encode(value, size, out);
|
||||
return size;
|
||||
}
|
||||
|
||||
vint_size_type unsigned_vint::serialized_size(uint64_t value) noexcept {
|
||||
// No need for the overhead of checking that all bits are zero.
|
||||
//
|
||||
// A signed quantity, to allow the case of `magnitude == 0` to result in a value of 9 below.
|
||||
const auto magnitude = static_cast<int64_t>(count_leading_zeros(value | uint64_t(1)));
|
||||
|
||||
return vint_size_type(9) - vint_size_type((magnitude - 1) / 7);
|
||||
}
|
||||
|
||||
unsigned_vint::deserialized_type unsigned_vint::deserialize(bytes_view v) {
|
||||
const int8_t first_byte = v[0];
|
||||
|
||||
// No additional bytes, since the most significant bit is not set.
|
||||
if (first_byte >= 0) {
|
||||
return deserialized_type{uint64_t(first_byte), 1};
|
||||
}
|
||||
|
||||
const auto extra_bytes_size = count_extra_bytes(first_byte);
|
||||
const auto total_size = extra_bytes_size + 1;
|
||||
|
||||
// Extract the bits not used for counting bytes.
|
||||
auto result = uint64_t(first_byte) & first_byte_value_mask(extra_bytes_size);
|
||||
|
||||
for (vint_size_type index = 0; index < extra_bytes_size; ++index) {
|
||||
result <<= 8;
|
||||
result |= (uint64_t(v[index + 1]) & uint64_t(0xff));
|
||||
}
|
||||
|
||||
return deserialized_type{result, total_size};
|
||||
}
|
||||
60
vint-serialization.hh
Normal file
60
vint-serialization.hh
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2017 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/>.
|
||||
*/
|
||||
|
||||
//
|
||||
// For reference, see https://developers.google.com/protocol-buffers/docs/encoding#varints
|
||||
//
|
||||
|
||||
#include "bytes.hh"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
using vint_size_type = bytes::size_type;
|
||||
|
||||
struct unsigned_vint final {
|
||||
using value_type = uint64_t;
|
||||
|
||||
struct deserialized_type final {
|
||||
value_type value;
|
||||
vint_size_type size;
|
||||
};
|
||||
|
||||
static vint_size_type serialized_size(value_type) noexcept;
|
||||
|
||||
static vint_size_type serialize(value_type, bytes::iterator out);
|
||||
|
||||
static deserialized_type deserialize(bytes_view v);
|
||||
};
|
||||
|
||||
struct signed_vint final {
|
||||
using value_type = int64_t;
|
||||
|
||||
struct deserialized_type final {
|
||||
value_type value;
|
||||
vint_size_type size;
|
||||
};
|
||||
|
||||
static vint_size_type serialized_size(value_type) noexcept;
|
||||
|
||||
static vint_size_type serialize(value_type, bytes::iterator out);
|
||||
|
||||
static deserialized_type deserialize(bytes_view v);
|
||||
};
|
||||
Reference in New Issue
Block a user