diff --git a/include/maxscale/queryclassifier.hh b/include/maxscale/queryclassifier.hh new file mode 100644 index 000000000..ae04a5fa2 --- /dev/null +++ b/include/maxscale/queryclassifier.hh @@ -0,0 +1,51 @@ +#pragma once +/* + * 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 +#include +#include + +namespace maxscale +{ + +class QueryClassifier +{ + QueryClassifier(const QueryClassifier&) = delete; + QueryClassifier& operator = (const QueryClassifier&) = delete; + +public: + // NOTE: For the time being these must be exactly like the ones in readwritesplit.hh + enum + { + TARGET_UNDEFINED = 0x00, + TARGET_MASTER = 0x01, + TARGET_SLAVE = 0x02, + TARGET_ALL = 0x08 + }; + + QueryClassifier(MXS_SESSION* pSession, + mxs_target_t use_sql_variables_in); + + void set_load_active(bool active); + bool load_active() const; + + uint32_t get_route_target(uint8_t command, uint32_t qtype); + +private: + MXS_SESSION* m_pSession; + mxs_target_t m_use_sql_variables_in; + bool m_load_active; +}; + +} diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index 503adf401..9ff490016 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -32,6 +32,7 @@ add_library(maxscale-common SHARED mysql_utils.cc paths.cc poll.cc + queryclassifier.cc query_classifier.cc random_jkiss.cc resultset.cc diff --git a/server/core/queryclassifier.cc b/server/core/queryclassifier.cc new file mode 100644 index 000000000..c26865efc --- /dev/null +++ b/server/core/queryclassifier.cc @@ -0,0 +1,168 @@ +/* + * 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 +#include +#include + +namespace maxscale +{ + +QueryClassifier::QueryClassifier(MXS_SESSION* pSession, + mxs_target_t use_sql_variables_in) + : m_pSession(pSession) + , m_use_sql_variables_in(use_sql_variables_in) + , m_load_active(false) +{ +} + +void QueryClassifier::set_load_active(bool active) +{ + m_load_active = active; +} + +bool QueryClassifier::load_active() const +{ + return m_load_active; +} + +uint32_t QueryClassifier::get_route_target(uint8_t command, uint32_t qtype) +{ + bool trx_active = session_trx_is_active(m_pSession); + uint32_t target = TARGET_UNDEFINED; + + /** + * Prepared statements preparations should go to all servers + */ + if (qc_query_is_type(qtype, QUERY_TYPE_PREPARE_STMT) || + qc_query_is_type(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) || + command == MXS_COM_STMT_CLOSE || + command == MXS_COM_STMT_RESET) + { + target = TARGET_ALL; + } + /** + * These queries should be routed to all servers + */ + else if (!m_load_active && + (qc_query_is_type(qtype, QUERY_TYPE_SESSION_WRITE) || + /** Configured to allow writing user variables to all nodes */ + (m_use_sql_variables_in == TYPE_ALL && + qc_query_is_type(qtype, QUERY_TYPE_USERVAR_WRITE)) || + qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_WRITE) || + /** enable or disable autocommit are always routed to all */ + qc_query_is_type(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) || + qc_query_is_type(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT))) + { + /** + * This is problematic query because it would be routed to all + * backends but since this is SELECT that is not possible: + * 1. response set is not handled correctly in clientReply and + * 2. multiple results can degrade performance. + * + * Prepared statements are an exception to this since they do not + * actually do anything but only prepare the statement to be used. + * They can be safely routed to all backends since the execution + * is done later. + * + * With prepared statement caching the task of routing + * the execution of the prepared statements to the right server would be + * an easy one. Currently this is not supported. + */ + if (qc_query_is_type(qtype, QUERY_TYPE_READ)) + { + MXS_WARNING("The query can't be routed to all " + "backend servers because it includes SELECT and " + "SQL variable modifications which is not supported. " + "Set use_sql_variables_in=master or split the " + "query to two, where SQL variable modifications " + "are done in the first and the SELECT in the " + "second one."); + + target = TARGET_MASTER; + } + target |= TARGET_ALL; + } + /** + * Hints may affect on routing of the following queries + */ + else if (!trx_active && !m_load_active && + !qc_query_is_type(qtype, QUERY_TYPE_MASTER_READ) && + !qc_query_is_type(qtype, QUERY_TYPE_WRITE) && + (qc_query_is_type(qtype, QUERY_TYPE_READ) || + qc_query_is_type(qtype, QUERY_TYPE_SHOW_TABLES) || + qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ) || + qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) || + qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_READ))) + { + if (qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ)) + { + if (m_use_sql_variables_in == TYPE_ALL) + { + target = TARGET_SLAVE; + } + } + else if (qc_query_is_type(qtype, QUERY_TYPE_READ) || // Normal read + qc_query_is_type(qtype, QUERY_TYPE_SHOW_TABLES) || // SHOW TABLES + qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) || // System variable + qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_READ)) // Global system variable + { + target = TARGET_SLAVE; + } + + /** If nothing matches then choose the master */ + if ((target & (TARGET_ALL | TARGET_SLAVE | TARGET_MASTER)) == 0) + { + target = TARGET_MASTER; + } + } + else if (session_trx_is_read_only(m_pSession)) + { + /* Force TARGET_SLAVE for READ ONLY transaction (active or ending) */ + target = TARGET_SLAVE; + } + else + { + ss_dassert(trx_active || m_load_active || + (qc_query_is_type(qtype, QUERY_TYPE_WRITE) || + qc_query_is_type(qtype, QUERY_TYPE_MASTER_READ) || + qc_query_is_type(qtype, QUERY_TYPE_SESSION_WRITE) || + (qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ) && + m_use_sql_variables_in == TYPE_MASTER) || + (qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) && + m_use_sql_variables_in == TYPE_MASTER) || + (qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_READ) && + m_use_sql_variables_in == TYPE_MASTER) || + (qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_WRITE) && + m_use_sql_variables_in == TYPE_MASTER) || + (qc_query_is_type(qtype, QUERY_TYPE_USERVAR_WRITE) && + m_use_sql_variables_in == TYPE_MASTER) || + qc_query_is_type(qtype, QUERY_TYPE_BEGIN_TRX) || + qc_query_is_type(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) || + qc_query_is_type(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT) || + qc_query_is_type(qtype, QUERY_TYPE_ROLLBACK) || + qc_query_is_type(qtype, QUERY_TYPE_COMMIT) || + qc_query_is_type(qtype, QUERY_TYPE_EXEC_STMT) || + qc_query_is_type(qtype, QUERY_TYPE_CREATE_TMP_TABLE) || + qc_query_is_type(qtype, QUERY_TYPE_READ_TMP_TABLE) || + qc_query_is_type(qtype, QUERY_TYPE_UNKNOWN)) || + qc_query_is_type(qtype, QUERY_TYPE_EXEC_STMT)); + + target = TARGET_MASTER; + } + + return target; +} + +} +