devapi/result.cc (439 lines of code) (raw):

/* * Copyright (c) 2015, 2024, Oracle and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, as * published by the Free Software Foundation. * * This program is designed to work with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, as * designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an additional * permission to link the program and your derivative works with the * separately licensed software that they have either included with * the program or referenced in the documentation. * * Without limiting anything contained in the foregoing, this file, * which is part of Connector/C++, is also subject to the * Universal FOSS Exception, version 1.0, a copy of which can be found at * https://oss.oracle.com/licenses/universal-foss-exception. * * This program 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, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include <mysql/cdk.h> #include <mysqlx/xdevapi.h> #include "impl.h" #include <vector> #include <sstream> #include <iomanip> #include <cctype> /* Implementation of Result and Row interfaces. */ using namespace ::mysqlx::impl::common; using namespace ::mysqlx::internal; using namespace ::mysqlx; using std::endl; class mysqlx::bytes::Access { public: static bytes mk(const cdk::bytes &data) { return bytes(data.begin(), data.end()); } }; /* Column implementation. */ void Column_detail::print(std::ostream &out) const { if (!get_impl().m_schema_name.empty()) out << "`" << get_impl().m_schema_name << "`."; string table_name = get_table_label(); if (!table_name.empty()) out << "`" << table_name << "`."; out << "`" << get_label() <<"`"; } /* Method getType() translates CDK type/format info into DevAPI type information. Note: Expected to return values of Type enum constants. */ Type get_api_type(cdk::Type_info, const Format_info &); unsigned Column_detail::get_type() const { return unsigned(get_api_type(get_impl().m_type, get_impl())); } Type get_api_type(cdk::Type_info type, const Format_info &fmt) { switch (type) { case cdk::TYPE_BYTES: return Type::BYTES; case cdk::TYPE_DOCUMENT: return Type::JSON; case cdk::TYPE_STRING: { const Format_descr<cdk::TYPE_STRING> &fd = fmt.get<cdk::TYPE_STRING>(); if (fd.m_format.is_enum()) return Type::ENUM; if (fd.m_format.is_set()) return Type::SET; return Type::STRING; } case cdk::TYPE_INTEGER: { const Format_descr<cdk::TYPE_INTEGER> &fd = fmt.get<cdk::TYPE_INTEGER>(); if(fd.m_format.is_bit()) return Type::BIT; size_t f_len = fd.m_format.length(); if (f_len < 5) return Type::TINYINT; if (f_len < 8) return Type::SMALLINT; if (f_len < 10) return Type::MEDIUMINT; if (f_len < 20) return Type::INT; return Type::BIGINT; } case cdk::TYPE_FLOAT: { const Format_descr<cdk::TYPE_FLOAT> &fd = fmt.get<cdk::TYPE_FLOAT>(); switch (fd.m_format.type()) { case cdk::Format<cdk::TYPE_FLOAT>::DOUBLE: return Type::DOUBLE; case cdk::Format<cdk::TYPE_FLOAT>::FLOAT: return Type::FLOAT; case cdk::Format<cdk::TYPE_FLOAT>::DECIMAL: return Type::DECIMAL; default: THROW("Unrecognized float value encoding format"); } } case cdk::TYPE_DATETIME: { const Format_descr<cdk::TYPE_DATETIME> &fd = fmt.get<cdk::TYPE_DATETIME>(); switch (fd.m_format.type()) { case cdk::Format<cdk::TYPE_DATETIME>::TIME: return Type::TIME; case cdk::Format<cdk::TYPE_DATETIME>::TIMESTAMP: return Type::TIMESTAMP; case cdk::Format<cdk::TYPE_DATETIME>::DATETIME: return fd.m_format.has_time() ? Type::DATETIME : Type::DATE; default: THROW("Unrecognized temporal value encoding format"); } } case cdk::TYPE_GEOMETRY: return Type::GEOMETRY; case cdk::TYPE_XML: default: return Type::BYTES; } } mysqlx::string Column_detail::get_name() const { return get_impl().m_name; } mysqlx::string Column_detail::get_label() const { return get_impl().m_label; } mysqlx::string Column_detail::get_schema_name() const { return get_impl().m_schema_name; } mysqlx::string Column_detail::get_table_name() const { return get_impl().m_table_name; } mysqlx::string Column_detail::get_table_label() const { return get_impl().m_table_label; } unsigned long Column_detail::get_length() const { return get_impl().m_length; } unsigned short Column_detail::get_decimals() const { return get_impl().m_decimals; } bool Column_detail::is_signed() const { if (cdk::TYPE_INTEGER != get_impl().m_type) return false; const Format_descr<cdk::TYPE_INTEGER> &fd = get_impl().get<cdk::TYPE_INTEGER>(); return !fd.m_format.is_unsigned(); } bool Column_detail::is_padded() const { return get_impl().m_padded; } /* Handling character set and collation information ----------------------------------------------- This information is obtained from format descriptor for columns of CDK STRING type. Format descriptor gives the MySQL collation id as given by the server. Function collation_from_charset_id() returns CollationInfo constant corresponding to given collation id. This CollationInfo instance can be then used to get collation name and the corresponding charcater set. */ #define CS_SWITCH(CS) COLLATIONS_##CS(COLL_SWITCH) #define COLL_SWITCH(CS,ID,COLL,CASE) \ case ID: return Collation<CharacterSet::CS>::COLL_CONST_NAME(COLL,CASE); const CollationInfo& collation_from_id(cdk::collation_id_t id) { switch (id) { CDK_CS_LIST(CS_SWITCH) default: THROW("Unknown collation id"); } } const CollationInfo& Column_detail::get_collation() const { try { switch (get_impl().m_type) { case cdk::TYPE_BYTES: return Collation<CharacterSet::binary>::bin; case cdk::TYPE_DOCUMENT: return Collation<CharacterSet::utf8mb3>::general_ci; case cdk::TYPE_STRING: { return collation_from_id(get_impl().m_collation); } case cdk::TYPE_INTEGER: case cdk::TYPE_FLOAT: case cdk::TYPE_DATETIME: default: THROW("No collation info for the type"); } } CATCH_AND_WRAP } CharacterSet Column_detail::get_charset() const { // TODO: Better use cdk encoding format information //const Format_descr<cdk::TYPE_STRING> &fd = m_impl->get<cdk::TYPE_STRING>(); return get_collation().getCharacterSet(); } /* Definitions of the CollationInfo constants describing all known collations as defined in mysqlx/collations.h. */ struct CollationInfo::Access { enum coll_case { case_ci = CollationInfo::case_ci, case_ai_ci = case_ci, case_as_ci = case_ci, case_cs = CollationInfo::case_cs, case_as_cs = case_cs, case_as_cs_ks = case_cs, case_bin = CollationInfo::case_bin }; static CollationInfo mk( CharacterSet _cs, unsigned _id, coll_case _case, const char *_name ) { CollationInfo ci; ci.m_cs = _cs; ci.m_id = _id; ci.m_case = CollationInfo::coll_case(_case); ci.m_name = _name; return ci; } }; /* A helper function that reconstructs MySQL collation name from the data given by COLLATIONS_XXX() lists. In most cases the collation name is just a concatenation of charset name, collation and sensitivity flags - this default name is passed as 'name' pre-allocated string. But there are few exceptions to the general rule: 'name_bin' is the name to be used for binary collations; also, individual components of the name are given to allow further customization. */ const char* coll_name( std::string cs, std::string coll, std::string sensitivity, const char *name, const char *name_bin) { static std::list<std::string> special; /* For generic UCA collations, such as uca0900, the "uca" prefix is not present in the MySQL collation name. For example, for the uca0900 collation with "ai_ci" sensitivity, the collation name is "utf8mb4_0900_ai_ci" but the value of 'name' is "utf8mb4_uca0900_ai_ci", so we need to correct this. */ if (coll.substr(0,3) == "uca") { special.push_back(cs + "_" + coll.substr(3) + "_" + sensitivity); return special.back().c_str(); } if (sensitivity == "bin") { // Note: special exception for "binary" collation (no _bin suffix) return cs == "binary" ? "binary" : name_bin; } else return name; } #define COLL_DEFS(CS) COLLATIONS_##CS(COLL_CONST_DEF) #define COLL_CONST_DEF(CS,ID,COLL,CASE) \ const CollationInfo \ Collation<CharacterSet::CS>::COLL_CONST_NAME(COLL,CASE) = \ CollationInfo::Access::mk(CharacterSet::CS, ID, \ CollationInfo::Access::case_##CASE, \ COLL_NAME(CS,COLL,CASE)); #define COLL_NAME(CS,COLL,CASE) \ coll_name(#CS, #COLL, #CASE, #CS "_" #COLL "_" #CASE, #CS "_bin") // Add utf8mb4 alias for bin collation for compatibility #undef COLLATIONS_utf8mb4_EXTRA #define COLLATIONS_utf8mb4_EXTRA \ const CollationInfo Collation<CharacterSet::utf8mb4>::utf8mb4 = \ Collation<CharacterSet::utf8mb4>::bin; CDK_CS_LIST(COLL_DEFS) #undef COLLATIONS_utf8mb4_EXTRA #define COLLATIONS_utf8mb4_EXTRA /* Handling result data ==================== */ /* Implementation of Row class --------------------------- */ Row_detail::Impl& Row_detail::get_impl() { if (!m_impl) THROW("Attempt to use null Row instance"); return *m_impl; } void Row_detail::ensure_impl() { if (!m_impl) m_impl = std::make_shared<Impl>(); } /* Decoding document values from raw bytes representation. Note: Conversions for other value types are handled by common::Value class. */ mysqlx::Value mysqlx::Value::Access::mk(cdk::bytes data, Format_descr<cdk::TYPE_DOCUMENT>&) { /* Note: this assumes that document is represented as json string - thanks to this we can take benefit of lazy parsing. Otherwise, implementation that would not assume what underlying representation is used for documnets should use a Codec to decode the raw bytes and build a representation of the documnent to be stored in the Value instance. */ // trim initial space unsigned i; for (i = 0; i < data.size() && std::isspace(*(data.begin() + i)); ++i); std::string json(data.begin() + i, data.end()-1); if ('{' == *(data.begin() + i)) return Value::Access::mk_doc(json); return Value::Access::mk_from_json(json); } mysqlx::col_count_t Row_detail::col_count() const { return get_impl().col_count(); } mysqlx::bytes Row_detail::get_bytes(mysqlx::col_count_t pos) const { cdk::bytes data = get_impl().m_data.at(pos).data(); return mysqlx::bytes::Access::mk(data); } mysqlx::Value& Row_detail::get_val(mysqlx::col_count_t pos) { return get_impl().get(pos); } void Row_detail::process_one( std::pair<Impl*, mysqlx::col_count_t> *data, const mysqlx::Value &val ) { Impl *impl = data->first; col_count_t pos = (data->second)++; impl->set(pos, val); } /* Result_detail ============= */ Result_detail::Result_detail(Result_init &init) { m_owns_impl = true; m_impl = new Impl(init); } Result_detail::~Result_detail() NOEXCEPT { try { if (m_owns_impl) delete m_impl; } catch(...) {} } auto Result_detail::operator=(Result_detail &&other) -> Result_detail& { if (m_impl && m_owns_impl) delete m_impl; m_impl = other.m_impl; if (!other.m_owns_impl) m_owns_impl = false; else { m_owns_impl = true; other.m_owns_impl = false; } return *this; } auto Result_detail::get_impl() -> Impl& { if (!m_impl) THROW("Invalid result set"); return *m_impl; } void Result_detail::check_result() const { if (!get_impl().has_data()) THROW("No result set"); } unsigned Result_detail::get_warning_count() const { return get_impl().get_warning_count(); } auto Result_detail::get_warning(size_t pos) -> Warning { if (!common::check_num_limits<unsigned>(pos)) throw std::out_of_range("No diagnostic entry at position ..."); get_warning_count(); auto &impl = get_impl(); auto &it = impl.get_entries(cdk::api::Severity::WARNING); size_t curr = SIZE_MAX; while( curr != pos && it.next()) { curr++; } if (curr != pos || pos >= get_warning_count() ) throw std::out_of_range("No diagnostic entry at position ..."); byte level = Warning::LEVEL_ERROR; switch (it.entry().severity()) { case cdk::api::Severity::ERROR: level = Warning::LEVEL_ERROR; break; case cdk::api::Severity::WARNING: level = Warning::LEVEL_WARNING; break; case cdk::api::Severity::INFO: level = Warning::LEVEL_INFO; break; } // TODO: handle error category return Warning_detail( level, (uint16_t)it.entry().code().value(), it.entry().description() ); } auto Result_detail::get_affected_rows() const -> uint64_t { return get_impl().get_affected_rows(); } auto Result_detail::get_auto_increment() const -> uint64_t { return get_impl().get_auto_increment(); } auto Result_detail::get_generated_ids() const -> DocIdList { return get_impl().get_generated_ids(); } bool Result_detail::has_data() const { return get_impl().has_data(); } bool Result_detail::next_result() { return get_impl().next_result(); } /* RowResult ========= */ template<> bool Row_result_detail<Columns>::iterator_next() { auto &impl = get_impl(); const Row_data *row = impl.get_row(); if (!row) return false; m_row = internal::Row_detail( std::make_shared<internal::Row_detail::Impl>(*row, impl.get_mdata()) ); return true; } template<> mysqlx::col_count_t Row_result_detail<Columns>::col_count() const { return get_impl().get_col_count(); } template<> Row_result_detail<Columns>::Row_result_detail(Result_init &init) : Result_detail(init) { next_result(); } template<> auto Row_result_detail<Columns>::get_column(mysqlx::col_count_t pos) const -> const Column& { return m_cols.at(pos); } template<> auto Row_result_detail<Columns>::get_columns() const -> const Columns& { return m_cols; } template<> void Columns_detail<Column>::init(const Result_detail::Impl &impl) { clear(); for (col_count_t pos = 0; pos < impl.get_col_count(); ++pos) { emplace_back(&impl.get_column(pos)); } } template<> mysqlx::row_count_t Row_result_detail<Columns>::row_count() { auto cnt = get_impl().count(); ASSERT_NUM_LIMITS(row_count_t, cnt); return (row_count_t)cnt; } /* DocResult ========= */ bool Doc_result_detail::iterator_next() { auto &impl = get_impl(); const Row_data *row = impl.get_row(); if (impl.entry_count()) impl.get_error().rethrow(); if (!row) return false; // @todo Avoid copying of document string. cdk::foundation::bytes data = row->at(0).data(); m_cur_doc = DbDoc(std::string(data.begin(),data.end()-1)); return true; } uint64_t Doc_result_detail::count() { auto cnt = get_impl().count(); if (get_impl().entry_count() > 0) get_impl().get_error().rethrow(); return cnt; }