/* * Copyright (C) 2022-present ScyllaDB */ /* * SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0 */ #include #include #include #include #include "cdc/cdc_options.hh" #include "cdc/log.hh" #include "cql3/column_specification.hh" #include "cql3/functions/function_name.hh" #include "cql3/statements/prepared_statement.hh" #include "exceptions/exceptions.hh" #include #include #include #include #include "index/vector_index.hh" #include "schema/schema.hh" #include "service/client_state.hh" #include "types/types.hh" #include "cql3/query_processor.hh" #include "cql3/cql_statement.hh" #include "cql3/statements/raw/describe_statement.hh" #include "cql3/statements/describe_statement.hh" #include #include #include "transport/messages/result_message.hh" #include "transport/messages/result_message_base.hh" #include "service/query_state.hh" #include "data_dictionary/keyspace_metadata.hh" #include "data_dictionary/data_dictionary.hh" #include "service/storage_proxy.hh" #include "cql3/ut_name.hh" #include "types/map.hh" #include "types/list.hh" #include "cql3/functions/functions.hh" #include "types/user.hh" #include "utils/managed_string.hh" #include "view_info.hh" #include "validation.hh" #include "index/secondary_index_manager.hh" #include "cql3/functions/user_function.hh" #include "cql3/functions/user_aggregate.hh" #include "utils/overloaded_functor.hh" #include "db/config.hh" #include "db/system_keyspace.hh" #include "db/extensions.hh" #include "utils/sorting.hh" #include "replica/database.hh" #include "cql3/description.hh" #include "replica/schema_describe_helper.hh" static logging::logger dlogger("describe"); bool is_internal_keyspace(std::string_view name); namespace replica { class database; } namespace cql3 { namespace statements { namespace { template requires std::is_invocable_r_v> future> generate_descriptions(Range&& range, const Describer& describer, bool sort_by_name = true) { std::vector result{}; if constexpr (std::ranges::sized_range) { result.reserve(std::ranges::size(range)); } for (const auto& element : range) { result.push_back(describer(element)); co_await coroutine::maybe_yield(); } if (sort_by_name) { std::ranges::sort(result, std::less<>{}, std::mem_fn(&description::name)); } co_return result; } // `cql3::description` is a move-only type at the moment, and so we cannot initialize a vector passing // its instances as elements of an initializer list. This function serves as an intermediary to avoid // unnecessarily bloating some parts of the code below. std::vector wrap_in_vector(description desc) { std::vector result{}; result.reserve(1); result.push_back(std::move(desc)); return result; } bool is_index(const data_dictionary::database& db, const schema_ptr& schema) { return db.find_column_family(schema->view_info()->base_id()).get_index_manager().is_index(*schema); } /** * DESCRIBE FUNCTIONS * * - "plular" functions (types/functions/aggregates/tables) * Return list of all elements in a given keyspace. Returned descriptions * don't contain create statements. * * - "singular" functions (keyspace/type/function/aggregate/view/index/table) * Return description of element. The description contain create_statement. * Those functions throw `invalid_request_exception` if element cannot be found. * Note: * - `table()` returns description of the table and its indexes and views * - `function()` and `aggregate()` might return multiple descriptions * since keyspace and name don't identify function uniquely */ description type(replica::database& db, const lw_shared_ptr& ks, const sstring& name) { auto udt_meta = ks->user_types(); if (!udt_meta.has_type(to_bytes(name))) { throw exceptions::invalid_request_exception(format("Type '{}' not found in keyspace '{}'", name, ks->name())); } auto udt = udt_meta.get_type(to_bytes(name)); return udt->describe(with_create_statement::yes); } // Because UDTs can depend on each other, we need to sort them topologically future> get_sorted_types(const lw_shared_ptr& ks) { struct udts_comparator { inline bool operator()(const user_type& a, const user_type& b) const { return a->get_name_as_string() < b->get_name_as_string(); } }; std::vector all_udts; std::multimap adjacency; for (auto& [_, udt]: ks->user_types().get_all_types()) { all_udts.push_back(udt); for (auto& ref_udt: udt->get_all_referenced_user_types()) { adjacency.insert({ref_udt, udt}); } } co_return co_await utils::topological_sort(all_udts, adjacency); } future> types(replica::database& db, const lw_shared_ptr& ks, with_create_statement with_stmt) { auto udts = co_await get_sorted_types(ks); auto describer = [with_stmt] (const user_type udt) -> cql3::description { return udt->describe(with_stmt); }; co_return co_await generate_descriptions(udts, describer, false); } future> function(replica::database& db, const sstring& ks, const sstring& name) { auto fs = functions::instance().find(functions::function_name(ks, name)); if (fs.empty()) { throw exceptions::invalid_request_exception(format("Function '{}' not found in keyspace '{}'", name, ks)); } auto udfs = fs | std::views::transform([] (const auto& f) { const auto& [function_name, function_ptr] = f; return dynamic_pointer_cast(function_ptr); }) | std::views::filter([] (shared_ptr f) { return f != nullptr; }); if (udfs.empty()) { throw exceptions::invalid_request_exception(format("Function '{}' not found in keyspace '{}'", name, ks)); } auto describer = [] (shared_ptr udf) { return udf->describe(cql3::with_create_statement::yes); }; co_return co_await generate_descriptions(udfs, describer, true); } future> functions(replica::database& db,const sstring& ks, with_create_statement with_stmt) { auto udfs = cql3::functions::instance().get_user_functions(ks); auto describer = [with_stmt] (shared_ptr udf) { return udf->describe(with_stmt); }; co_return co_await generate_descriptions(udfs, describer, true); } future> aggregate(replica::database& db, const sstring& ks, const sstring& name) { auto fs = functions::instance().find(functions::function_name(ks, name)); if (fs.empty()) { throw exceptions::invalid_request_exception(format("Aggregate '{}' not found in keyspace '{}'", name, ks)); } auto udas = fs | std::views::transform([] (const auto& f) { return dynamic_pointer_cast(f.second); }) | std::views::filter([] (shared_ptr f) { return f != nullptr; }); if (udas.empty()) { throw exceptions::invalid_request_exception(format("Aggregate '{}' not found in keyspace '{}'", name, ks)); } auto describer = [] (shared_ptr uda) { return uda->describe(with_create_statement::yes); }; co_return co_await generate_descriptions(udas, describer, true); } future> aggregates(replica::database& db, const sstring& ks, with_create_statement with_stmt) { auto udas = functions::instance().get_user_aggregates(ks); auto describer = [with_stmt] (shared_ptr uda) { return uda->describe(with_stmt); }; co_return co_await generate_descriptions(udas, describer, true); } description view(const data_dictionary::database& db, const sstring& ks, const sstring& name, bool with_internals) { auto view = db.try_find_table(ks, name); if (!view || !view->schema()->is_view()) { throw exceptions::invalid_request_exception(format("Materialized view '{}' not found in keyspace '{}'", name, ks)); } auto helper = replica::make_schema_describe_helper(view->schema(), db); // We want to get a `CREATE MATERIALIZED VIEW` statement, not a `CREATE INDEX` one. const auto actual_type = std::exchange(helper.type, schema_describe_helper::type::view); auto result = view->schema()->describe(helper, with_internals ? describe_option::STMTS_AND_INTERNALS : describe_option::STMTS); // However, if the view is the underlying materialized view of a secondary index, // we'd like to comment it out to implicitly convey that the statement should not // be executed to restore the schema entity. The user should restore the index instead. // It's the same for CDC log tables. if (actual_type == schema_describe_helper::type::index) { fragmented_ostringstream os{}; fmt::format_to(os.to_iter(), "/* Do NOT execute this statement! It's only for informational purposes.\n" " This materialized view is the underlying materialized view of a secondary\n" " index. It can be restored via restoring the index.\n" "\n{}\n" "*/", *result.create_statement); result.create_statement = std::move(os).to_managed_string(); } return result; } description index(const data_dictionary::database& db, const sstring& ks, const sstring& name, bool with_internals) { auto schema = db.find_indexed_table(ks, name); if (!schema) { throw exceptions::invalid_request_exception(format("Table for existing index '{}' not found in '{}'", name, ks)); } index_metadata im = schema->all_indices().find(name)->second; auto custom_class = secondary_index::secondary_index_manager::get_custom_class(im); if (custom_class) { auto desc = (*custom_class)->describe(im, *schema); if (desc) { return std::move(*desc); } } std::optional idx; auto views = db.find_table(ks, schema->cf_name()).views(); for (const auto& view: views) { if (is_index(db, view) && secondary_index::index_name_from_table_name(view->cf_name()) == name) { idx = view; break; } } return (**idx).describe(replica::make_schema_describe_helper(*idx, db), with_internals ? describe_option::STMTS_AND_INTERNALS : describe_option::STMTS); } // `base_name` should be a table with enabled cdc or a vector index std::optional describe_cdc_log_table(const data_dictionary::database& db, const sstring& ks, const sstring& base_name) { auto table = db.try_find_table(ks, cdc::log_name(base_name)); if (!table) { dlogger.warn("Couldn't find cdc log table for base table {}.{}", ks, base_name); return std::nullopt; } fragmented_ostringstream os; auto schema = table->schema(); auto describe_helper = replica::make_schema_describe_helper(schema, db); schema->describe_alter_with_properties(describe_helper, os); auto schema_desc = schema->describe(describe_helper, describe_option::NO_STMTS); schema_desc.create_statement = std::move(os).to_managed_string(); return schema_desc; } future> table(const data_dictionary::database& db, const sstring& ks, const sstring& name, bool with_internals) { auto table = db.try_find_table(ks, name); if (!table) { throw exceptions::invalid_request_exception(format("Table '{}' not found in keyspace '{}'", name, ks)); } auto s = validation::validate_column_family(db, ks, name); if (s->is_view()) { throw exceptions::invalid_request_exception("Cannot use DESC TABLE on materialized View"); } auto schema = table->schema(); auto idxs = table->get_index_manager().list_indexes(); auto views = table->views(); std::vector result; // table auto table_desc = schema->describe(replica::make_schema_describe_helper(schema, db), with_internals ? describe_option::STMTS_AND_INTERNALS : describe_option::STMTS); if (cdc::is_log_for_some_table(db.real_database(), ks, name)) { // If the table the user wants to describe is a CDC log table, we want to print it as a CQL comment. // This way, the user learns about the internals of the table, but they're also told not to execute it. fragmented_ostringstream os{}; fmt::format_to(os.to_iter(), "/* Do NOT execute this statement! It's only for informational purposes.\n" " A CDC log table is created automatically when creating the base with CDC\n" " enabled option or creating the vector index on the base table's vector column.\n" "\n{}\n" "*/", *table_desc.create_statement); table_desc.create_statement = std::move(os).to_managed_string(); } result.push_back(std::move(table_desc)); // indexes std::ranges::sort(idxs, std::ranges::less(), [] (const auto& a) { return a.metadata().name(); }); for (const auto& idx: idxs) { result.push_back(index(db, ks, idx.metadata().name(), with_internals)); co_await coroutine::maybe_yield(); } //views std::ranges::sort(views, std::ranges::less(), std::mem_fn(&schema::cf_name)); for (const auto& v: views) { if(!is_index(db, v)) { result.push_back(view(db, ks, v->cf_name(), with_internals)); } co_await coroutine::maybe_yield(); } if (cdc::cdc_enabled(*schema)) { auto cdc_log_alter = describe_cdc_log_table(db, ks, name); if (cdc_log_alter) { result.push_back(std::move(*cdc_log_alter)); } } co_return result; } future> tables(const data_dictionary::database& db, const lw_shared_ptr& ks, std::optional with_internals = std::nullopt) { auto& replica_db = db.real_database(); auto tables = ks->tables() | std::views::filter([&replica_db] (const schema_ptr& s) { return !cdc::is_log_for_some_table(replica_db, s->ks_name(), s->cf_name()); }) | std::ranges::to>(); std::ranges::sort(tables, std::ranges::less(), std::mem_fn(&schema::cf_name)); if (with_internals) { std::vector result; for (const auto& t: tables) { auto tables_desc = co_await table(db, ks->name(), t->cf_name(), *with_internals); result.insert(result.end(), std::make_move_iterator(tables_desc.begin()), std::make_move_iterator(tables_desc.end())); co_await coroutine::maybe_yield(); } co_return result; } co_return tables | std::views::transform([&db] (auto&& t) { return t->describe(replica::make_schema_describe_helper(t, db), describe_option::NO_STMTS); }) | std::ranges::to(); } // DESCRIBE UTILITY // Various utility functions to make description easier. /** * Wrapper over `data_dictionary::database::find_keyspace()` * * @return `data_dictionary::keyspace_metadata` for specified keyspace name * @throw `invalid_request_exception` if there is no such keyspace */ lw_shared_ptr get_keyspace_metadata(const data_dictionary::database& db, const sstring& keyspace) { auto ks = db.try_find_keyspace(keyspace); if (!ks) { throw exceptions::invalid_request_exception(format("'{}' not found in keyspaces", keyspace)); } return ks->metadata(); } /** * Lists keyspace elements for given keyspace * * @return vector of `description` for the specified keyspace element type in the specified keyspace. Descriptions don't contain create_statements. * @throw `invalid_request_exception` if there is no such keyspace */ future> list_elements(const data_dictionary::database& db, const sstring& ks, element_type element) { auto ks_meta = get_keyspace_metadata(db, ks); auto& replica_db = db.real_database(); switch (element) { case element_type::type: co_return co_await types(replica_db, ks_meta, with_create_statement::no); case element_type::function: co_return co_await functions(replica_db, ks, with_create_statement::no); case element_type::aggregate: co_return co_await aggregates(replica_db, ks, with_create_statement::no); case element_type::table: co_return co_await tables(db, ks_meta); case element_type::keyspace: co_return wrap_in_vector(ks_meta->describe(replica_db, with_create_statement::no)); case element_type::view: case element_type::index: on_internal_error(dlogger, "listing of views and indexes is unsupported"); } } /** * Describe specified keyspace element type in given keyspace * * @return `description` of the specified keyspace element type in the specified keyspace. Description contains create_statement. * @throw `invalid_request_exception` if there is no such keyspace or there is no element with given name */ future> describe_element(const data_dictionary::database& db, const sstring& ks, element_type element, const sstring& name, bool with_internals) { auto ks_meta = get_keyspace_metadata(db, ks); auto& replica_db = db.real_database(); switch (element) { case element_type::type: co_return wrap_in_vector(type(replica_db, ks_meta, name)); case element_type::function: co_return co_await function(replica_db, ks, name); case element_type::aggregate: co_return co_await aggregate(replica_db, ks, name); case element_type::table: co_return co_await table(db, ks, name, with_internals); case element_type::index: co_return wrap_in_vector(index(db, ks, name, with_internals)); case element_type::view: co_return wrap_in_vector(view(db, ks, name, with_internals)); case element_type::keyspace: co_return wrap_in_vector(ks_meta->describe(replica_db, with_create_statement::yes)); } } /** * Describe all elements in given keyspace. * Elements order: keyspace, user-defined types, user-defined functions, user-defined aggregates, tables * Table description contains: table, indexes, views * * @return `description`s of all elements in specified keyspace. Descriptions contain create_statements. * @throw `invalid_request_exception` if there is no such keyspace or there is no element with given name */ future> describe_all_keyspace_elements(const data_dictionary::database& db, const sstring& ks, bool with_internals) { auto ks_meta = get_keyspace_metadata(db, ks); auto& replica_db = db.real_database(); std::vector result; auto inserter = [&result] (std::vector&& elements) mutable { result.insert(result.end(), std::make_move_iterator(elements.begin()), std::make_move_iterator(elements.end())); }; result.push_back(ks_meta->describe(replica_db, with_create_statement::yes)); inserter(co_await types(replica_db, ks_meta, with_create_statement::yes)); inserter(co_await functions(replica_db, ks, with_create_statement::yes)); inserter(co_await aggregates(replica_db, ks, with_create_statement::yes)); inserter(co_await tables(db, ks_meta, with_internals)); co_return result; } } // Generic column specification for element describe statement std::vector> get_listing_column_specifications() { return std::vector> { make_lw_shared("system", "describe", ::make_shared("keyspace_name", true), utf8_type), make_lw_shared("system", "describe", ::make_shared("type", true), utf8_type), make_lw_shared("system", "describe", ::make_shared("name", true), utf8_type) }; } // Generic column specification for listing describe statement std::vector> get_element_column_specifications() { auto col_specs = get_listing_column_specifications(); col_specs.push_back(make_lw_shared("system", "describe", ::make_shared("create_statement", true), utf8_type)); return col_specs; } std::vector> serialize_descriptions(std::vector&& descs, bool serialize_create_statement = true) { return descs | std::views::as_rvalue | std::views::transform([serialize_create_statement] (description desc) { return std::move(desc).serialize(serialize_create_statement); }) | std::ranges::to(); } // DESCRIBE STATEMENT describe_statement::describe_statement() : cql_statement(&timeout_config::other_timeout) {} uint32_t describe_statement::get_bound_terms() const { return 0; } bool describe_statement::depends_on(std::string_view ks_name, std::optional cf_name) const { return false; } future<> describe_statement::check_access(query_processor& qp, const service::client_state& state) const { state.validate_login(); co_return; } seastar::shared_ptr describe_statement::get_result_metadata() const { return ::make_shared(get_column_specifications()); } seastar::future> describe_statement::execute(cql3::query_processor& qp, service::query_state& state, const query_options& options, std::optional guard) const { auto& client_state = state.get_client_state(); auto descriptions = co_await describe(qp, client_state); auto result = std::make_unique(get_column_specifications(qp.proxy().local_db(), client_state)); for (auto&& row: descriptions) { result->add_row(std::move(row)); } co_return ::make_shared(cql3::result(std::move(result))); } // CLUSTER DESCRIBE STATEMENT std::pair range_ownership_type() { auto list_type = list_type_impl::get_instance(utf8_type, false); auto map_type = map_type_impl::get_instance( utf8_type, list_type, false ); return {list_type, map_type}; } cluster_describe_statement::cluster_describe_statement() : describe_statement() {} std::vector> cluster_describe_statement::get_column_specifications() const { return std::vector> { make_lw_shared("system", "describe", ::make_shared("cluster", true), utf8_type), make_lw_shared("system", "describe", ::make_shared("partitioner", true), utf8_type), make_lw_shared("system", "describe", ::make_shared("snitch", true), utf8_type) }; } std::vector> cluster_describe_statement::get_column_specifications(replica::database& db, const service::client_state& client_state) const { auto spec = get_column_specifications(); if (should_add_range_ownership(db, client_state)) { auto map_type = range_ownership_type().second; spec.push_back( make_lw_shared("system", "describe", ::make_shared("range_ownership", true), map_type) ); } return spec; } bool cluster_describe_statement::should_add_range_ownership(replica::database& db, const service::client_state& client_state) const { auto ks = client_state.get_raw_keyspace(); //TODO: produce range ownership for tables using tablets too bool uses_tablets = false; try { uses_tablets = !ks.empty() && db.find_keyspace(ks).uses_tablets(); } catch (const data_dictionary::no_such_keyspace&) { // ignore } return !ks.empty() && !is_system_keyspace(ks) && !is_internal_keyspace(ks) && !uses_tablets; } future cluster_describe_statement::range_ownership(const service::storage_proxy& proxy, const sstring& ks) const { auto [list_type, map_type] = range_ownership_type(); auto ranges = co_await proxy.describe_ring(ks); std::ranges::sort(ranges, std::ranges::less(), [] (const dht::token_range_endpoints& r) { return std::stol(r._start_token); }); auto ring_ranges = ranges | std::views::transform([list_type = std::move(list_type)] (auto& range) { auto token_end = data_value(range._end_token); auto endpoints = range._endpoints | std::views::transform([] (const auto& endpoint) { return data_value(endpoint); }) | std::ranges::to(); auto endpoints_list = make_list_value(list_type, endpoints); return std::pair(token_end, endpoints_list); }) | std::ranges::to(); co_return make_map_value(map_type, map_type_impl::native_type( std::make_move_iterator(ring_ranges.begin()), std::make_move_iterator(ring_ranges.end()) )).serialize(); } future>> cluster_describe_statement::describe(cql3::query_processor& qp, const service::client_state& client_state) const { auto db = qp.db(); auto& proxy = qp.proxy(); auto cluster = to_managed_bytes(db.get_config().cluster_name()); auto partitioner = to_managed_bytes(db.get_config().partitioner()); auto snitch = to_managed_bytes(db.get_config().endpoint_snitch()); std::vector row { {cluster}, {partitioner}, {snitch} }; if (should_add_range_ownership(proxy.local_db(), client_state)) { auto ro_map = co_await range_ownership(proxy, client_state.get_raw_keyspace()); row.push_back(std::move(ro_map)); } std::vector> result {std::move(row)}; co_return result; } // SCHEMA DESCRIBE STATEMENT schema_describe_statement::schema_describe_statement(bool full_schema, bool with_hashed_passwords, bool with_internals) : describe_statement() , _config(schema_desc{.full_schema = full_schema, .with_hashed_passwords = with_hashed_passwords}) , _with_internals(with_internals) {} schema_describe_statement::schema_describe_statement(std::optional keyspace, bool only, bool with_internals) : describe_statement() , _config(keyspace_desc{keyspace, only}) , _with_internals(with_internals) {} std::vector> schema_describe_statement::get_column_specifications() const { return get_element_column_specifications(); } future>> schema_describe_statement::describe(cql3::query_processor& qp, const service::client_state& client_state) const { auto db = qp.db(); auto result = co_await std::visit(overloaded_functor{ [&] (const schema_desc& config) -> future> { auto& auth_service = *client_state.get_auth_service(); if (config.with_hashed_passwords) { const auto maybe_user = client_state.user(); if (!maybe_user || !co_await auth::has_superuser(auth_service, *maybe_user)) { co_await coroutine::return_exception(exceptions::unauthorized_exception( "DESCRIBE SCHEMA WITH INTERNALS AND PASSWORDS can only be issued by a superuser")); } } auto keyspaces = config.full_schema ? db.get_all_keyspaces() : db.get_user_keyspaces(); std::vector schema_result; for (auto&& ks: keyspaces) { if (!config.full_schema && db.extensions().is_extension_internal_keyspace(ks)) { continue; } auto ks_result = co_await describe_all_keyspace_elements(db, ks, _with_internals); schema_result.insert(schema_result.end(), std::make_move_iterator(ks_result.begin()), std::make_move_iterator(ks_result.end())); } if (_with_internals) { // The order is important here. We need to first restore auth // because we can only attach a service level to an existing role. auto auth_descs = co_await auth_service.describe_auth(config.with_hashed_passwords); auto service_level_descs = co_await client_state.get_service_level_controller().describe_service_levels(); schema_result.insert(schema_result.end(), std::make_move_iterator(auth_descs.begin()), std::make_move_iterator(auth_descs.end())); schema_result.insert(schema_result.end(), std::make_move_iterator(service_level_descs.begin()), std::make_move_iterator(service_level_descs.end())); } co_return schema_result; }, [&] (const keyspace_desc& config) -> future> { auto ks = client_state.get_raw_keyspace(); if (config.keyspace) { ks = *config.keyspace; } if (config.only_keyspace) { auto ks_meta = get_keyspace_metadata(db, ks); auto ks_desc = ks_meta->describe(db.real_database(), with_create_statement::yes); co_return wrap_in_vector(std::move(ks_desc)); } else { co_return co_await describe_all_keyspace_elements(db, ks, _with_internals); } } }, _config); co_return serialize_descriptions(std::move(result)); } // LISTING DESCRIBE STATEMENT listing_describe_statement::listing_describe_statement(element_type element, bool with_internals) : describe_statement() , _element(element) , _with_internals(with_internals) {} std::vector> listing_describe_statement::get_column_specifications() const { return get_listing_column_specifications(); } future>> listing_describe_statement::describe(cql3::query_processor& qp, const service::client_state& client_state) const { auto db = qp.db(); auto raw_ks = client_state.get_raw_keyspace(); std::vector keyspaces; if (!raw_ks.empty()) { keyspaces.push_back(raw_ks); } else { keyspaces = db.get_all_keyspaces(); std::ranges::sort(keyspaces); } std::vector result; for (auto&& ks: keyspaces) { auto ks_result = co_await list_elements(db, ks, _element); result.insert(result.end(), std::make_move_iterator(ks_result.begin()), std::make_move_iterator(ks_result.end())); co_await coroutine::maybe_yield(); } co_return serialize_descriptions(std::move(result), false); } // ELEMENT DESCRIBE STATEMENT element_describe_statement::element_describe_statement(element_type element, std::optional keyspace, sstring name, bool with_internals) : describe_statement() , _element(element) , _keyspace(std::move(keyspace)) , _name(std::move(name)) , _with_internals(with_internals) {} std::vector> element_describe_statement::get_column_specifications() const { return get_element_column_specifications(); } future>> element_describe_statement::describe(cql3::query_processor& qp, const service::client_state& client_state) const { auto ks = client_state.get_raw_keyspace(); if (_keyspace) { ks = *_keyspace; } if (ks.empty()) { throw exceptions::invalid_request_exception("No keyspace specified and no current keyspace"); } co_return serialize_descriptions(co_await describe_element(qp.db(), ks, _element, _name, _with_internals)); } //GENERIC DESCRIBE STATEMENT generic_describe_statement::generic_describe_statement(std::optional keyspace, sstring name, bool with_internals) : describe_statement() , _keyspace(std::move(keyspace)) , _name(std::move(name)) , _with_internals(with_internals) {} std::vector> generic_describe_statement::get_column_specifications() const { return get_element_column_specifications(); } future>> generic_describe_statement::describe(cql3::query_processor& qp, const service::client_state& client_state) const { auto db = qp.db(); auto& replica_db = db.real_database(); auto raw_ks = client_state.get_raw_keyspace(); auto ks_name = (_keyspace) ? *_keyspace : raw_ks; if (!_keyspace) { auto ks = db.try_find_keyspace(_name); if (ks) { co_return serialize_descriptions(co_await describe_all_keyspace_elements(db, ks->metadata()->name(), _with_internals)); } else if (raw_ks.empty()) { throw exceptions::invalid_request_exception(format("'{}' not found in keyspaces", _name)); } } auto ks = db.try_find_keyspace(ks_name); if (!ks) { throw exceptions::invalid_request_exception(format("'{}' not found in keyspaces", _name)); } auto ks_meta = ks->metadata(); auto tbl = db.try_find_table(ks_name, _name); if (tbl) { if (tbl->schema()->is_view()) { co_return serialize_descriptions(wrap_in_vector(view(db, ks_name, _name, _with_internals))); } else { co_return serialize_descriptions(co_await table(db, ks_name, _name, _with_internals)); } } auto idx_tbl = db.find_indexed_table(ks_name, _name); if (idx_tbl) { co_return serialize_descriptions(wrap_in_vector(index(db, ks_name, _name, _with_internals))); } auto udt_meta = ks_meta->user_types(); if (udt_meta.has_type(to_bytes(_name))) { co_return serialize_descriptions(wrap_in_vector(type(replica_db, ks_meta, _name))); } auto uf = functions::instance().find(functions::function_name(ks_name, _name)); if (!uf.empty()) { auto udfs = uf | std::views::transform([] (const auto& f) { const auto& [function_name, function_ptr] = f; return dynamic_pointer_cast(function_ptr); }) | std::views::filter([] (shared_ptr f) { return f != nullptr; }); auto udf_describer = [] (shared_ptr udf) { return udf->describe(with_create_statement::yes); }; if (!udfs.empty()) { co_return serialize_descriptions(co_await generate_descriptions(udfs, udf_describer, true)); } auto udas = uf | std::views::transform([] (const auto& f) { const auto& [function_name, function_ptr] = f; return dynamic_pointer_cast(function_ptr); }) | std::views::filter([] (shared_ptr f) { return f != nullptr; }); auto uda_describer = [] (shared_ptr uda) { return uda->describe(with_create_statement::yes); }; if (!udas.empty()) { co_return serialize_descriptions(co_await generate_descriptions(udas, uda_describer, true)); } } throw exceptions::invalid_request_exception(format("'{}' not found in keyspace '{}'", _name, ks_name)); } namespace raw { using ds = describe_statement; describe_statement::describe_statement(ds::describe_config config) : _config(std::move(config)), _with_internals(false) {} void describe_statement::with_internals_details(bool with_hashed_passwords) { _with_internals = internals(true); if (with_hashed_passwords && !std::holds_alternative(_config)) { throw exceptions::invalid_request_exception{"Option WITH PASSWORDS is only allowed with DESC SCHEMA"}; } if (std::holds_alternative(_config)) { std::get(_config).with_hashed_passwords = with_hashed_passwords; } } audit::statement_category describe_statement::category() const { return audit::statement_category::QUERY; } audit::audit_info_ptr describe_statement::audit_info() const { return audit::audit::create_audit_info(category(), "system", ""); } std::unique_ptr describe_statement::prepare(data_dictionary::database db, cql_stats &stats) { bool internals = bool(_with_internals); auto desc_stmt = std::visit(overloaded_functor{ [] (const describe_cluster&) -> ::shared_ptr { return ::make_shared(); }, [&] (const describe_schema& cfg) -> ::shared_ptr { return ::make_shared(cfg.full_schema, cfg.with_hashed_passwords, internals); }, [&] (const describe_keyspace& cfg) -> ::shared_ptr { return ::make_shared(std::move(cfg.keyspace), cfg.only_keyspace, internals); }, [&] (const describe_listing& cfg) -> ::shared_ptr { return ::make_shared(cfg.element_type, internals); }, [&] (const describe_element& cfg) -> ::shared_ptr { return ::make_shared(cfg.element_type, std::move(cfg.keyspace), std::move(cfg.name), internals); }, [&] (const describe_generic& cfg) -> ::shared_ptr { return ::make_shared(std::move(cfg.keyspace), std::move(cfg.name), internals); } }, _config); return std::make_unique(audit_info(), desc_stmt); } std::unique_ptr describe_statement::cluster() { return std::make_unique(ds::describe_cluster()); } std::unique_ptr describe_statement::schema(bool full) { return std::make_unique(ds::describe_schema{.full_schema = full}); } std::unique_ptr describe_statement::keyspaces() { return std::make_unique(ds::describe_listing{.element_type = ds::element_type::keyspace}); } std::unique_ptr describe_statement::keyspace(std::optional keyspace, bool only) { return std::make_unique(ds::describe_keyspace{.keyspace = keyspace, .only_keyspace = only}); } std::unique_ptr describe_statement::tables() { return std::make_unique(ds::describe_listing{.element_type = ds::element_type::table}); } std::unique_ptr describe_statement::table(const cf_name& cf_name) { auto ks = (cf_name.has_keyspace()) ? std::optional(cf_name.get_keyspace()) : std::nullopt; return std::make_unique(ds::describe_element{.element_type = ds::element_type::table, .keyspace = ks, .name = cf_name.get_column_family()}); } std::unique_ptr describe_statement::index(const cf_name& cf_name) { auto ks = (cf_name.has_keyspace()) ? std::optional(cf_name.get_keyspace()) : std::nullopt; return std::make_unique(ds::describe_element{.element_type = ds::element_type::index, .keyspace = ks, .name = cf_name.get_column_family()}); } std::unique_ptr describe_statement::view(const cf_name& cf_name) { auto ks = (cf_name.has_keyspace()) ? std::optional(cf_name.get_keyspace()) : std::nullopt; return std::make_unique(ds::describe_element{.element_type = ds::element_type::view, .keyspace = ks, .name = cf_name.get_column_family()}); } std::unique_ptr describe_statement::types() { return std::make_unique(ds::describe_listing{.element_type = ds::element_type::type}); } std::unique_ptr describe_statement::type(const ut_name& ut_name) { auto ks = (ut_name.has_keyspace()) ? std::optional(ut_name.get_keyspace()) : std::nullopt; return std::make_unique(ds::describe_element{.element_type = ds::element_type::type, .keyspace = ks, .name = ut_name.get_string_type_name()}); } std::unique_ptr describe_statement::functions() { return std::make_unique(ds::describe_listing{.element_type = ds::element_type::function}); } std::unique_ptr describe_statement::function(const functions::function_name& fn_name) { auto ks = (fn_name.has_keyspace()) ? std::optional(fn_name.keyspace) : std::nullopt; return std::make_unique(ds::describe_element{.element_type = ds::element_type::function, .keyspace = ks, .name = fn_name.name}); } std::unique_ptr describe_statement::aggregates() { return std::make_unique(ds::describe_listing{.element_type = ds::element_type::aggregate}); } std::unique_ptr describe_statement::aggregate(const functions::function_name& fn_name) { auto ks = (fn_name.has_keyspace()) ? std::optional(fn_name.keyspace) : std::nullopt; return std::make_unique(ds::describe_element{.element_type = ds::element_type::aggregate, .keyspace = ks, .name = fn_name.name}); } std::unique_ptr describe_statement::generic(std::optional keyspace, const sstring& name) { return std::make_unique(ds::describe_generic{.keyspace = keyspace, .name = name}); } } // namespace raw } // namespace statements } // namespace cql3