Files
MaxScale/server/modules/routing/readwritesplit/rwsplit_mysql.cc
Markus Mäkelä ecb56ef540 Use references instead of copies of SRWBackend
As the DCB passed as the clientReply parameter is guaranteed to match one
of the DCBs in the RWBackends. By using a reference, the need to copy a
shared_ptr is removed (along with the atomic operation that it implies)
thus reducing the overhead in the clientReply and the functions it uses.
2017-10-12 12:29:43 +03:00

337 lines
11 KiB
C++

/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2020-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include "readwritesplit.hh"
#include "rwsplit_internal.hh"
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <maxscale/router.h>
#include <maxscale/log_manager.h>
#include <maxscale/query_classifier.h>
#include <maxscale/dcb.h>
#include <maxscale/spinlock.h>
#include <maxscale/modinfo.h>
#include <maxscale/modutil.h>
#include <maxscale/protocol/mysql.h>
#include <maxscale/alloc.h>
#define RWSPLIT_TRACE_MSG_LEN 1000
/**
* Functions within the read-write split router that are specific to
* MySQL. The aim is to either remove these into a separate module or to
* move them into the MySQL protocol modules.
*/
/*
* The following functions are called from elsewhere in the router and
* are defined in rwsplit_internal.hh. They are not intended to be called
* from outside this router.
*/
/* This could be placed in the protocol, with a new API entry point
* It is certainly MySQL specific. Packet types are DB specific, but can be
* assumed to be enums, which can be handled as integers without knowing
* which DB is involved until the packet type needs to be interpreted.
*
*/
/**
* @brief Determine the type of a query
*
* @param querybuf GWBUF containing the query
* @param packet_type Integer denoting DB specific enum
* @param non_empty_packet Boolean to be set by this function
*
* @return uint32_t the query type; also the non_empty_packet bool is set
*/
uint32_t determine_query_type(GWBUF *querybuf, int command)
{
uint32_t type = QUERY_TYPE_UNKNOWN;
switch (command)
{
case MXS_COM_QUIT: /*< 1 QUIT will close all sessions */
case MXS_COM_INIT_DB: /*< 2 DDL must go to the master */
case MXS_COM_REFRESH: /*< 7 - I guess this is session but not sure */
case MXS_COM_DEBUG: /*< 0d all servers dump debug info to stdout */
case MXS_COM_PING: /*< 0e all servers are pinged */
case MXS_COM_CHANGE_USER: /*< 11 all servers change it accordingly */
case MXS_COM_SET_OPTION: /*< 1b send options to all servers */
type = QUERY_TYPE_SESSION_WRITE;
break;
case MXS_COM_CREATE_DB: /**< 5 DDL must go to the master */
case MXS_COM_DROP_DB: /**< 6 DDL must go to the master */
case MXS_COM_STMT_CLOSE: /*< free prepared statement */
case MXS_COM_STMT_SEND_LONG_DATA: /*< send data to column */
case MXS_COM_STMT_RESET: /*< resets the data of a prepared statement */
type = QUERY_TYPE_WRITE;
break;
case MXS_COM_QUERY:
type = qc_get_type_mask(querybuf);
break;
case MXS_COM_STMT_PREPARE:
type = qc_get_type_mask(querybuf);
type |= QUERY_TYPE_PREPARE_STMT;
break;
case MXS_COM_STMT_EXECUTE:
/** Parsing is not needed for this type of packet */
type = QUERY_TYPE_EXEC_STMT;
break;
case MXS_COM_SHUTDOWN: /**< 8 where should shutdown be routed ? */
case MXS_COM_STATISTICS: /**< 9 ? */
case MXS_COM_PROCESS_INFO: /**< 0a ? */
case MXS_COM_CONNECT: /**< 0b ? */
case MXS_COM_PROCESS_KILL: /**< 0c ? */
case MXS_COM_TIME: /**< 0f should this be run in gateway ? */
case MXS_COM_DELAYED_INSERT: /**< 10 ? */
case MXS_COM_DAEMON: /**< 1d ? */
default:
break;
}
return type;
}
/*
* This appears to be MySQL specific
*/
/**
* @brief Determine if a packet contains a SQL query
*
* Packet type tells us this, but in a DB specific way. This function is
* provided so that code that is not DB specific can find out whether a packet
* contains a SQL query. Clearly, to be effective different functions must be
* called for different DB types.
*
* @param packet_type Type of packet (integer)
* @return bool indicating whether packet contains a SQL query
*/
bool
is_packet_a_query(int packet_type)
{
return (packet_type == MXS_COM_QUERY);
}
/*
* This one is problematic because it is MySQL specific, but also router
* specific.
*/
/**
* @brief Log the transaction status
*
* The router session and the query buffer are used to log the transaction
* status, along with the query type (which is a generic description that
* should be usable across all DB types).
*
* @param rses Router session
* @param querybuf Query buffer
* @param qtype Query type
*/
void
log_transaction_status(RWSplitSession *rses, GWBUF *querybuf, uint32_t qtype)
{
if (rses->large_query)
{
MXS_INFO("> Processing large request with more than 2^24 bytes of data");
}
else if (rses->load_data_state == LOAD_DATA_INACTIVE)
{
uint8_t *packet = GWBUF_DATA(querybuf);
unsigned char command = packet[4];
int len = 0;
char* sql;
modutil_extract_SQL(querybuf, &sql, &len);
char *qtypestr = qc_typemask_to_string(qtype);
if (len > RWSPLIT_TRACE_MSG_LEN)
{
len = RWSPLIT_TRACE_MSG_LEN;
}
MXS_SESSION *ses = rses->client_dcb->session;
const char *autocommit = session_is_autocommit(ses) ? "[enabled]" : "[disabled]";
const char *transaction = session_trx_is_active(ses) ? "[open]" : "[not open]";
const char *querytype = qtypestr == NULL ? "N/A" : qtypestr;
const char *hint = querybuf->hint == NULL ? "" : ", Hint:";
const char *hint_type = querybuf->hint == NULL ? "" : STRHINTTYPE(querybuf->hint->type);
MXS_INFO("> Autocommit: %s, trx is %s, cmd: (0x%02x) %s, type: %s, stmt: %.*s%s %s",
autocommit, transaction, command, STRPACKETTYPE(command),
querytype, len, sql, hint, hint_type);
MXS_FREE(qtypestr);
}
else
{
MXS_INFO("> Processing LOAD DATA LOCAL INFILE: %lu bytes sent.",
rses->rses_load_data_sent);
}
}
/*
* This is mostly router code, but it contains MySQL specific operations that
* maybe could be moved to the protocol module. The modutil functions are mostly
* MySQL specific and could migrate to the MySQL protocol; likewise the
* utility to convert packet type to a string. The aim is for most of this
* code to remain as part of the router.
*/
/**
* @brief Operations to be carried out if request is for all backend servers
*
* If the choice of sending to all backends is in conflict with other bit
* settings in route_target, then error messages are written to the log.
*
* Otherwise, the function route_session_write is called to carry out the
* actual routing.
*
* @param route_target Bit map indicating where packet should be routed
* @param inst Router instance
* @param rses Router session
* @param querybuf Query buffer containing packet
* @param packet_type Integer (enum) indicating type of packet
* @param qtype Query type
* @return bool indicating whether the session can continue
*/
bool handle_target_is_all(route_target_t route_target, RWSplit *inst,
RWSplitSession *rses, GWBUF *querybuf,
int packet_type, uint32_t qtype)
{
bool result = false;
if (TARGET_IS_MASTER(route_target) || TARGET_IS_SLAVE(route_target))
{
/**
* Conflicting routing targets. Return an error to the client.
*/
char *query_str = modutil_get_query(querybuf);
char *qtype_str = qc_typemask_to_string(qtype);
MXS_ERROR("Can't route %s:%s:\"%s\". SELECT with session data "
"modification is not supported if configuration parameter "
"use_sql_variables_in=all .", STRPACKETTYPE(packet_type),
qtype_str, (query_str == NULL ? "(empty)" : query_str));
GWBUF *errbuf = modutil_create_mysql_err_msg(1, 0, 1064, "42000",
"Routing query to backend failed. "
"See the error log for further details.");
if (errbuf)
{
rses->client_dcb->func.write(rses->client_dcb, errbuf);
result = true;
}
MXS_FREE(query_str);
MXS_FREE(qtype_str);
}
else if (route_session_write(rses, gwbuf_clone(querybuf), packet_type, qtype))
{
result = true;
atomic_add_uint64(&inst->stats().n_all, 1);
}
return result;
}
/*
* Probably MySQL specific because of modutil function
*/
/**
* @brief Write an error message to the log for closed session
*
* This happens if a request is received for a session that is already
* closing down.
*
* @param querybuf Query buffer containing packet
*/
void closed_session_reply(GWBUF *querybuf)
{
uint8_t* data = GWBUF_DATA(querybuf);
if (GWBUF_LENGTH(querybuf) >= 5 && !MYSQL_IS_COM_QUIT(data))
{
/* Note that most modutil functions are MySQL specific */
char *query_str = modutil_get_query(querybuf);
MXS_ERROR("Can't route %s:\"%s\" to backend server. Router is closed.",
STRPACKETTYPE(data[4]), query_str ? query_str : "(empty)");
MXS_FREE(query_str);
}
}
/*
* Uses MySQL specific mechanisms
*/
/**
* @brief Check the reply from a backend server to a session command
*
* If the reply is an error, a message is logged.
*
* @param buffer Query buffer containing reply data
* @param backend Router session data for a backend server
*/
void check_session_command_reply(GWBUF *buffer, SRWBackend& backend)
{
if (MYSQL_IS_ERROR_PACKET(((uint8_t *)GWBUF_DATA(buffer))))
{
size_t replylen = MYSQL_GET_PAYLOAD_LEN(GWBUF_DATA(buffer));
char replybuf[replylen];
gwbuf_copy_data(buffer, 0, gwbuf_length(buffer), (uint8_t*)replybuf);
std::string err;
std::string msg;
err.append(replybuf + 8, 5);
msg.append(replybuf + 13, replylen - 4 - 5);
MXS_ERROR("Failed to execute session command in %s. Error was: %s %s",
backend->uri(), err.c_str(), msg.c_str());
}
}
/**
* @brief Send an error message to the client telling that the server is in read only mode
*
* @param dcb Client DCB
*
* @return True if sending the message was successful, false if an error occurred
*/
bool send_readonly_error(DCB *dcb)
{
bool succp = false;
const char* errmsg = "The MariaDB server is running with the --read-only"
" option so it cannot execute this statement";
GWBUF* err = modutil_create_mysql_err_msg(1, 0, ER_OPTION_PREVENTS_STATEMENT,
"HY000", errmsg);
if (err)
{
succp = dcb->func.write(dcb, err);
}
else
{
MXS_ERROR("Memory allocation failed when creating client error message.");
}
return succp;
}