Added thirdparty: boost library

This commit is contained in:
Viacheslav Demydiuk
2024-01-06 19:55:56 +02:00
parent bf49f439e1
commit bccd1e7051
15683 changed files with 3239840 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_ANY_STREAM_IMPL_IPP
#define BOOST_MYSQL_IMPL_ANY_STREAM_IMPL_IPP
#pragma once
#include <boost/mysql/detail/any_stream_impl.hpp>
#ifdef BOOST_MYSQL_SEPARATE_COMPILATION
template class boost::mysql::detail::any_stream_impl<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>;
template class boost::mysql::detail::any_stream_impl<boost::asio::ip::tcp::socket>;
#endif
#endif
+54
View File
@@ -0,0 +1,54 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_CHANNEL_PTR_IPP
#define BOOST_MYSQL_IMPL_CHANNEL_PTR_IPP
#pragma once
#include <boost/mysql/detail/channel_ptr.hpp>
#include <boost/mysql/impl/internal/channel/channel.hpp>
boost::mysql::detail::channel_ptr::channel_ptr(std::size_t read_buff_size, std::unique_ptr<any_stream> stream)
: chan_(new channel(read_buff_size, std::move(stream)))
{
}
boost::mysql::detail::channel_ptr::channel_ptr(channel_ptr&& rhs) noexcept : chan_(std::move(rhs.chan_)) {}
boost::mysql::detail::channel_ptr& boost::mysql::detail::channel_ptr::operator=(channel_ptr&& rhs) noexcept
{
chan_ = std::move(rhs.chan_);
return *this;
}
boost::mysql::detail::channel_ptr::~channel_ptr() {}
boost::mysql::detail::any_stream& boost::mysql::detail::channel_ptr::get_stream() const
{
return chan_->stream();
}
boost::mysql::metadata_mode boost::mysql::detail::channel_ptr::meta_mode() const noexcept
{
return chan_->meta_mode();
}
void boost::mysql::detail::channel_ptr::set_meta_mode(metadata_mode v) noexcept { chan_->set_meta_mode(v); }
boost::mysql::diagnostics& boost::mysql::detail::channel_ptr::shared_diag() noexcept
{
return chan_->shared_diag();
}
std::vector<boost::mysql::field_view>& boost::mysql::detail::get_shared_fields(channel& chan) noexcept
{
return chan.shared_fields();
}
#endif
+49
View File
@@ -0,0 +1,49 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_COLUMN_TYPE_IPP
#define BOOST_MYSQL_IMPL_COLUMN_TYPE_IPP
#pragma once
#include <boost/mysql/column_type.hpp>
#include <ostream>
std::ostream& boost::mysql::operator<<(std::ostream& os, column_type t)
{
switch (t)
{
case column_type::tinyint: return os << "tinyint";
case column_type::smallint: return os << "smallint";
case column_type::mediumint: return os << "mediumint";
case column_type::int_: return os << "int_";
case column_type::bigint: return os << "bigint";
case column_type::float_: return os << "float_";
case column_type::double_: return os << "double_";
case column_type::decimal: return os << "decimal";
case column_type::bit: return os << "bit";
case column_type::year: return os << "year";
case column_type::time: return os << "time";
case column_type::date: return os << "date";
case column_type::datetime: return os << "datetime";
case column_type::timestamp: return os << "timestamp";
case column_type::char_: return os << "char_";
case column_type::varchar: return os << "varchar";
case column_type::binary: return os << "binary";
case column_type::varbinary: return os << "varbinary";
case column_type::text: return os << "text";
case column_type::blob: return os << "blob";
case column_type::enum_: return os << "enum_";
case column_type::set: return os << "set";
case column_type::json: return os << "json";
case column_type::geometry: return os << "geometry";
default: return os << "<unknown column type>";
}
}
#endif
+34
View File
@@ -0,0 +1,34 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_DATE_IPP
#define BOOST_MYSQL_IMPL_DATE_IPP
#pragma once
#include <boost/mysql/date.hpp>
#include <cstdio>
#include <ostream>
std::ostream& boost::mysql::operator<<(std::ostream& os, const date& value)
{
// Worst-case output is 14 chars, extra space just in case
char buffer[32]{};
snprintf(
buffer,
sizeof(buffer),
"%04u-%02u-%02u",
static_cast<unsigned>(value.year()),
static_cast<unsigned>(value.month()),
static_cast<unsigned>(value.day())
);
os << buffer;
return os;
}
#endif
+38
View File
@@ -0,0 +1,38 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_DATETIME_IPP
#define BOOST_MYSQL_IMPL_DATETIME_IPP
#pragma once
#include <boost/mysql/datetime.hpp>
#include <cstdio>
#include <ostream>
std::ostream& boost::mysql::operator<<(std::ostream& os, const datetime& value)
{
// Worst-case output is 37 chars, extra space just in case
char buffer[64]{};
snprintf(
buffer,
sizeof(buffer),
"%04u-%02u-%02u %02d:%02u:%02u.%06u",
static_cast<unsigned>(value.year()),
static_cast<unsigned>(value.month()),
static_cast<unsigned>(value.day()),
static_cast<unsigned>(value.hour()),
static_cast<unsigned>(value.minute()),
static_cast<unsigned>(value.second()),
static_cast<unsigned>(value.microsecond())
);
os << buffer;
return os;
}
#endif
+143
View File
@@ -0,0 +1,143 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_ERROR_CATEGORIES_IPP
#define BOOST_MYSQL_IMPL_ERROR_CATEGORIES_IPP
#pragma once
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/common_server_errc.hpp>
#include <boost/mysql/error_categories.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/impl/internal/error/server_error_to_string.hpp>
namespace boost {
namespace mysql {
namespace detail {
BOOST_MYSQL_STATIC_OR_INLINE
const char* error_to_string(client_errc error) noexcept
{
switch (error)
{
case client_errc::incomplete_message: return "An incomplete message was received from the server";
case client_errc::extra_bytes: return "Unexpected extra bytes at the end of a message were received";
case client_errc::sequence_number_mismatch: return "Mismatched sequence numbers";
case client_errc::server_unsupported:
return "The server does not support the minimum required capabilities to establish the "
"connection";
case client_errc::protocol_value_error:
return "An unexpected value was found in a server-received message";
case client_errc::unknown_auth_plugin:
return "The user employs an authentication plugin not known to this library";
case client_errc::auth_plugin_requires_ssl:
return "The authentication plugin requires the connection to use SSL";
case client_errc::wrong_num_params:
return "The number of parameters passed to the prepared statement does not match the "
"number of actual parameters";
case boost::mysql::client_errc::server_doesnt_support_ssl:
return "The connection is configured to require SSL, but the server doesn't allow SSL connections. "
"Configure SSL on your server or change your connection to not require SSL";
case boost::mysql::client_errc::metadata_check_failed:
return "The static interface detected a type mismatch between your declared row type and what the "
"server returned. Verify your type definitions.";
case boost::mysql::client_errc::num_resultsets_mismatch:
return "The static interface detected a mismatch between the number of resultsets passed as template "
"arguments to static_results<T1, T2...>/static_execution_state<T1, T2...> and the number of "
"results returned by server";
case boost::mysql::client_errc::static_row_parsing_error:
return "The static interface encountered an error when parsing a field into a C++ data structure.";
case boost::mysql::client_errc::row_type_mismatch:
return "The StaticRow type passed to read_some_rows does not correspond to the resultset type being "
"read";
default: return "<unknown MySQL client error>";
}
}
BOOST_MYSQL_STATIC_OR_INLINE
const char* error_to_string(common_server_errc v) noexcept
{
const char* res = detail::common_error_to_string(static_cast<int>(v));
return res ? res : "<unknown server error>";
}
class client_category final : public boost::system::error_category
{
public:
const char* name() const noexcept final override { return "mysql.client"; }
std::string message(int ev) const final override { return error_to_string(static_cast<client_errc>(ev)); }
};
class common_server_category final : public boost::system::error_category
{
public:
const char* name() const noexcept final override { return "mysql.common-server"; }
std::string message(int ev) const final override
{
return error_to_string(static_cast<common_server_errc>(ev));
}
};
class mysql_server_category final : public boost::system::error_category
{
public:
const char* name() const noexcept final override { return "mysql.mysql-server"; }
std::string message(int ev) const final override { return detail::mysql_error_to_string(ev); }
};
class mariadb_server_category final : public boost::system::error_category
{
public:
const char* name() const noexcept final override { return "mysql.mariadb-server"; }
std::string message(int ev) const final override { return detail::mariadb_error_to_string(ev); }
};
// Optimization, so that static initialization happens only once (reduces C++11 thread-safe initialization
// overhead)
struct all_categories
{
client_category client;
common_server_category common_server;
mysql_server_category mysql_server;
mariadb_server_category mariadb_server;
static const all_categories& get() noexcept
{
static all_categories res;
return res;
}
};
} // namespace detail
} // namespace mysql
} // namespace boost
const boost::system::error_category& boost::mysql::get_client_category() noexcept
{
return detail::all_categories::get().client;
}
const boost::system::error_category& boost::mysql::get_common_server_category() noexcept
{
return detail::all_categories::get().common_server;
}
const boost::system::error_category& boost::mysql::get_mysql_server_category() noexcept
{
return detail::all_categories::get().mysql_server;
}
const boost::system::error_category& boost::mysql::get_mariadb_server_category() noexcept
{
return detail::all_categories::get().mariadb_server;
}
#endif
+77
View File
@@ -0,0 +1,77 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_EXECUTION_STATE_IMPL_IPP
#define BOOST_MYSQL_IMPL_EXECUTION_STATE_IMPL_IPP
#pragma once
#include <boost/mysql/detail/execution_processor/execution_state_impl.hpp>
#include <boost/mysql/detail/row_impl.hpp>
#include <boost/mysql/impl/internal/protocol/protocol.hpp>
void boost::mysql::detail::execution_state_impl::on_ok_packet_impl(const ok_view& pack)
{
eof_data_.has_value = true;
eof_data_.affected_rows = pack.affected_rows;
eof_data_.last_insert_id = pack.last_insert_id;
eof_data_.warnings = pack.warnings;
eof_data_.is_out_params = pack.is_out_params();
info_.assign(pack.info.begin(), pack.info.end());
}
void boost::mysql::detail::execution_state_impl::reset_impl() noexcept
{
meta_.clear();
eof_data_ = ok_data();
info_.clear();
}
boost::mysql::error_code boost::mysql::detail::execution_state_impl::
on_head_ok_packet_impl(const ok_view& pack, diagnostics&)
{
on_new_resultset();
on_ok_packet_impl(pack);
return error_code();
}
void boost::mysql::detail::execution_state_impl::on_num_meta_impl(std::size_t num_columns)
{
on_new_resultset();
meta_.reserve(num_columns);
}
boost::mysql::error_code boost::mysql::detail::execution_state_impl::
on_meta_impl(const coldef_view& coldef, bool, diagnostics&)
{
meta_.push_back(create_meta(coldef));
return error_code();
}
boost::mysql::error_code boost::mysql::detail::execution_state_impl::on_row_impl(
span<const std::uint8_t> msg,
const output_ref&,
std::vector<field_view>& fields
)
{
// add row storage
span<field_view> storage = add_fields(fields, meta_.size());
// deserialize the row
return deserialize_row(encoding(), msg, meta_, storage);
}
boost::mysql::error_code boost::mysql::detail::execution_state_impl::on_row_ok_packet_impl(const ok_view& pack
)
{
on_ok_packet_impl(pack);
return error_code();
}
#endif
+49
View File
@@ -0,0 +1,49 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_FIELD_IPP
#define BOOST_MYSQL_IMPL_FIELD_IPP
#pragma once
#include <boost/mysql/field.hpp>
#include <ostream>
namespace boost {
namespace mysql {
namespace detail {
inline blob to_blob(blob_view v) { return blob(v.data(), v.data() + v.size()); }
} // namespace detail
} // namespace mysql
} // namespace boost
void boost::mysql::field::from_view(const field_view& fv)
{
switch (fv.kind())
{
case field_kind::null: repr_.data.emplace<detail::field_impl::null_t>(); break;
case field_kind::int64: repr_.data.emplace<std::int64_t>(fv.get_int64()); break;
case field_kind::uint64: repr_.data.emplace<std::uint64_t>(fv.get_uint64()); break;
case field_kind::string: repr_.data.emplace<std::string>(fv.get_string()); break;
case field_kind::blob: repr_.data.emplace<blob>(detail::to_blob(fv.get_blob())); break;
case field_kind::float_: repr_.data.emplace<float>(fv.get_float()); break;
case field_kind::double_: repr_.data.emplace<double>(fv.get_double()); break;
case field_kind::date: repr_.data.emplace<date>(fv.get_date()); break;
case field_kind::datetime: repr_.data.emplace<datetime>(fv.get_datetime()); break;
case field_kind::time: repr_.data.emplace<time>(fv.get_time()); break;
}
}
std::ostream& boost::mysql::operator<<(std::ostream& os, const field& value)
{
return os << field_view(value);
}
#endif
+34
View File
@@ -0,0 +1,34 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_FIELD_KIND_IPP
#define BOOST_MYSQL_IMPL_FIELD_KIND_IPP
#pragma once
#include <boost/mysql/field_kind.hpp>
#include <ostream>
std::ostream& boost::mysql::operator<<(std::ostream& os, boost::mysql::field_kind v)
{
switch (v)
{
case field_kind::null: return os << "null";
case field_kind::int64: return os << "int64";
case field_kind::uint64: return os << "uint64";
case field_kind::string: return os << "string";
case field_kind::float_: return os << "float_";
case field_kind::double_: return os << "double_";
case field_kind::date: return os << "date";
case field_kind::datetime: return os << "datetime";
case field_kind::time: return os << "time";
default: return os << "<invalid>";
}
}
#endif
+185
View File
@@ -0,0 +1,185 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_FIELD_VIEW_HPP
#define BOOST_MYSQL_IMPL_FIELD_VIEW_HPP
#pragma once
#include <boost/mysql/bad_field_access.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/assert.hpp>
#include <boost/throw_exception.hpp>
#include <cstring>
#include <limits>
namespace boost {
namespace mysql {
namespace detail {
inline bool blobs_equal(blob_view b1, blob_view b2)
{
if (b1.size() != b2.size())
return false;
return b1.empty() || std::memcmp(b1.data(), b2.data(), b2.size()) == 0;
}
} // namespace detail
} // namespace mysql
} // namespace boost
BOOST_CXX14_CONSTEXPR inline boost::mysql::field_kind boost::mysql::field_view::kind() const noexcept
{
switch (impl_.ikind)
{
case internal_kind::null: return field_kind::null;
case internal_kind::int64: return field_kind::int64;
case internal_kind::uint64: return field_kind::uint64;
case internal_kind::string: return field_kind::string;
case internal_kind::blob: return field_kind::blob;
case internal_kind::float_: return field_kind::float_;
case internal_kind::double_: return field_kind::double_;
case internal_kind::date: return field_kind::date;
case internal_kind::datetime: return field_kind::datetime;
case internal_kind::time: return field_kind::time;
case internal_kind::field_ptr: return impl_.repr.field_ptr->kind();
// sv_offset values must be converted via offset_to_string_view before calling any other fn
default: return field_kind::null;
}
}
BOOST_CXX14_CONSTEXPR std::int64_t boost::mysql::field_view::as_int64() const
{
if (is_field_ptr())
return impl_.repr.field_ptr->as<std::int64_t>();
check_kind(internal_kind::int64);
return impl_.repr.int64;
}
BOOST_CXX14_CONSTEXPR std::uint64_t boost::mysql::field_view::as_uint64() const
{
if (is_field_ptr())
return impl_.repr.field_ptr->as<std::uint64_t>();
check_kind(internal_kind::uint64);
return impl_.repr.uint64;
}
BOOST_CXX14_CONSTEXPR boost::mysql::string_view boost::mysql::field_view::as_string() const
{
if (is_field_ptr())
return impl_.repr.field_ptr->as<std::string>();
check_kind(internal_kind::string);
return impl_.repr.string;
}
BOOST_CXX14_CONSTEXPR boost::mysql::blob_view boost::mysql::field_view::as_blob() const
{
if (is_field_ptr())
return impl_.repr.field_ptr->as<blob>();
check_kind(internal_kind::blob);
return impl_.repr.blob;
}
BOOST_CXX14_CONSTEXPR float boost::mysql::field_view::as_float() const
{
if (is_field_ptr())
return impl_.repr.field_ptr->as<float>();
check_kind(internal_kind::float_);
return impl_.repr.float_;
}
BOOST_CXX14_CONSTEXPR double boost::mysql::field_view::as_double() const
{
if (is_field_ptr())
return impl_.repr.field_ptr->as<double>();
check_kind(internal_kind::double_);
return impl_.repr.double_;
}
BOOST_CXX14_CONSTEXPR boost::mysql::date boost::mysql::field_view::as_date() const
{
if (is_field_ptr())
return impl_.repr.field_ptr->as<date>();
check_kind(internal_kind::date);
return impl_.repr.date_;
}
BOOST_CXX14_CONSTEXPR boost::mysql::datetime boost::mysql::field_view::as_datetime() const
{
if (is_field_ptr())
return impl_.repr.field_ptr->as<datetime>();
check_kind(internal_kind::datetime);
return impl_.repr.datetime_;
}
BOOST_CXX14_CONSTEXPR boost::mysql::time boost::mysql::field_view::as_time() const
{
if (is_field_ptr())
return impl_.repr.field_ptr->as<time>();
check_kind(internal_kind::time);
return impl_.repr.time_;
}
BOOST_CXX14_CONSTEXPR void boost::mysql::field_view::check_kind(internal_kind expected) const
{
if (impl_.ikind != expected)
BOOST_THROW_EXCEPTION(bad_field_access());
}
BOOST_CXX14_CONSTEXPR bool boost::mysql::field_view::operator==(const field_view& rhs) const noexcept
{
// Make operator== work for types not representable by field_kind
if (impl_.ikind == internal_kind::sv_offset_string || impl_.ikind == internal_kind::sv_offset_blob)
{
return rhs.impl_.ikind == impl_.ikind && impl_.repr.sv_offset_ == rhs.impl_.repr.sv_offset_;
}
auto k = kind(), rhs_k = rhs.kind();
switch (k)
{
case field_kind::null: return rhs_k == field_kind::null;
case field_kind::int64:
if (rhs_k == field_kind::int64)
return get_int64() == rhs.get_int64();
else if (rhs_k == field_kind::uint64)
{
std::int64_t this_val = get_int64();
if (this_val < 0)
return false;
else
return static_cast<std::uint64_t>(this_val) == rhs.get_uint64();
}
else
return false;
case field_kind::uint64:
if (rhs_k == field_kind::uint64)
return get_uint64() == rhs.get_uint64();
else if (rhs_k == field_kind::int64)
{
std::int64_t rhs_val = rhs.get_int64();
if (rhs_val < 0)
return false;
else
return static_cast<std::uint64_t>(rhs_val) == get_uint64();
}
else
return false;
case field_kind::string: return rhs_k == field_kind::string && get_string() == rhs.get_string();
case field_kind::blob:
return rhs_k == field_kind::blob && detail::blobs_equal(get_blob(), rhs.get_blob());
case field_kind::float_: return rhs_k == field_kind::float_ && get_float() == rhs.get_float();
case field_kind::double_: return rhs_k == field_kind::double_ && get_double() == rhs.get_double();
case field_kind::date: return rhs_k == field_kind::date && get_date() == rhs.get_date();
case field_kind::datetime: return rhs_k == field_kind::datetime && get_datetime() == rhs.get_datetime();
case field_kind::time: return rhs_k == field_kind::time && get_time() == rhs.get_time();
default: BOOST_ASSERT(false); return false;
}
}
#endif
+100
View File
@@ -0,0 +1,100 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_FIELD_VIEW_IPP
#define BOOST_MYSQL_IMPL_FIELD_VIEW_IPP
#pragma once
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/detail/config.hpp>
#include <ostream>
namespace boost {
namespace mysql {
namespace detail {
BOOST_MYSQL_STATIC_OR_INLINE
std::ostream& print_blob(std::ostream& os, blob_view value)
{
if (value.empty())
return os << "{}";
char buffer[16]{};
os << "{ ";
for (std::size_t i = 0; i < value.size(); ++i)
{
if (i != 0)
os << ", ";
unsigned byte = value[i];
std::snprintf(buffer, sizeof(buffer), "0x%02x", byte);
os << buffer;
}
os << " }";
return os;
}
BOOST_MYSQL_STATIC_OR_INLINE
std::ostream& print_time(std::ostream& os, const boost::mysql::time& value)
{
// Worst-case output is 26 chars, extra space just in case
char buffer[64]{};
using namespace std::chrono;
const char* sign = value < microseconds(0) ? "-" : "";
auto num_micros = value % seconds(1);
auto num_secs = duration_cast<seconds>(value % minutes(1) - num_micros);
auto num_mins = duration_cast<minutes>(value % hours(1) - num_secs);
auto num_hours = duration_cast<hours>(value - num_mins);
snprintf(
buffer,
sizeof(buffer),
"%s%02d:%02u:%02u.%06u",
sign,
static_cast<int>(std::abs(num_hours.count())),
static_cast<unsigned>(std::abs(num_mins.count())),
static_cast<unsigned>(std::abs(num_secs.count())),
static_cast<unsigned>(std::abs(num_micros.count()))
);
os << buffer;
return os;
}
} // namespace detail
} // namespace mysql
} // namespace boost
std::ostream& boost::mysql::operator<<(std::ostream& os, const field_view& value)
{
// Make operator<< work for detail::string_view_offset types
if (value.impl_.is_string_offset() || value.impl_.is_blob_offset())
{
return os << "<sv_offset>";
}
switch (value.kind())
{
case field_kind::null: return os << "<NULL>";
case field_kind::int64: return os << value.get_int64();
case field_kind::uint64: return os << value.get_uint64();
case field_kind::string: return os << value.get_string();
case field_kind::blob: return detail::print_blob(os, value.get_blob());
case field_kind::float_: return os << value.get_float();
case field_kind::double_: return os << value.get_double();
case field_kind::date: return os << value.get_date();
case field_kind::datetime: return os << value.get_datetime();
case field_kind::time: return detail::print_time(os, value.get_time());
default: BOOST_ASSERT(false); return os;
}
}
#endif
+48
View File
@@ -0,0 +1,48 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_AUTH_AUTH_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_AUTH_AUTH_HPP
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/core/span.hpp>
#include <vector>
namespace boost {
namespace mysql {
namespace detail {
struct auth_response
{
std::vector<std::uint8_t> data;
string_view plugin_name;
};
BOOST_ATTRIBUTE_NODISCARD
BOOST_MYSQL_DECL
error_code compute_auth_response(
string_view plugin_name,
string_view password,
span<const std::uint8_t> challenge,
bool use_ssl,
auth_response& output
);
} // namespace detail
} // namespace mysql
} // namespace boost
#ifdef BOOST_MYSQL_HEADER_ONLY
#include <boost/mysql/impl/internal/auth/auth.ipp>
#endif
#endif
+235
View File
@@ -0,0 +1,235 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_AUTH_AUTH_IPP
#define BOOST_MYSQL_IMPL_INTERNAL_AUTH_AUTH_IPP
#include "boost/mysql/detail/config.hpp"
#pragma once
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/impl/internal/auth/auth.hpp>
#include <boost/mysql/impl/internal/make_string_view.hpp>
#include <algorithm>
#include <cstring>
#include <openssl/sha.h>
namespace boost {
namespace mysql {
namespace detail {
// mysql_native_password
// Authorization for this plugin is always challenge (nonce) -> response
// (hashed password).
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t mnp_challenge_length = 20;
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t mnp_response_length = 20;
// challenge must point to challenge_length bytes of data
// output must point to response_length bytes of data
// SHA1( password ) XOR SHA1( "20-bytes random data from server" <concat> SHA1( SHA1( password ) ) )
BOOST_MYSQL_STATIC_OR_INLINE
void mnp_compute_auth_string(string_view password, const void* challenge, void* output)
{
// SHA1 (password)
using sha1_buffer = unsigned char[SHA_DIGEST_LENGTH];
sha1_buffer password_sha1;
SHA1(reinterpret_cast<const unsigned char*>(password.data()), password.size(), password_sha1);
// Add server challenge (salt)
unsigned char salted_buffer[mnp_challenge_length + SHA_DIGEST_LENGTH];
memcpy(salted_buffer, challenge, mnp_challenge_length);
SHA1(password_sha1, sizeof(password_sha1), salted_buffer + 20);
sha1_buffer salted_sha1;
SHA1(salted_buffer, sizeof(salted_buffer), salted_sha1);
// XOR
static_assert(mnp_response_length == SHA_DIGEST_LENGTH, "Buffer size mismatch");
for (std::size_t i = 0; i < SHA_DIGEST_LENGTH; ++i)
{
static_cast<std::uint8_t*>(output)[i] = password_sha1[i] ^ salted_sha1[i];
}
}
BOOST_MYSQL_STATIC_OR_INLINE
error_code mnp_compute_response(
string_view password,
boost::span<const std::uint8_t> challenge,
bool, // use_ssl
std::vector<std::uint8_t>& output
)
{
// Check challenge size
if (challenge.size() != mnp_challenge_length)
{
return make_error_code(client_errc::protocol_value_error);
}
// Do the calculation
output.resize(mnp_response_length);
mnp_compute_auth_string(password, challenge.data(), output.data());
return error_code();
}
// caching_sha2_password
// Authorization for this plugin may be cleartext password or challenge/response.
// The server has a cache that uses when employing challenge/response. When
// the server sends a challenge of challenge_length bytes, we should send
// the password hashed with the challenge. The server may send a challenge
// equals to perform_full_auth, meaning it could not use the cache to
// complete the auth. In this case, we should just send the cleartext password.
// Doing the latter requires a SSL connection. It is possible to perform full
// auth without an SSL connection, but that requires the server public key,
// and we do not implement that.
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t csha2p_challenge_length = 20;
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t csha2p_response_length = 32;
// challenge must point to challenge_length bytes of data
// output must point to response_length bytes of data
BOOST_MYSQL_STATIC_OR_INLINE
void csha2p_compute_auth_string(string_view password, const void* challenge, void* output)
{
static_assert(csha2p_response_length == SHA256_DIGEST_LENGTH, "Buffer size mismatch");
// SHA(SHA(password_sha) concat challenge) XOR password_sha
// hash1 = SHA(pass)
using sha_buffer = std::uint8_t[csha2p_response_length];
sha_buffer password_sha;
SHA256(reinterpret_cast<const unsigned char*>(password.data()), password.size(), password_sha);
// SHA(password_sha) concat challenge = buffer
std::uint8_t buffer[csha2p_response_length + csha2p_challenge_length];
SHA256(password_sha, csha2p_response_length, buffer);
std::memcpy(buffer + csha2p_response_length, challenge, csha2p_challenge_length);
// SHA(SHA(password_sha) concat challenge) = SHA(buffer) = salted_password
sha_buffer salted_password;
SHA256(buffer, sizeof(buffer), salted_password);
// salted_password XOR password_sha
for (unsigned i = 0; i < csha2p_response_length; ++i)
{
static_cast<std::uint8_t*>(output)[i] = salted_password[i] ^ password_sha[i];
}
}
BOOST_MYSQL_STATIC_OR_INLINE
bool should_perform_full_auth(boost::span<const std::uint8_t> challenge) noexcept
{
// A challenge of "\4" means "perform full auth"
return challenge.size() == 1u && challenge[0] == 4;
}
BOOST_MYSQL_STATIC_OR_INLINE
error_code csha2p_compute_response(
string_view password,
boost::span<const std::uint8_t> challenge,
bool use_ssl,
std::vector<std::uint8_t>& output
)
{
if (should_perform_full_auth(challenge))
{
if (!use_ssl)
{
return make_error_code(client_errc::auth_plugin_requires_ssl);
}
output.assign(password.begin(), password.end());
output.push_back(0);
return error_code();
}
else
{
// Check challenge size
if (challenge.size() != csha2p_challenge_length)
{
return make_error_code(client_errc::protocol_value_error);
}
// Do the calculation
output.resize(csha2p_response_length);
csha2p_compute_auth_string(password, challenge.data(), output.data());
return error_code();
}
}
// top-level API
struct authentication_plugin
{
using calculator_signature = error_code (*)(
string_view password,
boost::span<const std::uint8_t> challenge,
bool use_ssl,
std::vector<std::uint8_t>& output
);
string_view name;
calculator_signature calculator;
};
BOOST_MYSQL_STATIC_IF_COMPILED
constexpr authentication_plugin all_authentication_plugins[] = {
{
make_string_view("mysql_native_password"),
&mnp_compute_response,
},
{
make_string_view("caching_sha2_password"),
&csha2p_compute_response,
},
};
BOOST_MYSQL_STATIC_OR_INLINE
const authentication_plugin* find_plugin(string_view name)
{
auto it = std::find_if(
std::begin(all_authentication_plugins),
std::end(all_authentication_plugins),
[name](const authentication_plugin& plugin) { return plugin.name == name; }
);
return it == std::end(all_authentication_plugins) ? nullptr : it;
}
} // namespace detail
} // namespace mysql
} // namespace boost
boost::mysql::error_code boost::mysql::detail::compute_auth_response(
string_view plugin_name,
string_view password,
span<const std::uint8_t> challenge,
bool use_ssl,
auth_response& output
)
{
const auto* plugin = find_plugin(plugin_name);
if (plugin)
{
output.plugin_name = plugin->name;
if (password.empty())
{
// Blank password: we should just return an empty auth string
output.data.clear();
return error_code();
}
else
{
return plugin->calculator(password, challenge, use_ssl, output.data);
}
}
else
{
return client_errc::unknown_auth_plugin;
}
}
#endif
+150
View File
@@ -0,0 +1,150 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_CHANNEL_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_CHANNEL_HPP
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/metadata_mode.hpp>
#include <boost/mysql/detail/any_stream.hpp>
#include <boost/mysql/impl/internal/channel/message_reader.hpp>
#include <boost/mysql/impl/internal/channel/message_writer.hpp>
#include <boost/mysql/impl/internal/channel/write_message.hpp>
#include <boost/mysql/impl/internal/protocol/capabilities.hpp>
#include <boost/mysql/impl/internal/protocol/db_flavor.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/async_result.hpp>
#include <cstddef>
#include <memory>
#include <utility>
#include <vector>
namespace boost {
namespace mysql {
namespace detail {
// Implements the message layer of the MySQL protocol
class channel
{
db_flavor flavor_{db_flavor::mysql};
capabilities current_caps_;
std::uint8_t shared_sequence_number_{};
diagnostics shared_diag_; // for async ops
std::vector<field_view> shared_fields_;
metadata_mode meta_mode_{metadata_mode::minimal};
message_reader reader_;
message_writer writer_;
std::unique_ptr<any_stream> stream_;
public:
channel(std::size_t read_buffer_size, std::unique_ptr<any_stream> stream)
: reader_(read_buffer_size), stream_(std::move(stream))
{
}
// Executor
using executor_type = asio::any_io_executor;
executor_type get_executor() { return stream_->get_executor(); }
// Reading
bool has_read_messages() const noexcept { return reader_.has_message(); }
span<const std::uint8_t> next_read_message(std::uint8_t& seqnum, error_code& err) noexcept
{
return reader_.get_next_message(seqnum, err);
}
void read_some(error_code& code) { read_some_messages(*stream_, reader_, code); }
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code)) CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_read_some(CompletionToken&& token)
{
return async_read_some_messages(*stream_, reader_, std::forward<CompletionToken>(token));
}
span<const std::uint8_t> read_one(std::uint8_t& seqnum, error_code& ec)
{
return read_one_message(*stream_, reader_, seqnum, ec);
}
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, span<const std::uint8_t>)) CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, span<const std::uint8_t>))
async_read_one(std::uint8_t& seqnum, CompletionToken&& token)
{
return async_read_one_message(*stream_, reader_, seqnum, std::forward<CompletionToken>(token));
}
// Exposed for the sake of testing
std::size_t read_buffer_size() const noexcept { return reader_.buffer().size(); }
// Writing. serialize() gets all the required data into the write buffers so it can be written
template <class Serializable>
void serialize(const Serializable& message, std::uint8_t& sequence_number)
{
std::size_t size = message.get_size();
auto buff = writer_.prepare_buffer(size, sequence_number);
message.serialize(buff);
}
// Writes what has been set up by serialize()
void write(error_code& code) { write_message(*stream_, writer_, code); }
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code)) CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_write(CompletionToken&& token)
{
return async_write_message(*stream_, writer_, std::forward<CompletionToken>(token));
}
// Capabilities
capabilities current_capabilities() const noexcept { return current_caps_; }
void set_current_capabilities(capabilities value) noexcept { current_caps_ = value; }
// DB flavor
db_flavor flavor() const noexcept { return flavor_; }
void set_flavor(db_flavor v) noexcept { flavor_ = v; }
void reset()
{
flavor_ = db_flavor::mysql;
current_caps_ = capabilities();
reset_sequence_number();
stream_->reset_ssl_active();
// Metadata mode does not get reset on handshake
}
// Internal buffer, diagnostics and sequence_number to help async ops
diagnostics& shared_diag() noexcept { return shared_diag_; }
std::uint8_t& shared_sequence_number() noexcept { return shared_sequence_number_; }
std::uint8_t& reset_sequence_number() noexcept { return shared_sequence_number_ = 0; }
std::vector<field_view>& shared_fields() noexcept { return shared_fields_; }
const std::vector<field_view>& shared_fields() const noexcept { return shared_fields_; }
// Metadata mode
metadata_mode meta_mode() const noexcept { return meta_mode_; }
void set_meta_mode(metadata_mode v) noexcept { meta_mode_ = v; }
// SSL
bool ssl_active() const noexcept { return stream_->ssl_active(); }
// Getting the underlying stream
any_stream& stream() noexcept { return *stream_; }
const any_stream& stream() const noexcept { return *stream_; }
};
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
+92
View File
@@ -0,0 +1,92 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_MESSAGE_PARSER_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_MESSAGE_PARSER_HPP
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/impl/internal/channel/read_buffer.hpp>
#include <boost/mysql/impl/internal/protocol/constants.hpp>
#include <cstddef>
#include <cstdint>
namespace boost {
namespace mysql {
namespace detail {
class message_parser
{
struct state_t
{
bool is_first_frame{true};
std::uint8_t seqnum_first{};
std::uint8_t seqnum_last{};
bool reading_header{true};
std::size_t remaining_bytes{0};
bool more_frames_follow{false};
bool has_seqnum_mismatch{false};
};
std::size_t max_frame_size_;
state_t state_;
public:
struct result
{
// whether it has a message or not
bool has_message{false};
// if !has_message, number of bytes required to parse the current message
std::size_t required_size{0};
// if has_message, the actual parsed message
struct message_t
{
std::uint8_t seqnum_first;
std::uint8_t seqnum_last;
std::size_t size;
bool has_seqnum_mismatch; // for multi-frame messages, set to true if an error mismatch
// happened
} message{};
void set_required_size(std::size_t size) noexcept
{
has_message = false;
required_size = size;
}
void set_message(const message_t& msg) noexcept
{
has_message = true;
message = msg;
}
};
// max_frame_size is configurable so tests run faster
message_parser(std::size_t max_frame_size = MAX_PACKET_SIZE) noexcept : max_frame_size_(max_frame_size){};
// Attempts to process a message from buff and puts it into msg.
// If a message is read, res.has_message == true, and res.message will be populated.
// Otherwise, res.required_size will contain
// the number of bytes needed to complete the message part we're parsing.
// Doesn't cause buffer reallocations, and doesn't change the contents
// of buff's reserved area.
BOOST_MYSQL_DECL
void parse_message(read_buffer& buff, result& res) noexcept;
};
} // namespace detail
} // namespace mysql
} // namespace boost
#ifdef BOOST_MYSQL_HEADER_ONLY
#include <boost/mysql/impl/internal/channel/message_parser.ipp>
#endif
#endif
+110
View File
@@ -0,0 +1,110 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_MESSAGE_PARSER_IPP
#define BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_MESSAGE_PARSER_IPP
#pragma once
#include <boost/mysql/impl/internal/channel/message_parser.hpp>
#include <boost/mysql/impl/internal/protocol/protocol.hpp>
void boost::mysql::detail::message_parser::parse_message(read_buffer& buff, result& res) noexcept
{
while (true)
{
if (state_.reading_header)
{
// If there are not enough bytes to process a header, request more
if (buff.pending_size() < HEADER_SIZE)
{
res.set_required_size(HEADER_SIZE - buff.pending_size());
return;
}
// Mark the header as belonging to the current message
buff.move_to_current_message(HEADER_SIZE);
// Deserialize the header
auto header = deserialize_frame_header(
span<const std::uint8_t, frame_header_size>(buff.pending_first() - HEADER_SIZE, HEADER_SIZE)
);
// Process the sequence number
if (state_.is_first_frame)
{
state_.seqnum_first = header.sequence_number;
state_.seqnum_last = header.sequence_number;
}
else
{
std::uint8_t expected_seqnum = state_.seqnum_last + 1;
if (header.sequence_number != expected_seqnum)
{
state_.has_seqnum_mismatch = true;
}
state_.seqnum_last = expected_seqnum;
}
// Process the packet size
state_.remaining_bytes = header.size;
state_.more_frames_follow = (state_.remaining_bytes == max_frame_size_);
// We are done with the header
if (state_.is_first_frame)
{
// If it's the 1st frame, we can just move the header bytes to the reserved
// area, avoiding a big memmove
buff.move_to_reserved(HEADER_SIZE);
}
else
{
buff.remove_current_message_last(HEADER_SIZE);
}
state_.is_first_frame = false;
state_.reading_header = false;
}
if (!state_.reading_header)
{
// Get the number of bytes belonging to this message
std::size_t new_bytes = (std::min)(buff.pending_size(), state_.remaining_bytes);
// Mark them as belonging to the current message in the buffer
buff.move_to_current_message(new_bytes);
// Update remaining bytes
state_.remaining_bytes -= new_bytes;
if (state_.remaining_bytes == 0)
{
state_.reading_header = true;
}
else
{
res.set_required_size(state_.remaining_bytes);
return;
}
// If we've fully read a message, we're done
if (!state_.remaining_bytes && !state_.more_frames_follow)
{
std::size_t message_size = buff.current_message_size();
buff.move_to_reserved(message_size);
res.set_message({
state_.seqnum_first,
state_.seqnum_last,
message_size,
state_.has_seqnum_mismatch,
});
state_ = state_t();
return;
}
}
}
}
#endif
+282
View File
@@ -0,0 +1,282 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_MESSAGE_READER_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_MESSAGE_READER_HPP
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/detail/any_stream.hpp>
#include <boost/mysql/impl/internal/channel/message_parser.hpp>
#include <boost/mysql/impl/internal/channel/read_buffer.hpp>
#include <boost/mysql/impl/internal/channel/valgrind.hpp>
#include <boost/mysql/impl/internal/protocol/constants.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/post.hpp>
#include <boost/assert.hpp>
#include <cstddef>
#include <cstdint>
namespace boost {
namespace mysql {
namespace detail {
class message_reader
{
public:
message_reader(std::size_t initial_buffer_size, std::size_t max_frame_size = MAX_PACKET_SIZE)
: buffer_(initial_buffer_size), parser_(max_frame_size)
{
}
bool has_message() const noexcept { return result_.has_message; }
span<const std::uint8_t> get_next_message(std::uint8_t& seqnum, error_code& ec) noexcept
{
{
BOOST_ASSERT(has_message());
if (result_.message.has_seqnum_mismatch || seqnum != result_.message.seqnum_first)
{
ec = make_error_code(client_errc::sequence_number_mismatch);
return {};
}
seqnum = result_.message.seqnum_last + 1;
span<const std::uint8_t> res(
buffer_.current_message_first() - result_.message.size,
result_.message.size
);
parse_message();
ec = error_code();
return res;
}
}
// Reads some messages from stream, until there is at least one
// or an error happens. On success, has_message() returns true
// and get_next_message() returns the parsed message.
// May relocate the buffer, modifying buffer_first().
// The reserved area bytes will be removed before the actual read.
void read_some(any_stream& stream, error_code& ec)
{
// If we already have a message, complete immediately
if (has_message())
{
ec = error_code();
return;
}
// Remove processed messages
buffer_.remove_reserved();
while (!has_message())
{
// If any previous process_message indicated that we need more
// buffer space, resize the buffer now
maybe_resize_buffer();
// Actually read bytes
std::size_t bytes_read = stream.read_some(free_area(), ec);
if (ec)
break;
valgrind_make_mem_defined(buffer_.free_first(), bytes_read);
// Process them
on_read_bytes(bytes_read);
}
}
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_read_some(any_stream& stream, CompletionToken&& token);
// Exposed for the sake of testing
read_buffer& buffer() noexcept { return buffer_; }
const read_buffer& buffer() const noexcept { return buffer_; }
private:
struct read_some_op;
read_buffer buffer_;
message_parser parser_;
message_parser::result result_;
void parse_message() { parser_.parse_message(buffer_, result_); }
void maybe_resize_buffer()
{
if (!result_.has_message)
{
buffer_.grow_to_fit(result_.required_size);
}
}
void on_read_bytes(size_t num_bytes)
{
buffer_.move_to_pending(num_bytes);
parse_message();
}
asio::mutable_buffer free_area() noexcept
{
auto res = buffer_.free_area();
return asio::mutable_buffer(res.data(), res.size());
}
};
struct boost::mysql::detail::message_reader::read_some_op : boost::asio::coroutine
{
message_reader& reader_;
any_stream& stream_;
read_some_op(message_reader& reader, any_stream& stream) noexcept : reader_(reader), stream_(stream) {}
template <class Self>
void operator()(Self& self, error_code ec = {}, std::size_t bytes_read = 0)
{
// Error handling
if (ec)
{
self.complete(ec);
return;
}
// Non-error path
BOOST_ASIO_CORO_REENTER(*this)
{
// If we already have a message, complete immediately
if (reader_.has_message())
{
BOOST_ASIO_CORO_YIELD boost::asio::post(stream_.get_executor(), std::move(self));
self.complete(error_code());
BOOST_ASIO_CORO_YIELD break;
}
// Remove processed messages
reader_.buffer_.remove_reserved();
while (!reader_.has_message())
{
// If any previous process_message indicated that we need more
// buffer space, resize the buffer now
reader_.maybe_resize_buffer();
// Actually read bytes
BOOST_ASIO_CORO_YIELD stream_.async_read_some(reader_.free_area(), std::move(self));
valgrind_make_mem_defined(reader_.buffer_.free_first(), bytes_read);
// Process them
reader_.on_read_bytes(bytes_read);
}
self.complete(error_code());
}
}
};
// Public interface
inline void read_some_messages(any_stream& stream, message_reader& reader, error_code& ec)
{
return reader.read_some(stream, ec);
}
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_read_some_messages(any_stream& stream, message_reader& reader, CompletionToken&& token)
{
return reader.async_read_some(stream, std::forward<CompletionToken>(token));
}
// Equivalent to read_some + get_next_message
inline span<const std::uint8_t> read_one_message(
any_stream& stream,
message_reader& reader,
std::uint8_t& seqnum,
error_code& ec
)
{
read_some_messages(stream, reader, ec);
if (ec)
return {};
else
return reader.get_next_message(seqnum, ec);
}
struct read_one_message_op : boost::asio::coroutine
{
message_reader& reader_;
any_stream& stream_;
std::uint8_t& seqnum_;
read_one_message_op(message_reader& reader, any_stream& stream, std::uint8_t& seqnum)
: reader_(reader), stream_(stream), seqnum_(seqnum)
{
}
template <class Self>
void operator()(Self& self, error_code code = {})
{
// Error handling
if (code)
{
self.complete(code, span<const std::uint8_t>());
return;
}
// Non-error path
BOOST_ASIO_CORO_REENTER(*this)
{
BOOST_ASIO_CORO_YIELD reader_.async_read_some(stream_, std::move(self));
{
auto b = reader_.get_next_message(seqnum_, code);
self.complete(code, b);
}
}
}
};
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(
CompletionToken,
void(boost::mysql::error_code, ::boost::span<const std::uint8_t>)
)
async_read_one_message(
any_stream& stream,
message_reader& reader,
std::uint8_t& seqnum,
CompletionToken&& token
)
{
return boost::asio::async_compose<CompletionToken, void(error_code, span<const std::uint8_t>)>(
read_one_message_op(reader, stream, seqnum),
token,
stream
);
}
} // namespace detail
} // namespace mysql
} // namespace boost
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(::boost::mysql::error_code))
boost::mysql::detail::message_reader::async_read_some(any_stream& stream, CompletionToken&& token)
{
return boost::asio::async_compose<CompletionToken, void(error_code)>(
read_some_op{*this, stream},
token,
stream
);
}
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_AUXILIAR_STATIC_STRING_HPP_ */
+142
View File
@@ -0,0 +1,142 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_MESSAGE_WRITER_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_MESSAGE_WRITER_HPP
#include <boost/mysql/impl/internal/protocol/constants.hpp>
#include <boost/mysql/impl/internal/protocol/protocol.hpp>
#include <array>
#include <cstddef>
#include <cstdint>
namespace boost {
namespace mysql {
namespace detail {
class chunk_processor
{
std::size_t first_{};
std::size_t last_{};
std::size_t remaining() const noexcept { return last_ - first_; }
public:
chunk_processor() = default;
void reset() noexcept { reset(0, 0); }
void reset(std::size_t first, std::size_t last) noexcept
{
BOOST_ASSERT(last >= first);
first_ = first;
last_ = last;
}
void on_bytes_written(std::size_t n) noexcept
{
BOOST_ASSERT(remaining() >= n);
first_ += n;
}
bool done() const noexcept { return first_ == last_; }
span<const std::uint8_t> get_chunk(const std::vector<std::uint8_t>& buff) const noexcept
{
BOOST_ASSERT(buff.size() >= last_);
return {buff.data() + first_, remaining()};
}
};
class message_writer
{
std::vector<std::uint8_t> buffer_;
std::size_t max_frame_size_;
std::uint8_t* seqnum_{nullptr};
chunk_processor chunk_;
std::size_t total_bytes_{};
std::size_t total_bytes_written_{};
bool should_send_empty_frame_{};
void process_header_write(std::uint32_t size_to_write, std::uint8_t seqnum, std::size_t buff_offset)
{
serialize_frame_header(
frame_header{size_to_write, seqnum},
span<std::uint8_t, frame_header_size>(buffer_.data() + buff_offset, frame_header_size)
);
}
std::uint8_t next_seqnum() noexcept { return (*seqnum_)++; }
void prepare_next_chunk()
{
if (should_send_empty_frame_)
{
process_header_write(0, next_seqnum(), 0);
chunk_.reset(0, HEADER_SIZE);
should_send_empty_frame_ = false;
}
else if (total_bytes_written_ < total_bytes_)
{
std::size_t offset = total_bytes_written_;
std::size_t remaining = total_bytes_ - total_bytes_written_;
std::size_t size = (std::min)(max_frame_size_, remaining);
process_header_write(static_cast<std::uint32_t>(size), next_seqnum(), offset);
chunk_.reset(offset, offset + size + HEADER_SIZE);
if (remaining == max_frame_size_)
{
should_send_empty_frame_ = true;
}
total_bytes_written_ += size;
}
else
{
// We're done
chunk_.reset();
}
}
public:
message_writer(std::size_t max_frame_size = MAX_PACKET_SIZE) noexcept : max_frame_size_(max_frame_size) {}
span<std::uint8_t> prepare_buffer(std::size_t msg_size, std::uint8_t& seqnum)
{
buffer_.resize(msg_size + HEADER_SIZE);
total_bytes_ = msg_size;
total_bytes_written_ = 0;
should_send_empty_frame_ = msg_size == 0;
seqnum_ = &seqnum;
prepare_next_chunk();
return {buffer_.data() + HEADER_SIZE, msg_size};
}
bool done() const noexcept { return chunk_.done(); }
// This function returns an empty buffer to signal that we're done
span<const std::uint8_t> next_chunk() const
{
BOOST_ASSERT(!done());
return chunk_.get_chunk(buffer_);
}
void on_bytes_written(std::size_t n)
{
BOOST_ASSERT(!done());
// Acknowledge the written bytes
chunk_.on_bytes_written(n);
// Prepare the next chunk, if required
if (chunk_.done())
{
prepare_next_chunk();
}
}
};
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
+133
View File
@@ -0,0 +1,133 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_READ_BUFFER_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_READ_BUFFER_HPP
#include <boost/assert.hpp>
#include <boost/core/span.hpp>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <vector>
namespace boost {
namespace mysql {
namespace detail {
// Custom buffer type optimized for read operations performed in the MySQL protocol.
// The buffer is a single, resizable chunk of memory with four areas:
// - Reserved area: messages that have already been read but are kept alive,
// either because we need them or because we haven't cleaned them yet.
// - Current message area: delimits the message we are currently parsing.
// - Pending bytes area: bytes we've read but haven't been parsed into a message yet.
// - Free area: free space for more bytes to be read.
class read_buffer
{
std::vector<std::uint8_t> buffer_;
std::size_t current_message_offset_{0};
std::size_t pending_offset_{0};
std::size_t free_offset_{0};
public:
read_buffer(std::size_t size) : buffer_(size, std::uint8_t(0)) { buffer_.resize(buffer_.capacity()); }
// Whole buffer accessors
const std::uint8_t* first() const noexcept { return buffer_.data(); }
std::size_t size() const noexcept { return buffer_.size(); }
// Area accessors
std::uint8_t* reserved_first() noexcept { return buffer_.data(); }
const std::uint8_t* reserved_first() const noexcept { return buffer_.data(); }
std::uint8_t* current_message_first() noexcept { return buffer_.data() + current_message_offset_; }
const std::uint8_t* current_message_first() const noexcept
{
return buffer_.data() + current_message_offset_;
}
std::uint8_t* pending_first() noexcept { return buffer_.data() + pending_offset_; }
const std::uint8_t* pending_first() const noexcept { return buffer_.data() + pending_offset_; }
std::uint8_t* free_first() noexcept { return buffer_.data() + free_offset_; }
const std::uint8_t* free_first() const noexcept { return buffer_.data() + free_offset_; }
std::size_t reserved_size() const noexcept { return current_message_offset_; }
std::size_t current_message_size() const noexcept { return pending_offset_ - current_message_offset_; }
std::size_t pending_size() const noexcept { return free_offset_ - pending_offset_; }
std::size_t free_size() const noexcept { return buffer_.size() - free_offset_; }
span<const std::uint8_t> reserved_area() const noexcept { return {reserved_first(), reserved_size()}; }
span<const std::uint8_t> current_message() const noexcept
{
return {current_message_first(), current_message_size()};
}
span<const std::uint8_t> pending_area() const noexcept { return {pending_first(), pending_size()}; }
span<std::uint8_t> free_area() noexcept { return {free_first(), free_size()}; }
// Moves n bytes from the free to the processing area (e.g. they've been read)
void move_to_pending(std::size_t length) noexcept
{
BOOST_ASSERT(length <= free_size());
free_offset_ += length;
}
// Moves n bytes from the processing to the current message area
void move_to_current_message(std::size_t length) noexcept
{
BOOST_ASSERT(length <= pending_size());
pending_offset_ += length;
}
// Removes the last length bytes from the current message area,
// effectively memmove'ing all subsequent bytes backwards.
// Used to remove intermediate headers. length must be > 0
void remove_current_message_last(std::size_t length) noexcept
{
BOOST_ASSERT(length <= current_message_size());
BOOST_ASSERT(length > 0);
std::memmove(pending_first() - length, pending_first(), pending_size());
pending_offset_ -= length;
free_offset_ -= length;
}
// Moves length bytes from the current message area to the reserved area
// Used to move entire parsed messages or message headers
void move_to_reserved(std::size_t length) noexcept
{
BOOST_ASSERT(length <= current_message_size());
current_message_offset_ += length;
}
// Removes the reserved area, effectively memmove'ing evth backwards
void remove_reserved() noexcept
{
if (reserved_size() > 0)
{
std::size_t currmsg_size = current_message_size();
std::size_t pend_size = pending_size();
std::memmove(buffer_.data(), current_message_first(), currmsg_size + pend_size);
current_message_offset_ = 0;
pending_offset_ = currmsg_size;
free_offset_ = currmsg_size + pend_size;
}
}
// Makes sure the free size is at least n bytes long; resizes the buffer if required
void grow_to_fit(std::size_t n)
{
if (free_size() < n)
{
buffer_.resize(buffer_.size() + n - free_size());
buffer_.resize(buffer_.capacity());
}
}
};
} // namespace detail
} // namespace mysql
} // namespace boost
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_AUXILIAR_STATIC_STRING_HPP_ */
+38
View File
@@ -0,0 +1,38 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_VALGRIND_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_VALGRIND_HPP
#include <cstddef>
#ifdef BOOST_MYSQL_VALGRIND_TESTS
#include <valgrind/memcheck.h>
#endif
namespace boost {
namespace mysql {
namespace detail {
#ifdef BOOST_MYSQL_VALGRIND_TESTS
inline void valgrind_make_mem_defined(const void* data, std::size_t size)
{
VALGRIND_MAKE_MEM_DEFINED(data, size);
}
#else
inline void valgrind_make_mem_defined(const void*, std::size_t) noexcept {}
#endif
} // namespace detail
} // namespace mysql
} // namespace boost
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_AUXILIAR_VALGRIND_HPP_ */
+97
View File
@@ -0,0 +1,97 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_WRITE_MESSAGE_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_CHANNEL_WRITE_MESSAGE_HPP
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/detail/any_stream.hpp>
#include <boost/mysql/impl/internal/channel/message_writer.hpp>
#include <boost/mysql/impl/internal/protocol/constants.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/coroutine.hpp>
#include <cstddef>
#include <cstdint>
namespace boost {
namespace mysql {
namespace detail {
// Writes an entire message to stream; partitions the message into
// chunks and adds the required headers
inline void write_message(any_stream& stream, message_writer& processor, error_code& ec)
{
while (!processor.done())
{
std::size_t bytes_written = stream.write_some(asio::buffer(processor.next_chunk()), ec);
if (ec)
break;
processor.on_bytes_written(bytes_written);
}
}
struct write_message_op : boost::asio::coroutine
{
any_stream& stream_;
message_writer& processor_;
write_message_op(any_stream& stream, message_writer& processor) noexcept
: stream_(stream), processor_(processor)
{
}
template <class Self>
void operator()(Self& self, error_code ec = {}, std::size_t bytes_written = 0)
{
// Error handling
if (ec)
{
self.complete(ec);
return;
}
// Non-error path
BOOST_ASIO_CORO_REENTER(*this)
{
// done() never returns false after a call to prepare_buffer(), so no post() needed
BOOST_ASSERT(!processor_.done());
while (!processor_.done())
{
BOOST_ASIO_CORO_YIELD stream_.async_write_some(
asio::buffer(processor_.next_chunk()),
std::move(self)
);
processor_.on_bytes_written(bytes_written);
};
self.complete(error_code());
}
}
};
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code))
async_write_message(any_stream& stream, message_writer& processor, CompletionToken&& token)
{
return boost::asio::async_compose<CompletionToken, void(error_code)>(
write_message_op(stream, processor),
token,
stream
);
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
@@ -0,0 +1,36 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_ERROR_SERVER_ERROR_TO_STRING_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_ERROR_SERVER_ERROR_TO_STRING_HPP
#include <boost/mysql/detail/config.hpp>
namespace boost {
namespace mysql {
namespace detail {
// Returns NULL if this is not a common error (not a member of common_server_errc)
BOOST_MYSQL_DECL
const char* common_error_to_string(int v) noexcept;
// These return a default string if the error is not known
BOOST_MYSQL_DECL
const char* mysql_error_to_string(int v) noexcept;
BOOST_MYSQL_DECL
const char* mariadb_error_to_string(int v) noexcept;
} // namespace detail
} // namespace mysql
} // namespace boost
#ifdef BOOST_MYSQL_HEADER_ONLY
#include <boost/mysql/impl/internal/error/server_error_to_string.ipp>
#endif
#endif
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_MAKE_STRING_VIEW_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_MAKE_STRING_VIEW_HPP
#include <boost/mysql/string_view.hpp>
namespace boost {
namespace mysql {
namespace detail {
template <std::size_t N>
constexpr string_view make_string_view(const char (&buff)[N]) noexcept
{
static_assert(N >= 1, "Expected a C-array literal");
return string_view(buff, N - 1); // discard null terminator
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
@@ -0,0 +1,92 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_CLOSE_CONNECTION_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_CLOSE_CONNECTION_HPP
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/impl/internal/channel/channel.hpp>
#include <boost/mysql/impl/internal/network_algorithms/quit_connection.hpp>
#include <boost/asio/post.hpp>
namespace boost {
namespace mysql {
namespace detail {
struct close_connection_op : boost::asio::coroutine
{
channel& chan_;
diagnostics& diag_;
close_connection_op(channel& chan, diagnostics& diag) : chan_(chan), diag_(diag) {}
template <class Self>
void operator()(Self& self, error_code err = {})
{
error_code close_err;
BOOST_ASIO_CORO_REENTER(*this)
{
diag_.clear();
if (!chan_.stream().is_open())
{
BOOST_ASIO_CORO_YIELD boost::asio::post(chan_.get_executor(), std::move(self));
self.complete(error_code());
BOOST_ASIO_CORO_YIELD break;
}
BOOST_ASIO_CORO_YIELD async_quit_connection_impl(chan_, diag_, std::move(self));
// We call close regardless of the quit outcome
chan_.stream().close(close_err);
self.complete(err ? err : close_err);
}
}
};
// Interface
inline void close_connection_impl(channel& chan, error_code& err, diagnostics& diag)
{
err.clear();
diag.clear();
// Close = quit + close stream. We close the stream regardless of the quit failing or not
if (chan.stream().is_open())
{
// MySQL quit notification
quit_connection_impl(chan, err, diag);
error_code close_err;
chan.stream().close(close_err);
if (!err)
{
err = close_err;
}
}
}
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_close_connection_impl(channel& chan, diagnostics& diag, CompletionToken&& token)
{
return asio::async_compose<CompletionToken, void(error_code)>(
close_connection_op{chan, diag},
token,
chan
);
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_CLOSE_CONNECTION_HPP_ */
@@ -0,0 +1,61 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_CLOSE_STATEMENT_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_CLOSE_STATEMENT_HPP
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/impl/internal/channel/channel.hpp>
#include <boost/mysql/impl/internal/protocol/protocol.hpp>
#include <boost/asio/async_result.hpp>
namespace boost {
namespace mysql {
namespace detail {
inline void compose_close_statement(channel& chan, const statement& stmt)
{
chan.serialize(close_stmt_command{stmt.id()}, chan.reset_sequence_number());
}
inline void close_statement_impl(channel& chan, const statement& stmt, error_code& err, diagnostics& diag)
{
err.clear();
diag.clear();
// Serialize the close message
compose_close_statement(chan, stmt);
// Send it. No response is sent back
chan.write(err);
}
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_close_statement_impl(channel& chan, const statement& stmt, diagnostics& diag, CompletionToken&& token)
{
// We can do this here because we know no deferred tokens reach this function (thanks to erasing)
diag.clear();
// Serialize the close message
compose_close_statement(chan, stmt);
// Send it. No response is sent back
return chan.async_write(std::forward<CompletionToken>(token));
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_CLOSE_STATEMENT_HPP_ */
@@ -0,0 +1,113 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_CONNECT_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_CONNECT_HPP
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/impl/internal/channel/channel.hpp>
#include <boost/mysql/impl/internal/network_algorithms/handshake.hpp>
#include <boost/asio/coroutine.hpp>
namespace boost {
namespace mysql {
namespace detail {
struct connect_op : boost::asio::coroutine
{
channel& chan_;
diagnostics& diag_;
const void* ep_;
handshake_params params_;
connect_op(channel& chan, diagnostics& diag, const void* ep, const handshake_params& params)
: chan_(chan), diag_(diag), ep_(ep), params_(params)
{
}
template <class Self>
void operator()(Self& self, error_code code = {})
{
error_code ignored;
BOOST_ASIO_CORO_REENTER(*this)
{
diag_.clear();
// Physical connect
BOOST_ASIO_CORO_YIELD chan_.stream().async_connect(ep_, std::move(self));
if (code)
{
chan_.stream().close(ignored);
self.complete(code);
BOOST_ASIO_CORO_YIELD break;
}
// Handshake
BOOST_ASIO_CORO_YIELD async_handshake_impl(chan_, params_, diag_, std::move(self));
if (code)
{
chan_.stream().close(ignored);
}
self.complete(code);
}
}
};
// External interface
inline void connect_impl(
channel& chan,
const void* endpoint,
const handshake_params& params,
error_code& err,
diagnostics& diag
)
{
err.clear();
diag.clear();
error_code ignored;
chan.stream().connect(endpoint, err);
if (err)
{
chan.stream().close(ignored);
return;
}
handshake_impl(chan, params, err, diag);
if (err)
{
chan.stream().close(ignored);
}
}
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_connect_impl(
channel& chan,
const void* endpoint,
const handshake_params& params,
diagnostics& diag,
CompletionToken&& token
)
{
return asio::async_compose<CompletionToken, void(error_code)>(
connect_op{chan, diag, endpoint, params},
token,
chan
);
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_CONNECT_HPP_ */
@@ -0,0 +1,137 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_EXECUTE_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_EXECUTE_HPP
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
#include <boost/mysql/impl/internal/channel/channel.hpp>
#include <boost/mysql/impl/internal/network_algorithms/read_resultset_head.hpp>
#include <boost/mysql/impl/internal/network_algorithms/read_some_rows.hpp>
#include <boost/mysql/impl/internal/network_algorithms/start_execution.hpp>
#include <boost/asio/coroutine.hpp>
namespace boost {
namespace mysql {
namespace detail {
struct execute_impl_op : boost::asio::coroutine
{
channel& chan_;
any_execution_request req_;
execution_processor& output_;
diagnostics& diag_;
execute_impl_op(
channel& chan,
const any_execution_request& req,
execution_processor& output,
diagnostics& diag
) noexcept
: chan_(chan), req_(req), output_(output), diag_(diag)
{
}
template <class Self>
void operator()(Self& self, error_code err = {}, std::size_t = 0)
{
// Error checking
if (err)
{
self.complete(err);
return;
}
// Normal path
BOOST_ASIO_CORO_REENTER(*this)
{
// Send request and read the first response
BOOST_ASIO_CORO_YIELD async_start_execution_impl(chan_, req_, output_, diag_, std::move(self));
// Read anything else
while (!output_.is_complete())
{
if (output_.is_reading_head())
{
BOOST_ASIO_CORO_YIELD
async_read_resultset_head_impl(chan_, output_, diag_, std::move(self));
}
else if (output_.is_reading_rows())
{
BOOST_ASIO_CORO_YIELD
async_read_some_rows_impl(chan_, output_, output_ref(), diag_, std::move(self));
}
}
self.complete(error_code());
}
}
};
// External interface
inline void execute_impl(
channel& channel,
const any_execution_request& req,
execution_processor& output,
error_code& err,
diagnostics& diag
)
{
err.clear();
diag.clear();
// Send request and read the first response
start_execution_impl(channel, req, output, err, diag);
if (err)
return;
// Read rows and anything else
while (!output.is_complete())
{
if (output.is_reading_head())
{
read_resultset_head_impl(channel, output, err, diag);
if (err)
return;
}
else if (output.is_reading_rows())
{
read_some_rows_impl(channel, output, output_ref(), err, diag);
if (err)
return;
}
}
}
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_execute_impl(
channel& chan,
const any_execution_request& req,
execution_processor& output,
diagnostics& diag,
CompletionToken&& token
)
{
return asio::async_compose<CompletionToken, void(error_code)>(
execute_impl_op(chan, req, output, diag),
token,
chan
);
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
@@ -0,0 +1,391 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_HANDSHAKE_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_HANDSHAKE_HPP
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/handshake_params.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/impl/internal/auth/auth.hpp>
#include <boost/mysql/impl/internal/channel/channel.hpp>
#include <boost/mysql/impl/internal/protocol/capabilities.hpp>
#include <boost/mysql/impl/internal/protocol/protocol.hpp>
namespace boost {
namespace mysql {
namespace detail {
inline capabilities conditional_capability(bool condition, std::uint32_t cap)
{
return capabilities(condition ? cap : 0);
}
inline error_code process_capabilities(
const handshake_params& params,
const server_hello& hello,
bool is_ssl_stream,
capabilities& negotiated_caps
)
{
auto ssl = params.ssl();
capabilities server_caps = hello.server_capabilities;
capabilities required_caps = mandatory_capabilities |
conditional_capability(!params.database().empty(), CLIENT_CONNECT_WITH_DB) |
conditional_capability(params.multi_queries(), CLIENT_MULTI_STATEMENTS) |
conditional_capability(
ssl == ssl_mode::require && is_ssl_stream,
CLIENT_SSL
);
if (required_caps.has(CLIENT_SSL) && !server_caps.has(CLIENT_SSL))
{
// This happens if the server doesn't have SSL configured. This special
// error code helps users diagnosing their problem a lot (server_unsupported doesn't).
return make_error_code(client_errc::server_doesnt_support_ssl);
}
else if (!server_caps.has_all(required_caps))
{
return make_error_code(client_errc::server_unsupported);
}
negotiated_caps = server_caps &
(required_caps | optional_capabilities |
conditional_capability(ssl == ssl_mode::enable && is_ssl_stream, CLIENT_SSL));
return error_code();
}
// When receiving an auth response from the server, several things can happen:
// - An OK packet. It means we are done with the auth phase. auth_result::complete.
// - An auth switch response. It means we should change the auth plugin,
// recalculate the auth response and send it back. auth_result::send_more_data.
// - An auth more data. Same as auth switch response, but without changing
// the authentication plugin. Also auth_result::send_more_data.
// - An auth more data with a challenge equals to fast_auth_complete_challenge.
// This means auth is complete and we should wait for an OK packet (auth_result::wait_for_ok).
// I have no clue why the server sends this instead of just an OK packet. It
// happens just for caching_sha2_password.
enum class auth_state
{
complete,
send_more_data,
wait_for_ok,
invalid
};
class handshake_processor
{
handshake_params params_;
diagnostics& diag_;
channel& channel_;
auth_response auth_resp_;
auth_state auth_state_{auth_state::invalid};
public:
handshake_processor(const handshake_params& params, diagnostics& diag, channel& channel)
: params_(params), diag_(diag), channel_(channel){};
const handshake_params& params() const noexcept { return params_; }
channel& get_channel() noexcept { return channel_; }
void clear_diagnostics() noexcept { diag_.clear(); }
// Once the handshake is processed, the capabilities are stored in the channel
bool use_ssl() const noexcept { return channel_.current_capabilities().has(CLIENT_SSL); }
// Initial greeting processing
error_code process_handshake(span<const std::uint8_t> buffer, bool is_ssl_stream)
{
// Deserialize server hello
server_hello hello{};
auto err = deserialize_server_hello(buffer, hello, diag_);
if (err)
return err;
// Check capabilities
capabilities negotiated_caps;
err = process_capabilities(params_, hello, is_ssl_stream, negotiated_caps);
if (err)
return err;
// Set capabilities & db flavor
channel_.set_current_capabilities(negotiated_caps);
channel_.set_flavor(hello.server);
// Compute auth response
return compute_auth_response(
hello.auth_plugin_name,
params_.password(),
hello.auth_plugin_data.to_span(),
use_ssl(),
auth_resp_
);
}
// Response to that initial greeting
void compose_ssl_request()
{
ssl_request sslreq{
channel_.current_capabilities(),
static_cast<std::uint32_t>(MAX_PACKET_SIZE),
params_.connection_collation(),
};
channel_.serialize(sslreq, channel_.shared_sequence_number());
}
void compose_login_request()
{
// Compose login request
login_request response{
channel_.current_capabilities(),
static_cast<std::uint32_t>(MAX_PACKET_SIZE),
params_.connection_collation(),
params_.username(),
auth_resp_.data,
params_.database(),
auth_resp_.plugin_name,
};
// Serialize
channel_.serialize(response, channel_.shared_sequence_number());
}
// Server handshake response
error_code process_handshake_server_response(span<const std::uint8_t> msg)
{
error_code err;
auto response = deserialize_handshake_server_response(msg, channel_.flavor(), diag_);
switch (response.type)
{
case handhake_server_response::type_t::ok:
// Auth success
auth_state_ = auth_state::complete;
return error_code();
case handhake_server_response::type_t::error: return response.data.err;
case handhake_server_response::type_t::auth_switch:
// Compute response
err = compute_auth_response(
response.data.auth_sw.plugin_name,
params_.password(),
response.data.auth_sw.auth_data,
use_ssl(),
auth_resp_
);
if (err)
return err;
// Serialize
channel_.serialize(auth_switch_response{auth_resp_.data}, channel_.shared_sequence_number());
auth_state_ = auth_state::send_more_data;
return error_code();
case handhake_server_response::type_t::ok_follows:
// The next packet will be an OK packet
auth_state_ = auth_state::wait_for_ok;
return error_code();
case handhake_server_response::type_t::auth_more_data:
// Compute response
err = compute_auth_response(
auth_resp_.plugin_name,
params_.password(),
response.data.more_data,
use_ssl(),
auth_resp_
);
if (err)
return err;
channel_.serialize(auth_switch_response{auth_resp_.data}, channel_.shared_sequence_number());
auth_state_ = auth_state::send_more_data;
return error_code();
default: BOOST_ASSERT(false); return error_code();
}
}
bool should_send_auth_switch_response() const noexcept
{
return auth_state_ == auth_state::send_more_data;
}
bool auth_complete() const noexcept { return auth_state_ == auth_state::complete; }
};
struct handshake_op : boost::asio::coroutine
{
handshake_processor processor_;
handshake_op(const handshake_params& params, diagnostics& diag, channel& channel)
: processor_(params, diag, channel)
{
}
channel& get_channel() noexcept { return processor_.get_channel(); }
template <class Self>
void operator()(Self& self, error_code err = {}, span<const std::uint8_t> read_msg = {})
{
// Error checking
if (err)
{
self.complete(err);
return;
}
// Non-error path
BOOST_ASIO_CORO_REENTER(*this)
{
processor_.clear_diagnostics();
// Setup the channel
get_channel().reset();
// Read server greeting
BOOST_ASIO_CORO_YIELD get_channel().async_read_one(
get_channel().shared_sequence_number(),
std::move(self)
);
// Process server greeting
err = processor_.process_handshake(read_msg, get_channel().stream().supports_ssl());
if (err)
{
self.complete(err);
BOOST_ASIO_CORO_YIELD break;
}
// SSL
if (processor_.use_ssl())
{
// Send SSL request
processor_.compose_ssl_request();
BOOST_ASIO_CORO_YIELD get_channel().async_write(std::move(self));
// SSL handshake
BOOST_ASIO_CORO_YIELD get_channel().stream().async_handshake(std::move(self));
}
// Compose and send handshake response
processor_.compose_login_request();
BOOST_ASIO_CORO_YIELD get_channel().async_write(std::move(self));
while (!processor_.auth_complete())
{
// Receive response
BOOST_ASIO_CORO_YIELD get_channel().async_read_one(
get_channel().shared_sequence_number(),
std::move(self)
);
// Process it
err = processor_.process_handshake_server_response(read_msg);
if (err)
{
self.complete(err);
BOOST_ASIO_CORO_YIELD break;
}
// We received an auth switch response and we have the response ready to be sent
if (processor_.should_send_auth_switch_response())
{
BOOST_ASIO_CORO_YIELD get_channel().async_write(std::move(self));
}
}
self.complete(error_code());
}
}
};
// External interface
inline void handshake_impl(
channel& channel,
const handshake_params& params,
error_code& err,
diagnostics& diag
)
{
err.clear();
diag.clear();
channel.reset();
// Set up processor
handshake_processor processor(params, diag, channel);
// Read server greeting
auto read_message = channel.read_one(channel.shared_sequence_number(), err);
if (err)
return;
// Process server greeting (handshake)
err = processor.process_handshake(read_message, channel.stream().supports_ssl());
if (err)
return;
// SSL
if (processor.use_ssl())
{
// Send SSL request
processor.compose_ssl_request();
channel.write(err);
if (err)
return;
// SSL handshake
channel.stream().handshake(err);
if (err)
return;
}
// Handshake response
processor.compose_login_request();
channel.write(err);
if (err)
return;
while (!processor.auth_complete())
{
// Receive response
read_message = channel.read_one(channel.shared_sequence_number(), err);
if (err)
return;
// Process it
err = processor.process_handshake_server_response(read_message);
if (err)
return;
if (processor.should_send_auth_switch_response())
{
// We received an auth switch request and we have the response ready to be sent
channel.write(err);
if (err)
return;
}
};
}
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_handshake_impl(
channel& chan,
const handshake_params& params,
diagnostics& diag,
CompletionToken&& token
)
{
return boost::asio::async_compose<CompletionToken, void(error_code)>(
handshake_op(params, diag, chan),
token,
chan
);
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
+103
View File
@@ -0,0 +1,103 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_PING_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_PING_HPP
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/impl/internal/channel/channel.hpp>
#include <boost/mysql/impl/internal/protocol/protocol.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/coroutine.hpp>
namespace boost {
namespace mysql {
namespace detail {
inline void serialize_ping_message(channel& chan)
{
chan.serialize(ping_command(), chan.reset_sequence_number());
}
struct ping_op : boost::asio::coroutine
{
channel& chan_;
diagnostics& diag_;
ping_op(channel& chan, diagnostics& diag) noexcept : chan_(chan), diag_(diag) {}
template <class Self>
void operator()(Self& self, error_code err = {}, span<const std::uint8_t> buff = {})
{
// Error checking
if (err)
{
self.complete(err);
return;
}
// Regular coroutine body; if there has been an error, we don't get here
BOOST_ASIO_CORO_REENTER(*this)
{
diag_.clear();
// Serialize the message
serialize_ping_message(chan_);
// Write message
BOOST_ASIO_CORO_YIELD chan_.async_write(std::move(self));
// Read response
BOOST_ASIO_CORO_YIELD chan_.async_read_one(chan_.shared_sequence_number(), std::move(self));
// Verify it's what we expected
self.complete(deserialize_ok_response(buff, chan_.flavor(), diag_));
}
}
};
// Interface
inline void ping_impl(channel& chan, error_code& err, diagnostics& diag)
{
err.clear();
diag.clear();
// Serialize the message
serialize_ping_message(chan);
// Send it
chan.write(err);
if (err)
return;
// Read response
auto response = chan.read_one(chan.shared_sequence_number(), err);
if (err)
return;
// Verify it's what we expected
err = deserialize_ok_response(response, chan.flavor(), diag);
}
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_ping_impl(channel& chan, diagnostics& diag, CompletionToken&& token)
{
return asio::async_compose<CompletionToken, void(error_code)>(ping_op(chan, diag), token, chan);
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
@@ -0,0 +1,209 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_PREPARE_STATEMENT_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_PREPARE_STATEMENT_HPP
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/detail/access.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/impl/internal/channel/channel.hpp>
#include <boost/mysql/impl/internal/protocol/protocol.hpp>
#include <cstdint>
namespace boost {
namespace mysql {
namespace detail {
class prepare_statement_processor
{
channel& channel_;
string_view stmt_sql_;
diagnostics& diag_;
statement res_;
unsigned remaining_meta_{};
public:
prepare_statement_processor(channel& chan, string_view stmt_sql, diagnostics& diag) noexcept
: channel_(chan), stmt_sql_(stmt_sql), diag_(diag)
{
}
void clear_diag() noexcept { diag_.clear(); }
void process_request()
{
channel_.serialize(prepare_stmt_command{stmt_sql_}, channel_.reset_sequence_number());
}
void process_response(span<const std::uint8_t> message, error_code& err)
{
prepare_stmt_response response{};
err = deserialize_prepare_stmt_response(message, channel_.flavor(), response, diag_);
if (err)
return;
res_ = access::construct<statement>(response.id, response.num_params);
remaining_meta_ = response.num_columns + response.num_params;
}
bool has_remaining_meta() const noexcept { return remaining_meta_ != 0; }
void on_meta_received() noexcept { --remaining_meta_; }
const statement& result() const noexcept { return res_; }
channel& get_channel() noexcept { return channel_; }
};
struct prepare_statement_op : boost::asio::coroutine
{
prepare_statement_processor processor_;
prepare_statement_op(channel& chan, string_view stmt_sql, diagnostics& diag)
: processor_(chan, stmt_sql, diag)
{
}
channel& get_channel() noexcept { return processor_.get_channel(); }
template <class Self>
void operator()(Self& self, error_code err = {}, span<const std::uint8_t> read_message = {})
{
// Error checking
if (err)
{
self.complete(err, statement());
return;
}
// Regular coroutine body; if there has been an error, we don't get here
BOOST_ASIO_CORO_REENTER(*this)
{
processor_.clear_diag();
// Serialize request
processor_.process_request();
// Write message
BOOST_ASIO_CORO_YIELD get_channel().async_write(std::move(self));
// Read response
BOOST_ASIO_CORO_YIELD get_channel().async_read_one(
get_channel().shared_sequence_number(),
std::move(self)
);
// Process response
processor_.process_response(read_message, err);
if (err)
{
self.complete(err, statement());
BOOST_ASIO_CORO_YIELD break;
}
// Server sends now one packet per parameter and field.
// We ignore these for now.
while (processor_.has_remaining_meta())
{
// Read from the stream if necessary
if (!get_channel().has_read_messages())
{
BOOST_ASIO_CORO_YIELD get_channel().async_read_some(std::move(self));
}
// Read the message
read_message = get_channel().next_read_message(get_channel().shared_sequence_number(), err);
if (err)
{
self.complete(err, statement());
BOOST_ASIO_CORO_YIELD break;
}
// Note it as processed
processor_.on_meta_received();
}
// Complete
self.complete(error_code(), processor_.result());
}
}
};
// External interface
inline statement prepare_statement_impl(
channel& channel,
string_view stmt_sql,
error_code& err,
diagnostics& diag
)
{
err.clear();
diag.clear();
prepare_statement_processor processor(channel, stmt_sql, diag);
// Prepare message
processor.process_request();
// Write message
channel.write(err);
if (err)
return statement();
// Read response
auto read_buffer = channel.read_one(channel.shared_sequence_number(), err);
if (err)
return statement();
// Process response
processor.process_response(read_buffer, err);
if (err)
return statement();
// Server sends now one packet per parameter and field.
// We ignore these for now.
while (processor.has_remaining_meta())
{
// Read from the stream if necessary
if (!channel.has_read_messages())
{
channel.read_some(err);
if (err)
return statement();
}
// Discard the message
channel.next_read_message(channel.shared_sequence_number(), err);
if (err)
return statement();
// Update the processor state
processor.on_meta_received();
}
return processor.result();
}
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, statement))
async_prepare_statement_impl(channel& chan, string_view stmt_sql, diagnostics& diag, CompletionToken&& token)
{
return asio::async_compose<CompletionToken, void(error_code, statement)>(
prepare_statement_op(chan, stmt_sql, diag),
token,
chan
);
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_PREPARE_STATEMENT_HPP_ */
@@ -0,0 +1,95 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_QUIT_CONNECTION_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_QUIT_CONNECTION_HPP
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/impl/internal/channel/channel.hpp>
#include <boost/mysql/impl/internal/protocol/protocol.hpp>
#include <boost/asio/coroutine.hpp>
namespace boost {
namespace mysql {
namespace detail {
inline void compose_quit(channel& chan) { chan.serialize(quit_command(), chan.reset_sequence_number()); }
struct quit_connection_op : boost::asio::coroutine
{
channel& chan_;
diagnostics& diag_;
quit_connection_op(channel& chan, diagnostics& diag) noexcept : chan_(chan), diag_(diag) {}
template <class Self>
void operator()(Self& self, error_code err = {})
{
BOOST_ASIO_CORO_REENTER(*this)
{
diag_.clear();
// Quit message
compose_quit(chan_);
BOOST_ASIO_CORO_YIELD chan_.async_write(std::move(self));
if (err)
{
self.complete(err);
}
// SSL shutdown error ignored, as MySQL doesn't always gracefully
// close SSL connections.
if (chan_.stream().ssl_active())
{
BOOST_ASIO_CORO_YIELD chan_.stream().async_shutdown(std::move(self));
}
self.complete(error_code());
}
}
};
// Interface
inline void quit_connection_impl(channel& chan, error_code& err, diagnostics& diag)
{
err.clear();
diag.clear();
compose_quit(chan);
chan.write(err);
if (err)
return;
if (chan.stream().ssl_active())
{
// SSL shutdown. Result ignored as MySQL does not always perform
// graceful SSL shutdowns
error_code ignored;
chan.stream().shutdown(ignored);
}
}
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_quit_connection_impl(channel& chan, diagnostics& diag, CompletionToken&& token)
{
return asio::async_compose<CompletionToken, void(error_code)>(
quit_connection_op(chan, diag),
token,
chan
);
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_QUIT_CONNECTION_HPP_ */
@@ -0,0 +1,204 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_READ_RESULTSET_HEAD_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_READ_RESULTSET_HEAD_HPP
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/metadata.hpp>
#include <boost/mysql/detail/coldef_view.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
#include <boost/mysql/impl/internal/channel/channel.hpp>
#include <boost/mysql/impl/internal/protocol/protocol.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/assert.hpp>
namespace boost {
namespace mysql {
namespace detail {
inline error_code process_execution_response(
channel& chan,
execution_processor& proc,
span<const std::uint8_t> msg,
diagnostics& diag
)
{
auto response = deserialize_execute_response(msg, chan.flavor(), diag);
error_code err;
switch (response.type)
{
case execute_response::type_t::error: err = response.data.err; break;
case execute_response::type_t::ok_packet:
err = proc.on_head_ok_packet(response.data.ok_pack, diag);
break;
case execute_response::type_t::num_fields: proc.on_num_meta(response.data.num_fields); break;
}
return err;
}
inline error_code process_field_definition(channel& chan, execution_processor& proc, diagnostics& diag)
{
// Read the field definition packet (it's cached at this point)
BOOST_ASSERT(chan.has_read_messages());
error_code err;
auto msg = chan.next_read_message(proc.sequence_number(), err);
if (err)
return err;
// Deserialize
coldef_view coldef{};
err = deserialize_column_definition(msg, coldef);
if (err)
return err;
// Notify the processor
return proc.on_meta(coldef, diag);
}
struct read_resultset_head_op : boost::asio::coroutine
{
channel& chan_;
execution_processor& proc_;
diagnostics& diag_;
read_resultset_head_op(channel& chan, execution_processor& proc, diagnostics& diag)
: chan_(chan), proc_(proc), diag_(diag)
{
}
template <class Self>
void operator()(Self& self, error_code err = {}, span<const std::uint8_t> read_message = {})
{
// Error checking
if (err)
{
self.complete(err);
return;
}
// Non-error path
BOOST_ASIO_CORO_REENTER(*this)
{
// Setup
diag_.clear();
// If we're not reading head, return
if (!proc_.is_reading_head())
{
BOOST_ASIO_CORO_YIELD boost::asio::post(chan_.get_executor(), std::move(self));
self.complete(error_code());
BOOST_ASIO_CORO_YIELD break;
}
// Read the response
BOOST_ASIO_CORO_YIELD chan_.async_read_one(proc_.sequence_number(), std::move(self));
// Response may be: ok_packet, err_packet, local infile request
// (not implemented), or response with fields
err = process_execution_response(chan_, proc_, read_message, diag_);
if (err)
{
self.complete(err);
BOOST_ASIO_CORO_YIELD break;
}
// Read all of the field definitions
while (proc_.is_reading_meta())
{
// Read from the stream if we need it
if (!chan_.has_read_messages())
{
BOOST_ASIO_CORO_YIELD chan_.async_read_some(std::move(self));
}
// Process the metadata packet
err = process_field_definition(chan_, proc_, diag_);
if (err)
{
self.complete(err);
BOOST_ASIO_CORO_YIELD break;
}
}
// No EOF packet is expected here, as we require deprecate EOF capabilities
self.complete(err);
}
}
};
// External interface
inline void read_resultset_head_impl(
channel& chan,
execution_processor& proc,
error_code& err,
diagnostics& diag
)
{
// Setup
err.clear();
diag.clear();
// If we're not reading head, return
if (!proc.is_reading_head())
return;
// Read the response
auto msg = chan.read_one(proc.sequence_number(), err);
if (err)
return;
// Response may be: ok_packet, err_packet, local infile request
// (not implemented), or response with fields
err = process_execution_response(chan, proc, msg, diag);
if (err)
return;
// Read all of the field definitions (zero if empty resultset)
while (proc.is_reading_meta())
{
// Read from the stream if required
if (!chan.has_read_messages())
{
chan.read_some(err);
if (err)
return;
}
// Process the packet
err = process_field_definition(chan, proc, diag);
if (err)
return;
}
}
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_read_resultset_head_impl(
channel& channel,
execution_processor& proc,
diagnostics& diag,
CompletionToken&& token
)
{
return asio::async_compose<CompletionToken, void(error_code)>(
read_resultset_head_op(channel, proc, diag),
token,
channel
);
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
@@ -0,0 +1,184 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_READ_SOME_ROWS_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_READ_SOME_ROWS_HPP
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
#include <boost/mysql/impl/internal/channel/channel.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/post.hpp>
#include <cstddef>
namespace boost {
namespace mysql {
namespace detail {
BOOST_ATTRIBUTE_NODISCARD inline error_code process_some_rows(
channel& chan,
execution_processor& proc,
output_ref output,
std::size_t& read_rows,
diagnostics& diag
)
{
// Process all read messages until they run out, an error happens
// or an EOF is received
read_rows = 0;
error_code err;
proc.on_row_batch_start();
while (chan.has_read_messages() && proc.is_reading_rows() && read_rows < output.max_size())
{
// Get the row message
auto buff = chan.next_read_message(proc.sequence_number(), err);
if (err)
return err;
// Deserialize it
auto res = deserialize_row_message(buff, chan.flavor(), diag);
if (res.type == row_message::type_t::error)
{
err = res.data.err;
}
else if (res.type == row_message::type_t::row)
{
output.set_offset(read_rows);
err = proc.on_row(res.data.row, output, chan.shared_fields());
if (!err)
++read_rows;
}
else
{
err = proc.on_row_ok_packet(res.data.ok_pack);
}
if (err)
return err;
}
proc.on_row_batch_finish();
return error_code();
}
struct read_some_rows_impl_op : boost::asio::coroutine
{
channel& chan_;
diagnostics& diag_;
execution_processor& proc_;
output_ref output_;
read_some_rows_impl_op(
channel& chan,
diagnostics& diag,
execution_processor& proc,
output_ref output
) noexcept
: chan_(chan), diag_(diag), proc_(proc), output_(output)
{
}
template <class Self>
void operator()(Self& self, error_code err = {})
{
// Error checking
if (err)
{
self.complete(err, 0);
return;
}
// Normal path
std::size_t read_rows = 0;
BOOST_ASIO_CORO_REENTER(*this)
{
diag_.clear();
// If we are not reading rows, return
if (!proc_.is_reading_rows())
{
BOOST_ASIO_CORO_YIELD boost::asio::post(chan_.get_executor(), std::move(self));
self.complete(error_code(), 0);
BOOST_ASIO_CORO_YIELD break;
}
// Read at least one message
BOOST_ASIO_CORO_YIELD chan_.async_read_some(std::move(self));
// Process messages
err = process_some_rows(chan_, proc_, output_, read_rows, diag_);
if (err)
{
self.complete(err, 0);
BOOST_ASIO_CORO_YIELD break;
}
self.complete(error_code(), read_rows);
}
}
};
// External interface
inline std::size_t read_some_rows_impl(
channel& chan,
execution_processor& proc,
const output_ref& output,
error_code& err,
diagnostics& diag
)
{
err.clear();
diag.clear();
// If we are not reading rows, just return
if (!proc.is_reading_rows())
{
return 0;
}
// Read from the stream until there is at least one message
chan.read_some(err);
if (err)
return 0;
// Process read messages
std::size_t read_rows = 0;
err = process_some_rows(chan, proc, output, read_rows, diag);
if (err)
return 0;
return read_rows;
}
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, std::size_t))
async_read_some_rows_impl(
channel& chan,
execution_processor& proc,
const output_ref& output,
diagnostics& diag,
CompletionToken&& token
)
{
return asio::async_compose<CompletionToken, void(error_code, std::size_t)>(
read_some_rows_impl_op(chan, diag, proc, output),
token,
chan
);
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
@@ -0,0 +1,105 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_READ_SOME_ROWS_DYNAMIC_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_READ_SOME_ROWS_DYNAMIC_HPP
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/rows_view.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/detail/execution_processor/execution_state_impl.hpp>
#include <boost/mysql/impl/internal/channel/channel.hpp>
#include <boost/mysql/impl/internal/network_algorithms/read_some_rows.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/coroutine.hpp>
namespace boost {
namespace mysql {
namespace detail {
inline rows_view get_some_rows(const channel& ch, const execution_state_impl& st)
{
return access::construct<rows_view>(
ch.shared_fields().data(),
ch.shared_fields().size(),
st.meta().size()
);
}
struct read_some_rows_dynamic_op : boost::asio::coroutine
{
channel& chan_;
diagnostics& diag_;
execution_state_impl& st_;
read_some_rows_dynamic_op(channel& chan, diagnostics& diag, execution_state_impl& st) noexcept
: chan_(chan), diag_(diag), st_(st)
{
}
template <class Self>
void operator()(Self& self, error_code err = {}, std::size_t = 0)
{
// Error checking
if (err)
{
self.complete(err, rows_view());
return;
}
// Normal path
BOOST_ASIO_CORO_REENTER(*this)
{
chan_.shared_fields().clear();
BOOST_ASIO_CORO_YIELD async_read_some_rows_impl(chan_, st_, output_ref(), diag_, std::move(self));
self.complete(error_code(), get_some_rows(chan_, st_));
}
}
};
// External interface
inline rows_view read_some_rows_dynamic_impl(
channel& channel,
execution_state_impl& st,
error_code& err,
diagnostics& diag
)
{
err.clear();
diag.clear();
channel.shared_fields().clear();
read_some_rows_impl(channel, st, output_ref(), err, diag);
if (err)
return rows_view();
return get_some_rows(channel, st);
}
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, rows_view))
async_read_some_rows_dynamic_impl(
channel& channel,
execution_state_impl& st,
diagnostics& diag,
CompletionToken&& token
)
{
return boost::asio::async_compose<CompletionToken, void(error_code, rows_view)>(
read_some_rows_dynamic_op(channel, diag, st),
token,
channel
);
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
@@ -0,0 +1,109 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_RESET_CONNECTION_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_RESET_CONNECTION_HPP
#pragma once
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/impl/internal/channel/channel.hpp>
#include <boost/mysql/impl/internal/protocol/protocol.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/coroutine.hpp>
namespace boost {
namespace mysql {
namespace detail {
inline void serialize_reset_connection_message(channel& chan)
{
chan.serialize(reset_connection_command(), chan.reset_sequence_number());
}
struct reset_connection_op : boost::asio::coroutine
{
channel& chan_;
diagnostics& diag_;
reset_connection_op(channel& chan, diagnostics& diag) noexcept : chan_(chan), diag_(diag) {}
template <class Self>
void operator()(Self& self, error_code err = {}, span<const std::uint8_t> buff = {})
{
// Error checking
if (err)
{
self.complete(err);
return;
}
// Regular coroutine body; if there has been an error, we don't get here
BOOST_ASIO_CORO_REENTER(*this)
{
diag_.clear();
// Serialize the message
serialize_reset_connection_message(chan_);
// Write message
BOOST_ASIO_CORO_YIELD chan_.async_write(std::move(self));
// Read response
BOOST_ASIO_CORO_YIELD chan_.async_read_one(chan_.shared_sequence_number(), std::move(self));
// Verify it's what we expected
self.complete(deserialize_ok_response(buff, chan_.flavor(), diag_));
}
}
};
// Interface
inline void reset_connection_impl(channel& chan, error_code& err, diagnostics& diag)
{
err.clear();
diag.clear();
// Serialize the message
serialize_reset_connection_message(chan);
// Send it
chan.write(err);
if (err)
return;
// Read response
auto response = chan.read_one(chan.shared_sequence_number(), err);
if (err)
return;
// Verify it's what we expected
err = deserialize_ok_response(response, chan.flavor(), diag);
}
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_reset_connection_impl(channel& chan, diagnostics& diag, CompletionToken&& token)
{
return asio::async_compose<CompletionToken, void(error_code)>(
reset_connection_op(chan, diag),
token,
chan
);
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_IMPL_CLOSE_STATEMENT_HPP_ */
@@ -0,0 +1,183 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_START_EXECUTION_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_NETWORK_ALGORITHMS_START_EXECUTION_HPP
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/statement.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/detail/any_execution_request.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/detail/execution_processor/execution_processor.hpp>
#include <boost/mysql/detail/resultset_encoding.hpp>
#include <boost/mysql/impl/internal/channel/channel.hpp>
#include <boost/mysql/impl/internal/network_algorithms/read_resultset_head.hpp>
#include <boost/mysql/impl/internal/protocol/protocol.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/post.hpp>
namespace boost {
namespace mysql {
namespace detail {
inline error_code check_client_errors(const any_execution_request& req)
{
if (req.is_query)
return error_code();
return req.data.stmt.stmt.num_params() == req.data.stmt.params.size() ? error_code()
: client_errc::wrong_num_params;
}
inline resultset_encoding get_encoding(const any_execution_request& req)
{
return req.is_query ? resultset_encoding::text : resultset_encoding::binary;
}
inline void serialize_execution_request(
const any_execution_request& req,
channel& chan,
std::uint8_t& sequence_number
)
{
if (req.is_query)
{
chan.serialize(query_command{req.data.query}, sequence_number);
}
else
{
chan.serialize(execute_stmt_command{req.data.stmt.stmt.id(), req.data.stmt.params}, sequence_number);
}
}
inline void execution_setup(const any_execution_request& req, channel& chan, execution_processor& proc)
{
// Reeset the processor
proc.reset(get_encoding(req), chan.meta_mode());
// Serialize the execution request
serialize_execution_request(req, chan, proc.sequence_number());
}
struct start_execution_impl_op : boost::asio::coroutine
{
channel& chan_;
any_execution_request req_;
execution_processor& proc_;
diagnostics& diag_;
error_code client_err_; // keep it across posts
start_execution_impl_op(
channel& chan,
const any_execution_request& req,
execution_processor& proc,
diagnostics& diag
)
: chan_(chan), req_(req), proc_(proc), diag_(diag)
{
}
template <class Self>
void operator()(Self& self, error_code err = {})
{
// Error checking
if (err)
{
self.complete(err);
return;
}
// Non-error path
BOOST_ASIO_CORO_REENTER(*this)
{
diag_.clear();
// Check for errors
err = check_client_errors(req_);
if (err)
{
client_err_ = err;
BOOST_ASIO_CORO_YIELD boost::asio::post(chan_.get_executor(), std::move(self));
self.complete(client_err_);
BOOST_ASIO_CORO_YIELD break;
}
// Setup
execution_setup(req_, chan_, proc_);
// Send the execution request (serialized by setup)
BOOST_ASIO_CORO_YIELD chan_.async_write(std::move(self));
// Read the first resultset's head
BOOST_ASIO_CORO_YIELD
async_read_resultset_head_impl(chan_, proc_, diag_, std::move(self));
self.complete(error_code());
}
}
};
// External interface
inline void start_execution_impl(
channel& channel,
const any_execution_request& req,
execution_processor& proc,
error_code& err,
diagnostics& diag
)
{
err.clear();
diag.clear();
// Check for errors
err = check_client_errors(req);
if (err)
return;
// Setup
execution_setup(req, channel, proc);
// Send the execution request (serialized by setup)
channel.write(err);
if (err)
return;
// Read the first resultset's head
read_resultset_head_impl(channel, proc, err, diag);
if (err)
return;
}
template <class CompletionToken>
BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code))
async_start_execution_impl(
channel& channel,
const any_execution_request& req,
execution_processor& proc,
diagnostics& diag,
CompletionToken&& token
)
{
return boost::asio::async_compose<CompletionToken, void(error_code)>(
start_execution_impl_op(channel, req, proc, diag),
token,
channel
);
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif /* INCLUDE_MYSQL_IMPL_NETWORK_ALGORITHMS_READ_RESULTSET_HEAD_HPP_ */
+55
View File
@@ -0,0 +1,55 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_BASIC_TYPES_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_BASIC_TYPES_HPP
#include <boost/mysql/string_view.hpp>
#include <array>
#include <cstdint>
namespace boost {
namespace mysql {
namespace detail {
struct int3
{
std::uint32_t value;
};
struct int_lenenc
{
std::uint64_t value;
};
struct string_null
{
string_view value;
};
struct string_eof
{
string_view value;
};
struct string_lenenc
{
string_view value;
};
template <std::size_t N>
struct string_fixed
{
std::array<char, N> value;
};
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
@@ -0,0 +1,35 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_BINARY_SERIALIZATION_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_BINARY_SERIALIZATION_HPP
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
namespace boost {
namespace mysql {
namespace detail {
BOOST_MYSQL_DECL
std::size_t get_size(field_view input) noexcept;
BOOST_MYSQL_DECL
void serialize(serialization_context& ctx, field_view input) noexcept;
} // namespace detail
} // namespace mysql
} // namespace boost
#ifdef BOOST_MYSQL_HEADER_ONLY
#include <boost/mysql/impl/internal/protocol/binary_serialization.ipp>
#endif
#endif
@@ -0,0 +1,133 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_BINARY_SERIALIZATION_IPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_BINARY_SERIALIZATION_IPP
#pragma once
#include <boost/mysql/days.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/impl/internal/protocol/binary_serialization.hpp>
#include <boost/mysql/impl/internal/protocol/constants.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <chrono>
namespace boost {
namespace mysql {
namespace detail {
// Binary serialization
template <class T>
BOOST_MYSQL_STATIC_OR_INLINE void serialize_binary_float(serialization_context& ctx, T input)
{
boost::endian::endian_store<T, sizeof(T), boost::endian::order::little>(ctx.first(), input);
ctx.advance(sizeof(T));
}
BOOST_MYSQL_STATIC_OR_INLINE
void serialize_binary_date(serialization_context& ctx, const date& input)
{
using namespace binc;
serialize(ctx, static_cast<std::uint8_t>(date_sz), input.year(), input.month(), input.day());
}
BOOST_MYSQL_STATIC_OR_INLINE
void serialize_binary_datetime(serialization_context& ctx, const datetime& input)
{
using namespace binc;
// Serialize
serialize(
ctx,
static_cast<std::uint8_t>(datetime_dhmsu_sz),
input.year(),
input.month(),
input.day(),
input.hour(),
input.minute(),
input.second(),
input.microsecond()
);
}
BOOST_MYSQL_STATIC_OR_INLINE
void serialize_binary_time(serialization_context& ctx, const boost::mysql::time& input)
{
using namespace binc;
using boost::mysql::days;
using std::chrono::duration_cast;
using std::chrono::hours;
using std::chrono::microseconds;
using std::chrono::minutes;
using std::chrono::seconds;
// Break time
auto num_micros = duration_cast<microseconds>(input % seconds(1));
auto num_secs = duration_cast<seconds>(input % minutes(1) - num_micros);
auto num_mins = duration_cast<minutes>(input % hours(1) - num_secs);
auto num_hours = duration_cast<hours>(input % days(1) - num_mins);
auto num_days = duration_cast<days>(input - num_hours);
std::uint8_t is_negative = (input.count() < 0) ? 1 : 0;
// Serialize
serialize(
ctx,
static_cast<std::uint8_t>(time_dhmsu_sz),
is_negative,
static_cast<std::uint32_t>(std::abs(num_days.count())),
static_cast<std::uint8_t>(std::abs(num_hours.count())),
static_cast<std::uint8_t>(std::abs(num_mins.count())),
static_cast<std::uint8_t>(std::abs(num_secs.count())),
static_cast<std::uint32_t>(std::abs(num_micros.count()))
);
}
} // namespace detail
} // namespace mysql
} // namespace boost
std::size_t boost::mysql::detail::get_size(field_view input) noexcept
{
switch (input.kind())
{
case field_kind::null: return 0;
case field_kind::int64: return 8;
case field_kind::uint64: return 8;
case field_kind::string: return get_size(string_lenenc{input.get_string()});
case field_kind::blob: return get_size(string_lenenc{to_string(input.get_blob())});
case field_kind::float_: return 4;
case field_kind::double_: return 8;
case field_kind::date: return binc::date_sz + binc::length_sz;
case field_kind::datetime: return binc::datetime_dhmsu_sz + binc::length_sz;
case field_kind::time: return binc::time_dhmsu_sz + binc::length_sz;
default: BOOST_ASSERT(false); return 0;
}
}
void boost::mysql::detail::serialize(serialization_context& ctx, field_view input) noexcept
{
switch (input.kind())
{
case field_kind::null: break;
case field_kind::int64: serialize(ctx, input.get_int64()); break;
case field_kind::uint64: serialize(ctx, input.get_uint64()); break;
case field_kind::string: serialize(ctx, string_lenenc{input.get_string()}); break;
case field_kind::blob: serialize(ctx, string_lenenc{to_string(input.get_blob())}); break;
case field_kind::float_: serialize_binary_float(ctx, input.get_float()); break;
case field_kind::double_: serialize_binary_float(ctx, input.get_double()); break;
case field_kind::date: serialize_binary_date(ctx, input.get_date()); break;
case field_kind::datetime: serialize_binary_datetime(ctx, input.get_datetime()); break;
case field_kind::time: serialize_binary_time(ctx, input.get_time()); break;
default: BOOST_ASSERT(false); break;
}
}
#endif
@@ -0,0 +1,47 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_BIT_DESERIALIZATION_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_BIT_DESERIALIZATION_HPP
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <boost/endian/conversion.hpp>
#include <cstring>
namespace boost {
namespace mysql {
namespace detail {
// All BIT values come as binary values between 1 and 8 bytes length packed in string_lenenc's,
// for both the text and the binary protocols. As the text protocol already unpacks the
// string_lenenc layer, this function is in charge of just parsing the binary payload. The length of
// the BIT value depends on how the type was defined in the table (e.g. BIT(14) will send a 2 byte
// value; BIT(54) will send a 7 byte one). Values are sent as big-endian.
inline deserialize_errc deserialize_bit(string_view from, field_view& to) noexcept
{
std::size_t num_bytes = from.size();
if (num_bytes < 1 || num_bytes > 8)
{
return deserialize_errc::protocol_value_error;
}
unsigned char temp[8]{};
unsigned char* dest = temp + sizeof(temp) - num_bytes;
std::memcpy(dest, from.data(), num_bytes);
to = field_view(endian::load_big_u64(temp));
return deserialize_errc::ok;
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
+130
View File
@@ -0,0 +1,130 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_CAPABILITIES_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_CAPABILITIES_HPP
#include <cstdint>
namespace boost {
namespace mysql {
namespace detail {
// Server/client capabilities
// clang-format off
constexpr std::uint32_t CLIENT_LONG_PASSWORD = 1; // Use the improved version of Old Password Authentication
constexpr std::uint32_t CLIENT_FOUND_ROWS = 2; // Send found rows instead of affected rows in EOF_Packet
constexpr std::uint32_t CLIENT_LONG_FLAG = 4; // Get all column flags
constexpr std::uint32_t CLIENT_CONNECT_WITH_DB = 8; // Database (schema) name can be specified on connect in Handshake Response Packet
constexpr std::uint32_t CLIENT_NO_SCHEMA = 16; // Don't allow database.table.column
constexpr std::uint32_t CLIENT_COMPRESS = 32; // Compression protocol supported
constexpr std::uint32_t CLIENT_ODBC = 64; // Special handling of ODBC behavior
constexpr std::uint32_t CLIENT_LOCAL_FILES = 128; // Can use LOAD DATA LOCAL
constexpr std::uint32_t CLIENT_IGNORE_SPACE = 256; // Ignore spaces before '('
constexpr std::uint32_t CLIENT_PROTOCOL_41 = 512; // New 4.1 protocol
constexpr std::uint32_t CLIENT_INTERACTIVE = 1024; // This is an interactive client
constexpr std::uint32_t CLIENT_SSL = 2048; // Use SSL encryption for the session
constexpr std::uint32_t CLIENT_IGNORE_SIGPIPE = 4096; // Client only flag
constexpr std::uint32_t CLIENT_TRANSACTIONS = 8192; // Client knows about transactions
constexpr std::uint32_t CLIENT_RESERVED = 16384; // DEPRECATED: Old flag for 4.1 protocol
constexpr std::uint32_t CLIENT_SECURE_CONNECTION = 32768; // DEPRECATED: Old flag for 4.1 authentication, required by MariaDB
constexpr std::uint32_t CLIENT_MULTI_STATEMENTS = (1UL << 16); // Enable/disable multi-stmt support
constexpr std::uint32_t CLIENT_MULTI_RESULTS = (1UL << 17); // Enable/disable multi-results
constexpr std::uint32_t CLIENT_PS_MULTI_RESULTS = (1UL << 18); // Multi-results and OUT parameters in PS-protocol
constexpr std::uint32_t CLIENT_PLUGIN_AUTH = (1UL << 19); // Client supports plugin authentication
constexpr std::uint32_t CLIENT_CONNECT_ATTRS = (1UL << 20); // Client supports connection attributes
constexpr std::uint32_t CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA = (1UL << 21); // Enable authentication response packet to be larger than 255 bytes
constexpr std::uint32_t CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS = (1UL << 22); // Don't close the connection for a user account with expired password
constexpr std::uint32_t CLIENT_SESSION_TRACK = (1UL << 23); // Capable of handling server state change information
constexpr std::uint32_t CLIENT_DEPRECATE_EOF = (1UL << 24); // Client no longer needs EOF_Packet and will use OK_Packet instead
constexpr std::uint32_t CLIENT_SSL_VERIFY_SERVER_CERT = (1UL << 30); // Verify server certificate
constexpr std::uint32_t CLIENT_OPTIONAL_RESULTSET_METADATA = (1UL << 25); // The client can handle optional metadata information in the resultset
constexpr std::uint32_t CLIENT_REMEMBER_OPTIONS = (1UL << 31); // Don't reset the options after an unsuccessful connect
// clang-format on
class capabilities
{
std::uint32_t value_;
public:
constexpr explicit capabilities(std::uint32_t value = 0) noexcept : value_(value){};
constexpr std::uint32_t get() const noexcept { return value_; }
void set(std::uint32_t value) noexcept { value_ = value; }
constexpr bool has(std::uint32_t cap) const noexcept { return value_ & cap; }
constexpr bool has_all(capabilities other) const noexcept
{
return (value_ & other.get()) == other.get();
}
constexpr capabilities operator|(capabilities rhs) const noexcept
{
return capabilities(value_ | rhs.value_);
}
constexpr capabilities operator&(capabilities rhs) const noexcept
{
return capabilities(value_ & rhs.value_);
}
constexpr bool operator==(const capabilities& rhs) const noexcept { return value_ == rhs.value_; }
constexpr bool operator!=(const capabilities& rhs) const noexcept { return value_ != rhs.value_; }
};
/*
* CLIENT_LONG_PASSWORD: unset // Use the improved version of Old Password Authentication
* CLIENT_FOUND_ROWS: unset // Send found rows instead of affected rows in EOF_Packet
* CLIENT_LONG_FLAG: unset // Get all column flags
* CLIENT_CONNECT_WITH_DB: optional // Database (schema) name can be specified on connect in
* Handshake Response Packet CLIENT_NO_SCHEMA: unset // Don't allow database.table.column
* CLIENT_COMPRESS: unset // Compression protocol supported
* CLIENT_ODBC: unset // Special handling of ODBC behavior
* CLIENT_LOCAL_FILES: unset // Can use LOAD DATA LOCAL
* CLIENT_IGNORE_SPACE: unset // Ignore spaces before '('
* CLIENT_PROTOCOL_41: mandatory // New 4.1 protocol
* CLIENT_INTERACTIVE: unset // This is an interactive client
* CLIENT_SSL: unset // Use SSL encryption for the session
* CLIENT_IGNORE_SIGPIPE: unset // Client only flag
* CLIENT_TRANSACTIONS: unset // Client knows about transactions
* CLIENT_RESERVED: unset // DEPRECATED: Old flag for 4.1 protocol
* CLIENT_RESERVED2: unset // DEPRECATED: Old flag for 4.1 authentication
* \ CLIENT_SECURE_CONNECTION CLIENT_MULTI_STATEMENTS: unset // Enable/disable multi-stmt support
* CLIENT_MULTI_RESULTS: unset // Enable/disable multi-results
* CLIENT_PS_MULTI_RESULTS: unset // Multi-results and OUT parameters in PS-protocol
* CLIENT_PLUGIN_AUTH: mandatory // Client supports plugin authentication
* CLIENT_CONNECT_ATTRS: unset // Client supports connection attributes
* CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA: mandatory // Enable authentication response packet to be
* larger than 255 bytes CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS: unset // Don't close the connection
* for a user account with expired password CLIENT_SESSION_TRACK: unset // Capable of handling
* server state change information CLIENT_DEPRECATE_EOF: mandatory // Client no longer needs
* EOF_Packet and will use OK_Packet instead CLIENT_SSL_VERIFY_SERVER_CERT: unset // Verify server
* certificate CLIENT_OPTIONAL_RESULTSET_METADATA: unset // The client can handle optional metadata
* information in the resultset CLIENT_REMEMBER_OPTIONS: unset // Don't reset the options after an
* unsuccessful connect
*
* We pay attention to:
* CLIENT_CONNECT_WITH_DB: optional // Database (schema) name can be specified on connect in
* Handshake Response Packet CLIENT_PROTOCOL_41: mandatory // New 4.1 protocol CLIENT_PLUGIN_AUTH:
* mandatory // Client supports plugin authentication CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA:
* mandatory // Enable authentication response packet to be larger than 255 bytes
* CLIENT_DEPRECATE_EOF: mandatory // Client no longer needs EOF_Packet and will use OK_Packet
* instead
*/
// clang-format off
constexpr capabilities mandatory_capabilities{
CLIENT_PROTOCOL_41 |
CLIENT_PLUGIN_AUTH |
CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA |
CLIENT_DEPRECATE_EOF |
CLIENT_SECURE_CONNECTION
};
// clang-format on
constexpr capabilities optional_capabilities{CLIENT_MULTI_RESULTS | CLIENT_PS_MULTI_RESULTS};
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
+65
View File
@@ -0,0 +1,65 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_CONSTANTS_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_CONSTANTS_HPP
#include <cstddef>
#include <cstdint>
namespace boost {
namespace mysql {
namespace detail {
constexpr std::size_t MAX_PACKET_SIZE = 0xffffff;
constexpr std::size_t HEADER_SIZE = 4;
// The binary collation number, used to distinguish blobs from strings
constexpr std::uint16_t binary_collation = 63;
// Prepared statements
namespace cursor_types {
constexpr std::uint8_t no_cursor = 0;
constexpr std::uint8_t read_only = 1;
constexpr std::uint8_t for_update = 2;
constexpr std::uint8_t scrollable = 4;
} // namespace cursor_types
// Binary protocol (de)serialization constants
namespace binc {
constexpr std::size_t length_sz = 1; // length byte, for date, datetime and time
constexpr std::size_t year_sz = 2;
constexpr std::size_t month_sz = 1;
constexpr std::size_t date_day_sz = 1;
constexpr std::size_t time_days_sz = 4;
constexpr std::size_t hours_sz = 1;
constexpr std::size_t mins_sz = 1;
constexpr std::size_t secs_sz = 1;
constexpr std::size_t micros_sz = 4;
constexpr std::size_t time_sign_sz = 1;
constexpr std::size_t date_sz = year_sz + month_sz + date_day_sz; // does not include length
constexpr std::size_t datetime_d_sz = date_sz;
constexpr std::size_t datetime_dhms_sz = datetime_d_sz + hours_sz + mins_sz + secs_sz;
constexpr std::size_t datetime_dhmsu_sz = datetime_dhms_sz + micros_sz;
constexpr std::size_t time_dhms_sz = time_sign_sz + time_days_sz + hours_sz + mins_sz + secs_sz;
constexpr std::size_t time_dhmsu_sz = time_dhms_sz + micros_sz;
constexpr std::size_t time_max_days = 34; // equivalent to the 839 hours, in the broken format
} // namespace binc
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
+25
View File
@@ -0,0 +1,25 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_DB_FLAVOR_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_DB_FLAVOR_HPP
namespace boost {
namespace mysql {
namespace detail {
enum class db_flavor
{
mysql,
mariadb
};
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
@@ -0,0 +1,37 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_DESERIALIZE_BINARY_FIELD_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_DESERIALIZE_BINARY_FIELD_HPP
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/metadata.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
namespace boost {
namespace mysql {
namespace detail {
BOOST_MYSQL_DECL
deserialize_errc deserialize_binary_field(
deserialization_context& ctx,
const metadata& meta,
field_view& output
);
} // namespace detail
} // namespace mysql
} // namespace boost
#ifdef BOOST_MYSQL_HEADER_ONLY
#include <boost/mysql/impl/internal/protocol/deserialize_binary_field.ipp>
#endif
#endif /* INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_BINARY_DESERIALIZATION_HPP_ */
@@ -0,0 +1,319 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_DESERIALIZE_BINARY_FIELD_IPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_DESERIALIZE_BINARY_FIELD_IPP
#pragma once
#include <boost/mysql/field_kind.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/metadata.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/detail/datetime.hpp>
#include <boost/mysql/impl/internal/protocol/bit_deserialization.hpp>
#include <boost/mysql/impl/internal/protocol/constants.hpp>
#include <boost/mysql/impl/internal/protocol/deserialize_binary_field.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <cmath>
#include <cstddef>
namespace boost {
namespace mysql {
namespace detail {
// strings
BOOST_MYSQL_STATIC_OR_INLINE
deserialize_errc deserialize_binary_field_string(
deserialization_context& ctx,
field_view& output,
bool is_blob
) noexcept
{
string_lenenc deser;
auto err = deserialize(ctx, deser);
if (err != deserialize_errc::ok)
return err;
if (is_blob)
{
output = field_view(
blob_view(reinterpret_cast<const unsigned char*>(deser.value.data()), deser.value.size())
);
}
else
{
output = field_view(deser.value);
}
return deserialize_errc::ok;
}
// ints
template <class TargetType, class DeserializableType>
BOOST_MYSQL_STATIC_OR_INLINE deserialize_errc
deserialize_binary_field_int_impl(deserialization_context& ctx, field_view& output) noexcept
{
DeserializableType deser;
auto err = deserialize(ctx, deser);
if (err != deserialize_errc::ok)
return err;
output = field_view(static_cast<TargetType>(deser));
return deserialize_errc::ok;
}
template <class DeserializableTypeUnsigned, class DeserializableTypeSigned>
BOOST_MYSQL_STATIC_OR_INLINE deserialize_errc
deserialize_binary_field_int(const metadata& meta, deserialization_context& ctx, field_view& output) noexcept
{
return meta.is_unsigned()
? deserialize_binary_field_int_impl<std::uint64_t, DeserializableTypeUnsigned>(ctx, output)
: deserialize_binary_field_int_impl<std::int64_t, DeserializableTypeSigned>(ctx, output);
}
// Bits. These come as a binary value between 1 and 8 bytes,
// packed in a string
BOOST_MYSQL_STATIC_OR_INLINE deserialize_errc
deserialize_binary_field_bit(deserialization_context& ctx, field_view& output) noexcept
{
string_lenenc buffer;
auto err = deserialize(ctx, buffer);
if (err != deserialize_errc::ok)
return err;
return boost::mysql::detail::deserialize_bit(buffer.value, output);
}
// Floats
template <class T>
BOOST_MYSQL_STATIC_OR_INLINE deserialize_errc
deserialize_binary_field_float(deserialization_context& ctx, field_view& output) noexcept
{
// Size check
if (!ctx.enough_size(sizeof(T)))
return deserialize_errc::incomplete_message;
// Endianness conversion. Boost.Endian support for floats start at 1.71
T v = boost::endian::endian_load<T, sizeof(T), boost::endian::order::little>(ctx.first());
// Nans and infs not allowed in SQL
if (std::isnan(v) || std::isinf(v))
return deserialize_errc::protocol_value_error;
// Done
ctx.advance(sizeof(T));
output = field_view(v);
return deserialize_errc::ok;
}
// Time types
BOOST_MYSQL_STATIC_OR_INLINE
deserialize_errc deserialize_binary_ymd(deserialization_context& ctx, boost::mysql::date& output)
{
using namespace boost::mysql::detail;
std::uint16_t year;
std::uint8_t month;
std::uint8_t day;
// Deserialize
auto err = deserialize(ctx, year, month, day);
if (err != deserialize_errc::ok)
return err;
// Range check
if (year > max_year || month > max_month || day > max_day)
{
return deserialize_errc::protocol_value_error;
}
output = boost::mysql::date(year, month, day);
return deserialize_errc::ok;
}
BOOST_MYSQL_STATIC_OR_INLINE
deserialize_errc deserialize_binary_field_date(deserialization_context& ctx, field_view& output) noexcept
{
using namespace boost::mysql::detail::binc;
// Deserialize length
std::uint8_t length;
auto err = deserialize(ctx, length);
if (err != deserialize_errc::ok)
return err;
// Check for zero dates
if (length < date_sz)
{
output = field_view(boost::mysql::date());
return deserialize_errc::ok;
}
// Deserialize rest of fields
boost::mysql::date d;
err = deserialize_binary_ymd(ctx, d);
if (err != deserialize_errc::ok)
return err;
output = field_view(d);
return deserialize_errc::ok;
}
BOOST_MYSQL_STATIC_OR_INLINE
deserialize_errc deserialize_binary_field_datetime(deserialization_context& ctx, field_view& output) noexcept
{
using namespace binc;
// Deserialize length
std::uint8_t length;
auto err = deserialize(ctx, length);
if (err != deserialize_errc::ok)
return err;
// If the DATETIME does not contain some of the values below,
// they are supposed to be zero
boost::mysql::date d{};
std::uint8_t hours = 0;
std::uint8_t minutes = 0;
std::uint8_t seconds = 0;
std::uint32_t micros = 0;
// Date part
if (length >= datetime_d_sz)
{
err = deserialize_binary_ymd(ctx, d);
if (err != deserialize_errc::ok)
return err;
}
// Hours, minutes, seconds
if (length >= datetime_dhms_sz)
{
err = deserialize(ctx, hours, minutes, seconds);
if (err != deserialize_errc::ok)
return err;
}
// Microseconds
if (length >= datetime_dhmsu_sz)
{
err = deserialize(ctx, micros);
if (err != deserialize_errc::ok)
return err;
}
// Validity check. deserialize_binary_ymd already does it for date
if (hours > max_hour || minutes > max_min || seconds > max_sec || micros > max_micro)
{
return deserialize_errc::protocol_value_error;
}
// Compose the final datetime
boost::mysql::datetime dt(d.year(), d.month(), d.day(), hours, minutes, seconds, micros);
output = field_view(dt);
return deserialize_errc::ok;
}
BOOST_MYSQL_STATIC_OR_INLINE
deserialize_errc deserialize_binary_field_time(deserialization_context& ctx, field_view& output) noexcept
{
using namespace boost::mysql::detail;
using namespace boost::mysql::detail::binc;
// Deserialize length
std::uint8_t length;
auto err = deserialize(ctx, length);
if (err != deserialize_errc::ok)
return err;
// If the TIME contains no value for these fields, they are zero
std::uint8_t is_negative = 0;
std::uint32_t num_days = 0;
std::uint8_t hours = 0;
std::uint8_t minutes = 0;
std::uint8_t seconds = 0;
std::uint32_t microseconds = 0;
// Sign, days, hours, minutes, seconds
if (length >= time_dhms_sz)
{
err = deserialize(ctx, is_negative, num_days, hours, minutes, seconds);
if (err != deserialize_errc::ok)
return err;
}
// Microseconds
if (length >= time_dhmsu_sz)
{
err = deserialize(ctx, microseconds);
if (err != deserialize_errc::ok)
return err;
}
// Range check
if (num_days > time_max_days || hours > max_hour || minutes > max_min || seconds > max_sec ||
microseconds > max_micro)
{
return deserialize_errc::protocol_value_error;
}
// Compose the final time
output = field_view(boost::mysql::time(
(is_negative ? -1 : 1) *
(boost::mysql::days(num_days) + std::chrono::hours(hours) + std::chrono::minutes(minutes) +
std::chrono::seconds(seconds) + std::chrono::microseconds(microseconds))
));
return deserialize_errc::ok;
}
} // namespace detail
} // namespace mysql
} // namespace boost
boost::mysql::detail::deserialize_errc boost::mysql::detail::deserialize_binary_field(
deserialization_context& ctx,
const metadata& meta,
field_view& output
)
{
switch (meta.type())
{
case column_type::tinyint:
return deserialize_binary_field_int<std::uint8_t, std::int8_t>(meta, ctx, output);
case column_type::smallint:
case column_type::year:
return deserialize_binary_field_int<std::uint16_t, std::int16_t>(meta, ctx, output);
case column_type::mediumint:
case column_type::int_:
return deserialize_binary_field_int<std::uint32_t, std::int32_t>(meta, ctx, output);
case column_type::bigint:
return deserialize_binary_field_int<std::uint64_t, std::int64_t>(meta, ctx, output);
case column_type::bit: return deserialize_binary_field_bit(ctx, output);
case column_type::float_: return deserialize_binary_field_float<float>(ctx, output);
case column_type::double_: return deserialize_binary_field_float<double>(ctx, output);
case column_type::timestamp:
case column_type::datetime: return deserialize_binary_field_datetime(ctx, output);
case column_type::date: return deserialize_binary_field_date(ctx, output);
case column_type::time: return deserialize_binary_field_time(ctx, output);
// True string types
case column_type::char_:
case column_type::varchar:
case column_type::text:
case column_type::enum_:
case column_type::set:
case column_type::decimal:
case column_type::json: return deserialize_binary_field_string(ctx, output, false);
// Blobs and anything else
case column_type::binary:
case column_type::varbinary:
case column_type::blob:
case column_type::geometry:
default: return deserialize_binary_field_string(ctx, output, true);
}
}
#endif
@@ -0,0 +1,33 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_DESERIALIZE_TEXT_FIELD_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_DESERIALIZE_TEXT_FIELD_HPP
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/metadata.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
namespace boost {
namespace mysql {
namespace detail {
BOOST_MYSQL_DECL
deserialize_errc deserialize_text_field(string_view from, const metadata& meta, field_view& output);
} // namespace detail
} // namespace mysql
} // namespace boost
#ifdef BOOST_MYSQL_HEADER_ONLY
#include <boost/mysql/impl/internal/protocol/deserialize_text_field.ipp>
#endif
#endif
@@ -0,0 +1,336 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_DESERIALIZE_TEXT_FIELD_IPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_DESERIALIZE_TEXT_FIELD_IPP
#pragma once
#include <boost/mysql/blob_view.hpp>
#include <boost/mysql/datetime.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/metadata.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/detail/datetime.hpp>
#include <boost/mysql/impl/internal/protocol/bit_deserialization.hpp>
#include <boost/mysql/impl/internal/protocol/constants.hpp>
#include <boost/mysql/impl/internal/protocol/deserialize_text_field.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <boost/assert.hpp>
#include <boost/lexical_cast/try_lexical_convert.hpp>
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <type_traits>
namespace boost {
namespace mysql {
namespace detail {
#ifdef BOOST_MSVC
#pragma warning(push)
#pragma warning(disable : 4996) // MSVC doesn't like my sscanf's
#endif
// Constants
BOOST_MYSQL_STATIC_IF_COMPILED constexpr unsigned max_decimals = 6u;
namespace textc {
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t year_sz = 4;
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t month_sz = 2;
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t day_sz = 2;
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t hours_min_sz = 2; // in TIME, it may be longer
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t mins_sz = 2;
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t secs_sz = 2;
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t date_sz = year_sz + month_sz + day_sz + 2; // delimiters
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t time_min_sz = hours_min_sz + mins_sz + secs_sz +
2; // delimiters
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t time_max_sz = time_min_sz + max_decimals +
3; // sign, period, hour extra character
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t datetime_min_sz = date_sz + time_min_sz +
1; // delimiter
BOOST_MYSQL_STATIC_IF_COMPILED constexpr std::size_t datetime_max_sz = datetime_min_sz + max_decimals +
1; // period
BOOST_MYSQL_STATIC_IF_COMPILED constexpr unsigned time_max_hour = 838;
} // namespace textc
// Integers
template <class T>
BOOST_MYSQL_STATIC_OR_INLINE deserialize_errc
deserialize_text_value_int_impl(string_view from, field_view& to) noexcept
{
T v;
bool ok = boost::conversion::try_lexical_convert(from.data(), from.size(), v);
if (!ok)
return deserialize_errc::protocol_value_error;
to = field_view(v);
return deserialize_errc::ok;
}
BOOST_MYSQL_STATIC_OR_INLINE deserialize_errc
deserialize_text_value_int(string_view from, field_view& to, const metadata& meta) noexcept
{
return meta.is_unsigned() ? deserialize_text_value_int_impl<std::uint64_t>(from, to)
: deserialize_text_value_int_impl<std::int64_t>(from, to);
}
// Floating points
template <class T>
BOOST_MYSQL_STATIC_OR_INLINE deserialize_errc
deserialize_text_value_float(string_view from, field_view& to) noexcept
{
T val;
bool ok = boost::conversion::try_lexical_convert(from.data(), from.size(), val);
if (!ok || std::isnan(val) || std::isinf(val)) // SQL std forbids these values
return deserialize_errc::protocol_value_error;
to = field_view(val);
return deserialize_errc::ok;
}
// Strings
BOOST_MYSQL_STATIC_OR_INLINE deserialize_errc
deserialize_text_value_string(string_view from, field_view& to) noexcept
{
to = field_view(from);
return deserialize_errc::ok;
}
BOOST_MYSQL_STATIC_OR_INLINE deserialize_errc
deserialize_text_value_blob(string_view from, field_view& to) noexcept
{
to = field_view(to_span(from));
return deserialize_errc::ok;
}
// Date/time types
BOOST_MYSQL_STATIC_OR_INLINE unsigned sanitize_decimals(unsigned decimals) noexcept
{
return (std::min)(decimals, max_decimals);
}
// Computes the meaning of the parsed microsecond number, taking into
// account decimals (85 with 2 decimals means 850000us)
BOOST_MYSQL_STATIC_OR_INLINE unsigned compute_micros(unsigned parsed_micros, unsigned decimals) noexcept
{
return parsed_micros * static_cast<unsigned>(std::pow(10, max_decimals - decimals));
}
BOOST_MYSQL_STATIC_OR_INLINE deserialize_errc deserialize_text_ymd(string_view from, date& to)
{
using namespace textc;
// Size check
if (from.size() != date_sz)
return deserialize_errc::protocol_value_error;
// Copy to a NULL-terminated buffer
char buffer[date_sz + 1]{};
std::memcpy(buffer, from.data(), from.size());
// Parse individual components
unsigned year, month, day;
char extra_char;
int parsed = sscanf(buffer, "%4u-%2u-%2u%c", &year, &month, &day, &extra_char);
if (parsed != 3)
return deserialize_errc::protocol_value_error;
// Range check for individual components. MySQL doesn't allow invidiual components
// to be out of range, although they may be zero or representing an invalid date
if (year > max_year || month > max_month || day > max_day)
return deserialize_errc::protocol_value_error;
to = date(
static_cast<std::uint16_t>(year),
static_cast<std::uint8_t>(month),
static_cast<std::uint8_t>(day)
);
return deserialize_errc::ok;
}
BOOST_MYSQL_STATIC_OR_INLINE deserialize_errc
deserialize_text_value_date(string_view from, field_view& to) noexcept
{
date d;
auto err = deserialize_text_ymd(from, d);
if (err != deserialize_errc::ok)
return err;
to = field_view(d);
return deserialize_errc::ok;
}
BOOST_MYSQL_STATIC_OR_INLINE deserialize_errc
deserialize_text_value_datetime(string_view from, field_view& to, const metadata& meta) noexcept
{
using namespace textc;
// Sanitize decimals
unsigned decimals = sanitize_decimals(meta.decimals());
// Length check
std::size_t expected_size = datetime_min_sz + (decimals ? decimals + 1 : 0);
if (from.size() != expected_size)
return deserialize_errc::protocol_value_error;
// Deserialize date part
date d;
auto err = deserialize_text_ymd(from.substr(0, date_sz), d);
if (err != deserialize_errc::ok)
return err;
// Copy to NULL-terminated buffer
constexpr std::size_t datetime_time_first = date_sz + 1; // date + space
char buffer[datetime_max_sz - datetime_time_first + 1]{};
std::memcpy(buffer, from.data() + datetime_time_first, from.size() - datetime_time_first);
// Parse
unsigned hours, minutes, seconds;
unsigned micros = 0;
char extra_char;
if (decimals)
{
int parsed = sscanf(buffer, "%2u:%2u:%2u.%6u%c", &hours, &minutes, &seconds, &micros, &extra_char);
if (parsed != 4)
return deserialize_errc::protocol_value_error;
micros = compute_micros(micros, decimals);
}
else
{
int parsed = sscanf(buffer, "%2u:%2u:%2u%c", &hours, &minutes, &seconds, &extra_char);
if (parsed != 3)
return deserialize_errc::protocol_value_error;
}
// Validity check. Although MySQL allows invalid and zero datetimes, it doesn't allow
// individual components to be out of range.
if (hours > max_hour || minutes > max_min || seconds > max_sec || micros > max_micro)
{
return deserialize_errc::protocol_value_error;
}
datetime dt(
d.year(),
d.month(),
d.day(),
static_cast<std::uint8_t>(hours),
static_cast<std::uint8_t>(minutes),
static_cast<std::uint8_t>(seconds),
static_cast<std::uint32_t>(micros)
);
to = field_view(dt);
return deserialize_errc::ok;
}
BOOST_MYSQL_STATIC_OR_INLINE deserialize_errc
deserialize_text_value_time(string_view from, field_view& to, const metadata& meta) noexcept
{
using namespace textc;
// Sanitize decimals
unsigned decimals = sanitize_decimals(meta.decimals());
// size check
std::size_t actual_min_size = time_min_sz + (decimals ? decimals + 1 : 0);
std::size_t actual_max_size = actual_min_size + 1 + 1; // hour extra character and sign
BOOST_ASSERT(actual_max_size <= time_max_sz);
if (from.size() < actual_min_size || from.size() > actual_max_size)
return deserialize_errc::protocol_value_error;
// Copy to NULL-terminated buffer
char buffer[time_max_sz + 1]{};
memcpy(buffer, from.data(), from.size());
// Sign
bool is_negative = from[0] == '-';
const char* first = is_negative ? buffer + 1 : buffer;
// Parse it
unsigned hours, minutes, seconds;
unsigned micros = 0;
char extra_char;
if (decimals)
{
int parsed = sscanf(first, "%3u:%2u:%2u.%6u%c", &hours, &minutes, &seconds, &micros, &extra_char);
if (parsed != 4)
return deserialize_errc::protocol_value_error;
micros = compute_micros(micros, decimals);
}
else
{
int parsed = sscanf(first, "%3u:%2u:%2u%c", &hours, &minutes, &seconds, &extra_char);
if (parsed != 3)
return deserialize_errc::protocol_value_error;
}
// Range check
if (hours > time_max_hour || minutes > max_min || seconds > max_sec || micros > max_micro)
{
return deserialize_errc::protocol_value_error;
}
// Sum it
auto res = std::chrono::hours(hours) + std::chrono::minutes(minutes) + std::chrono::seconds(seconds) +
std::chrono::microseconds(micros);
if (is_negative)
{
res = -res;
}
// Done
to = field_view(res);
return deserialize_errc::ok;
}
} // namespace detail
} // namespace mysql
} // namespace boost
boost::mysql::detail::deserialize_errc boost::mysql::detail::deserialize_text_field(
string_view from,
const metadata& meta,
field_view& output
)
{
switch (meta.type())
{
case column_type::tinyint:
case column_type::smallint:
case column_type::mediumint:
case column_type::int_:
case column_type::bigint:
case column_type::year: return deserialize_text_value_int(from, output, meta);
case column_type::bit: return deserialize_bit(from, output);
case column_type::float_: return deserialize_text_value_float<float>(from, output);
case column_type::double_: return deserialize_text_value_float<double>(from, output);
case column_type::timestamp:
case column_type::datetime: return deserialize_text_value_datetime(from, output, meta);
case column_type::date: return deserialize_text_value_date(from, output);
case column_type::time: return deserialize_text_value_time(from, output, meta);
// True string types
case column_type::char_:
case column_type::varchar:
case column_type::text:
case column_type::enum_:
case column_type::set:
case column_type::decimal:
case column_type::json: return deserialize_text_value_string(from, output);
// Blobs and anything else
case column_type::binary:
case column_type::varbinary:
case column_type::blob:
case column_type::geometry:
default: return deserialize_text_value_blob(from, output);
}
}
#endif
@@ -0,0 +1,54 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_NULL_BITMAP_TRAITS_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_NULL_BITMAP_TRAITS_HPP
#include <boost/assert.hpp>
#include <cstddef>
#include <cstdint>
namespace boost {
namespace mysql {
namespace detail {
class null_bitmap_traits
{
std::size_t offset_;
std::size_t num_fields_;
constexpr std::size_t byte_pos(std::size_t field_pos) const noexcept { return (field_pos + offset_) / 8; }
constexpr std::size_t bit_pos(std::size_t field_pos) const noexcept { return (field_pos + offset_) % 8; }
public:
constexpr null_bitmap_traits(std::size_t offset, std::size_t num_fields) noexcept
: offset_(offset), num_fields_{num_fields} {};
constexpr std::size_t offset() const noexcept { return offset_; }
constexpr std::size_t num_fields() const noexcept { return num_fields_; }
constexpr std::size_t byte_count() const noexcept { return (num_fields_ + 7 + offset_) / 8; }
bool is_null(const std::uint8_t* null_bitmap_begin, std::size_t field_pos) const noexcept
{
BOOST_ASSERT(field_pos < num_fields_);
return null_bitmap_begin[byte_pos(field_pos)] & (1 << bit_pos(field_pos));
}
void set_null(std::uint8_t* null_bitmap_begin, std::size_t field_pos) const noexcept
{
BOOST_ASSERT(field_pos < num_fields_);
null_bitmap_begin[byte_pos(field_pos)] |= (1 << bit_pos(field_pos));
}
};
constexpr std::size_t stmt_execute_null_bitmap_offset = 0;
constexpr std::size_t binary_row_null_bitmap_offset = 2;
} // namespace detail
} // namespace mysql
} // namespace boost
#endif /* INCLUDE_NULL_BITMAP_HPP_ */
+341
View File
@@ -0,0 +1,341 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_PROTOCOL_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_PROTOCOL_HPP
#include <boost/mysql/column_type.hpp>
#include <boost/mysql/diagnostics.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/metadata_collection_view.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/detail/coldef_view.hpp>
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/detail/ok_view.hpp>
#include <boost/mysql/detail/resultset_encoding.hpp>
#include <boost/mysql/impl/internal/protocol/capabilities.hpp>
#include <boost/mysql/impl/internal/protocol/constants.hpp>
#include <boost/mysql/impl/internal/protocol/db_flavor.hpp>
#include <boost/mysql/impl/internal/protocol/static_buffer.hpp>
#include <boost/config.hpp>
#include <boost/core/span.hpp>
#include <cstddef>
#include <cstdint>
#include <type_traits>
namespace boost {
namespace mysql {
namespace detail {
// Frame header
constexpr std::size_t frame_header_size = 4;
struct frame_header
{
std::uint32_t size;
std::uint8_t sequence_number;
};
BOOST_MYSQL_DECL
void serialize_frame_header(frame_header, span<std::uint8_t, frame_header_size> buffer) noexcept;
BOOST_MYSQL_DECL
frame_header deserialize_frame_header(span<const std::uint8_t, frame_header_size> buffer) noexcept;
// OK packets (views because strings are non-owning)
BOOST_MYSQL_DECL
error_code deserialize_ok_packet(span<const std::uint8_t> msg, ok_view& output) noexcept; // for testing
// Error packets (exposed for testing)
struct err_view
{
std::uint16_t error_code;
string_view error_message;
};
BOOST_ATTRIBUTE_NODISCARD BOOST_MYSQL_DECL error_code
deserialize_error_packet(span<const std::uint8_t> message, err_view& pack) noexcept;
BOOST_ATTRIBUTE_NODISCARD BOOST_MYSQL_DECL error_code
process_error_packet(span<const std::uint8_t> message, db_flavor flavor, diagnostics& diag);
// Column definition
BOOST_ATTRIBUTE_NODISCARD BOOST_MYSQL_DECL error_code
deserialize_column_definition(span<const std::uint8_t> input, coldef_view& output) noexcept;
// Quit
struct quit_command
{
BOOST_MYSQL_DECL std::size_t get_size() const noexcept;
BOOST_MYSQL_DECL void serialize(span<std::uint8_t> buffer) const noexcept;
};
// Ping
struct ping_command
{
BOOST_MYSQL_DECL std::size_t get_size() const noexcept;
BOOST_MYSQL_DECL void serialize(span<std::uint8_t> buffer) const noexcept;
};
// Reset connection
struct reset_connection_command
{
BOOST_MYSQL_DECL std::size_t get_size() const noexcept;
BOOST_MYSQL_DECL void serialize(span<std::uint8_t> buffer) const noexcept;
};
// Deserializes a response that may be an OK or an error packet.
// Applicable for ping and reset connection
BOOST_ATTRIBUTE_NODISCARD BOOST_MYSQL_DECL error_code
deserialize_ok_response(span<const std::uint8_t> message, db_flavor flavor, diagnostics& diag);
// Query
struct query_command
{
string_view query;
BOOST_MYSQL_DECL std::size_t get_size() const noexcept;
BOOST_MYSQL_DECL void serialize(span<std::uint8_t> buffer) const noexcept;
};
// Prepare statement
struct prepare_stmt_command
{
string_view stmt;
BOOST_MYSQL_DECL std::size_t get_size() const noexcept;
BOOST_MYSQL_DECL void serialize(span<std::uint8_t> buffer) const noexcept;
};
struct prepare_stmt_response
{
std::uint32_t id;
std::uint16_t num_columns;
std::uint16_t num_params;
};
BOOST_ATTRIBUTE_NODISCARD BOOST_MYSQL_DECL error_code deserialize_prepare_stmt_response_impl(
span<const std::uint8_t> message,
prepare_stmt_response& output
) noexcept; // exposed for testing, doesn't take header into account
BOOST_ATTRIBUTE_NODISCARD BOOST_MYSQL_DECL error_code deserialize_prepare_stmt_response(
span<const std::uint8_t> message,
db_flavor flavor,
prepare_stmt_response& output,
diagnostics& diag
);
// Execute statement
struct execute_stmt_command
{
std::uint32_t statement_id;
span<const field_view> params;
BOOST_MYSQL_DECL std::size_t get_size() const noexcept;
BOOST_MYSQL_DECL void serialize(span<std::uint8_t> buffer) const noexcept;
};
// Close statement
struct close_stmt_command
{
std::uint32_t statement_id{};
constexpr close_stmt_command() = default;
constexpr close_stmt_command(std::uint32_t statement_id) noexcept : statement_id(statement_id) {}
BOOST_MYSQL_DECL std::size_t get_size() const noexcept;
BOOST_MYSQL_DECL void serialize(span<std::uint8_t> buffer) const noexcept;
};
// Execution messages
static_assert(std::is_trivially_destructible<error_code>::value, "");
struct execute_response
{
enum class type_t
{
num_fields,
ok_packet,
error
} type;
union data_t
{
std::size_t num_fields;
ok_view ok_pack;
error_code err;
data_t(size_t v) noexcept : num_fields(v) {}
data_t(const ok_view& v) noexcept : ok_pack(v) {}
data_t(error_code v) noexcept : err(v) {}
} data;
execute_response(std::size_t v) noexcept : type(type_t::num_fields), data(v) {}
execute_response(const ok_view& v) noexcept : type(type_t::ok_packet), data(v) {}
execute_response(error_code v) noexcept : type(type_t::error), data(v) {}
};
BOOST_MYSQL_DECL
execute_response deserialize_execute_response(
span<const std::uint8_t> msg,
db_flavor flavor,
diagnostics& diag
) noexcept;
struct row_message
{
enum class type_t
{
row,
ok_packet,
error
} type;
union data_t
{
span<const std::uint8_t> row;
ok_view ok_pack;
error_code err;
data_t(span<const std::uint8_t> row) noexcept : row(row) {}
data_t(const ok_view& ok_pack) noexcept : ok_pack(ok_pack) {}
data_t(error_code err) noexcept : err(err) {}
} data;
row_message(span<const std::uint8_t> row) noexcept : type(type_t::row), data(row) {}
row_message(const ok_view& ok_pack) noexcept : type(type_t::ok_packet), data(ok_pack) {}
row_message(error_code v) noexcept : type(type_t::error), data(v) {}
};
BOOST_MYSQL_DECL
row_message deserialize_row_message(span<const std::uint8_t> msg, db_flavor flavor, diagnostics& diag);
BOOST_MYSQL_DECL
error_code deserialize_row(
resultset_encoding encoding,
span<const std::uint8_t> message,
metadata_collection_view meta,
span<field_view> output // Should point to meta.size() field_view objects
);
// Server hello
struct server_hello
{
using auth_buffer_type = static_buffer<8 + 0xff>;
db_flavor server;
auth_buffer_type auth_plugin_data;
capabilities server_capabilities{};
string_view auth_plugin_name;
};
BOOST_ATTRIBUTE_NODISCARD BOOST_MYSQL_DECL error_code deserialize_server_hello_impl(
span<const std::uint8_t> msg,
server_hello& output
); // exposed for testing, doesn't take message header into account
BOOST_ATTRIBUTE_NODISCARD BOOST_MYSQL_DECL error_code
deserialize_server_hello(span<const std::uint8_t> msg, server_hello& output, diagnostics& diag);
// Login & ssl requests
struct login_request
{
capabilities negotiated_capabilities; // capabilities
std::uint32_t max_packet_size;
std::uint32_t collation_id;
string_view username;
span<const std::uint8_t> auth_response;
string_view database;
string_view auth_plugin_name;
BOOST_MYSQL_DECL std::size_t get_size() const noexcept;
BOOST_MYSQL_DECL void serialize(span<std::uint8_t> buffer) const noexcept;
};
struct ssl_request
{
capabilities negotiated_capabilities;
std::uint32_t max_packet_size;
std::uint32_t collation_id;
BOOST_MYSQL_DECL std::size_t get_size() const noexcept;
BOOST_MYSQL_DECL void serialize(span<std::uint8_t> buffer) const noexcept;
};
// Auth switch
struct auth_switch
{
string_view plugin_name;
span<const std::uint8_t> auth_data;
};
BOOST_ATTRIBUTE_NODISCARD BOOST_MYSQL_DECL error_code deserialize_auth_switch(
span<const std::uint8_t> msg,
auth_switch& output
) noexcept; // exposed for testing
struct handhake_server_response
{
struct ok_follows_t
{
};
enum class type_t
{
ok,
error,
ok_follows,
auth_switch,
auth_more_data
} type;
union data_t
{
ok_view ok;
error_code err;
ok_follows_t ok_follows;
auth_switch auth_sw;
span<const std::uint8_t> more_data;
data_t(const ok_view& ok) noexcept : ok(ok) {}
data_t(error_code err) noexcept : err(err) {}
data_t(ok_follows_t) noexcept : ok_follows({}) {}
data_t(auth_switch msg) noexcept : auth_sw(msg) {}
data_t(span<const std::uint8_t> more_data) noexcept : more_data(more_data) {}
} data;
handhake_server_response(const ok_view& ok) noexcept : type(type_t::ok), data(ok) {}
handhake_server_response(error_code err) noexcept : type(type_t::error), data(err) {}
handhake_server_response(ok_follows_t) noexcept : type(type_t::ok_follows), data(ok_follows_t{}) {}
handhake_server_response(auth_switch auth_switch) noexcept : type(type_t::auth_switch), data(auth_switch)
{
}
handhake_server_response(span<const std::uint8_t> more_data) noexcept
: type(type_t::auth_more_data), data(more_data)
{
}
};
BOOST_MYSQL_DECL
handhake_server_response deserialize_handshake_server_response(
span<const std::uint8_t> buff,
db_flavor flavor,
diagnostics& diag
);
struct auth_switch_response
{
span<const std::uint8_t> auth_plugin_data;
BOOST_MYSQL_DECL std::size_t get_size() const noexcept;
BOOST_MYSQL_DECL void serialize(span<std::uint8_t> buffer) const noexcept;
};
} // namespace detail
} // namespace mysql
} // namespace boost
#ifdef BOOST_MYSQL_HEADER_ONLY
#include <boost/mysql/impl/internal/protocol/protocol.ipp>
#endif
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,67 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_PROTOCOL_FIELD_TYPE_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_PROTOCOL_FIELD_TYPE_HPP
#include <boost/mysql/column_type.hpp>
#include <boost/mysql/detail/config.hpp>
#include <cstdint>
namespace boost {
namespace mysql {
namespace detail {
enum class protocol_field_type : std::uint8_t
{
decimal = 0x00, // Apparently not sent
tiny = 0x01, // TINYINT
short_ = 0x02, // SMALLINT
long_ = 0x03, // INT
float_ = 0x04, // FLOAT
double_ = 0x05, // DOUBLE
null = 0x06, // Apparently not sent
timestamp = 0x07, // TIMESTAMP
longlong = 0x08, // BIGINT
int24 = 0x09, // MEDIUMINT
date = 0x0a, // DATE
time = 0x0b, // TIME
datetime = 0x0c, // DATETIME
year = 0x0d, // YEAR
varchar = 0x0f, // Apparently not sent
bit = 0x10, // BIT
json = 0xf5, // JSON
newdecimal = 0xf6, // DECIMAL
enum_ = 0xf7, // Apparently not sent
set = 0xf8, // Apperently not sent
tiny_blob = 0xf9, // Apparently not sent
medium_blob = 0xfa, // Apparently not sent
long_blob = 0xfb, // Apparently not sent
blob = 0xfc, // Used for all TEXT and BLOB types
var_string = 0xfd, // Used for VARCHAR and VARBINARY
string = 0xfe, // Used for CHAR and BINARY, ENUM (enum flag set), SET (set flag set)
geometry = 0xff // GEOMETRY
};
BOOST_MYSQL_DECL
column_type compute_column_type(
protocol_field_type protocol_type,
std::uint16_t flags,
std::uint16_t collation
) noexcept;
} // namespace detail
} // namespace mysql
} // namespace boost
#ifdef BOOST_MYSQL_HEADER_ONLY
#include <boost/mysql/impl/internal/protocol/protocol_field_type.ipp>
#endif
#endif
@@ -0,0 +1,94 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_PROTOCOL_FIELD_TYPE_IPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_PROTOCOL_FIELD_TYPE_IPP
#pragma once
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/detail/flags.hpp>
#include <boost/mysql/impl/internal/protocol/constants.hpp>
#include <boost/mysql/impl/internal/protocol/protocol_field_type.hpp>
namespace boost {
namespace mysql {
namespace detail {
BOOST_MYSQL_STATIC_OR_INLINE
column_type compute_field_type_string(std::uint16_t flags, std::uint16_t collation) noexcept
{
if (flags & column_flags::set)
return column_type::set;
else if (flags & column_flags::enum_)
return column_type::enum_;
else if (collation == binary_collation)
return column_type::binary;
else
return column_type::char_;
}
BOOST_MYSQL_STATIC_OR_INLINE
column_type compute_field_type_var_string(std::uint16_t collation) noexcept
{
return collation == binary_collation ? column_type::varbinary : column_type::varchar;
}
BOOST_MYSQL_STATIC_OR_INLINE
column_type compute_field_type_blob(std::uint16_t collation) noexcept
{
return collation == binary_collation ? column_type::blob : column_type::text;
}
} // namespace detail
} // namespace mysql
} // namespace boost
boost::mysql::column_type boost::mysql::detail::compute_column_type(
protocol_field_type protocol_type,
std::uint16_t flags,
std::uint16_t collation
) noexcept
{
// Some protocol_field_types seem to not be sent by the server. We've found instances
// where some servers, with certain SQL statements, send some of the "apparently not sent"
// types (e.g. MariaDB was sending medium_blob only if you SELECT TEXT variables - but not with TEXT
// columns). So we've taken a defensive approach here
switch (protocol_type)
{
case protocol_field_type::decimal:
case protocol_field_type::newdecimal: return column_type::decimal;
case protocol_field_type::geometry: return column_type::geometry;
case protocol_field_type::tiny: return column_type::tinyint;
case protocol_field_type::short_: return column_type::smallint;
case protocol_field_type::int24: return column_type::mediumint;
case protocol_field_type::long_: return column_type::int_;
case protocol_field_type::longlong: return column_type::bigint;
case protocol_field_type::float_: return column_type::float_;
case protocol_field_type::double_: return column_type::double_;
case protocol_field_type::bit: return column_type::bit;
case protocol_field_type::date: return column_type::date;
case protocol_field_type::datetime: return column_type::datetime;
case protocol_field_type::timestamp: return column_type::timestamp;
case protocol_field_type::time: return column_type::time;
case protocol_field_type::year: return column_type::year;
case protocol_field_type::json: return column_type::json;
case protocol_field_type::enum_: return column_type::enum_; // in theory not set
case protocol_field_type::set: return column_type::set; // in theory not set
case protocol_field_type::string: return compute_field_type_string(flags, collation);
case protocol_field_type::varchar: // in theory not sent
case protocol_field_type::var_string: return compute_field_type_var_string(collation);
case protocol_field_type::tiny_blob: // in theory not sent
case protocol_field_type::medium_blob: // in theory not sent
case protocol_field_type::long_blob: // in theory not sent
case protocol_field_type::blob: return compute_field_type_blob(collation);
default: return column_type::unknown;
}
}
#endif
+387
View File
@@ -0,0 +1,387 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_SERIALIZATION_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_SERIALIZATION_HPP
#include <boost/mysql/client_errc.hpp>
#include <boost/mysql/error_code.hpp>
#include <boost/mysql/field_view.hpp>
#include <boost/mysql/string_view.hpp>
#include <boost/mysql/impl/internal/protocol/basic_types.hpp>
#include <boost/mysql/impl/internal/protocol/capabilities.hpp>
#include <boost/mysql/impl/internal/protocol/protocol_field_type.hpp>
#include <boost/assert.hpp>
#include <boost/core/span.hpp>
#include <boost/endian/conversion.hpp>
#include <boost/endian/detail/endian_load.hpp>
#include <boost/endian/detail/endian_store.hpp>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <type_traits>
namespace boost {
namespace mysql {
namespace detail {
// We operate with this enum directly in the deserialization routines for efficiency, then transform it to an
// actual error code
enum class deserialize_errc
{
ok = 0,
incomplete_message = 1,
protocol_value_error,
server_unsupported
};
inline error_code to_error_code(deserialize_errc v) noexcept
{
switch (v)
{
case deserialize_errc::ok: return error_code();
case deserialize_errc::incomplete_message: return error_code(client_errc::incomplete_message);
case deserialize_errc::protocol_value_error: return error_code(client_errc::protocol_value_error);
case deserialize_errc::server_unsupported: return error_code(client_errc::server_unsupported);
default: BOOST_ASSERT(false); return error_code(); // avoid warnings
}
}
class serialization_context
{
std::uint8_t* first_;
public:
explicit serialization_context(std::uint8_t* first) noexcept : first_(first) {}
std::uint8_t* first() const noexcept { return first_; }
void advance(std::size_t size) noexcept { first_ += size; }
void write(const void* buffer, std::size_t size) noexcept
{
if (size)
{
BOOST_ASSERT(buffer != nullptr);
std::memcpy(first_, buffer, size);
advance(size);
}
}
void write(std::uint8_t elm) noexcept
{
*first_ = elm;
++first_;
}
};
class deserialization_context
{
const std::uint8_t* first_;
const std::uint8_t* last_;
public:
deserialization_context(span<const std::uint8_t> data) noexcept
: deserialization_context(data.data(), data.size())
{
}
deserialization_context(const std::uint8_t* first, std::size_t size) noexcept
: first_(first), last_(first + size){};
const std::uint8_t* first() const noexcept { return first_; }
const std::uint8_t* last() const noexcept { return last_; }
void advance(std::size_t sz) noexcept
{
first_ += sz;
BOOST_ASSERT(last_ >= first_);
}
void rewind(std::size_t sz) noexcept { first_ -= sz; }
std::size_t size() const noexcept { return last_ - first_; }
bool empty() const noexcept { return last_ == first_; }
bool enough_size(std::size_t required_size) const noexcept { return size() >= required_size; }
deserialize_errc copy(void* to, std::size_t sz) noexcept
{
if (!enough_size(sz))
return deserialize_errc::incomplete_message;
memcpy(to, first_, sz);
advance(sz);
return deserialize_errc::ok;
}
string_view get_string(std::size_t sz) const noexcept
{
return string_view(reinterpret_cast<const char*>(first_), sz);
}
error_code check_extra_bytes() const noexcept
{
return empty() ? error_code() : error_code(client_errc::extra_bytes);
}
span<const std::uint8_t> to_span() const noexcept { return span<const std::uint8_t>(first_, size()); }
};
// integers
template <class T, class = typename std::enable_if<std::is_integral<T>::value>::type>
deserialize_errc deserialize(deserialization_context& ctx, T& output) noexcept
{
constexpr std::size_t sz = sizeof(T);
if (!ctx.enough_size(sz))
{
return deserialize_errc::incomplete_message;
}
output = endian::endian_load<T, sz, boost::endian::order::little>(ctx.first());
ctx.advance(sz);
return deserialize_errc::ok;
}
template <class T, class = typename std::enable_if<std::is_integral<T>::value>::type>
void serialize(serialization_context& ctx, T input) noexcept
{
endian::endian_store<T, sizeof(T), endian::order::little>(ctx.first(), input);
ctx.advance(sizeof(T));
}
template <class T, class = typename std::enable_if<std::is_integral<T>::value>::type>
constexpr std::size_t get_size(T) noexcept
{
return sizeof(T);
}
// int3
inline deserialize_errc deserialize(deserialization_context& ctx, int3& output) noexcept
{
if (!ctx.enough_size(3))
return deserialize_errc::incomplete_message;
output.value = endian::load_little_u24(ctx.first());
ctx.advance(3);
return deserialize_errc::ok;
}
inline void serialize(serialization_context& ctx, int3 input) noexcept
{
endian::store_little_u24(ctx.first(), input.value);
ctx.advance(3);
}
constexpr std::size_t get_size(int3) noexcept { return 3; }
// int_lenenc
inline deserialize_errc deserialize(deserialization_context& ctx, int_lenenc& output) noexcept
{
std::uint8_t first_byte = 0;
auto err = deserialize(ctx, first_byte);
if (err != deserialize_errc::ok)
{
return err;
}
if (first_byte == 0xFC)
{
std::uint16_t value = 0;
err = deserialize(ctx, value);
output.value = value;
}
else if (first_byte == 0xFD)
{
int3 value{};
err = deserialize(ctx, value);
output.value = value.value;
}
else if (first_byte == 0xFE)
{
std::uint64_t value = 0;
err = deserialize(ctx, value);
output.value = value;
}
else
{
err = deserialize_errc::ok;
output.value = first_byte;
}
return err;
}
inline void serialize(serialization_context& ctx, int_lenenc input) noexcept
{
if (input.value < 251)
{
serialize(ctx, static_cast<std::uint8_t>(input.value));
}
else if (input.value < 0x10000)
{
ctx.write(0xfc);
serialize(ctx, static_cast<std::uint16_t>(input.value));
}
else if (input.value < 0x1000000)
{
ctx.write(0xfd);
serialize(ctx, int3{static_cast<std::uint32_t>(input.value)});
}
else
{
ctx.write(0xfe);
serialize(ctx, static_cast<std::uint64_t>(input.value));
}
}
inline std::size_t get_size(int_lenenc input) noexcept
{
if (input.value < 251)
return 1;
else if (input.value < 0x10000)
return 3;
else if (input.value < 0x1000000)
return 4;
else
return 9;
}
// protocol_field_type
inline deserialize_errc deserialize(deserialization_context& ctx, protocol_field_type& output) noexcept
{
std::underlying_type<protocol_field_type>::type value = 0;
auto err = deserialize(ctx, value);
output = static_cast<protocol_field_type>(value);
return err;
}
inline void serialize(serialization_context& ctx, protocol_field_type input) noexcept
{
serialize(ctx, static_cast<std::underlying_type<protocol_field_type>::type>(input));
}
constexpr std::size_t get_size(protocol_field_type) noexcept { return sizeof(protocol_field_type); }
// string_fixed
template <std::size_t N>
deserialize_errc deserialize(deserialization_context& ctx, string_fixed<N>& output) noexcept
{
if (!ctx.enough_size(N))
return deserialize_errc::incomplete_message;
memcpy(output.value.data(), ctx.first(), N);
ctx.advance(N);
return deserialize_errc::ok;
}
template <std::size_t N>
void serialize(serialization_context& ctx, const string_fixed<N>& input) noexcept
{
ctx.write(input.value.data(), N);
}
template <std::size_t N>
constexpr std::size_t get_size(const string_fixed<N>&) noexcept
{
return N;
}
// string_null
inline deserialize_errc deserialize(deserialization_context& ctx, string_null& output) noexcept
{
auto string_end = std::find(ctx.first(), ctx.last(), 0);
if (string_end == ctx.last())
{
return deserialize_errc::incomplete_message;
}
std::size_t length = string_end - ctx.first();
output.value = ctx.get_string(length);
ctx.advance(length + 1); // skip the null terminator
return deserialize_errc::ok;
}
inline void serialize(serialization_context& ctx, string_null input) noexcept
{
ctx.write(input.value.data(), input.value.size());
ctx.write(0); // null terminator
}
inline std::size_t get_size(string_null input) noexcept { return input.value.size() + 1; }
// string_eof
inline deserialize_errc deserialize(deserialization_context& ctx, string_eof& output) noexcept
{
std::size_t size = ctx.size();
output.value = ctx.get_string(size);
ctx.advance(size);
return deserialize_errc::ok;
}
inline void serialize(serialization_context& ctx, string_eof input) noexcept
{
ctx.write(input.value.data(), input.value.size());
}
inline std::size_t get_size(string_eof input) noexcept { return input.value.size(); }
// string_lenenc
inline deserialize_errc deserialize(deserialization_context& ctx, string_lenenc& output) noexcept
{
int_lenenc length;
auto err = deserialize(ctx, length);
if (err != deserialize_errc::ok)
{
return err;
}
if (length.value > (std::numeric_limits<std::size_t>::max)())
{
return deserialize_errc::protocol_value_error;
}
auto len = static_cast<std::size_t>(length.value);
if (!ctx.enough_size(len))
{
return deserialize_errc::incomplete_message;
}
output.value = ctx.get_string(len);
ctx.advance(len);
return deserialize_errc::ok;
}
inline void serialize(serialization_context& ctx, string_lenenc input) noexcept
{
serialize(ctx, int_lenenc{input.value.size()});
ctx.write(input.value.data(), input.value.size());
}
inline std::size_t get_size(string_lenenc input) noexcept
{
return get_size(int_lenenc{input.value.size()}) + input.value.size();
}
// serialize, deserialize, and get size of multiple fields at the same time
template <class FirstType, class SecondType, class... Rest>
deserialize_errc deserialize(
deserialization_context& ctx,
FirstType& first,
SecondType& second,
Rest&... tail
) noexcept
{
deserialize_errc err = deserialize(ctx, first);
if (err == deserialize_errc::ok)
{
err = deserialize(ctx, second, tail...);
}
return err;
}
template <class FirstType, class SecondType, class... Rest>
void serialize(
serialization_context& ctx,
const FirstType& first,
const SecondType& second,
const Rest&... rest
) noexcept
{
serialize(ctx, first);
serialize(ctx, second, rest...);
}
template <class FirstType, class SecondType, class... Rest>
std::size_t get_size(const FirstType& first, const SecondType& second, const Rest&... rest) noexcept
{
return get_size(first) + get_size(second, rest...);
}
// helpers
inline string_view to_string(span<const std::uint8_t> v) noexcept
{
return string_view(reinterpret_cast<const char*>(v.data()), v.size());
}
inline span<const std::uint8_t> to_span(string_view v) noexcept
{
return span<const std::uint8_t>(reinterpret_cast<const std::uint8_t*>(v.data()), v.size());
}
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
+46
View File
@@ -0,0 +1,46 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_STATIC_BUFFER_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_STATIC_BUFFER_HPP
// A very simplified variable-length buffer with fixed max-size
#include <boost/assert.hpp>
#include <boost/core/span.hpp>
#include <array>
#include <cstring>
namespace boost {
namespace mysql {
namespace detail {
template <std::size_t max_size>
class static_buffer
{
std::array<std::uint8_t, max_size> buffer_{};
std::size_t size_{};
public:
static_buffer() noexcept = default;
span<const std::uint8_t> to_span() const noexcept { return {buffer_.data(), size_}; }
void append(const void* data, std::size_t data_size) noexcept
{
std::size_t new_size = size_ + data_size;
BOOST_ASSERT(new_size <= max_size);
std::memcpy(buffer_.data() + size_, data, data_size);
size_ = new_size;
}
void clear() noexcept { size_ = 0; }
};
} // namespace detail
} // namespace mysql
} // namespace boost
#endif
+58
View File
@@ -0,0 +1,58 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_META_CHECK_CONTEXT_IPP
#define BOOST_MYSQL_IMPL_META_CHECK_CONTEXT_IPP
#pragma once
#include <boost/mysql/detail/typing/meta_check_context.hpp>
void boost::mysql::detail::meta_check_context::add_field_absent_error()
{
auto& stream = add_error();
stream << "Field ";
insert_field_name(stream);
if (has_field_names(name_table_))
{
stream << " is not present in the data returned by the server";
}
else
{
stream << " can't be mapped: there are more fields in your C++ data type than in your query";
}
}
void boost::mysql::detail::meta_check_context::add_type_mismatch_error(const char* cpp_type_name)
{
auto& stream = add_error();
stream << "Incompatible types for field ";
insert_field_name(stream);
stream << ": C++ type '" << cpp_type_name << "' is not compatible with DB type '"
<< column_type_to_str(current_meta()) << "'";
}
void boost::mysql::detail::meta_check_context::add_nullability_error()
{
auto& stream = add_error();
stream << "NULL checks failed for field ";
insert_field_name(stream);
stream << ": the database type may be NULL, but the C++ type cannot. Use std::optional<T> or "
"boost::optional<T>";
}
boost::mysql::error_code boost::mysql::detail::meta_check_context::check_errors(diagnostics& diag) const
{
if (errors_ != nullptr)
{
access::get_impl(diag).assign_client(errors_->str());
return client_errc::metadata_check_failed;
}
return error_code();
}
#endif
+269
View File
@@ -0,0 +1,269 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_NETWORK_ALGORITHMS_IPP
#define BOOST_MYSQL_IMPL_NETWORK_ALGORITHMS_IPP
#pragma once
#include <boost/mysql/detail/network_algorithms.hpp>
#include <boost/mysql/impl/internal/network_algorithms/close_connection.hpp>
#include <boost/mysql/impl/internal/network_algorithms/close_statement.hpp>
#include <boost/mysql/impl/internal/network_algorithms/connect.hpp>
#include <boost/mysql/impl/internal/network_algorithms/execute.hpp>
#include <boost/mysql/impl/internal/network_algorithms/handshake.hpp>
#include <boost/mysql/impl/internal/network_algorithms/ping.hpp>
#include <boost/mysql/impl/internal/network_algorithms/prepare_statement.hpp>
#include <boost/mysql/impl/internal/network_algorithms/quit_connection.hpp>
#include <boost/mysql/impl/internal/network_algorithms/read_resultset_head.hpp>
#include <boost/mysql/impl/internal/network_algorithms/read_some_rows.hpp>
#include <boost/mysql/impl/internal/network_algorithms/read_some_rows_dynamic.hpp>
#include <boost/mysql/impl/internal/network_algorithms/reset_connection.hpp>
#include <boost/mysql/impl/internal/network_algorithms/start_execution.hpp>
void boost::mysql::detail::connect_erased(
channel& chan,
const void* endpoint,
const handshake_params& params,
error_code& err,
diagnostics& diag
)
{
connect_impl(chan, endpoint, params, err, diag);
}
void boost::mysql::detail::async_connect_erased(
channel& chan,
const void* endpoint,
const handshake_params& params,
diagnostics& diag,
any_void_handler handler
)
{
async_connect_impl(chan, endpoint, params, diag, std::move(handler));
}
void boost::mysql::detail::handshake_erased(
channel& channel,
const handshake_params& params,
error_code& err,
diagnostics& diag
)
{
handshake_impl(channel, params, err, diag);
}
void boost::mysql::detail::async_handshake_erased(
channel& chan,
const handshake_params& params,
diagnostics& diag,
any_void_handler handler
)
{
async_handshake_impl(chan, params, diag, std::move(handler));
}
void boost::mysql::detail::execute_erased(
channel& channel,
const any_execution_request& req,
execution_processor& output,
error_code& err,
diagnostics& diag
)
{
execute_impl(channel, req, output, err, diag);
}
void boost::mysql::detail::async_execute_erased(
channel& chan,
const any_execution_request& req,
execution_processor& output,
diagnostics& diag,
any_void_handler handler
)
{
async_execute_impl(chan, req, output, diag, std::move(handler));
}
void boost::mysql::detail::start_execution_erased(
channel& channel,
const any_execution_request& req,
execution_processor& proc,
error_code& err,
diagnostics& diag
)
{
start_execution_impl(channel, req, proc, err, diag);
}
void boost::mysql::detail::async_start_execution_erased(
channel& channel,
const any_execution_request& req,
execution_processor& proc,
diagnostics& diag,
any_void_handler handler
)
{
async_start_execution_impl(channel, req, proc, diag, std::move(handler));
}
boost::mysql::statement boost::mysql::detail::prepare_statement_erased(
channel& chan,
string_view stmt,
error_code& err,
diagnostics& diag
)
{
return prepare_statement_impl(chan, stmt, err, diag);
}
void boost::mysql::detail::async_prepare_statement_erased(
channel& chan,
string_view stmt,
diagnostics& diag,
any_handler<statement> handler
)
{
async_prepare_statement_impl(chan, stmt, diag, std::move(handler));
}
void boost::mysql::detail::close_statement_erased(
channel& chan,
const statement& stmt,
error_code& err,
diagnostics& diag
)
{
close_statement_impl(chan, stmt, err, diag);
}
void boost::mysql::detail::async_close_statement_erased(
channel& chan,
const statement& stmt,
diagnostics& diag,
any_void_handler handler
)
{
async_close_statement_impl(chan, stmt, diag, std::move(handler));
}
boost::mysql::rows_view boost::mysql::detail::read_some_rows_dynamic_erased(
channel& chan,
execution_state_impl& st,
error_code& err,
diagnostics& diag
)
{
return read_some_rows_dynamic_impl(chan, st, err, diag);
}
void boost::mysql::detail::async_read_some_rows_dynamic_erased(
channel& chan,
execution_state_impl& st,
diagnostics& diag,
any_handler<rows_view> handler
)
{
async_read_some_rows_dynamic_impl(chan, st, diag, std::move(handler));
}
std::size_t boost::mysql::detail::read_some_rows_static_erased(
channel& chan,
execution_processor& proc,
const output_ref& output,
error_code& err,
diagnostics& diag
)
{
return read_some_rows_impl(chan, proc, output, err, diag);
}
void boost::mysql::detail::async_read_some_rows_erased(
channel& chan,
execution_processor& proc,
const output_ref& output,
diagnostics& diag,
any_handler<std::size_t> handler
)
{
async_read_some_rows_impl(chan, proc, output, diag, std::move(handler));
}
void boost::mysql::detail::read_resultset_head_erased(
channel& channel,
execution_processor& proc,
error_code& err,
diagnostics& diag
)
{
read_resultset_head_impl(channel, proc, err, diag);
}
void boost::mysql::detail::async_read_resultset_head_erased(
channel& chan,
execution_processor& proc,
diagnostics& diag,
any_void_handler handler
)
{
async_read_resultset_head_impl(chan, proc, diag, std::move(handler));
}
void boost::mysql::detail::ping_erased(channel& chan, error_code& code, diagnostics& diag)
{
ping_impl(chan, code, diag);
}
void boost::mysql::detail::async_ping_erased(channel& chan, diagnostics& diag, any_void_handler handler)
{
async_ping_impl(chan, diag, std::move(handler));
}
void boost::mysql::detail::reset_connection_erased(channel& chan, error_code& code, diagnostics& diag)
{
reset_connection_impl(chan, code, diag);
}
void boost::mysql::detail::async_reset_connection_erased(
channel& chan,
diagnostics& diag,
any_void_handler handler
)
{
async_reset_connection_impl(chan, diag, std::move(handler));
}
void boost::mysql::detail::close_connection_erased(channel& chan, error_code& code, diagnostics& diag)
{
close_connection_impl(chan, code, diag);
}
void boost::mysql::detail::async_close_connection_erased(
channel& chan,
diagnostics& diag,
any_void_handler handler
)
{
async_close_connection_impl(chan, diag, std::move(handler));
}
void boost::mysql::detail::quit_connection_erased(channel& chan, error_code& err, diagnostics& diag)
{
quit_connection_impl(chan, err, diag);
}
void boost::mysql::detail::async_quit_connection_erased(
channel& chan,
diagnostics& diag,
any_void_handler handler
)
{
async_quit_connection_impl(chan, diag, std::move(handler));
}
#endif
+148
View File
@@ -0,0 +1,148 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_RESULTS_IMPL_IPP
#define BOOST_MYSQL_IMPL_RESULTS_IMPL_IPP
#pragma once
#include <boost/mysql/detail/execution_processor/results_impl.hpp>
#include <boost/mysql/impl/internal/protocol/protocol.hpp>
boost::mysql::detail::per_resultset_data& boost::mysql::detail::resultset_container::emplace_back()
{
if (!first_has_data_)
{
first_ = per_resultset_data();
first_has_data_ = true;
return first_;
}
else
{
rest_.emplace_back();
return rest_.back();
}
}
boost::mysql::row_view boost::mysql::detail::results_impl::get_out_params() const noexcept
{
BOOST_ASSERT(is_complete());
for (std::size_t i = 0; i < per_result_.size(); ++i)
{
if (per_result_[i].is_out_params)
{
auto res = get_rows(i);
return res.empty() ? row_view() : res[0];
}
}
return row_view();
}
void boost::mysql::detail::results_impl::reset_impl() noexcept
{
meta_.clear();
per_result_.clear();
info_.clear();
rows_.clear();
num_fields_at_batch_start_ = no_batch;
}
void boost::mysql::detail::results_impl::on_num_meta_impl(std::size_t num_columns)
{
auto& resultset_data = add_resultset();
meta_.reserve(meta_.size() + num_columns);
resultset_data.num_columns = num_columns;
}
boost::mysql::error_code boost::mysql::detail::results_impl::
on_head_ok_packet_impl(const ok_view& pack, diagnostics&)
{
add_resultset();
on_ok_packet_impl(pack);
return error_code();
}
boost::mysql::error_code boost::mysql::detail::results_impl::
on_meta_impl(const coldef_view& coldef, bool, diagnostics&)
{
meta_.push_back(create_meta(coldef));
return error_code();
}
boost::mysql::error_code boost::mysql::detail::results_impl::
on_row_impl(span<const std::uint8_t> msg, const output_ref&, std::vector<field_view>&)
{
BOOST_ASSERT(has_active_batch());
// add row storage
std::size_t num_fields = current_resultset().num_columns;
span<field_view> storage = rows_.add_fields(num_fields);
++current_resultset().num_rows;
// deserialize the row
auto err = deserialize_row(encoding(), msg, current_resultset_meta(), storage);
if (err)
return err;
return error_code();
}
boost::mysql::error_code boost::mysql::detail::results_impl::on_row_ok_packet_impl(const ok_view& pack)
{
on_ok_packet_impl(pack);
return error_code();
}
void boost::mysql::detail::results_impl::on_row_batch_start_impl()
{
BOOST_ASSERT(!has_active_batch());
num_fields_at_batch_start_ = rows_.fields().size();
}
void boost::mysql::detail::results_impl::on_row_batch_finish_impl() { finish_batch(); }
void boost::mysql::detail::results_impl::finish_batch()
{
if (has_active_batch())
{
rows_.copy_strings_as_offsets(
num_fields_at_batch_start_,
rows_.fields().size() - num_fields_at_batch_start_
);
num_fields_at_batch_start_ = no_batch;
}
}
boost::mysql::detail::per_resultset_data& boost::mysql::detail::results_impl::add_resultset()
{
// Allocate a new per-resultset object
auto& resultset_data = per_result_.emplace_back();
resultset_data.meta_offset = meta_.size();
resultset_data.field_offset = rows_.fields().size();
resultset_data.info_offset = info_.size();
return resultset_data;
}
void boost::mysql::detail::results_impl::on_ok_packet_impl(const ok_view& pack)
{
auto& resultset_data = current_resultset();
resultset_data.affected_rows = pack.affected_rows;
resultset_data.last_insert_id = pack.last_insert_id;
resultset_data.warnings = pack.warnings;
resultset_data.info_size = pack.info.size();
resultset_data.has_ok_packet_data = true;
resultset_data.is_out_params = pack.is_out_params();
info_.insert(info_.end(), pack.info.begin(), pack.info.end());
if (!pack.more_results())
{
finish_batch();
rows_.offsets_to_string_views();
}
}
#endif
+40
View File
@@ -0,0 +1,40 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_RESULTSET_IPP
#define BOOST_MYSQL_IMPL_RESULTSET_IPP
#pragma once
#include <boost/mysql/resultset.hpp>
void boost::mysql::resultset::assign(resultset_view v)
{
has_value_ = v.has_value();
if (has_value_)
{
meta_.assign(v.meta().begin(), v.meta().end());
rws_ = v.rows();
affected_rows_ = v.affected_rows();
last_insert_id_ = v.last_insert_id();
warnings_ = v.warning_count();
info_.assign(v.info().begin(), v.info().end());
is_out_params_ = v.is_out_params();
}
else
{
meta_.clear();
rws_ = ::boost::mysql::rows();
affected_rows_ = 0;
last_insert_id_ = 0;
warnings_ = 0;
info_.clear();
is_out_params_ = false;
}
}
#endif
+206
View File
@@ -0,0 +1,206 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_ROW_IMPL_IPP
#define BOOST_MYSQL_IMPL_ROW_IMPL_IPP
#pragma once
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/detail/row_impl.hpp>
namespace boost {
namespace mysql {
namespace detail {
BOOST_MYSQL_STATIC_OR_INLINE
std::size_t get_string_size(field_view f) noexcept
{
switch (f.kind())
{
case field_kind::string: return f.get_string().size();
case field_kind::blob: return f.get_blob().size();
default: return 0;
}
}
BOOST_MYSQL_STATIC_OR_INLINE
unsigned char* copy_string(unsigned char* buffer_it, field_view& f) noexcept
{
auto str = f.get_string();
if (!str.empty())
{
std::memcpy(buffer_it, str.data(), str.size());
f = field_view(string_view(reinterpret_cast<const char*>(buffer_it), str.size()));
buffer_it += str.size();
}
return buffer_it;
}
BOOST_MYSQL_STATIC_OR_INLINE
unsigned char* copy_blob(unsigned char* buffer_it, field_view& f) noexcept
{
auto b = f.get_blob();
if (!b.empty())
{
std::memcpy(buffer_it, b.data(), b.size());
f = field_view(blob_view(buffer_it, b.size()));
buffer_it += b.size();
}
return buffer_it;
}
BOOST_MYSQL_STATIC_OR_INLINE
std::size_t copy_string_as_offset(unsigned char* buffer_first, std::size_t offset, field_view& f) noexcept
{
auto str = f.get_string();
if (!str.empty())
{
std::memcpy(buffer_first + offset, str.data(), str.size());
f = detail::access::construct<field_view>(detail::string_view_offset{offset, str.size()}, false);
return str.size();
}
return 0;
}
BOOST_MYSQL_STATIC_OR_INLINE
std::size_t copy_blob_as_offset(unsigned char* buffer_first, std::size_t offset, field_view& f) noexcept
{
auto str = f.get_blob();
if (!str.empty())
{
std::memcpy(buffer_first + offset, str.data(), str.size());
f = detail::access::construct<field_view>(detail::string_view_offset{offset, str.size()}, true);
return str.size();
}
return 0;
}
BOOST_MYSQL_STATIC_OR_INLINE
void copy_strings(std::vector<field_view>& fields, std::vector<unsigned char>& string_buffer)
{
// Calculate the required size for the new strings
std::size_t size = 0;
for (auto f : fields)
{
size += get_string_size(f);
}
// Make space. The previous fields should be in offset form
string_buffer.resize(string_buffer.size() + size);
// Copy strings and blobs
unsigned char* buffer_it = string_buffer.data();
for (auto& f : fields)
{
switch (f.kind())
{
case field_kind::string: buffer_it = copy_string(buffer_it, f); break;
case field_kind::blob: buffer_it = copy_blob(buffer_it, f); break;
default: break;
}
}
BOOST_ASSERT(buffer_it == string_buffer.data() + size);
}
BOOST_MYSQL_STATIC_OR_INLINE
field_view offset_to_string_view(field_view fv, const std::uint8_t* buffer_first) noexcept
{
auto& impl = detail::access::get_impl(fv);
if (impl.is_string_offset())
{
return field_view(string_view(
reinterpret_cast<const char*>(buffer_first) + impl.repr.sv_offset_.offset,
impl.repr.sv_offset_.size
));
}
else if (impl.is_blob_offset())
{
return field_view(blob_view(buffer_first + impl.repr.sv_offset_.offset, impl.repr.sv_offset_.size));
}
else
{
return fv;
}
}
} // namespace detail
} // namespace mysql
} // namespace boost
boost::mysql::detail::row_impl::row_impl(const field_view* fields, std::size_t size)
: fields_(fields, fields + size)
{
copy_strings(fields_, string_buffer_);
}
boost::mysql::detail::row_impl::row_impl(const row_impl& rhs) : fields_(rhs.fields_)
{
copy_strings(fields_, string_buffer_);
}
boost::mysql::detail::row_impl& boost::mysql::detail::row_impl::operator=(const row_impl& rhs)
{
assign(rhs.fields_.data(), rhs.fields_.size());
return *this;
}
void boost::mysql::detail::row_impl::assign(const field_view* fields, std::size_t size)
{
// Protect against self-assignment. This is valid as long as we
// don't implement sub-range operators (e.g. row_view[2:4])
if (fields_.data() == fields)
{
BOOST_ASSERT(fields_.size() == size);
}
else
{
fields_.assign(fields, fields + size);
string_buffer_.clear();
copy_strings(fields_, string_buffer_);
}
}
void boost::mysql::detail::row_impl::copy_strings_as_offsets(std::size_t first, std::size_t num_fields)
{
// Preconditions
BOOST_ASSERT(first <= fields_.size());
BOOST_ASSERT(first + num_fields <= fields_.size());
// Calculate the required size for the new strings
std::size_t size = 0;
for (std::size_t i = first; i < first + num_fields; ++i)
{
size += get_string_size(fields_[i]);
}
// Make space. The previous fields should be in offset form
std::size_t old_string_buffer_size = string_buffer_.size();
string_buffer_.resize(old_string_buffer_size + size);
// Copy strings and blobs
std::size_t offset = old_string_buffer_size;
for (std::size_t i = first; i < first + num_fields; ++i)
{
auto& f = fields_[i];
switch (f.kind())
{
case field_kind::string: offset += copy_string_as_offset(string_buffer_.data(), offset, f); break;
case field_kind::blob: offset += copy_blob_as_offset(string_buffer_.data(), offset, f); break;
default: break;
}
}
BOOST_ASSERT(offset == string_buffer_.size());
}
void boost::mysql::detail::row_impl::offsets_to_string_views()
{
for (auto& f : fields_)
f = offset_to_string_view(f, string_buffer_.data());
}
#endif
+82
View File
@@ -0,0 +1,82 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_STATEMENT_HPP
#define BOOST_MYSQL_IMPL_STATEMENT_HPP
#pragma once
#include <boost/mysql/statement.hpp>
#include <boost/mysql/detail/access.hpp>
#include <boost/assert.hpp>
template <BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple>
class boost::mysql::bound_statement_tuple
{
friend class statement;
friend struct detail::access;
struct impl
{
statement stmt;
WritableFieldTuple params;
} impl_;
template <typename TupleType>
bound_statement_tuple(const statement& stmt, TupleType&& t) : impl_{stmt, std::forward<TupleType>(t)}
{
}
};
template <BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator>
class boost::mysql::bound_statement_iterator_range
{
friend class statement;
friend struct detail::access;
struct impl
{
statement stmt;
FieldViewFwdIterator first;
FieldViewFwdIterator last;
} impl_;
bound_statement_iterator_range(
const statement& stmt,
FieldViewFwdIterator first,
FieldViewFwdIterator last
)
: impl_{stmt, first, last}
{
}
};
template <BOOST_MYSQL_WRITABLE_FIELD_TUPLE WritableFieldTuple, typename EnableIf>
boost::mysql::bound_statement_tuple<typename std::decay<WritableFieldTuple>::type> boost::mysql::statement::
bind(WritableFieldTuple&& args) const
{
BOOST_ASSERT(valid());
return bound_statement_tuple<typename std::decay<WritableFieldTuple>::type>(
*this,
std::forward<WritableFieldTuple>(args)
);
}
template <BOOST_MYSQL_FIELD_VIEW_FORWARD_ITERATOR FieldViewFwdIterator, typename EnableIf>
boost::mysql::bound_statement_iterator_range<FieldViewFwdIterator> boost::mysql::statement::bind(
FieldViewFwdIterator first,
FieldViewFwdIterator last
) const
{
BOOST_ASSERT(valid());
return bound_statement_iterator_range<FieldViewFwdIterator>(*this, first, last);
}
#endif
+123
View File
@@ -0,0 +1,123 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_STATIC_EXECUTION_STATE_IMPL_IPP
#define BOOST_MYSQL_IMPL_STATIC_EXECUTION_STATE_IMPL_IPP
#pragma once
#include <boost/mysql/detail/execution_processor/static_execution_state_impl.hpp>
#include <boost/mysql/detail/row_impl.hpp>
#include <boost/mysql/impl/internal/protocol/protocol.hpp>
#ifdef BOOST_MYSQL_CXX14
void boost::mysql::detail::static_execution_state_erased_impl::reset_impl() noexcept
{
resultset_index_ = 0;
ok_data_ = ok_packet_data();
info_.clear();
meta_.clear();
}
boost::mysql::error_code boost::mysql::detail::static_execution_state_erased_impl::on_head_ok_packet_impl(
const ok_view& pack,
diagnostics& diag
)
{
on_new_resultset();
auto err = on_ok_packet_impl(pack);
if (err)
return err;
return meta_check(diag);
}
void boost::mysql::detail::static_execution_state_erased_impl::on_num_meta_impl(std::size_t num_columns)
{
on_new_resultset();
meta_.reserve(num_columns);
}
boost::mysql::error_code boost::mysql::detail::static_execution_state_erased_impl::on_meta_impl(
const coldef_view& coldef,
bool is_last,
diagnostics& diag
)
{
std::size_t meta_index = meta_.size();
// Store the object
meta_.push_back(create_meta(coldef));
// Record its position
pos_map_add_field(current_pos_map(), current_name_table(), meta_index, coldef.name);
return is_last ? meta_check(diag) : error_code();
}
boost::mysql::error_code boost::mysql::detail::static_execution_state_erased_impl::on_row_impl(
span<const std::uint8_t> msg,
const output_ref& ref,
std::vector<field_view>& fields
)
{
// check output
if (ref.type_index() != ext_.type_index(resultset_index_ - 1))
return client_errc::row_type_mismatch;
// Allocate temporary space
fields.clear();
span<field_view> storage = add_fields(fields, meta_.size());
// deserialize the row
auto err = deserialize_row(encoding(), msg, meta_, storage);
if (err)
return err;
// parse it into the output ref
err = ext_.parse_fn(resultset_index_ - 1)(current_pos_map(), storage, ref);
if (err)
return err;
return error_code();
}
boost::mysql::error_code boost::mysql::detail::static_execution_state_erased_impl::on_row_ok_packet_impl(
const ok_view& pack
)
{
return on_ok_packet_impl(pack);
}
void boost::mysql::detail::static_execution_state_erased_impl::on_new_resultset() noexcept
{
++resultset_index_;
ok_data_ = ok_packet_data{};
info_.clear();
meta_.clear();
pos_map_reset(current_pos_map());
}
boost::mysql::error_code boost::mysql::detail::static_execution_state_erased_impl::on_ok_packet_impl(
const ok_view& pack
)
{
ok_data_.has_value = true;
ok_data_.affected_rows = pack.affected_rows;
ok_data_.last_insert_id = pack.last_insert_id;
ok_data_.warnings = pack.warnings;
ok_data_.is_out_params = pack.is_out_params();
info_.assign(pack.info.begin(), pack.info.end());
bool should_be_last = resultset_index_ == ext_.num_resultsets();
bool is_last = !pack.more_results();
return should_be_last == is_last ? error_code() : client_errc::num_resultsets_mismatch;
}
#endif
#endif
+124
View File
@@ -0,0 +1,124 @@
//
// Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_IMPL_STATIC_RESULTS_IMPL_IPP
#define BOOST_MYSQL_IMPL_STATIC_RESULTS_IMPL_IPP
#pragma once
#include <boost/mysql/detail/config.hpp>
#include <boost/mysql/detail/execution_processor/static_results_impl.hpp>
#include <boost/mysql/detail/row_impl.hpp>
#include <boost/mysql/impl/internal/protocol/protocol.hpp>
#ifdef BOOST_MYSQL_CXX14
void boost::mysql::detail::static_results_erased_impl::reset_impl() noexcept
{
ext_.reset_fn()(ext_.rows());
info_.clear();
meta_.clear();
resultset_index_ = 0;
}
boost::mysql::error_code boost::mysql::detail::static_results_erased_impl::on_head_ok_packet_impl(
const ok_view& pack,
diagnostics& diag
)
{
add_resultset();
auto err = on_ok_packet_impl(pack);
if (err)
return err;
return meta_check(diag);
}
void boost::mysql::detail::static_results_erased_impl::on_num_meta_impl(std::size_t num_columns)
{
auto& resultset_data = add_resultset();
meta_.reserve(meta_.size() + num_columns);
resultset_data.meta_size = num_columns;
}
boost::mysql::error_code boost::mysql::detail::static_results_erased_impl::on_meta_impl(
const coldef_view& coldef,
bool is_last,
diagnostics& diag
)
{
std::size_t meta_index = meta_.size() - current_resultset().meta_offset;
// Store the new object
meta_.push_back(create_meta(coldef));
// Fill the pos map entry for this field, if any
pos_map_add_field(current_pos_map(), current_name_table(), meta_index, coldef.name);
return is_last ? meta_check(diag) : error_code();
}
boost::mysql::error_code boost::mysql::detail::static_results_erased_impl::on_row_impl(
span<const std::uint8_t> msg,
const output_ref&,
std::vector<field_view>& fields
)
{
auto meta = current_resultset_meta();
// Allocate temporary storage
fields.clear();
span<field_view> storage = add_fields(fields, meta.size());
// deserialize the row
auto err = deserialize_row(encoding(), msg, meta, storage);
if (err)
return err;
// parse it against the appropriate tuple element
return ext_.parse_fn(resultset_index_ - 1)(current_pos_map(), storage, ext_.rows());
}
boost::mysql::error_code boost::mysql::detail::static_results_erased_impl::on_row_ok_packet_impl(
const ok_view& pack
)
{
return on_ok_packet_impl(pack);
}
boost::mysql::detail::static_per_resultset_data& boost::mysql::detail::static_results_erased_impl::
add_resultset()
{
++resultset_index_;
auto& resultset_data = current_resultset();
resultset_data = static_per_resultset_data();
resultset_data.meta_offset = meta_.size();
resultset_data.info_offset = info_.size();
pos_map_reset(current_pos_map());
return resultset_data;
}
boost::mysql::error_code boost::mysql::detail::static_results_erased_impl::on_ok_packet_impl(
const ok_view& pack
)
{
auto& resultset_data = current_resultset();
resultset_data.affected_rows = pack.affected_rows;
resultset_data.last_insert_id = pack.last_insert_id;
resultset_data.warnings = pack.warnings;
resultset_data.info_size = pack.info.size();
resultset_data.has_ok_packet_data = true;
resultset_data.is_out_params = pack.is_out_params();
info_.insert(info_.end(), pack.info.begin(), pack.info.end());
bool should_be_last = resultset_index_ == ext_.num_resultsets();
bool is_last = !pack.more_results();
return should_be_last == is_last ? error_code() : client_errc::num_resultsets_mismatch;
}
#endif
#endif