diff --git a/Documentation/Routers/ReadConnRoute.md b/Documentation/Routers/ReadConnRoute.md index 7ac74c3b3..c726d7469 100644 --- a/Documentation/Routers/ReadConnRoute.md +++ b/Documentation/Routers/ReadConnRoute.md @@ -1,20 +1,30 @@ # Readconnroute -This document provides an overview of the **readconnroute** router module and its intended use case scenarios. It also displays all router configuration parameters with their descriptions. +This document provides an overview of the **readconnroute** router module +and its intended use case scenarios. It also displays all router +configuration parameters with their descriptions. ## Overview -The readconnroute router provides simple and lightweight load balancing across a set of servers. The router can also be configured to balance connections based on a weighting parameter defined in the server's section. +The readconnroute router provides simple and lightweight load balancing +across a set of servers. The router can also be configured to balance +connections based on a weighting parameter defined in the server's section. ## Configuration -For more details about the standard service parameters, refer to the [Configuration Guide](../Getting-Started/Configuration-Guide.md). +For more details about the standard service parameters, refer to the +[Configuration Guide](../Getting-Started/Configuration-Guide.md). ### Router Options -**`router_options`** can contain a list of valid server roles. These roles are used as the valid types of servers the router will form connections to when new sessions are created. +**`router_options`** can contain a comma separated list of valid server +roles. These roles are used as the valid types of servers the router will +form connections to when new sessions are created. + +Examples: ``` - router_options=slave +router_options=slave +router_options=master,slave ``` Here is a list of all possible values for the `router_options`. @@ -26,22 +36,30 @@ synced| A Galera cluster node which is in a synced state with the cluster. ndb|A MySQL Replication Cluster node running|A server that is up and running. All servers that MariaDB MaxScale can connect to are labeled as running. -If no `router_options` parameter is configured in the service definition, the router will use the default value of `running`. This means that it will load balance connections across all running servers defined in the `servers` parameter of the service. +If no `router_options` parameter is configured in the service definition, +the router will use the default value of `running`. This means that it will +load balance connections across all running servers defined in the `servers` +parameter of the service. -When a connection is being created and the candidate server is being chosen, the -list of servers is processed in from first entry to last. This means that if two -servers with equal weight and status are found, the one that's listed first in -the _servers_ parameter for the service is chosen. +When a connection is being created and the candidate server is being chosen, +the list of servers is processed in from first entry to last. This means +that if two servers with equal weight and status are found, the one that's +listed first in the _servers_ parameter for the service is chosen. ## Limitations -For a list of readconnroute limitations, please read the [Limitations](../About/Limitations.md) document. +For a list of readconnroute limitations, please read the +[Limitations](../About/Limitations.md) document. ## Examples -The most common use for the readconnroute is to provide either a read or write port for an application. This provides a more lightweight routing solution than the more complex readwritesplit router but requires the application to be able to use distinct write and read ports. +The most common use for the readconnroute is to provide either a read or +write port for an application. This provides a more lightweight routing +solution than the more complex readwritesplit router but requires the +application to be able to use distinct write and read ports. -To configure a read-only service that tolerates master failures, we first need to add a new section in to the configuration file. +To configure a read-only service that tolerates master failures, we first +need to add a new section in to the configuration file. ``` [Read Service] @@ -51,6 +69,9 @@ servers=slave1,slave2,slave3 router_options=slave ``` -Here the `router_options` designates slaves as the only valid server type. With this configuration, the queries are load balanced across the slave servers. +Here the `router_options` designates slaves as the only valid server +type. With this configuration, the queries are load balanced across the +slave servers. -For more complex examples of the readconnroute router, take a look at the examples in the [Tutorials](../Tutorials) folder. +For more complex examples of the readconnroute router, take a look at the +examples in the [Tutorials](../Tutorials) folder. diff --git a/include/maxscale/query_classifier.h b/include/maxscale/query_classifier.h index 116e59a1b..5ae2b5731 100644 --- a/include/maxscale/query_classifier.h +++ b/include/maxscale/query_classifier.h @@ -84,7 +84,8 @@ typedef enum qc_query_type QUERY_TYPE_CREATE_TMP_TABLE = 0x080000, /*< Create temporary table:master (could be all) */ QUERY_TYPE_READ_TMP_TABLE = 0x100000, /*< Read temporary table:master (could be any) */ QUERY_TYPE_SHOW_DATABASES = 0x200000, /*< Show list of databases */ - QUERY_TYPE_SHOW_TABLES = 0x400000 /*< Show list of tables */ + QUERY_TYPE_SHOW_TABLES = 0x400000, /*< Show list of tables */ + QUERY_TYPE_DEALLOC_PREPARE = 0x1000000 /*< Dealloc named prepare stmt:all */ } qc_query_type_t; /** diff --git a/include/maxscale/queryclassifier.hh b/include/maxscale/queryclassifier.hh index 2716e66ab..10545dd96 100644 --- a/include/maxscale/queryclassifier.hh +++ b/include/maxscale/queryclassifier.hh @@ -217,6 +217,13 @@ public: */ void ps_store(GWBUF* buffer, uint32_t id); + /** + * @brief Remove a prepared statement + * + * @param buffer Buffer containing a DEALLOCATE statement or a binary protocol command + */ + void ps_erase(GWBUF* buffer); + /** * @brief Store a mapping from an external id to the corresponding internal id * @@ -297,14 +304,6 @@ private: uint32_t ps_get_type(uint32_t id) const; uint32_t ps_get_type(std::string id) const; - /** - * @brief Remove a prepared statement - * - * @param id Statement identifier to remove - */ - void ps_erase(std::string id); - void ps_erase(uint32_t id); - /** * @brief Get the internal ID for the given binary prepared statement * diff --git a/query_classifier/qc_sqlite/qc_sqlite.cc b/query_classifier/qc_sqlite/qc_sqlite.cc index a71e501e2..4ec882119 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.cc +++ b/query_classifier/qc_sqlite/qc_sqlite.cc @@ -2110,15 +2110,25 @@ public: update_names(zDatabase, table, NULL, NULL); } - void maxscaleComment() + int maxscaleComment() { - ss_dassert(this_thread.initialized); + // We are regularily parsing if the thread has been initialized. + // In that case # should be interpreted as the start of a comment, + // otherwise it should not. + int regular_parsing = false; - if (m_status == QC_QUERY_INVALID) + if (this_thread.initialized) { - m_status = QC_QUERY_PARSED; - m_type_mask = QUERY_TYPE_READ; + regular_parsing = true; + + if (m_status == QC_QUERY_INVALID) + { + m_status = QC_QUERY_PARSED; + m_type_mask = QUERY_TYPE_READ; + } } + + return regular_parsing; } void maxscaleDeclare(Parse* pParse) @@ -2136,7 +2146,7 @@ public: ss_dassert(this_thread.initialized); m_status = QC_QUERY_PARSED; - m_type_mask = QUERY_TYPE_WRITE; + m_type_mask = QUERY_TYPE_DEALLOC_PREPARE; // If information is collected in several passes, then we may // this information already. diff --git a/query_classifier/qc_sqlite/sqlite-src-3110100/src/tokenize.c b/query_classifier/qc_sqlite/sqlite-src-3110100/src/tokenize.c index fd875b64e..3284a2e2e 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/src/tokenize.c +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/src/tokenize.c @@ -198,6 +198,7 @@ int sqlite3IsIdChar(u8 c){ return IdChar(c); } ** Store the token type in *tokenType before returning. */ #ifdef MAXSCALE +extern int maxscaleComment(); int sqlite3GetToken(Parse* pParse, const unsigned char *z, int *tokenType){ #else int sqlite3GetToken(const unsigned char *z, int *tokenType){ @@ -219,8 +220,7 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){ case CC_MINUS: { if( z[1]=='-' ){ #ifdef MAXSCALE - extern void maxscaleComment(); - maxscaleComment(); + maxscaleComment(); #endif for(i=2; (c=z[i])!=0 && c!='\n'; i++){} *tokenType = TK_SPACE; /* IMP: R-22934-25134 */ @@ -466,6 +466,13 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){ testcase( z[0]=='$' ); testcase( z[0]=='@' ); testcase( z[0]==':' ); testcase( z[0]=='#' ); #ifdef MAXSCALE + if (z[0]=='#') { + if (maxscaleComment()) { + for(i=1; (c=z[i])!=0 && c!='\n'; i++){} + *tokenType = TK_SPACE; + return i; + } + } if (z[0]==':' && z[1]=='=') { *tokenType = TK_EQ; return 2; diff --git a/query_classifier/test/expected.sql b/query_classifier/test/expected.sql index f57402db2..b3ef5061f 100644 --- a/query_classifier/test/expected.sql +++ b/query_classifier/test/expected.sql @@ -23,3 +23,4 @@ QUERY_TYPE_READ|QUERY_TYPE_WRITE QUERY_TYPE_READ|QUERY_TYPE_WRITE QUERY_TYPE_READ|QUERY_TYPE_WRITE QUERY_TYPE_READ|QUERY_TYPE_WRITE +QUERY_TYPE_DEALLOC_PREPARE diff --git a/query_classifier/test/input.sql b/query_classifier/test/input.sql index 643e2c2d0..1ddd21a60 100644 --- a/query_classifier/test/input.sql +++ b/query_classifier/test/input.sql @@ -23,3 +23,4 @@ SELECT GET_LOCK('lock1',10); SELECT IS_FREE_LOCK('lock1'); SELECT IS_USED_LOCK('lock1'); SELECT RELEASE_LOCK('lock1'); +deallocate prepare select_stmt; diff --git a/query_classifier/test/testreader.cc b/query_classifier/test/testreader.cc index 7beffb30c..e53561f55 100644 --- a/query_classifier/test/testreader.cc +++ b/query_classifier/test/testreader.cc @@ -37,6 +37,7 @@ enum skip_action_t typedef std::map KeywordActionMapping; static KeywordActionMapping mtl_keywords; +static KeywordActionMapping plsql_keywords; void init_keywords() { @@ -46,7 +47,7 @@ void init_keywords() skip_action_t action; }; - static const Keyword KEYWORDS[] = + static const Keyword MTL_KEYWORDS[] = { { "append_file", SKIP_LINE }, { "cat_file", SKIP_LINE }, @@ -91,10 +92,8 @@ void init_keywords() { "error", SKIP_NEXT_STATEMENT }, { "eval", SKIP_STATEMENT }, { "exec", SKIP_LINE }, - { "exit", SKIP_LINE }, { "file_exists", SKIP_LINE }, { "horizontal_results", SKIP_LINE }, - { "if", SKIP_BLOCK }, { "inc", SKIP_LINE }, { "let", SKIP_LINE }, { "let", SKIP_LINE }, @@ -138,15 +137,28 @@ void init_keywords() { "sync_with_master", SKIP_LINE }, { "system", SKIP_LINE }, { "vertical_results", SKIP_LINE }, - { "while", SKIP_BLOCK }, { "write_file", SKIP_LINE }, }; - const size_t N_KEYWORDS = sizeof(KEYWORDS)/sizeof(KEYWORDS[0]); + const size_t N_MTL_KEYWORDS = sizeof(MTL_KEYWORDS)/sizeof(MTL_KEYWORDS[0]); - for (size_t i = 0; i < N_KEYWORDS; ++i) + for (size_t i = 0; i < N_MTL_KEYWORDS; ++i) { - mtl_keywords[KEYWORDS[i].z_keyword] = KEYWORDS[i].action; + mtl_keywords[MTL_KEYWORDS[i].z_keyword] = MTL_KEYWORDS[i].action; + } + + static const Keyword PLSQL_KEYWORDS[] = + { + { "exit", SKIP_LINE }, + { "if", SKIP_BLOCK }, + { "while", SKIP_BLOCK }, + }; + + const size_t N_PLSQL_KEYWORDS = sizeof(PLSQL_KEYWORDS)/sizeof(PLSQL_KEYWORDS[0]); + + for (size_t i = 0; i < N_PLSQL_KEYWORDS; ++i) + { + plsql_keywords[PLSQL_KEYWORDS[i].z_keyword] = PLSQL_KEYWORDS[i].action; } } @@ -163,18 +175,24 @@ skip_action_t get_action(const string& keyword, const string& delimiter) // be handled explicitly. action = SKIP_DELIMITER; } - else if (delimiter == ";") + else + { + KeywordActionMapping::iterator i = mtl_keywords.find(key); + + if (i != mtl_keywords.end()) + { + action = i->second; + } + } + + if ((action == SKIP_NOTHING) && (delimiter == ";")) { // Some mysqltest keywords, such as "while", "exit" and "if" are also // PL/SQL keywords. We assume they can only be used in the former role, // if the delimiter is ";". - string key(keyword); + KeywordActionMapping::iterator i = plsql_keywords.find(key); - std::transform(key.begin(), key.end(), key.begin(), ::tolower); - - KeywordActionMapping::iterator i = mtl_keywords.find(key); - - if (i != mtl_keywords.end()) + if (i != plsql_keywords.end()) { action = i->second; } diff --git a/server/core/dcb.cc b/server/core/dcb.cc index 2a55779e4..6c567f4c9 100644 --- a/server/core/dcb.cc +++ b/server/core/dcb.cc @@ -2915,10 +2915,15 @@ public: dcb && atomic_load_int32(&m_more); dcb = dcb->thread.next) { - if (!m_func(dcb, m_data)) + ss_dassert(dcb->session); + + if (dcb->session->state != SESSION_STATE_DUMMY) { - atomic_store_int32(&m_more, 0); - break; + if (!m_func(dcb, m_data)) + { + atomic_store_int32(&m_more, 0); + break; + } } } } diff --git a/server/core/queryclassifier.cc b/server/core/queryclassifier.cc index 89a3c1ae3..3bb0bd84b 100644 --- a/server/core/queryclassifier.cc +++ b/server/core/queryclassifier.cc @@ -26,6 +26,22 @@ using namespace maxscale; const int QC_TRACE_MSG_LEN = 1000; +// Copy of mxs_mysql_extract_ps_id() in modules/protocol/MySQL/mysql_common.cc, +// but we do not want to create a dependency from maxscale-common to that. + +uint32_t mysql_extract_ps_id(GWBUF* buffer) +{ + uint32_t rval = 0; + uint8_t id[MYSQL_PS_ID_SIZE]; + + if (gwbuf_copy_data(buffer, MYSQL_PS_ID_OFFSET, sizeof(id), id) == sizeof(id)) + { + rval = gw_mysql_get_byte4(id); + } + + return rval; +} + // Copied from mysql_common.c // TODO: The current database should somehow be available in a generic fashion. const char* qc_mysql_get_current_db(MXS_SESSION* session) @@ -285,6 +301,24 @@ public: } } + void erase(GWBUF* buffer) + { + uint8_t cmd = mxs_mysql_get_command(buffer); + + if (cmd == MXS_COM_QUERY) + { + erase(get_text_ps_id(buffer)); + } + else if (qc_mysql_is_ps_command(cmd)) + { + erase(mysql_extract_ps_id(buffer)); + } + else + { + ss_info_dassert(!true, "QueryClassifier::PSManager::erase called with invalid query"); + } + } + private: typedef std::tr1::unordered_map BinaryPSMap; typedef std::tr1::unordered_map TextPSMap; @@ -328,14 +362,9 @@ uint32_t QueryClassifier::ps_get_type(std::string id) const return m_sPs_manager->get_type(id); } -void QueryClassifier::ps_erase(std::string id) +void QueryClassifier::ps_erase(GWBUF* buffer) { - return m_sPs_manager->erase(id); -} - -void QueryClassifier::ps_erase(uint32_t id) -{ - return m_sPs_manager->erase(id); + return m_sPs_manager->erase(buffer); } uint32_t QueryClassifier::get_route_target(uint8_t command, uint32_t qtype, HINT* pHints) @@ -525,27 +554,6 @@ uint32_t QueryClassifier::get_route_target(uint8_t command, uint32_t qtype, HINT return target; } -namespace -{ - -// Copy of mxs_mysql_extract_ps_id() in modules/protocol/MySQL/mysql_common.cc, -// but we do not want to create a dependency from maxscale-common to that. - -uint32_t mysql_extract_ps_id(GWBUF* buffer) -{ - uint32_t rval = 0; - uint8_t id[MYSQL_PS_ID_SIZE]; - - if (gwbuf_copy_data(buffer, MYSQL_PS_ID_OFFSET, sizeof(id), id) == sizeof(id)) - { - rval = gw_mysql_get_byte4(id); - } - - return rval; -} - -} - uint32_t QueryClassifier::ps_id_internal_get(GWBUF* pBuffer) { uint32_t internal_id = 0; diff --git a/server/modules/protocol/MySQL/mariadbclient/mysql_client.cc b/server/modules/protocol/MySQL/mariadbclient/mysql_client.cc index 9b6d3a1f1..6c34f20e5 100644 --- a/server/modules/protocol/MySQL/mariadbclient/mysql_client.cc +++ b/server/modules/protocol/MySQL/mariadbclient/mysql_client.cc @@ -1631,7 +1631,7 @@ static bool reauthenticate_client(MXS_SESSION* session, GWBUF* packetbuf) */ static int route_by_statement(MXS_SESSION* session, uint64_t capabilities, GWBUF** p_readbuf) { - int rc; + int rc = 1; GWBUF* packetbuf; do { @@ -1738,6 +1738,7 @@ static int route_by_statement(MXS_SESSION* session, uint64_t capabilities, GWBUF // Store the original COM_CHANGE_USER for later proto->stored_query = packetbuf; packetbuf = NULL; + rc = 1; } else if (proto->changing_user) { diff --git a/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc b/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc index cb0454169..2fc3d5add 100644 --- a/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc +++ b/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc @@ -360,6 +360,10 @@ bool RWSplitSession::route_session_write(GWBUF *querybuf, uint8_t command, uint3 { m_qc.ps_store(querybuf, id); } + else if (qc_query_is_type(type, QUERY_TYPE_DEALLOC_PREPARE)) + { + m_qc.ps_erase(querybuf); + } MXS_INFO("Session write, routing to all servers.");