251 lines
8.4 KiB
C++
251 lines
8.4 KiB
C++
|
//
|
||
|
// Copyright (c) 2019-2024 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_SANSIO_RUN_PIPELINE_HPP
|
||
|
#define BOOST_MYSQL_IMPL_INTERNAL_SANSIO_RUN_PIPELINE_HPP
|
||
|
|
||
|
#include <boost/mysql/character_set.hpp>
|
||
|
#include <boost/mysql/diagnostics.hpp>
|
||
|
#include <boost/mysql/error_code.hpp>
|
||
|
#include <boost/mysql/is_fatal_error.hpp>
|
||
|
#include <boost/mysql/pipeline.hpp>
|
||
|
|
||
|
#include <boost/mysql/detail/access.hpp>
|
||
|
#include <boost/mysql/detail/algo_params.hpp>
|
||
|
#include <boost/mysql/detail/next_action.hpp>
|
||
|
#include <boost/mysql/detail/pipeline.hpp>
|
||
|
|
||
|
#include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
|
||
|
#include <boost/mysql/impl/internal/sansio/execute.hpp>
|
||
|
#include <boost/mysql/impl/internal/sansio/ping.hpp>
|
||
|
#include <boost/mysql/impl/internal/sansio/prepare_statement.hpp>
|
||
|
#include <boost/mysql/impl/internal/sansio/reset_connection.hpp>
|
||
|
#include <boost/mysql/impl/internal/sansio/set_character_set.hpp>
|
||
|
|
||
|
#include <boost/assert.hpp>
|
||
|
#include <boost/core/span.hpp>
|
||
|
|
||
|
#include <cstddef>
|
||
|
#include <vector>
|
||
|
|
||
|
namespace boost {
|
||
|
namespace mysql {
|
||
|
namespace detail {
|
||
|
|
||
|
class run_pipeline_algo
|
||
|
{
|
||
|
union any_read_algo
|
||
|
{
|
||
|
std::nullptr_t nothing;
|
||
|
read_execute_response_algo execute;
|
||
|
read_prepare_statement_response_algo prepare_statement;
|
||
|
read_reset_connection_response_algo reset_connection;
|
||
|
read_ping_response_algo ping;
|
||
|
read_set_character_set_response_algo set_character_set;
|
||
|
|
||
|
any_read_algo() noexcept : nothing{} {}
|
||
|
};
|
||
|
|
||
|
diagnostics* diag_;
|
||
|
span<const std::uint8_t> request_buffer_;
|
||
|
span<const pipeline_request_stage> stages_;
|
||
|
std::vector<stage_response>* response_;
|
||
|
|
||
|
int resume_point_{0};
|
||
|
std::size_t current_stage_index_{0};
|
||
|
error_code pipeline_ec_; // Result of the entire operation
|
||
|
bool has_hatal_error_{}; // If true, fail further stages with pipeline_ec_
|
||
|
any_read_algo read_response_algo_;
|
||
|
diagnostics temp_diag_;
|
||
|
|
||
|
void setup_response()
|
||
|
{
|
||
|
if (response_)
|
||
|
{
|
||
|
// Create as many response items as request stages
|
||
|
response_->resize(stages_.size());
|
||
|
|
||
|
// Setup them
|
||
|
for (std::size_t i = 0u; i < stages_.size(); ++i)
|
||
|
{
|
||
|
// Execution stages need to be initialized to results objects.
|
||
|
// Otherwise, clear any previous content
|
||
|
auto& impl = access::get_impl((*response_)[i]);
|
||
|
if (stages_[i].kind == pipeline_stage_kind::execute)
|
||
|
impl.emplace_results();
|
||
|
else
|
||
|
impl.emplace_error();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void setup_current_stage(const connection_state_data& st)
|
||
|
{
|
||
|
// Reset previous data
|
||
|
temp_diag_.clear();
|
||
|
|
||
|
// Setup read algo
|
||
|
auto stage = stages_[current_stage_index_];
|
||
|
switch (stage.kind)
|
||
|
{
|
||
|
case pipeline_stage_kind::execute:
|
||
|
{
|
||
|
BOOST_ASSERT(response_ != nullptr); // we don't support execution ignoring the response
|
||
|
auto& processor = access::get_impl((*response_)[current_stage_index_]).get_processor();
|
||
|
processor.reset(stage.stage_specific.enc, st.meta_mode);
|
||
|
processor.sequence_number() = stage.seqnum;
|
||
|
read_response_algo_.execute = {&temp_diag_, &processor};
|
||
|
break;
|
||
|
}
|
||
|
case pipeline_stage_kind::prepare_statement:
|
||
|
read_response_algo_.prepare_statement = {&temp_diag_, stage.seqnum};
|
||
|
break;
|
||
|
case pipeline_stage_kind::close_statement:
|
||
|
// Close statement doesn't have a response
|
||
|
read_response_algo_.nothing = nullptr;
|
||
|
break;
|
||
|
case pipeline_stage_kind::set_character_set:
|
||
|
read_response_algo_.set_character_set = {&temp_diag_, stage.stage_specific.charset, stage.seqnum};
|
||
|
break;
|
||
|
case pipeline_stage_kind::reset_connection:
|
||
|
read_response_algo_.reset_connection = {&temp_diag_, stage.seqnum};
|
||
|
break;
|
||
|
case pipeline_stage_kind::ping: read_response_algo_.ping = {&temp_diag_, stage.seqnum}; break;
|
||
|
default: BOOST_ASSERT(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void set_stage_error(error_code ec, diagnostics&& diag)
|
||
|
{
|
||
|
if (response_)
|
||
|
{
|
||
|
access::get_impl((*response_)[current_stage_index_]).set_error(ec, std::move(diag));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void on_stage_finished(const connection_state_data& st, error_code stage_ec)
|
||
|
{
|
||
|
if (stage_ec)
|
||
|
{
|
||
|
// If the error was fatal, fail successive stages.
|
||
|
// This error is the result of the operation
|
||
|
if (is_fatal_error(stage_ec))
|
||
|
{
|
||
|
pipeline_ec_ = stage_ec;
|
||
|
*diag_ = temp_diag_;
|
||
|
has_hatal_error_ = true;
|
||
|
}
|
||
|
else if (!pipeline_ec_)
|
||
|
{
|
||
|
// In the absence of fatal errors, the first error we encounter is the result of the operation
|
||
|
pipeline_ec_ = stage_ec;
|
||
|
*diag_ = temp_diag_;
|
||
|
}
|
||
|
|
||
|
// Propagate the error
|
||
|
if (response_ != nullptr)
|
||
|
{
|
||
|
set_stage_error(stage_ec, std::move(temp_diag_));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (stages_[current_stage_index_].kind == pipeline_stage_kind::prepare_statement)
|
||
|
{
|
||
|
// Propagate results. We don't support prepare statements ignoring the response
|
||
|
BOOST_ASSERT(response_ != nullptr);
|
||
|
access::get_impl((*response_)[current_stage_index_])
|
||
|
.set_result(read_response_algo_.prepare_statement.result(st));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
next_action resume_read_algo(connection_state_data& st, error_code ec)
|
||
|
{
|
||
|
switch (stages_[current_stage_index_].kind)
|
||
|
{
|
||
|
case pipeline_stage_kind::execute: return read_response_algo_.execute.resume(st, ec);
|
||
|
case pipeline_stage_kind::prepare_statement:
|
||
|
return read_response_algo_.prepare_statement.resume(st, ec);
|
||
|
case pipeline_stage_kind::reset_connection:
|
||
|
return read_response_algo_.reset_connection.resume(st, ec);
|
||
|
case pipeline_stage_kind::set_character_set:
|
||
|
return read_response_algo_.set_character_set.resume(st, ec);
|
||
|
case pipeline_stage_kind::ping: return read_response_algo_.ping.resume(st, ec);
|
||
|
case pipeline_stage_kind::close_statement: return next_action(); // has no response
|
||
|
default: BOOST_ASSERT(false); return next_action();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
run_pipeline_algo(run_pipeline_algo_params params) noexcept
|
||
|
: diag_(params.diag),
|
||
|
request_buffer_(params.request_buffer),
|
||
|
stages_(params.request_stages),
|
||
|
response_(params.response)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
next_action resume(connection_state_data& st, error_code ec)
|
||
|
{
|
||
|
next_action act;
|
||
|
|
||
|
switch (resume_point_)
|
||
|
{
|
||
|
case 0:
|
||
|
|
||
|
// Clear previous state
|
||
|
diag_->clear();
|
||
|
setup_response();
|
||
|
|
||
|
// If the request is empty, don't do anything
|
||
|
if (stages_.empty())
|
||
|
break;
|
||
|
|
||
|
// Write the request. use_ssl is attached by top_level_algo
|
||
|
BOOST_MYSQL_YIELD(resume_point_, 1, next_action::write({request_buffer_, false}))
|
||
|
|
||
|
// If writing the request failed, fail all the stages with the given error code
|
||
|
if (ec)
|
||
|
{
|
||
|
pipeline_ec_ = ec;
|
||
|
has_hatal_error_ = true;
|
||
|
}
|
||
|
|
||
|
// For each stage
|
||
|
for (; current_stage_index_ < stages_.size(); ++current_stage_index_)
|
||
|
{
|
||
|
// If there was a fatal error, just set the error and move forward
|
||
|
if (has_hatal_error_)
|
||
|
{
|
||
|
set_stage_error(pipeline_ec_, diagnostics(*diag_));
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Setup the stage
|
||
|
setup_current_stage(st);
|
||
|
|
||
|
// Run it until completion
|
||
|
ec.clear();
|
||
|
while (!(act = resume_read_algo(st, ec)).is_done())
|
||
|
BOOST_MYSQL_YIELD(resume_point_, 2, act)
|
||
|
|
||
|
// Process the stage's result
|
||
|
on_stage_finished(st, act.error());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return pipeline_ec_;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
} // namespace detail
|
||
|
} // namespace mysql
|
||
|
} // namespace boost
|
||
|
|
||
|
#endif
|