Our documentation states that writing an item with "USING TTL 0" means it
should never expire. This should be true even if the table has a default
TTL. But Scylla mistakenly handled "USING TTL 0" exactly like having no
USING TTL at all (i.e., it took the default TTL, instead of unlimited).
We had two xfailing tests demonstrating that Scylla's behavior in this
is different from Cassandra. Scylla's behavior in this case was also
undocumented.
By the way, Cassandra used to have the same bug (CASSANDRA-11207) but
it was fixed already in 2016 (Cassandra 3.6).
So in this patch we fix Scylla's "USING TTL 0" behavior to match the
documentation and Cassandra's behavior since 2016. One xfailing test
starts to pass and the second test passes this bug and fails on a
different one. This patch also adds a third test for "USING TTL ?"
with UNSET_VALUE - it behaves, on both Scylla and Cassandra, like a
missing "USING TTL".
The origin of this bug was that after parsing the statement, we saved
the USING TTL in an integer, and used 0 for the case of no USING TTL
given. This meant that we couldn't tell if we have USING TTL 0 or
no USING TTL at all. This patch uses an std::optional so we can tell
the case of a missing USING TTL from the case of USING TTL 0.
Fixes #6447
Signed-off-by: Nadav Har'El <nyh@scylladb.com>
Closes #13079
(cherry picked from commit a4a318f394)
Signed-off-by: Nadav Har'El <nyh@scylladb.com>
154 lines
5.4 KiB
C++
154 lines
5.4 KiB
C++
/*
|
|
* Copyright (C) 2015-present ScyllaDB
|
|
*
|
|
* Modified by ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: (AGPL-3.0-or-later and Apache-2.0)
|
|
*/
|
|
|
|
#include "cql3/attributes.hh"
|
|
#include "cql3/column_identifier.hh"
|
|
#include <optional>
|
|
|
|
namespace cql3 {
|
|
|
|
std::unique_ptr<attributes> attributes::none() {
|
|
return std::unique_ptr<attributes>{new attributes{{}, {}, {}}};
|
|
}
|
|
|
|
attributes::attributes(std::optional<cql3::expr::expression>&& timestamp,
|
|
std::optional<cql3::expr::expression>&& time_to_live,
|
|
std::optional<cql3::expr::expression>&& timeout)
|
|
: _timestamp{std::move(timestamp)}
|
|
, _time_to_live{std::move(time_to_live)}
|
|
, _timeout{std::move(timeout)}
|
|
{ }
|
|
|
|
bool attributes::is_timestamp_set() const {
|
|
return bool(_timestamp);
|
|
}
|
|
|
|
bool attributes::is_time_to_live_set() const {
|
|
return bool(_time_to_live);
|
|
}
|
|
|
|
bool attributes::is_timeout_set() const {
|
|
return bool(_timeout);
|
|
}
|
|
|
|
int64_t attributes::get_timestamp(int64_t now, const query_options& options) {
|
|
if (!_timestamp.has_value()) {
|
|
return now;
|
|
}
|
|
|
|
cql3::raw_value tval = expr::evaluate(*_timestamp, options);
|
|
if (tval.is_null()) {
|
|
throw exceptions::invalid_request_exception("Invalid null value of timestamp");
|
|
}
|
|
if (tval.is_unset_value()) {
|
|
return now;
|
|
}
|
|
try {
|
|
return tval.view().validate_and_deserialize<int64_t>(*long_type, cql_serialization_format::internal());
|
|
} catch (marshal_exception& e) {
|
|
throw exceptions::invalid_request_exception("Invalid timestamp value");
|
|
}
|
|
}
|
|
|
|
std::optional<int32_t> attributes::get_time_to_live(const query_options& options) {
|
|
if (!_time_to_live.has_value())
|
|
return std::nullopt;
|
|
|
|
cql3::raw_value tval = expr::evaluate(*_time_to_live, options);
|
|
if (tval.is_null()) {
|
|
throw exceptions::invalid_request_exception("Invalid null value of TTL");
|
|
}
|
|
if (tval.is_unset_value()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
int32_t ttl;
|
|
try {
|
|
ttl = tval.view().validate_and_deserialize<int32_t>(*int32_type, cql_serialization_format::internal());
|
|
}
|
|
catch (marshal_exception& e) {
|
|
throw exceptions::invalid_request_exception("Invalid TTL value");
|
|
}
|
|
|
|
if (ttl < 0) {
|
|
throw exceptions::invalid_request_exception("A TTL must be greater or equal to 0");
|
|
}
|
|
|
|
if (ttl > max_ttl.count()) {
|
|
throw exceptions::invalid_request_exception("ttl is too large. requested (" + std::to_string(ttl) +
|
|
") maximum (" + std::to_string(max_ttl.count()) + ")");
|
|
}
|
|
|
|
return ttl;
|
|
}
|
|
|
|
|
|
db::timeout_clock::duration attributes::get_timeout(const query_options& options) const {
|
|
cql3::raw_value timeout = expr::evaluate(*_timeout, options);
|
|
if (timeout.is_null() || timeout.is_unset_value()) {
|
|
throw exceptions::invalid_request_exception("Timeout value cannot be unset/null");
|
|
}
|
|
cql_duration duration = timeout.view().deserialize<cql_duration>(*duration_type);
|
|
if (duration.months || duration.days) {
|
|
throw exceptions::invalid_request_exception("Timeout values cannot be expressed in days/months");
|
|
}
|
|
if (duration.nanoseconds % 1'000'000 != 0) {
|
|
throw exceptions::invalid_request_exception("Timeout values cannot have granularity finer than milliseconds");
|
|
}
|
|
if (duration.nanoseconds < 0) {
|
|
throw exceptions::invalid_request_exception("Timeout values must be non-negative");
|
|
}
|
|
return std::chrono::duration_cast<db::timeout_clock::duration>(std::chrono::nanoseconds(duration.nanoseconds));
|
|
}
|
|
|
|
void attributes::fill_prepare_context(prepare_context& ctx) {
|
|
if (_timestamp.has_value()) {
|
|
expr::fill_prepare_context(*_timestamp, ctx);
|
|
}
|
|
if (_time_to_live.has_value()) {
|
|
expr::fill_prepare_context(*_time_to_live, ctx);
|
|
}
|
|
if (_timeout.has_value()) {
|
|
expr::fill_prepare_context(*_timeout, ctx);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<attributes> attributes::raw::prepare(data_dictionary::database db, const sstring& ks_name, const sstring& cf_name) const {
|
|
std::optional<expr::expression> ts, ttl, to;
|
|
|
|
if (timestamp.has_value()) {
|
|
ts = prepare_expression(*timestamp, db, ks_name, nullptr, timestamp_receiver(ks_name, cf_name));
|
|
}
|
|
|
|
if (time_to_live.has_value()) {
|
|
ttl = prepare_expression(*time_to_live, db, ks_name, nullptr, time_to_live_receiver(ks_name, cf_name));
|
|
}
|
|
|
|
if (timeout.has_value()) {
|
|
to = prepare_expression(*timeout, db, ks_name, nullptr, timeout_receiver(ks_name, cf_name));
|
|
}
|
|
|
|
return std::unique_ptr<attributes>{new attributes{std::move(ts), std::move(ttl), std::move(to)}};
|
|
}
|
|
|
|
lw_shared_ptr<column_specification> attributes::raw::timestamp_receiver(const sstring& ks_name, const sstring& cf_name) const {
|
|
return make_lw_shared<column_specification>(ks_name, cf_name, ::make_shared<column_identifier>("[timestamp]", true), data_type_for<int64_t>());
|
|
}
|
|
|
|
lw_shared_ptr<column_specification> attributes::raw::time_to_live_receiver(const sstring& ks_name, const sstring& cf_name) const {
|
|
return make_lw_shared<column_specification>(ks_name, cf_name, ::make_shared<column_identifier>("[ttl]", true), data_type_for<int32_t>());
|
|
}
|
|
|
|
lw_shared_ptr<column_specification> attributes::raw::timeout_receiver(const sstring& ks_name, const sstring& cf_name) const {
|
|
return make_lw_shared<column_specification>(ks_name, cf_name, ::make_shared<column_identifier>("[timeout]", true), duration_type);
|
|
}
|
|
|
|
}
|