/* * Copyright 2014 Cloudius Systems */ // Some thrift headers include other files from within namespaces, // which is totally broken. Include those files here to avoid // breakage: #include // end thrift workaround #include "Cassandra.h" #include "core/distributed.hh" #include "database.hh" #include "core/sstring.hh" #include "core/print.hh" #include "frozen_mutation.hh" #include "utils/UUID_gen.hh" #include #include #include "utils/class_registrator.hh" using namespace ::apache::thrift; using namespace ::apache::thrift::protocol; using namespace ::apache::thrift::transport; using namespace ::apache::thrift::async; using namespace ::org::apache::cassandra; class unimplemented_exception : public std::exception { public: virtual const char* what() const throw () override { return "sorry, not implemented"; } }; void pass_unimplemented(const tcxx::function& exn_cob) { exn_cob(::apache::thrift::TDelayedException::delayException(unimplemented_exception())); } template Ex make_exception(const char* fmt, Args&&... args) { Ex ex; ex.why = sprint(fmt, std::forward(args)...); return ex; } template void complete_with_exception(tcxx::function&& exn_cob, const char* fmt, Args&&... args) { exn_cob(TDelayedException::delayException(make_exception(fmt, std::forward(args)...))); } class delayed_exception_wrapper : public ::apache::thrift::TDelayedException { std::exception_ptr _ex; public: delayed_exception_wrapper(std::exception_ptr ex) : _ex(std::move(ex)) {} virtual void throw_it() override { // Thrift auto-wraps unexpected exceptions (those not derived from TException) // with a TException, but with a fairly bad what(). So detect this, and // provide our own TException with a better what(). try { std::rethrow_exception(std::move(_ex)); } catch (const ::apache::thrift::TException&) { // It's an expected exception, so assume the message // is fine. Also, we don't want to change its type. throw; } catch (no_such_class& nc) { throw make_exception(nc.what()); } catch (std::exception& e) { // Unexpected exception, wrap it throw ::apache::thrift::TException(std::string("Internal server error: ") + e.what()); } catch (...) { // Unexpected exception, wrap it, unfortunately without any info throw ::apache::thrift::TException("Internal server error"); } } }; template void complete(future& fut, const tcxx::function& cob, const tcxx::function& exn_cob) { try { apply(std::move(cob), fut.get()); } catch (...) { delayed_exception_wrapper dew(std::current_exception()); exn_cob(&dew); } } template void complete(future>>& fut, const tcxx::function& cob, const tcxx::function& exn_cob) { try { cob(*std::get<0>(fut.get())); } catch (...) { delayed_exception_wrapper dew(std::current_exception()); exn_cob(&dew); } } std::string bytes_to_string(bytes_view v) { return { reinterpret_cast(v.begin()), v.size() }; } class thrift_handler : public CassandraCobSvIf { distributed& _db; sstring _ks_name; sstring _cql_version; public: explicit thrift_handler(distributed& db) : _db(db) {} void login(tcxx::function cob, tcxx::function exn_cob, const AuthenticationRequest& auth_request) { // FIXME: implement return pass_unimplemented(exn_cob); } void set_keyspace(tcxx::function cob, tcxx::function exn_cob, const std::string& keyspace) { if (!_db.local().has_keyspace(keyspace)) { complete_with_exception(std::move(exn_cob), "keyspace %s does not exist", keyspace); } else { _ks_name = keyspace; cob(); } } void get(tcxx::function cob, tcxx::function exn_cob, const std::string& key, const ColumnPath& column_path, const ConsistencyLevel::type consistency_level) { ColumnOrSuperColumn _return; // FIXME: implement return pass_unimplemented(exn_cob); } void get_slice(tcxx::function const& _return)> cob, tcxx::function exn_cob, const std::string& key, const ColumnParent& column_parent, const SlicePredicate& predicate, const ConsistencyLevel::type consistency_level) { schema_ptr schema; try { schema = _db.local().find_schema(_ks_name, column_parent.column_family); } catch (...) { return complete_with_exception(std::move(exn_cob), "column family %s not found", column_parent.column_family); } auto pk = key_from_thrift(schema, to_bytes(key)); auto dk = dht::global_partitioner().decorate_key(*schema, pk); auto shard = _db.local().shard_of(dk._token); auto do_get = [this, dk = std::move(dk), column_parent = std::move(column_parent), predicate = std::move(predicate)] (database& db) { if (!column_parent.super_column.empty()) { throw unimplemented_exception(); } auto& cf = lookup_column_family(_db.local(), _ks_name, column_parent.column_family); if (predicate.__isset.column_names) { throw unimplemented_exception(); } else if (predicate.__isset.slice_range) { auto&& range = predicate.slice_range; return cf.find_row(dk, clustering_key::make_empty(*cf.schema())).then([&cf, range = std::move(range)] (column_family::const_row_ptr rw) { std::vector ret; if (rw) { auto beg = cf.schema()->regular_begin(); if (!range.start.empty()) { beg = cf.schema()->regular_lower_bound(to_bytes(range.start)); } auto end = cf.schema()->regular_end(); if (!range.finish.empty()) { end = cf.schema()->regular_upper_bound(to_bytes(range.finish)); } auto count = range.count; // FIXME: force limit count? while (beg != end && count--) { const column_definition& def = range.reversed ? *--end : *beg++; atomic_cell_view cell = (*rw).cell_at(def.id).as_atomic_cell(); if (def.is_atomic()) { if (cell.is_live()) { // FIXME: we should actually use tombstone information from all levels Column col; col.__set_name(bytes_to_string(def.name())); col.__set_value(bytes_to_string(cell.value())); col.__set_timestamp(cell.timestamp()); // FIXME: set ttl ColumnOrSuperColumn v; v.__set_column(std::move(col)); ret.push_back(std::move(v)); }; } } } return make_foreign(make_lw_shared(std::move(ret))); }); } else { throw make_exception("empty SlicePredicate"); } }; _db.invoke_on(shard, [do_get = std::move(do_get)] (database& db) { return do_get(db); }).then_wrapped([cob = std::move(cob), exn_cob = std::move(exn_cob)] (future>>> ret) { complete(ret, cob, exn_cob); return make_ready_future<>(); }); } void get_count(tcxx::function cob, tcxx::function exn_cob, const std::string& key, const ColumnParent& column_parent, const SlicePredicate& predicate, const ConsistencyLevel::type consistency_level) { // FIXME: implement return pass_unimplemented(exn_cob); } void multiget_slice(tcxx::function > const& _return)> cob, tcxx::function exn_cob, const std::vector & keys, const ColumnParent& column_parent, const SlicePredicate& predicate, const ConsistencyLevel::type consistency_level) { std::map > _return; // FIXME: implement return pass_unimplemented(exn_cob); } void multiget_count(tcxx::function const& _return)> cob, tcxx::function exn_cob, const std::vector & keys, const ColumnParent& column_parent, const SlicePredicate& predicate, const ConsistencyLevel::type consistency_level) { std::map _return; // FIXME: implement return pass_unimplemented(exn_cob); } void get_range_slices(tcxx::function const& _return)> cob, tcxx::function exn_cob, const ColumnParent& column_parent, const SlicePredicate& predicate, const KeyRange& range, const ConsistencyLevel::type consistency_level) { std::vector _return; // FIXME: implement return pass_unimplemented(exn_cob); } void get_paged_slice(tcxx::function const& _return)> cob, tcxx::function exn_cob, const std::string& column_family, const KeyRange& range, const std::string& start_column, const ConsistencyLevel::type consistency_level) { std::vector _return; // FIXME: implement return pass_unimplemented(exn_cob); } void get_indexed_slices(tcxx::function const& _return)> cob, tcxx::function exn_cob, const ColumnParent& column_parent, const IndexClause& index_clause, const SlicePredicate& column_predicate, const ConsistencyLevel::type consistency_level) { std::vector _return; // FIXME: implement return pass_unimplemented(exn_cob); } void insert(tcxx::function cob, tcxx::function exn_cob, const std::string& key, const ColumnParent& column_parent, const Column& column, const ConsistencyLevel::type consistency_level) { // FIXME: implement return pass_unimplemented(exn_cob); } void add(tcxx::function cob, tcxx::function exn_cob, const std::string& key, const ColumnParent& column_parent, const CounterColumn& column, const ConsistencyLevel::type consistency_level) { // FIXME: implement return pass_unimplemented(exn_cob); } void cas(tcxx::function cob, tcxx::function exn_cob, const std::string& key, const std::string& column_family, const std::vector & expected, const std::vector & updates, const ConsistencyLevel::type serial_consistency_level, const ConsistencyLevel::type commit_consistency_level) { CASResult _return; // FIXME: implement return pass_unimplemented(exn_cob); } void remove(tcxx::function cob, tcxx::function exn_cob, const std::string& key, const ColumnPath& column_path, const int64_t timestamp, const ConsistencyLevel::type consistency_level) { // FIXME: implement return pass_unimplemented(exn_cob); } void remove_counter(tcxx::function cob, tcxx::function exn_cob, const std::string& key, const ColumnPath& path, const ConsistencyLevel::type consistency_level) { // FIXME: implement return pass_unimplemented(exn_cob); } void batch_mutate(tcxx::function cob, tcxx::function exn_cob, const std::map > > & mutation_map, const ConsistencyLevel::type consistency_level) { if (_ks_name.empty()) { return complete_with_exception(std::move(exn_cob), "keyspace not set"); } // Would like to use move_iterator below, but Mutation is filled with some const stuff. parallel_for_each(mutation_map.begin(), mutation_map.end(), [this] (std::pair>> key_cf) { bytes thrift_key = to_bytes(key_cf.first); std::map>& cf_mutations_map = key_cf.second; return parallel_for_each( boost::make_move_iterator(cf_mutations_map.begin()), boost::make_move_iterator(cf_mutations_map.end()), [this, thrift_key] (std::pair> cf_mutations) { sstring cf_name = cf_mutations.first; const std::vector& mutations = cf_mutations.second; auto& cf = lookup_column_family(_db.local(), _ks_name, cf_name); mutation m_to_apply(key_from_thrift(cf.schema(), thrift_key), cf.schema()); auto empty_clustering_key = clustering_key::make_empty(*cf.schema()); for (const Mutation& m : mutations) { if (m.__isset.column_or_supercolumn) { auto&& cosc = m.column_or_supercolumn; if (cosc.__isset.column) { auto&& col = cosc.column; bytes cname = to_bytes(col.name); auto def = cf.schema()->get_column_definition(cname); if (!def) { throw make_exception("column %s not found", col.name); } if (def->kind != column_kind::regular_column) { throw make_exception("Column %s is not settable", col.name); } gc_clock::duration ttl; if (col.__isset.ttl) { ttl = std::chrono::duration_cast(std::chrono::seconds(col.ttl)); } if (ttl.count() <= 0) { ttl = cf.schema()->default_time_to_live(); } ttl_opt maybe_ttl; if (ttl.count() > 0) { maybe_ttl = ttl; } m_to_apply.set_clustered_cell(empty_clustering_key, *def, atomic_cell::make_live(col.timestamp, to_bytes(col.value), maybe_ttl)); } else if (cosc.__isset.super_column) { // FIXME: implement } else if (cosc.__isset.counter_column) { // FIXME: implement } else if (cosc.__isset.counter_super_column) { // FIXME: implement } else { throw make_exception("Empty ColumnOrSuperColumn"); } } else if (m.__isset.deletion) { // FIXME: implement abort(); } else { throw make_exception("Mutation must have either column or deletion"); } } auto shard = _db.local().shard_of(m_to_apply); return _db.invoke_on(shard, [this, cf_name, m = freeze(m_to_apply)] (database& db) { return db.apply(m); }); }); }).then_wrapped([this, cob = std::move(cob), exn_cob = std::move(exn_cob)] (future<> ret) { complete(ret, cob, exn_cob); return make_ready_future<>(); }); } void atomic_batch_mutate(tcxx::function cob, tcxx::function exn_cob, const std::map > > & mutation_map, const ConsistencyLevel::type consistency_level) { // FIXME: implement return pass_unimplemented(exn_cob); } void truncate(tcxx::function cob, tcxx::function exn_cob, const std::string& cfname) { // FIXME: implement return pass_unimplemented(exn_cob); } void get_multi_slice(tcxx::function const& _return)> cob, tcxx::function exn_cob, const MultiSliceRequest& request) { std::vector _return; // FIXME: implement return pass_unimplemented(exn_cob); } void describe_schema_versions(tcxx::function > const& _return)> cob, tcxx::function exn_cob) { std::map > _return; // FIXME: implement return pass_unimplemented(exn_cob); } void describe_keyspaces(tcxx::function const& _return)> cob, tcxx::function exn_cob) { std::vector ret; for (auto&& ks : _db.local().keyspaces()) { ret.emplace_back(get_keyspace_definition(ks.second)); } cob(ret); } void describe_cluster_name(tcxx::function cob) { std::string _return; // FIXME: implement cob("seastar"); } void describe_version(tcxx::function cob) { std::string _return; // FIXME: implement cob("0.0.0"); } void describe_ring(tcxx::function const& _return)> cob, tcxx::function exn_cob, const std::string& keyspace) { std::vector _return; // FIXME: implement return pass_unimplemented(exn_cob); } void describe_local_ring(tcxx::function const& _return)> cob, tcxx::function exn_cob, const std::string& keyspace) { std::vector _return; // FIXME: implement return pass_unimplemented(exn_cob); } void describe_token_map(tcxx::function const& _return)> cob, tcxx::function exn_cob) { std::map _return; // FIXME: implement return pass_unimplemented(exn_cob); } void describe_partitioner(tcxx::function cob) { std::string _return; // FIXME: implement return cob("dummy paritioner"); } void describe_snitch(tcxx::function cob) { std::string _return; // FIXME: implement return cob("dummy snitch"); } void describe_keyspace(tcxx::function cob, tcxx::function exn_cob, const std::string& keyspace) { try { auto& ks = _db.local().find_keyspace(keyspace); KsDef ret = get_keyspace_definition(ks); cob(ret); } catch (no_such_keyspace& nsk) { complete_with_exception(std::move(exn_cob), "keyspace %s does not exist", keyspace); } } void describe_splits(tcxx::function const& _return)> cob, tcxx::function exn_cob, const std::string& cfName, const std::string& start_token, const std::string& end_token, const int32_t keys_per_split) { std::vector _return; // FIXME: implement return pass_unimplemented(exn_cob); } void trace_next_query(tcxx::function cob) { std::string _return; // FIXME: implement return cob("dummy trace"); } void describe_splits_ex(tcxx::function const& _return)> cob, tcxx::function exn_cob, const std::string& cfName, const std::string& start_token, const std::string& end_token, const int32_t keys_per_split) { std::vector _return; // FIXME: implement return pass_unimplemented(exn_cob); } void system_add_column_family(tcxx::function cob, tcxx::function exn_cob, const CfDef& cf_def) { std::string _return; // FIXME: implement return pass_unimplemented(exn_cob); } void system_drop_column_family(tcxx::function cob, tcxx::function exn_cob, const std::string& column_family) { std::string _return; // FIXME: implement return pass_unimplemented(exn_cob); } void system_add_keyspace(tcxx::function cob, tcxx::function exn_cob, const KsDef& ks_def) { std::string schema_id = "schema-id"; // FIXME: make meaningful if (_db.local().has_keyspace(ks_def.name)) { InvalidRequestException ire; ire.why = sprint("Keyspace %s already exists", ks_def.name); exn_cob(TDelayedException::delayException(ire)); } std::vector cf_ids; cf_ids.reserve(ks_def.cf_defs.size()); boost::for_each(ks_def.cf_defs, [&cf_ids] (auto&&) { cf_ids.push_back(utils::UUID_gen::get_time_UUID()); }); _db.invoke_on_all([this, ks_def, cf_ids] (database& db) { auto ksm = make_lw_shared(to_sstring(ks_def.name), to_sstring(ks_def.strategy_class), std::map{ks_def.strategy_options.begin(), ks_def.strategy_options.end()}, ks_def.durable_writes, std::vector{}); // FIXME return db.create_keyspace(ksm); }).then([this, ks_def, cf_ids] { return parallel_for_each(boost::combine(ks_def.cf_defs, cf_ids), [this, ks_def, cf_ids] (auto&& cf_def_and_id) { // We create the directory on the local shard, since the same directory is // used for all shards. auto&& name = boost::get<0>(cf_def_and_id).name; auto&& uuid = boost::get<1>(cf_def_and_id); return _db.local().find_keyspace(ks_def.name).make_directory_for_column_family(name, uuid); }); }).then([this, ks_def, cf_ids] { return _db.invoke_on_all([this, ks_def = std::move(ks_def), cf_ids = std::move(cf_ids)] (database& db) { std::vector cf_defs; cf_defs.reserve(ks_def.cf_defs.size()); auto id_iterator = cf_ids.begin(); for (const CfDef& cf_def : ks_def.cf_defs) { std::vector partition_key; std::vector clustering_key; std::vector regular_columns; // FIXME: get this from comparator auto column_name_type = utf8_type; // FIXME: look at key_alias and key_validator first partition_key.push_back({"key", bytes_type}); // FIXME: guess clustering keys for (const ColumnDef& col_def : cf_def.column_metadata) { // FIXME: look at all fields, not just name regular_columns.push_back({to_bytes(col_def.name), bytes_type}); } auto id = *id_iterator++; auto s = make_lw_shared(schema(id, ks_def.name, cf_def.name, std::move(partition_key), std::move(clustering_key), std::move(regular_columns), std::vector(), column_name_type)); auto& ks = db.find_keyspace(ks_def.name); db.add_column_family(s, ks.make_column_family_config(*s)); cf_defs.push_back(s); } }); }).then([schema_id = std::move(schema_id)] { return make_ready_future(std::move(schema_id)); }).then_wrapped([cob = std::move(cob), exn_cob = std::move(exn_cob)] (future result) { complete(result, cob, exn_cob); }); } void system_drop_keyspace(tcxx::function cob, tcxx::function exn_cob, const std::string& keyspace) { std::string _return; // FIXME: implement return pass_unimplemented(exn_cob); } void system_update_keyspace(tcxx::function cob, tcxx::function exn_cob, const KsDef& ks_def) { std::string _return; // FIXME: implement return pass_unimplemented(exn_cob); } void system_update_column_family(tcxx::function cob, tcxx::function exn_cob, const CfDef& cf_def) { std::string _return; // FIXME: implement return pass_unimplemented(exn_cob); } void execute_cql_query(tcxx::function cob, tcxx::function exn_cob, const std::string& query, const Compression::type compression) { CqlResult _return; // FIXME: implement return pass_unimplemented(exn_cob); } void execute_cql3_query(tcxx::function cob, tcxx::function exn_cob, const std::string& query, const Compression::type compression, const ConsistencyLevel::type consistency) { print("warning: ignoring query %s\n", query); cob({}); #if 0 CqlResult _return; // FIXME: implement return pass_unimplemented(exn_cob); #endif } void prepare_cql_query(tcxx::function cob, tcxx::function exn_cob, const std::string& query, const Compression::type compression) { CqlPreparedResult _return; // FIXME: implement return pass_unimplemented(exn_cob); } void prepare_cql3_query(tcxx::function cob, tcxx::function exn_cob, const std::string& query, const Compression::type compression) { CqlPreparedResult _return; // FIXME: implement return pass_unimplemented(exn_cob); } void execute_prepared_cql_query(tcxx::function cob, tcxx::function exn_cob, const int32_t itemId, const std::vector & values) { CqlResult _return; // FIXME: implement return pass_unimplemented(exn_cob); } void execute_prepared_cql3_query(tcxx::function cob, tcxx::function exn_cob, const int32_t itemId, const std::vector & values, const ConsistencyLevel::type consistency) { CqlResult _return; // FIXME: implement return pass_unimplemented(exn_cob); } void set_cql_version(tcxx::function cob, tcxx::function exn_cob, const std::string& version) { _cql_version = version; cob(); } private: static sstring class_from_data_type(const data_type& dt) { static const std::unordered_map types = { { "boolean", "BooleanType" }, { "bytes", "BytesType" }, { "double", "DoubleType" }, { "int32", "Int32Type" }, { "long", "LongType" }, { "timestamp", "DateType" }, { "timeuuid", "TimeUUIDType" }, { "utf8", "UTF8Type" }, { "uuid", "UUIDType" }, // FIXME: missing types }; auto it = types.find(dt->name()); if (it == types.end()) { return sstring(" ") + dt->name(); } return sstring("org.apache.cassandra.db.marshal.") + it->second; } static sstring class_from_compound_type(const compound_type& ct) { if (ct.is_singular()) { return class_from_data_type(ct.types().front()); } sstring type = "org.apache.cassandra.db.marshal.CompositeType("; for (auto& dt : ct.types()) { type += class_from_data_type(dt); if (&dt != &*ct.types().rbegin()) { type += ","; } } type += ")"; return type; } static KsDef get_keyspace_definition(const keyspace& ks) { auto&& meta = ks.metadata(); KsDef def; def.__set_name(meta->name()); def.__set_strategy_class(meta->strategy_name()); std::map options( meta->strategy_options().begin(), meta->strategy_options().end()); def.__set_strategy_options(options); std::vector cfs; for (auto&& cf : meta->cf_meta_data()) { // FIXME: skip cql3 column families auto&& s = cf.second; CfDef cf_def; cf_def.__set_keyspace(s->ks_name()); cf_def.__set_name(s->cf_name()); cf_def.__set_key_validation_class(class_from_compound_type(*s->partition_key_type())); if (s->clustering_key_size()) { cf_def.__set_comparator_type(class_from_compound_type(*s->clustering_key_type())); } else { cf_def.__set_comparator_type(class_from_data_type(s->regular_column_name_type())); } cf_def.__set_comment(s->comment()); cf_def.__set_bloom_filter_fp_chance(s->bloom_filter_fp_chance()); if (s->regular_columns_count()) { std::vector columns; for (auto&& c : s->regular_columns()) { ColumnDef c_def; c_def.__set_name(c.name_as_text()); c_def.__set_validation_class(class_from_data_type(c.type)); columns.emplace_back(std::move(c_def)); } cf_def.__set_column_metadata(columns); } // FIXME: there are more fields that should be filled... cfs.emplace_back(std::move(cf_def)); } def.__set_cf_defs(cfs); def.__set_durable_writes(meta->durable_writes()); return std::move(def); } static column_family& lookup_column_family(database& db, const sstring& ks_name, const sstring& cf_name) { try { return db.find_column_family(ks_name, cf_name); } catch (std::out_of_range&) { throw make_exception("column family %s not found", cf_name); } } static partition_key key_from_thrift(schema_ptr s, bytes k) { if (s->partition_key_size() != 1) { fail(unimplemented::cause::THRIFT); } return partition_key::from_single_value(*s, std::move(k)); } }; class handler_factory : public CassandraCobSvIfFactory { distributed& _db; public: explicit handler_factory(distributed& db) : _db(db) {} typedef CassandraCobSvIf Handler; virtual CassandraCobSvIf* getHandler(const ::apache::thrift::TConnectionInfo& connInfo) { return new thrift_handler(_db); } virtual void releaseHandler(CassandraCobSvIf* handler) { delete handler; } }; std::unique_ptr create_handler_factory(distributed& db) { return std::make_unique(db); }