Files
MaxScale/server/modules/routing/readwritesplit/rwbackend.cc
Markus Mäkelä 91cc5b1e89 MXS-1828: Simplify LOAD DATA LOCAL INFILE handling
By relying on the server to tell us that it is requesting the loading of a
local infile, we can remove one state from the state machine that governs
the loading of local files. It also removes the need to handle error and
success cases separately.

A side-effect of this change is that execution of multi-statement LOAD
DATA LOCAL INFILE no longer hangs. This is done by checking whether the
completion of one command initiates a new load.

The current code recursively checks the reply state and clones the
buffers. Neither of these are required nor should they be done but
refactoring the code is to be done in a separate commit.

Added two helper functions that are used to detect requests for local
infiles and to extract the total packet length from a non-contiguous
GWBUF.
2018-05-18 09:46:07 +03:00

249 lines
6.9 KiB
C++

#include "rwbackend.hh"
#include <maxscale/modutil.h>
#include <maxscale/protocol/mysql.h>
#include <maxscale/log_manager.h>
namespace maxscale
{
RWBackend::RWBackend(SERVER_REF* ref):
mxs::Backend(ref),
m_reply_state(REPLY_STATE_DONE),
m_modutil_state({}),
m_command(0),
m_opening_cursor(false),
m_expected_rows(0),
m_local_infile_requested(false)
{
}
RWBackend::~RWBackend()
{
}
bool RWBackend::execute_session_command()
{
m_command = next_session_command()->get_command();
bool expect_response = mxs_mysql_command_will_respond(m_command);
bool rval = mxs::Backend::execute_session_command();
if (rval && expect_response)
{
set_reply_state(REPLY_STATE_START);
}
return rval;
}
bool RWBackend::continue_session_command(GWBUF* buffer)
{
return Backend::write(buffer, NO_RESPONSE);
}
void RWBackend::add_ps_handle(uint32_t id, uint32_t handle)
{
m_ps_handles[id] = handle;
MXS_INFO("PS response for %s: %u -> %u", name(), id, handle);
}
uint32_t RWBackend::get_ps_handle(uint32_t id) const
{
BackendHandleMap::const_iterator it = m_ps_handles.find(id);
if (it != m_ps_handles.end())
{
return it->second;
}
return 0;
}
bool RWBackend::write(GWBUF* buffer, response_type type)
{
uint8_t cmd = mxs_mysql_get_command(buffer);
m_command = cmd;
if (mxs_mysql_is_ps_command(cmd))
{
uint32_t id = mxs_mysql_extract_ps_id(buffer);
BackendHandleMap::iterator it = m_ps_handles.find(id);
if (it != m_ps_handles.end())
{
/** Replace the client handle with the real PS handle */
uint8_t* ptr = GWBUF_DATA(buffer) + MYSQL_PS_ID_OFFSET;
gw_mysql_set_byte4(ptr, it->second);
if (cmd == MXS_COM_STMT_EXECUTE)
{
// Extract the flag byte after the statement ID
uint8_t flags = 0;
gwbuf_copy_data(buffer, MYSQL_PS_ID_OFFSET + MYSQL_PS_ID_SIZE, 1, &flags);
// Any non-zero flag value means that we have an open cursor
m_opening_cursor = flags != 0;
}
else if (cmd == MXS_COM_STMT_FETCH)
{
// Number of rows to fetch is a 4 byte integer after the ID
uint8_t buf[4];
gwbuf_copy_data(buffer, MYSQL_PS_ID_OFFSET + MYSQL_PS_ID_SIZE, 4, buf);
m_expected_rows = gw_mysql_get_byte4(buf);
}
}
}
return mxs::Backend::write(buffer);
}
void RWBackend::close(close_type type)
{
m_reply_state = REPLY_STATE_DONE;
mxs::Backend::close(type);
}
bool RWBackend::consume_fetched_rows(GWBUF* buffer)
{
m_expected_rows -= modutil_count_packets(buffer);
ss_dassert(m_expected_rows >= 0);
return m_expected_rows == 0;
}
static inline bool have_next_packet(GWBUF* buffer)
{
uint32_t len = MYSQL_GET_PAYLOAD_LEN(GWBUF_DATA(buffer)) + MYSQL_HEADER_LEN;
return gwbuf_length(buffer) > len;
}
/**
* @brief Check if we have received a complete reply from the backend
*
* @param backend Backend reference
* @param buffer Buffer containing the response
*
* @return True if the complete response has been received
*/
bool RWBackend::reply_is_complete(GWBUF *buffer)
{
if (current_command() == MXS_COM_STMT_FETCH)
{
bool more = false;
int n_eof = modutil_count_signal_packets(buffer, 0, &more, &m_modutil_state);
// If the server responded with an error, n_eof > 0
if (n_eof > 0 || consume_fetched_rows(buffer))
{
set_reply_state(REPLY_STATE_DONE);
}
}
else if (current_command() == MXS_COM_STATISTICS)
{
// COM_STATISTICS returns a single string and thus requires special handling
set_reply_state(REPLY_STATE_DONE);
}
else if (get_reply_state() == REPLY_STATE_START &&
(!mxs_mysql_is_result_set(buffer) || GWBUF_IS_COLLECTED_RESULT(buffer)))
{
m_local_infile_requested = false;
if (GWBUF_IS_COLLECTED_RESULT(buffer) ||
current_command() == MXS_COM_STMT_PREPARE ||
!mxs_mysql_is_ok_packet(buffer) ||
!mxs_mysql_more_results_after_ok(buffer))
{
/** Not a result set, we have the complete response */
set_reply_state(REPLY_STATE_DONE);
if (mxs_mysql_is_local_infile(buffer))
{
m_local_infile_requested = true;
}
}
else
{
// This is an OK packet and more results will follow
ss_dassert(mxs_mysql_is_ok_packet(buffer) &&
mxs_mysql_more_results_after_ok(buffer));
if (have_next_packet(buffer))
{
// TODO: Don't clone the buffer
GWBUF* tmp = gwbuf_clone(buffer);
tmp = gwbuf_consume(tmp, mxs_mysql_get_packet_len(tmp));
bool rval = reply_is_complete(tmp);
gwbuf_free(tmp);
return rval;
}
}
}
else
{
bool more = false;
int n_old_eof = get_reply_state() == REPLY_STATE_RSET_ROWS ? 1 : 0;
int n_eof = modutil_count_signal_packets(buffer, n_old_eof, &more, &m_modutil_state);
if (n_eof > 2)
{
/**
* We have multiple results in the buffer, we only care about
* the state of the last one. Skip the complete result sets and act
* like we're processing a single result set.
*/
n_eof = n_eof % 2 ? 1 : 2;
}
if (n_eof == 0)
{
/** Waiting for the EOF packet after the column definitions */
set_reply_state(REPLY_STATE_RSET_COLDEF);
}
else if (n_eof == 1 && current_command() != MXS_COM_FIELD_LIST)
{
/** Waiting for the EOF packet after the rows */
set_reply_state(REPLY_STATE_RSET_ROWS);
if (is_opening_cursor())
{
set_cursor_opened();
MXS_INFO("Cursor successfully opened");
set_reply_state(REPLY_STATE_DONE);
}
}
else
{
/** We either have a complete result set or a response to
* a COM_FIELD_LIST command */
ss_dassert(n_eof == 2 || (n_eof == 1 && current_command() == MXS_COM_FIELD_LIST));
set_reply_state(REPLY_STATE_DONE);
if (more)
{
/** The server will send more resultsets */
set_reply_state(REPLY_STATE_START);
}
}
}
return get_reply_state() == REPLY_STATE_DONE;
}
SRWBackendList RWBackend::from_servers(SERVER_REF* servers)
{
SRWBackendList backends;
for (SERVER_REF *ref = servers; ref; ref = ref->next)
{
if (ref->active)
{
backends.push_back(mxs::SRWBackend(new mxs::RWBackend(ref)));
}
}
return backends;
}
}