1130 lines
38 KiB
C++
1130 lines
38 KiB
C++
/*
|
|
* Copyright (C) 2014 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 "utils/UUID_gen.hh"
|
|
#include "cql3/column_identifier.hh"
|
|
#include "schema.hh"
|
|
#include "schema_builder.hh"
|
|
#include "md5_hasher.hh"
|
|
#include <boost/algorithm/cxx11/any_of.hpp>
|
|
#include <boost/range/adaptor/transformed.hpp>
|
|
#include "db/marshal/type_parser.hh"
|
|
#include "version.hh"
|
|
#include "schema_registry.hh"
|
|
#include <boost/range/adaptor/map.hpp>
|
|
#include <boost/range/algorithm.hpp>
|
|
#include <boost/algorithm/cxx11/any_of.hpp>
|
|
#include "view_info.hh"
|
|
|
|
constexpr int32_t schema::NAME_LENGTH;
|
|
|
|
sstring to_sstring(column_kind k) {
|
|
switch (k) {
|
|
case column_kind::partition_key: return "PARTITION_KEY";
|
|
case column_kind::clustering_key: return "CLUSTERING_COLUMN";
|
|
case column_kind::static_column: return "STATIC";
|
|
case column_kind::regular_column: return "REGULAR";
|
|
}
|
|
throw std::invalid_argument("unknown column kind");
|
|
}
|
|
|
|
sstring to_sstring(index_type t) {
|
|
switch (t) {
|
|
case index_type::keys: return "KEYS";
|
|
case index_type::custom: return "CUSTOM";
|
|
case index_type::composites: return "COMPOSITES";
|
|
case index_type::none: return "null";
|
|
}
|
|
throw std::invalid_argument("unknown index type");
|
|
}
|
|
|
|
bool is_compatible(column_kind k1, column_kind k2) {
|
|
return k1 == k2;
|
|
}
|
|
|
|
column_mapping_entry::column_mapping_entry(bytes name, sstring type_name)
|
|
: _name(std::move(name))
|
|
, _type(db::marshal::type_parser::parse(type_name))
|
|
{
|
|
}
|
|
|
|
column_mapping_entry::column_mapping_entry(const column_mapping_entry& o)
|
|
: _name(o._name)
|
|
, _type(db::marshal::type_parser::parse(o._type->name()))
|
|
{
|
|
}
|
|
|
|
column_mapping_entry& column_mapping_entry::operator=(const column_mapping_entry& o) {
|
|
if (this != &o) {
|
|
auto tmp = o;
|
|
this->~column_mapping_entry();
|
|
new (this) column_mapping_entry(std::move(tmp));
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
template<typename Sequence>
|
|
std::vector<data_type>
|
|
get_column_types(const Sequence& column_definitions) {
|
|
std::vector<data_type> result;
|
|
for (auto&& col : column_definitions) {
|
|
result.push_back(col.type);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& out, const column_mapping& cm) {
|
|
column_id n_static = cm.n_static();
|
|
column_id n_regular = cm.columns().size() - n_static;
|
|
|
|
auto pr_entry = [] (column_id i, const column_mapping_entry& e) {
|
|
// Without schema we don't know if name is UTF8. If we had schema we could use
|
|
// s->regular_column_name_type()->to_string(e.name()).
|
|
return sprint("{id=%s, name=0x%s, type=%s}", i, e.name(), e.type()->name());
|
|
};
|
|
|
|
return out << "{static=[" << ::join(", ", boost::irange<column_id>(0, n_static) |
|
|
boost::adaptors::transformed([&] (column_id i) { return pr_entry(i, cm.static_column_at(i)); }))
|
|
<< "], regular=[" << ::join(", ", boost::irange<column_id>(0, n_regular) |
|
|
boost::adaptors::transformed([&] (column_id i) { return pr_entry(i, cm.regular_column_at(i)); }))
|
|
<< "]}";
|
|
}
|
|
|
|
::shared_ptr<cql3::column_specification>
|
|
schema::make_column_specification(const column_definition& def) {
|
|
auto id = ::make_shared<cql3::column_identifier>(def.name(), column_name_type(def));
|
|
return ::make_shared<cql3::column_specification>(_raw._ks_name, _raw._cf_name, std::move(id), def.type);
|
|
}
|
|
|
|
void schema::rebuild() {
|
|
_partition_key_type = make_lw_shared<compound_type<>>(get_column_types(partition_key_columns()));
|
|
_clustering_key_type = make_lw_shared<compound_prefix>(get_column_types(clustering_key_columns()));
|
|
|
|
_columns_by_name.clear();
|
|
_regular_columns_by_name.clear();
|
|
|
|
for (const column_definition& def : all_columns_in_select_order()) {
|
|
_columns_by_name[def.name()] = &def;
|
|
}
|
|
|
|
static_assert(row_column_ids_are_ordered_by_name::value, "row columns don't need to be ordered by name");
|
|
if (!std::is_sorted(regular_columns().begin(), regular_columns().end(), column_definition::name_comparator())) {
|
|
throw std::runtime_error("Regular columns should be sorted by name");
|
|
}
|
|
if (!std::is_sorted(static_columns().begin(), static_columns().end(), column_definition::name_comparator())) {
|
|
throw std::runtime_error("Static columns should be sorted by name");
|
|
}
|
|
|
|
for (const column_definition& def : regular_columns()) {
|
|
_regular_columns_by_name[def.name()] = &def;
|
|
}
|
|
|
|
{
|
|
std::vector<column_mapping_entry> cm_columns;
|
|
for (const column_definition& def : boost::range::join(static_columns(), regular_columns())) {
|
|
cm_columns.emplace_back(column_mapping_entry{def.name(), def.type});
|
|
}
|
|
_column_mapping = column_mapping(std::move(cm_columns), static_columns_count());
|
|
}
|
|
|
|
thrift()._compound = is_compound();
|
|
thrift()._is_dynamic = clustering_key_size() > 0;
|
|
|
|
if (default_validator()->is_counter()) {
|
|
for (auto&& cdef : boost::range::join(static_columns(), regular_columns())) {
|
|
if (!cdef.type->is_counter()) {
|
|
throw exceptions::configuration_exception(sprint("Cannot add a non counter column (%s) in a counter column family", cdef.name_as_text()));
|
|
}
|
|
}
|
|
} else {
|
|
for (auto&& cdef : all_columns()) {
|
|
if (cdef.second->type->is_counter()) {
|
|
throw exceptions::configuration_exception(sprint("Cannot add a counter column (%s) in a non counter column family", cdef.second->name_as_text()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const column_mapping& schema::get_column_mapping() const {
|
|
return _column_mapping;
|
|
}
|
|
|
|
schema::raw_schema::raw_schema(utils::UUID id)
|
|
: _id(id)
|
|
{ }
|
|
|
|
schema::schema(const raw_schema& raw, stdx::optional<raw_view_info> raw_view_info)
|
|
: _raw(raw)
|
|
, _offsets([this] {
|
|
if (_raw._columns.size() > std::numeric_limits<column_count_type>::max()) {
|
|
throw std::runtime_error(sprint("Column count limit (%d) overflowed: %d",
|
|
std::numeric_limits<column_count_type>::max(), _raw._columns.size()));
|
|
}
|
|
|
|
auto& cols = _raw._columns;
|
|
std::array<column_count_type, 4> count = { 0, 0, 0, 0 };
|
|
auto i = cols.begin();
|
|
auto e = cols.end();
|
|
for (auto k : { column_kind::partition_key, column_kind::clustering_key, column_kind::static_column, column_kind::regular_column }) {
|
|
auto j = std::stable_partition(i, e, [k](const auto& c) {
|
|
return c.kind == k;
|
|
});
|
|
count[column_count_type(k)] = std::distance(i, j);
|
|
i = j;
|
|
}
|
|
return std::array<column_count_type, 3> {
|
|
count[0],
|
|
count[0] + count[1],
|
|
count[0] + count[1] + count[2],
|
|
};
|
|
}())
|
|
, _regular_columns_by_name(serialized_compare(_raw._regular_column_name_type))
|
|
, _is_counter(_raw._default_validator->is_counter())
|
|
{
|
|
struct name_compare {
|
|
data_type type;
|
|
name_compare(data_type type) : type(type) {}
|
|
bool operator()(const column_definition& cd1, const column_definition& cd2) const {
|
|
return type->less(cd1.name(), cd2.name());
|
|
}
|
|
};
|
|
|
|
|
|
std::sort(
|
|
_raw._columns.begin() + column_offset(column_kind::static_column),
|
|
_raw._columns.begin()
|
|
+ column_offset(column_kind::regular_column),
|
|
name_compare(utf8_type));
|
|
std::sort(
|
|
_raw._columns.begin()
|
|
+ column_offset(column_kind::regular_column),
|
|
_raw._columns.end(), name_compare(regular_column_name_type()));
|
|
|
|
std::sort(_raw._columns.begin(),
|
|
_raw._columns.begin() + column_offset(column_kind::clustering_key),
|
|
[] (auto x, auto y) { return x.id < y.id; });
|
|
std::sort(_raw._columns.begin() + column_offset(column_kind::clustering_key),
|
|
_raw._columns.begin() + column_offset(column_kind::static_column),
|
|
[] (auto x, auto y) { return x.id < y.id; });
|
|
|
|
column_id id = 0;
|
|
for (auto& def : _raw._columns) {
|
|
def.column_specification = make_column_specification(def);
|
|
assert(!def.id || def.id == id - column_offset(def.kind));
|
|
def.id = id - column_offset(def.kind);
|
|
|
|
auto dropped_at_it = _raw._dropped_columns.find(def.name_as_text());
|
|
if (dropped_at_it != _raw._dropped_columns.end()) {
|
|
def._dropped_at = std::max(def._dropped_at, dropped_at_it->second);
|
|
}
|
|
|
|
def._thrift_bits = column_definition::thrift_bits();
|
|
|
|
{
|
|
// is_on_all_components
|
|
// TODO : In origin, this predicate is "componentIndex == null", which is true in
|
|
// a number of cases, some of which I've most likely missed...
|
|
switch (def.kind) {
|
|
case column_kind::partition_key:
|
|
// In origin, ci == null is true for a PK column where CFMetaData "keyValidator" is non-composite.
|
|
// Which is true of #pk == 1
|
|
def._thrift_bits.is_on_all_components = partition_key_size() == 1;
|
|
break;
|
|
case column_kind::regular_column:
|
|
if (_raw._is_dense) {
|
|
// regular values in dense tables are alone, so they have no index
|
|
def._thrift_bits.is_on_all_components = true;
|
|
break;
|
|
}
|
|
default:
|
|
// Or any other column where "comparator" is not compound
|
|
def._thrift_bits.is_on_all_components = !thrift().has_compound_comparator();
|
|
break;
|
|
}
|
|
}
|
|
|
|
++id;
|
|
}
|
|
|
|
rebuild();
|
|
if (raw_view_info) {
|
|
_view_info = std::make_unique<::view_info>(*this, *raw_view_info);
|
|
}
|
|
}
|
|
|
|
schema::schema(std::experimental::optional<utils::UUID> id,
|
|
sstring ks_name,
|
|
sstring cf_name,
|
|
std::vector<column> partition_key,
|
|
std::vector<column> clustering_key,
|
|
std::vector<column> regular_columns,
|
|
std::vector<column> static_columns,
|
|
data_type regular_column_name_type,
|
|
sstring comment)
|
|
: schema([&] {
|
|
raw_schema raw(id ? *id : utils::UUID_gen::get_time_UUID());
|
|
|
|
raw._comment = std::move(comment);
|
|
raw._ks_name = std::move(ks_name);
|
|
raw._cf_name = std::move(cf_name);
|
|
raw._regular_column_name_type = regular_column_name_type;
|
|
|
|
auto build_columns = [&raw](std::vector<column>& columns, column_kind kind) {
|
|
for (auto& sc : columns) {
|
|
if (sc.type->is_multi_cell()) {
|
|
raw._collections.emplace(sc.name, sc.type);
|
|
}
|
|
raw._columns.emplace_back(std::move(sc.name), std::move(sc.type), kind);
|
|
}
|
|
};
|
|
|
|
build_columns(partition_key, column_kind::partition_key);
|
|
build_columns(clustering_key, column_kind::clustering_key);
|
|
build_columns(static_columns, column_kind::static_column);
|
|
build_columns(regular_columns, column_kind::regular_column);
|
|
|
|
return raw;
|
|
}(), stdx::nullopt)
|
|
{}
|
|
|
|
schema::schema(const schema& o)
|
|
: _raw(o._raw)
|
|
, _offsets(o._offsets)
|
|
, _regular_columns_by_name(serialized_compare(_raw._regular_column_name_type))
|
|
, _is_counter(o._is_counter)
|
|
{
|
|
rebuild();
|
|
if (o.is_view()) {
|
|
_view_info = std::make_unique<::view_info>(*this, o.view_info()->raw());
|
|
}
|
|
}
|
|
|
|
schema::~schema() {
|
|
if (_registry_entry) {
|
|
_registry_entry->detach_schema();
|
|
}
|
|
}
|
|
|
|
schema_registry_entry*
|
|
schema::registry_entry() const noexcept {
|
|
return _registry_entry;
|
|
}
|
|
|
|
sstring schema::thrift_key_validator() const {
|
|
if (partition_key_size() == 1) {
|
|
return partition_key_columns().begin()->type->name();
|
|
} else {
|
|
sstring type_params = ::join(", ", partition_key_columns()
|
|
| boost::adaptors::transformed(std::mem_fn(&column_definition::type))
|
|
| boost::adaptors::transformed(std::mem_fn(&abstract_type::name)));
|
|
return "org.apache.cassandra.db.marshal.CompositeType(" + type_params + ")";
|
|
}
|
|
}
|
|
|
|
bool
|
|
schema::has_multi_cell_collections() const {
|
|
return boost::algorithm::any_of(all_columns_in_select_order(), [] (const column_definition& cdef) {
|
|
return cdef.type->is_collection() && cdef.type->is_multi_cell();
|
|
});
|
|
}
|
|
|
|
bool operator==(const schema& x, const schema& y)
|
|
{
|
|
return x._raw._id == y._raw._id
|
|
&& x._raw._ks_name == y._raw._ks_name
|
|
&& x._raw._cf_name == y._raw._cf_name
|
|
&& x._raw._columns == y._raw._columns
|
|
&& x._raw._comment == y._raw._comment
|
|
&& x._raw._default_time_to_live == y._raw._default_time_to_live
|
|
&& x._raw._regular_column_name_type->equals(y._raw._regular_column_name_type)
|
|
&& x._raw._bloom_filter_fp_chance == y._raw._bloom_filter_fp_chance
|
|
&& x._raw._compressor_params == y._raw._compressor_params
|
|
&& x._raw._is_dense == y._raw._is_dense
|
|
&& x._raw._is_compound == y._raw._is_compound
|
|
&& x._raw._type == y._raw._type
|
|
&& x._raw._gc_grace_seconds == y._raw._gc_grace_seconds
|
|
&& x._raw._dc_local_read_repair_chance == y._raw._dc_local_read_repair_chance
|
|
&& x._raw._read_repair_chance == y._raw._read_repair_chance
|
|
&& x._raw._min_compaction_threshold == y._raw._min_compaction_threshold
|
|
&& x._raw._max_compaction_threshold == y._raw._max_compaction_threshold
|
|
&& x._raw._min_index_interval == y._raw._min_index_interval
|
|
&& x._raw._max_index_interval == y._raw._max_index_interval
|
|
&& x._raw._memtable_flush_period == y._raw._memtable_flush_period
|
|
&& x._raw._speculative_retry == y._raw._speculative_retry
|
|
&& x._raw._compaction_strategy == y._raw._compaction_strategy
|
|
&& x._raw._compaction_strategy_options == y._raw._compaction_strategy_options
|
|
&& x._raw._caching_options == y._raw._caching_options
|
|
&& x._raw._dropped_columns == y._raw._dropped_columns
|
|
&& x._raw._collections == y._raw._collections
|
|
&& indirect_equal_to<std::unique_ptr<::view_info>>()(x._view_info, y._view_info)
|
|
&& x._raw._indices_by_name == y._raw._indices_by_name;
|
|
#if 0
|
|
&& Objects.equal(triggers, other.triggers)
|
|
#endif
|
|
}
|
|
|
|
index_info::index_info(::index_type idx_type,
|
|
std::experimental::optional<sstring> idx_name,
|
|
std::experimental::optional<index_options_map> idx_options)
|
|
: index_type(idx_type), index_name(idx_name), index_options(idx_options)
|
|
{}
|
|
|
|
index_metadata::index_metadata(const sstring& name,
|
|
const index_options_map& options,
|
|
index_metadata_kind kind)
|
|
: _id{utils::UUID_gen::get_name_UUID(name)}
|
|
, _name{name}
|
|
, _kind{kind}
|
|
, _options{options}
|
|
{}
|
|
|
|
bool index_metadata::operator==(const index_metadata& other) const {
|
|
return _id == other._id
|
|
&& _name == other._name
|
|
&& _kind == other._kind
|
|
&& _options == other._options;
|
|
}
|
|
|
|
bool index_metadata::equals_noname(const index_metadata& other) const {
|
|
return _kind == other._kind && _options == other._options;
|
|
}
|
|
|
|
const utils::UUID& index_metadata::id() const {
|
|
return _id;
|
|
}
|
|
|
|
const sstring& index_metadata::name() const {
|
|
return _name;
|
|
}
|
|
|
|
const index_metadata_kind index_metadata::kind() const {
|
|
return _kind;
|
|
}
|
|
|
|
const index_options_map& index_metadata::options() const {
|
|
return _options;
|
|
}
|
|
|
|
sstring index_metadata::get_default_index_name(const sstring& cf_name,
|
|
std::experimental::optional<sstring> root) {
|
|
if (root) {
|
|
return cf_name + "_" + root.value() + "_idx";
|
|
}
|
|
return cf_name + "_idx";
|
|
}
|
|
|
|
column_definition::column_definition(bytes name, data_type type, column_kind kind, column_id component_index, index_info idx, api::timestamp_type dropped_at)
|
|
: _name(std::move(name))
|
|
, _dropped_at(dropped_at)
|
|
, _is_atomic(type->is_atomic())
|
|
, _is_counter(type->is_counter())
|
|
, type(std::move(type))
|
|
, id(component_index)
|
|
, kind(kind)
|
|
, idx_info(std::move(idx))
|
|
{}
|
|
|
|
std::ostream& operator<<(std::ostream& os, const column_definition& cd) {
|
|
os << "ColumnDefinition{";
|
|
os << "name=" << cd.name_as_text();
|
|
os << ", type=" << cd.type->name();
|
|
os << ", kind=" << to_sstring(cd.kind);
|
|
os << ", componentIndex=" << (cd.has_component_index() ? std::to_string(cd.component_index()) : "null");
|
|
os << ", indexName=" << (cd.idx_info.index_name ? *cd.idx_info.index_name : "null");
|
|
os << ", indexType=" << to_sstring(cd.idx_info.index_type);
|
|
os << ", droppedAt=" << cd._dropped_at;
|
|
os << "}";
|
|
return os;
|
|
}
|
|
|
|
const column_definition*
|
|
schema::get_column_definition(const bytes& name) const {
|
|
auto i = _columns_by_name.find(name);
|
|
if (i == _columns_by_name.end()) {
|
|
return nullptr;
|
|
}
|
|
return i->second;
|
|
}
|
|
|
|
const column_definition&
|
|
schema::column_at(column_kind kind, column_id id) const {
|
|
return _raw._columns.at(column_offset(kind) + id);
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& os, const schema& s) {
|
|
os << "org.apache.cassandra.config.CFMetaData@" << &s << "[";
|
|
os << "cfId=" << s._raw._id;
|
|
os << ",ksName=" << s._raw._ks_name;
|
|
os << ",cfName=" << s._raw._cf_name;
|
|
os << ",cfType=" << cf_type_to_sstring(s._raw._type);
|
|
os << ",comparator=" << cell_comparator::to_sstring(s);
|
|
os << ",comment=" << s._raw._comment;
|
|
os << ",readRepairChance=" << s._raw._read_repair_chance;
|
|
os << ",dcLocalReadRepairChance=" << s._raw._dc_local_read_repair_chance;
|
|
os << ",gcGraceSeconds=" << s._raw._gc_grace_seconds;
|
|
os << ",defaultValidator=" << s._raw._default_validator->name();
|
|
os << ",keyValidator=" << s.thrift_key_validator();
|
|
os << ",minCompactionThreshold=" << s._raw._min_compaction_threshold;
|
|
os << ",maxCompactionThreshold=" << s._raw._max_compaction_threshold;
|
|
os << ",columnMetadata=[";
|
|
int n = 0;
|
|
for (auto& cdef : s._raw._columns) {
|
|
if (n++ != 0) {
|
|
os << ", ";
|
|
}
|
|
os << cdef;
|
|
}
|
|
os << "]";
|
|
os << ",compactionStrategyClass=class org.apache.cassandra.db.compaction." << sstables::compaction_strategy::name(s._raw._compaction_strategy);
|
|
os << ",compactionStrategyOptions={";
|
|
n = 0;
|
|
for (auto& p : s._raw._compaction_strategy_options) {
|
|
if (n++ != 0) {
|
|
os << ", ";
|
|
}
|
|
os << p.first << "=" << p.second;
|
|
}
|
|
os << "}";
|
|
os << ",compressionParameters={";
|
|
n = 0;
|
|
for (auto& p : s._raw._compressor_params.get_options() ) {
|
|
if (n++ != 0) {
|
|
os << ", ";
|
|
}
|
|
os << p.first << "=" << p.second;
|
|
}
|
|
os << "}";
|
|
os << ",bloomFilterFpChance=" << s._raw._bloom_filter_fp_chance;
|
|
os << ",memtableFlushPeriod=" << s._raw._memtable_flush_period;
|
|
os << ",caching=" << s._raw._caching_options.to_sstring();
|
|
os << ",defaultTimeToLive=" << s._raw._default_time_to_live.count();
|
|
os << ",minIndexInterval=" << s._raw._min_index_interval;
|
|
os << ",maxIndexInterval=" << s._raw._max_index_interval;
|
|
os << ",speculativeRetry=" << s._raw._speculative_retry.to_sstring();
|
|
os << ",droppedColumns={}";
|
|
os << ",triggers=[]";
|
|
os << ",isDense=" << std::boolalpha << s._raw._is_dense;
|
|
os << ",version=" << s.version();
|
|
os << ",droppedColumns={";
|
|
n = 0;
|
|
for (auto& dc : s._raw._dropped_columns) {
|
|
if (n++ != 0) {
|
|
os << ", ";
|
|
}
|
|
os << dc.first << " : " << dc.second;
|
|
}
|
|
os << "}";
|
|
os << ",collections={";
|
|
n = 0;
|
|
for (auto& c : s._raw._collections) {
|
|
if (n++ != 0) {
|
|
os << ", ";
|
|
}
|
|
os << c.first << " : " << c.second->name();
|
|
}
|
|
os << "}";
|
|
os << ",indices={";
|
|
n = 0;
|
|
for (auto& c : s._raw._indices_by_name) {
|
|
if (n++ != 0) {
|
|
os << ", ";
|
|
}
|
|
os << c.first << " : " << c.second.id();
|
|
}
|
|
os << "}";
|
|
if (s.is_view()) {
|
|
os << ", viewInfo=" << *s.view_info();
|
|
}
|
|
os << "]";
|
|
return os;
|
|
}
|
|
|
|
const sstring&
|
|
column_definition::name_as_text() const {
|
|
return column_specification->name->text();
|
|
}
|
|
|
|
const bytes&
|
|
column_definition::name() const {
|
|
return _name;
|
|
}
|
|
|
|
bool column_definition::is_on_all_components() const {
|
|
return _thrift_bits.is_on_all_components;
|
|
}
|
|
|
|
bool operator==(const column_definition& x, const column_definition& y)
|
|
{
|
|
return x._name == y._name
|
|
&& x.type->equals(y.type)
|
|
&& x.id == y.id
|
|
&& x.kind == y.kind
|
|
&& x._dropped_at == y._dropped_at;
|
|
}
|
|
|
|
// Based on org.apache.cassandra.config.CFMetaData#generateLegacyCfId
|
|
utils::UUID
|
|
generate_legacy_id(const sstring& ks_name, const sstring& cf_name) {
|
|
return utils::UUID_gen::get_name_UUID(ks_name + cf_name);
|
|
}
|
|
|
|
bool thrift_schema::has_compound_comparator() const {
|
|
return _compound;
|
|
}
|
|
|
|
bool thrift_schema::is_dynamic() const {
|
|
return _is_dynamic;
|
|
}
|
|
|
|
schema_builder::schema_builder(const sstring& ks_name, const sstring& cf_name,
|
|
std::experimental::optional<utils::UUID> id, data_type rct)
|
|
: _raw(id ? *id : utils::UUID_gen::get_time_UUID())
|
|
{
|
|
_raw._ks_name = ks_name;
|
|
_raw._cf_name = cf_name;
|
|
_raw._regular_column_name_type = rct;
|
|
}
|
|
|
|
schema_builder::schema_builder(const schema_ptr s)
|
|
: schema_builder(s->_raw)
|
|
{
|
|
if (s->is_view()) {
|
|
_view_info = s->view_info()->raw();
|
|
}
|
|
}
|
|
|
|
schema_builder::schema_builder(const schema::raw_schema& raw)
|
|
: _raw(raw)
|
|
{
|
|
static_assert(schema::row_column_ids_are_ordered_by_name::value, "row columns don't need to be ordered by name");
|
|
// Schema builder may add or remove columns and their ids need to be
|
|
// recomputed in build().
|
|
for (auto& def : _raw._columns | boost::adaptors::filtered([] (auto& def) { return !def.is_primary_key(); })) {
|
|
def.id = 0;
|
|
}
|
|
}
|
|
|
|
column_definition& schema_builder::find_column(const cql3::column_identifier& c) {
|
|
auto i = std::find_if(_raw._columns.begin(), _raw._columns.end(), [c](auto& p) {
|
|
return p.name() == c.name();
|
|
});
|
|
if (i != _raw._columns.end()) {
|
|
return *i;
|
|
}
|
|
throw std::invalid_argument(sprint("No such column %s", c.name()));
|
|
}
|
|
|
|
void schema_builder::add_default_index_names(database& db) {
|
|
auto s = db.find_schema(ks_name(), cf_name());
|
|
|
|
if (s) {
|
|
for (auto& sc : _raw._columns) {
|
|
if (sc.idx_info.index_type == index_type::none) {
|
|
continue;
|
|
}
|
|
auto* c = s->get_column_definition(sc.name());
|
|
if (c == nullptr || !c->idx_info.index_name) {
|
|
continue;
|
|
}
|
|
if (sc.idx_info.index_name
|
|
&& sc.idx_info.index_name != c->idx_info.index_name) {
|
|
throw exceptions::configuration_exception(
|
|
sprint(
|
|
"Can't modify index name: was '%s' changed to '%s'",
|
|
*c->idx_info.index_name,
|
|
*sc.idx_info.index_name));
|
|
|
|
}
|
|
sc.idx_info.index_name = c->idx_info.index_name;
|
|
}
|
|
}
|
|
|
|
|
|
auto existing_names = db.existing_index_names(ks_name());
|
|
for (auto& sc : _raw._columns) {
|
|
if (sc.idx_info.index_type != index_type::none && sc.idx_info.index_name) {
|
|
sstring base_name = cf_name() + "_" + *sc.idx_info.index_name + "_idx";
|
|
auto i = std::remove_if(base_name.begin(), base_name.end(), [](char c) {
|
|
return ::isspace(c);
|
|
});
|
|
base_name.erase(i, base_name.end());
|
|
auto index_name = base_name;
|
|
int n = 0;
|
|
while (existing_names.count(index_name)) {
|
|
index_name = base_name + "_" + to_sstring(++n);
|
|
}
|
|
sc.idx_info.index_name = index_name;
|
|
}
|
|
}
|
|
}
|
|
|
|
schema_builder& schema_builder::with_column(const column_definition& c) {
|
|
return with_column(bytes(c.name()), data_type(c.type), index_info(c.idx_info), column_kind(c.kind), c.position());
|
|
}
|
|
|
|
schema_builder& schema_builder::with_column(bytes name, data_type type, column_kind kind) {
|
|
return with_column(name, type, index_info(), kind);
|
|
}
|
|
|
|
schema_builder& schema_builder::with_column(bytes name, data_type type, index_info info, column_kind kind) {
|
|
// component_index will be determined by schema cosntructor
|
|
return with_column(name, type, info, kind, 0);
|
|
}
|
|
|
|
schema_builder& schema_builder::with_column(bytes name, data_type type, index_info info, column_kind kind, column_id component_index) {
|
|
_raw._columns.emplace_back(name, type, kind, component_index, info);
|
|
if (type->is_multi_cell()) {
|
|
with_collection(name, type);
|
|
} else if (type->is_counter()) {
|
|
set_default_validator(counter_type);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
schema_builder& schema_builder::without_column(bytes name)
|
|
{
|
|
auto it = boost::range::find_if(_raw._columns, [&] (auto& column) {
|
|
return column.name() == name;
|
|
});
|
|
assert(it != _raw._columns.end());
|
|
auto now = api::new_timestamp();
|
|
auto ret = _raw._dropped_columns.emplace(it->name_as_text(), now);
|
|
if (!ret.second) {
|
|
ret.first->second = std::max(ret.first->second, now);
|
|
}
|
|
_raw._columns.erase(it);
|
|
return *this;
|
|
}
|
|
|
|
schema_builder& schema_builder::without_column(sstring name, api::timestamp_type timestamp)
|
|
{
|
|
auto ret = _raw._dropped_columns.emplace(name, timestamp);
|
|
if (!ret.second) {
|
|
ret.first->second = std::max(ret.first->second, timestamp);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
schema_builder& schema_builder::with_column_rename(bytes from, bytes to)
|
|
{
|
|
auto it = std::find_if(_raw._columns.begin(), _raw._columns.end(), [&] (auto& col) {
|
|
return col.name() == from;
|
|
});
|
|
assert(it != _raw._columns.end());
|
|
auto& def = *it;
|
|
column_definition new_def(to, def.type, def.kind, def.component_index(), def.idx_info);
|
|
_raw._columns.erase(it);
|
|
return with_column(new_def);
|
|
}
|
|
|
|
schema_builder& schema_builder::with_altered_column_type(bytes name, data_type new_type)
|
|
{
|
|
auto it = boost::find_if(_raw._columns, [&name] (auto& c) { return c.name() == name; });
|
|
assert(it != _raw._columns.end());
|
|
it->type = new_type;
|
|
|
|
if (new_type->is_multi_cell()) {
|
|
auto c_it = _raw._collections.find(name);
|
|
assert(c_it != _raw._collections.end());
|
|
c_it->second = new_type;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
schema_builder& schema_builder::with_collection(bytes name, data_type type)
|
|
{
|
|
_raw._collections.emplace(name, type);
|
|
return *this;
|
|
}
|
|
|
|
schema_builder& schema_builder::with(compact_storage cs) {
|
|
_compact_storage = cs;
|
|
return *this;
|
|
}
|
|
|
|
schema_builder& schema_builder::with_version(table_schema_version v) {
|
|
_version = v;
|
|
return *this;
|
|
}
|
|
|
|
void schema_builder::prepare_dense_schema(schema::raw_schema& raw) {
|
|
if (raw._is_dense) {
|
|
auto regular_cols = std::count_if(raw._columns.begin(), raw._columns.end(), [](auto&& col) {
|
|
return col.kind == column_kind::regular_column;
|
|
});
|
|
// In Origin, dense CFs always have at least one regular column
|
|
if (regular_cols == 0) {
|
|
raw._columns.emplace_back(bytes(""), raw._regular_column_name_type, column_kind::regular_column, 0, index_info());
|
|
} else if (regular_cols > 1) {
|
|
throw exceptions::configuration_exception(sprint("Expecting exactly one regular column. Found %d", regular_cols));
|
|
}
|
|
}
|
|
}
|
|
|
|
schema_builder& schema_builder::with_view_info(utils::UUID base_id, sstring base_name, bool include_all_columns, sstring where_clause) {
|
|
_view_info = raw_view_info(std::move(base_id), std::move(base_name), include_all_columns, std::move(where_clause));
|
|
return *this;
|
|
}
|
|
|
|
schema_builder& schema_builder::with_index(const index_metadata& im) {
|
|
_raw._indices_by_name.emplace(im.name(), im);
|
|
return *this;
|
|
}
|
|
|
|
schema_builder& schema_builder::without_index(const sstring& name) {
|
|
const auto& it = _raw._indices_by_name.find(name);
|
|
if (it != _raw._indices_by_name.end()) {
|
|
_raw._indices_by_name.erase(name);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
schema_ptr schema_builder::build() {
|
|
schema::raw_schema new_raw = _raw; // Copy so that build() remains idempotent.
|
|
|
|
if (_version) {
|
|
new_raw._version = *_version;
|
|
} else {
|
|
new_raw._version = utils::UUID_gen::get_time_UUID();
|
|
}
|
|
|
|
if (_compact_storage) {
|
|
// Dense means that no part of the comparator stores a CQL column name. This means
|
|
// COMPACT STORAGE with at least one columnAliases (otherwise it's a thrift "static" CF).
|
|
auto clustering_key_size = std::count_if(new_raw._columns.begin(), new_raw._columns.end(), [](auto&& col) {
|
|
return col.kind == column_kind::clustering_key;
|
|
});
|
|
new_raw._is_dense = (*_compact_storage == compact_storage::yes) && (clustering_key_size > 0);
|
|
|
|
if (clustering_key_size == 0) {
|
|
if (*_compact_storage == compact_storage::yes) {
|
|
new_raw._is_compound = false;
|
|
} else {
|
|
new_raw._is_compound = true;
|
|
}
|
|
} else {
|
|
if ((*_compact_storage == compact_storage::yes) && clustering_key_size == 1) {
|
|
new_raw._is_compound = false;
|
|
} else {
|
|
new_raw._is_compound = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
prepare_dense_schema(new_raw);
|
|
return make_lw_shared<schema>(schema(new_raw, _view_info));
|
|
}
|
|
|
|
schema_ptr schema_builder::build(compact_storage cp) {
|
|
return with(cp).build();
|
|
}
|
|
|
|
// Useful functions to manipulate the schema's comparator field
|
|
namespace cell_comparator {
|
|
|
|
static constexpr auto _composite_str = "org.apache.cassandra.db.marshal.CompositeType";
|
|
static constexpr auto _collection_str = "org.apache.cassandra.db.marshal.ColumnToCollectionType";
|
|
|
|
static sstring compound_name(const schema& s) {
|
|
sstring compound(_composite_str);
|
|
|
|
compound += "(";
|
|
if (s.clustering_key_size()) {
|
|
for (auto &t : s.clustering_key_columns()) {
|
|
compound += t.type->name() + ",";
|
|
}
|
|
}
|
|
|
|
if (!s.is_dense()) {
|
|
compound += s.regular_column_name_type()->name() + ",";
|
|
}
|
|
|
|
if (!s.collections().empty()) {
|
|
compound += _collection_str;
|
|
compound += "(";
|
|
for (auto& c : s.collections()) {
|
|
auto ct = static_pointer_cast<const collection_type_impl>(c.second);
|
|
compound += sprint("%s:%s,", to_hex(c.first), ct->name());
|
|
}
|
|
compound.back() = ')';
|
|
compound += ",";
|
|
}
|
|
// last one will be a ',', just replace it.
|
|
compound.back() = ')';
|
|
return compound;
|
|
}
|
|
|
|
sstring to_sstring(const schema& s) {
|
|
if (s.is_compound()) {
|
|
return compound_name(s);
|
|
} else if (s.clustering_key_size() == 1) {
|
|
assert(s.is_dense());
|
|
return s.clustering_key_columns().front().type->name();
|
|
} else {
|
|
return s.regular_column_name_type()->name();
|
|
}
|
|
}
|
|
|
|
bool check_compound(sstring comparator) {
|
|
static sstring compound(_composite_str);
|
|
return comparator.compare(0, compound.size(), compound) == 0;
|
|
}
|
|
|
|
void read_collections(schema_builder& builder, sstring comparator)
|
|
{
|
|
// The format of collection entries in the comparator is:
|
|
// org.apache.cassandra.db.marshal.ColumnToCollectionType(<name1>:<type1>, ...)
|
|
|
|
auto find_closing_parenthesis = [] (sstring_view str, size_t start) {
|
|
auto pos = start;
|
|
auto nest_level = 0;
|
|
do {
|
|
pos = str.find_first_of("()", pos);
|
|
if (pos == sstring::npos) {
|
|
throw marshal_exception();
|
|
}
|
|
if (str[pos] == ')') {
|
|
nest_level--;
|
|
} else if (str[pos] == '(') {
|
|
nest_level++;
|
|
}
|
|
pos++;
|
|
} while (nest_level > 0);
|
|
return pos - 1;
|
|
};
|
|
|
|
auto collection_str_length = strlen(_collection_str);
|
|
|
|
auto pos = comparator.find(_collection_str);
|
|
if (pos == sstring::npos) {
|
|
return;
|
|
}
|
|
pos += collection_str_length + 1;
|
|
while (pos < comparator.size()) {
|
|
size_t end = comparator.find('(', pos);
|
|
if (end == sstring::npos) {
|
|
throw marshal_exception();
|
|
}
|
|
end = find_closing_parenthesis(comparator, end) + 1;
|
|
|
|
auto colon = comparator.find(':', pos);
|
|
if (colon == sstring::npos || colon > end) {
|
|
throw marshal_exception();
|
|
}
|
|
|
|
auto name = from_hex(sstring_view(comparator.c_str() + pos, colon - pos));
|
|
|
|
colon++;
|
|
auto type_str = sstring_view(comparator.c_str() + colon, end - colon);
|
|
auto type = db::marshal::type_parser::parse(type_str);
|
|
|
|
builder.with_collection(name, type);
|
|
|
|
if (end < comparator.size() && comparator[end] == ',') {
|
|
pos = end + 1;
|
|
} else if (end < comparator.size() && comparator[end] == ')') {
|
|
pos = sstring::npos;
|
|
} else {
|
|
throw marshal_exception();
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
schema::const_iterator
|
|
schema::regular_begin() const {
|
|
return regular_columns().begin();
|
|
}
|
|
|
|
schema::const_iterator
|
|
schema::regular_end() const {
|
|
return regular_columns().end();
|
|
}
|
|
|
|
schema::const_iterator
|
|
schema::regular_lower_bound(const bytes& name) const {
|
|
// TODO: use regular_columns and a version of std::lower_bound() with heterogeneous comparator
|
|
auto i = _regular_columns_by_name.lower_bound(name);
|
|
if (i == _regular_columns_by_name.end()) {
|
|
return regular_end();
|
|
} else {
|
|
return regular_begin() + i->second->id;
|
|
}
|
|
}
|
|
|
|
schema::const_iterator
|
|
schema::regular_upper_bound(const bytes& name) const {
|
|
// TODO: use regular_columns and a version of std::upper_bound() with heterogeneous comparator
|
|
auto i = _regular_columns_by_name.upper_bound(name);
|
|
if (i == _regular_columns_by_name.end()) {
|
|
return regular_end();
|
|
} else {
|
|
return regular_begin() + i->second->id;
|
|
}
|
|
}
|
|
|
|
data_type
|
|
schema::column_name_type(const column_definition& def) const {
|
|
return def.kind == column_kind::regular_column ? _raw._regular_column_name_type : utf8_type;
|
|
}
|
|
|
|
const column_definition&
|
|
schema::regular_column_at(column_id id) const {
|
|
if (id > regular_columns_count()) {
|
|
throw std::out_of_range("column_id");
|
|
}
|
|
return _raw._columns.at(column_offset(column_kind::regular_column) + id);
|
|
}
|
|
|
|
const column_definition&
|
|
schema::static_column_at(column_id id) const {
|
|
if (id > static_columns_count()) {
|
|
throw std::out_of_range("column_id");
|
|
}
|
|
return _raw._columns.at(column_offset(column_kind::static_column) + id);
|
|
}
|
|
|
|
bool
|
|
schema::is_last_partition_key(const column_definition& def) const {
|
|
return &_raw._columns.at(partition_key_size() - 1) == &def;
|
|
}
|
|
|
|
bool
|
|
schema::has_static_columns() const {
|
|
return !static_columns().empty();
|
|
}
|
|
|
|
column_count_type
|
|
schema::partition_key_size() const {
|
|
return column_offset(column_kind::clustering_key);
|
|
}
|
|
|
|
column_count_type
|
|
schema::clustering_key_size() const {
|
|
return column_offset(column_kind::static_column) - column_offset(column_kind::clustering_key);
|
|
}
|
|
|
|
column_count_type
|
|
schema::static_columns_count() const {
|
|
return column_offset(column_kind::regular_column) - column_offset(column_kind::static_column);
|
|
}
|
|
|
|
column_count_type
|
|
schema::regular_columns_count() const {
|
|
return _raw._columns.size() - column_offset(column_kind::regular_column);
|
|
}
|
|
|
|
schema::const_iterator_range_type
|
|
schema::partition_key_columns() const {
|
|
return boost::make_iterator_range(_raw._columns.begin() + column_offset(column_kind::partition_key)
|
|
, _raw._columns.begin() + column_offset(column_kind::clustering_key));
|
|
}
|
|
|
|
schema::const_iterator_range_type
|
|
schema::clustering_key_columns() const {
|
|
return boost::make_iterator_range(_raw._columns.begin() + column_offset(column_kind::clustering_key)
|
|
, _raw._columns.begin() + column_offset(column_kind::static_column));
|
|
}
|
|
|
|
schema::const_iterator_range_type
|
|
schema::static_columns() const {
|
|
return boost::make_iterator_range(_raw._columns.begin() + column_offset(column_kind::static_column)
|
|
, _raw._columns.begin() + column_offset(column_kind::regular_column));
|
|
}
|
|
|
|
schema::const_iterator_range_type
|
|
schema::regular_columns() const {
|
|
return boost::make_iterator_range(_raw._columns.begin() + column_offset(column_kind::regular_column)
|
|
, _raw._columns.end());
|
|
}
|
|
|
|
const schema::columns_type&
|
|
schema::all_columns_in_select_order() const {
|
|
return _raw._columns;
|
|
}
|
|
|
|
uint32_t
|
|
schema::position(const column_definition& column) const {
|
|
if (column.is_primary_key()) {
|
|
return column.id;
|
|
}
|
|
return clustering_key_size();
|
|
}
|
|
|
|
stdx::optional<index_metadata> schema::find_index_noname(const index_metadata& target) const {
|
|
const auto& it = boost::find_if(_raw._indices_by_name, [&] (auto&& e) {
|
|
return e.second.equals_noname(target);
|
|
});
|
|
if (it != _raw._indices_by_name.end()) {
|
|
return it->second;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
std::vector<index_metadata> schema::indices() const {
|
|
return boost::copy_range<std::vector<index_metadata>>(_raw._indices_by_name | boost::adaptors::map_values);
|
|
}
|
|
|
|
bool schema::has_index(const sstring& index_name) const {
|
|
return _raw._indices_by_name.count(index_name) > 0;
|
|
}
|
|
|
|
std::vector<sstring> schema::index_names() const {
|
|
return boost::copy_range<std::vector<sstring>>(_raw._indices_by_name | boost::adaptors::map_keys);
|
|
}
|
|
|
|
bool schema::is_synced() const {
|
|
return _registry_entry && _registry_entry->is_synced();
|
|
}
|
|
|
|
bool schema::equal_columns(const schema& other) const {
|
|
return boost::equal(all_columns_in_select_order(), other.all_columns_in_select_order());
|
|
}
|
|
|
|
raw_view_info::raw_view_info(utils::UUID base_id, sstring base_name, bool include_all_columns, sstring where_clause)
|
|
: _base_id(std::move(base_id))
|
|
, _base_name(std::move(base_name))
|
|
, _include_all_columns(include_all_columns)
|
|
, _where_clause(where_clause)
|
|
{ }
|
|
|
|
bool operator==(const raw_view_info& x, const raw_view_info& y) {
|
|
return x._base_id == y._base_id
|
|
&& x._base_name == y._base_name
|
|
&& x._include_all_columns == y._include_all_columns
|
|
&& x._where_clause == y._where_clause;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& os, const raw_view_info& view) {
|
|
os << "ViewInfo{";
|
|
os << "baseTableId=" << view._base_id;
|
|
os << ", baseTableName=" << view._base_name;
|
|
os << ", includeAllColumns=" << view._include_all_columns;
|
|
os << ", whereClause=" << view._where_clause;
|
|
os << "}";
|
|
return os;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& os, const view_ptr& view) {
|
|
return view ? os << *view : os << "null";
|
|
}
|