diff --git a/include/maxscale/queryclassifier.hh b/include/maxscale/queryclassifier.hh index b499879c3..712d0bd69 100644 --- a/include/maxscale/queryclassifier.hh +++ b/include/maxscale/queryclassifier.hh @@ -217,6 +217,12 @@ public: uint8_t packet_type, uint32_t *qtype); + uint32_t get_target_type(QueryClassifier::current_target_t current_target, + GWBUF *buffer, + uint8_t* command, + uint32_t* type, + uint32_t* stmt_id); + private: class PSManager; typedef std::shared_ptr SPSManager; diff --git a/server/core/queryclassifier.cc b/server/core/queryclassifier.cc index 07c96886e..7198df727 100644 --- a/server/core/queryclassifier.cc +++ b/server/core/queryclassifier.cc @@ -34,6 +34,16 @@ const char* qc_mysql_get_current_db(MXS_SESSION* session) return data->db; } +// Copied from mysql_common.c +bool qc_mysql_is_ps_command(uint8_t cmd) +{ + return cmd == MXS_COM_STMT_EXECUTE || + cmd == MXS_COM_STMT_SEND_LONG_DATA || + cmd == MXS_COM_STMT_CLOSE || + cmd == MXS_COM_STMT_FETCH || + cmd == MXS_COM_STMT_RESET; +} + bool have_semicolon(const char* ptr, int len) { for (int i = 0; i < len; i++) @@ -814,6 +824,114 @@ QueryClassifier::handle_multi_temp_and_load(QueryClassifier::current_target_t cu return rv; } +uint32_t QueryClassifier::get_target_type(QueryClassifier::current_target_t current_target, + GWBUF *buffer, + uint8_t* command, + uint32_t* type, + uint32_t* stmt_id) +{ + uint32_t route_target = TARGET_MASTER; + // TODO: It may be sufficient to simply check whether we are in a read-only + // TODO: transaction. + bool in_read_only_trx = + (current_target != QueryClassifier::CURRENT_TARGET_UNDEFINED) && + session_trx_is_read_only(session()); + + if (gwbuf_length(buffer) > MYSQL_HEADER_LEN) + { + *command = mxs_mysql_get_command(buffer); + + /** + * If the session is inside a read-only transaction, we trust that the + * server acts properly even when non-read-only queries are executed. + * For this reason, we can skip the parsing of the statement completely. + */ + if (in_read_only_trx) + { + *type = QUERY_TYPE_READ; + } + else + { + *type = QueryClassifier::determine_query_type(buffer, *command); + + current_target = handle_multi_temp_and_load(current_target, + buffer, *command, type); + + if (current_target == QueryClassifier::CURRENT_TARGET_MASTER) + { + /* If we do not have a master node, assigning the forced node is not + * effective since we don't have a node to force queries to. In this + * situation, assigning QUERY_TYPE_WRITE for the query will trigger + * the error processing. */ + if (!handler()->lock_to_master()) + { + *type |= QUERY_TYPE_WRITE; + } + } + } + + if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) + { + log_transaction_status(buffer, *type); + } + /** + * Find out where to route the query. Result may not be clear; it is + * possible to have a hint for routing to a named server which can + * be either slave or master. + * If query would otherwise be routed to slave then the hint determines + * actual target server if it exists. + * + * route_target is a bitfield and may include : + * TARGET_ALL + * - route to all connected backend servers + * TARGET_SLAVE[|TARGET_NAMED_SERVER|TARGET_RLAG_MAX] + * - route primarily according to hints, then to slave and if those + * failed, eventually to master + * TARGET_MASTER[|TARGET_NAMED_SERVER|TARGET_RLAG_MAX] + * - route primarily according to the hints and if they failed, + * eventually to master + */ + + if (handler()->is_locked_to_master()) + { + /** The session is locked to the master */ + route_target = TARGET_MASTER; + + if (qc_query_is_type(*type, QUERY_TYPE_PREPARE_NAMED_STMT) || + qc_query_is_type(*type, QUERY_TYPE_PREPARE_STMT)) + { + gwbuf_set_type(buffer, GWBUF_TYPE_COLLECT_RESULT); + } + } + else + { + if (!in_read_only_trx && + *command == MXS_COM_QUERY && + qc_get_operation(buffer) == QUERY_OP_EXECUTE) + { + std::string id = get_text_ps_id(buffer); + *type = ps_get_type(id); + } + else if (qc_mysql_is_ps_command(*command)) + { + *stmt_id = ps_id_internal_get(buffer); + *type = ps_get_type(*stmt_id); + } + + route_target = get_route_target(*command, *type, buffer->hint); + } + } + else + { + /** Empty packet signals end of LOAD DATA LOCAL INFILE, send it to master*/ + set_load_data_state(QueryClassifier::LOAD_DATA_END); + append_load_data_sent(buffer); + MXS_INFO("> LOAD DATA LOCAL INFILE finished: %lu bytes sent.", + load_data_sent()); + } + + return route_target; } +} diff --git a/server/modules/routing/readwritesplit/routeinfo.cc b/server/modules/routing/readwritesplit/routeinfo.cc index 9833010d0..d5cf1f0d8 100644 --- a/server/modules/routing/readwritesplit/routeinfo.cc +++ b/server/modules/routing/readwritesplit/routeinfo.cc @@ -12,141 +12,10 @@ */ #include "routeinfo.hh" -#include #include #include "rwsplitsession.hh" -#define RWSPLIT_TRACE_MSG_LEN 1000 - -using mxs::QueryClassifier; - -namespace -{ - -/** - * @brief Get the routing requirements for a query - * - * @param qc The query classifier. - * @param current_target - * @param buffer Buffer containing the query - * @param command Output parameter where the packet command is stored - * @param type Output parameter where the query type is stored - * @param stmt_id Output parameter where statement ID, if the query is a binary protocol command, is stored - * - * @return The target type where this query should be routed - */ -route_target_t get_target_type(QueryClassifier& qc, - QueryClassifier::current_target_t current_target, - GWBUF *buffer, - uint8_t* command, - uint32_t* type, - uint32_t* stmt_id) -{ - route_target_t route_target = TARGET_MASTER; - - // TODO: It may be sufficient to simply check whether we are in a read-only - // TODO: transaction. - bool in_read_only_trx = - (current_target != QueryClassifier::CURRENT_TARGET_UNDEFINED) && - session_trx_is_read_only(qc.session()); - - if (gwbuf_length(buffer) > MYSQL_HEADER_LEN) - { - *command = mxs_mysql_get_command(buffer); - - /** - * If the session is inside a read-only transaction, we trust that the - * server acts properly even when non-read-only queries are executed. - * For this reason, we can skip the parsing of the statement completely. - */ - if (in_read_only_trx) - { - *type = QUERY_TYPE_READ; - } - else - { - *type = QueryClassifier::determine_query_type(buffer, *command); - - current_target = qc.handle_multi_temp_and_load(current_target, - buffer, *command, type); - - if (current_target == QueryClassifier::CURRENT_TARGET_MASTER) - { - /* If we do not have a master node, assigning the forced node is not - * effective since we don't have a node to force queries to. In this - * situation, assigning QUERY_TYPE_WRITE for the query will trigger - * the error processing. */ - if (!qc.handler()->lock_to_master()) - { - *type |= QUERY_TYPE_WRITE; - } - } - } - - if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) - { - qc.log_transaction_status(buffer, *type); - } - /** - * Find out where to route the query. Result may not be clear; it is - * possible to have a hint for routing to a named server which can - * be either slave or master. - * If query would otherwise be routed to slave then the hint determines - * actual target server if it exists. - * - * route_target is a bitfield and may include : - * TARGET_ALL - * - route to all connected backend servers - * TARGET_SLAVE[|TARGET_NAMED_SERVER|TARGET_RLAG_MAX] - * - route primarily according to hints, then to slave and if those - * failed, eventually to master - * TARGET_MASTER[|TARGET_NAMED_SERVER|TARGET_RLAG_MAX] - * - route primarily according to the hints and if they failed, - * eventually to master - */ - - if (qc.handler()->is_locked_to_master()) - { - /** The session is locked to the master */ - route_target = TARGET_MASTER; - - if (qc_query_is_type(*type, QUERY_TYPE_PREPARE_NAMED_STMT) || - qc_query_is_type(*type, QUERY_TYPE_PREPARE_STMT)) - { - gwbuf_set_type(buffer, GWBUF_TYPE_COLLECT_RESULT); - } - } - else - { - if (!in_read_only_trx && - *command == MXS_COM_QUERY && - qc_get_operation(buffer) == QUERY_OP_EXECUTE) - { - std::string id = get_text_ps_id(buffer); - *type = qc.ps_get_type(id); - } - else if (mxs_mysql_is_ps_command(*command)) - { - *stmt_id = qc.ps_id_internal_get(buffer); - *type = qc.ps_get_type(*stmt_id); - } - - route_target = static_cast(qc.get_route_target(*command, *type, buffer->hint)); - } - } - else - { - /** Empty packet signals end of LOAD DATA LOCAL INFILE, send it to master*/ - qc.set_load_data_state(QueryClassifier::LOAD_DATA_END); - qc.append_load_data_sent(buffer); - MXS_INFO("> LOAD DATA LOCAL INFILE finished: %lu bytes sent.", - qc.load_data_sent()); - } - - return route_target; -} - -} +using namespace maxscale; RouteInfo::RouteInfo(RWSplitSession* rses, GWBUF* buffer) : target(TARGET_UNDEFINED) @@ -174,5 +43,9 @@ RouteInfo::RouteInfo(RWSplitSession* rses, GWBUF* buffer) current_target = QueryClassifier::CURRENT_TARGET_SLAVE; } - target = get_target_type(rses->qc(), current_target, buffer, &command, &type, &stmt_id); + target = static_cast(rses->qc().get_target_type(current_target, + buffer, + &command, + &type, + &stmt_id)); }