mirror of
https://github.com/vdemydiuk/mtapi.git
synced 2026-08-03 22:17:47 +00:00
Added thirdparty: boost library
This commit is contained in:
+20
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
+36
@@ -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
|
||||
+2209
File diff suppressed because it is too large
Load Diff
+28
@@ -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
|
||||
Vendored
Executable
+92
@@ -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_ */
|
||||
Vendored
Executable
+61
@@ -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_ */
|
||||
+113
@@ -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_ */
|
||||
+137
@@ -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
|
||||
+391
@@ -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
@@ -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
|
||||
Vendored
Executable
+209
@@ -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_ */
|
||||
Vendored
Executable
+95
@@ -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_ */
|
||||
Vendored
Executable
+204
@@ -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
|
||||
Vendored
Executable
+184
@@ -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
|
||||
Vendored
Executable
+105
@@ -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
|
||||
Vendored
Executable
+109
@@ -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_ */
|
||||
Vendored
Executable
+183
@@ -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
@@ -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
|
||||
+35
@@ -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
|
||||
+133
@@ -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
|
||||
+47
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Vendored
Executable
+37
@@ -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_ */
|
||||
Vendored
Executable
+319
@@ -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
|
||||
Vendored
Executable
+33
@@ -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
|
||||
Vendored
Executable
+336
@@ -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, µs, &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, µs, &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
|
||||
+54
@@ -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
@@ -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
|
||||
+1092
File diff suppressed because it is too large
Load Diff
+67
@@ -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
|
||||
+94
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user