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;
}