diff --git a/BUILD/install_build_deps.sh b/BUILD/install_build_deps.sh index 82970f39a..4a99d9d98 100755 --- a/BUILD/install_build_deps.sh +++ b/BUILD/install_build_deps.sh @@ -154,7 +154,7 @@ then echo "Error getting avro-c" exit 1 fi -avro_filename=`ls -1 *.tar.gz` +avro_filename=`ls -1 avro*.tar.gz` avro_dir=`echo "$avro_filename" | sed "s/.tar.gz//"` tar -axf $avro_filename mkdir $avro_dir/build diff --git a/CMakeLists.txt b/CMakeLists.txt index 85e9f8229..bf0c1ecac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ endif() # Set default values for cache entries and set the MaxScale version include(cmake/defaults.cmake) -include(VERSION.cmake) +include(VERSION22.cmake) include(ExternalProject) set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.") @@ -41,7 +41,6 @@ find_package(Git) find_package(CURL) find_package(RabbitMQ) find_package(LibUUID) -find_package(Jansson) find_package(Avro) find_package(GSSAPI) find_package(SQLite) @@ -62,11 +61,7 @@ else() message(STATUS "Using system Connector-C") endif() -if(NOT JANSSON_FOUND) - message(STATUS "Building Jansson from source.") - include(cmake/BuildJansson.cmake) -endif() - +include(cmake/BuildJansson.cmake) include(cmake/BuildMicroHttpd.cmake) include_directories(${JANSSON_INCLUDE_DIR}) diff --git a/Documentation/Changelog.md b/Documentation/Changelog.md index 208cd24ee..20b4d173e 100644 --- a/Documentation/Changelog.md +++ b/Documentation/Changelog.md @@ -37,6 +37,7 @@ For more details, please refer to: as JSON objects (beta level functionality). For more details, please refer to: +* [MariaDB MaxScale 2.0.6 Release Notes](Release-Notes/MaxScale-2.0.6-Release-Notes.md) * [MariaDB MaxScale 2.0.5 Release Notes](Release-Notes/MaxScale-2.0.5-Release-Notes.md) * [MariaDB MaxScale 2.0.4 Release Notes](Release-Notes/MaxScale-2.0.4-Release-Notes.md) * [MariaDB MaxScale 2.0.3 Release Notes](Release-Notes/MaxScale-2.0.3-Release-Notes.md) diff --git a/Documentation/Filters/Tee-Filter.md b/Documentation/Filters/Tee-Filter.md index 72e7f4424..99fb0eec8 100644 --- a/Documentation/Filters/Tee-Filter.md +++ b/Documentation/Filters/Tee-Filter.md @@ -31,9 +31,41 @@ passwd=mypasswd filters=DataMartFilter ``` -## Filter Options +## Filter Parameters -The tee filter accepts the following options. +The tee filter requires a mandatory parameter to define the service to replicate +statements to and accepts a number of optional parameters. + +### `match` + +An optional parameter used to limit the queries that will be replicated by the +tee filter. The parameter value is a PCRE2 regular expression that is used to +match against the SQL text. Only SQL statements that match the text passed as +the value of this parameter will be sent to the service defined in the filter +section. + +``` +match=/insert.*into.*order*/ +``` + +### `exclude` + +An optional parameter used to limit the queries that will be replicated by the +tee filter. The parameter value is a PCRE2 regular expression that is used to +match against the SQL text. Any SQL statements that match the text passed as the +value of this parameter will be excluded from the replication stream. + +``` +exclude=/select.*from.*t1/ +``` + +If both `match` and `exclude` parameters are defined, `exclude` takes +precedence. + +### `options` + +The options parameter controls the regular expression options. The following +options are accepted. |Option |Description | |----------|--------------------------------------------| @@ -47,43 +79,7 @@ To use multiple filter options, list them in a comma-separated list. options=case,extended ``` -## Filter Parameters - -The tee filter requires a mandatory parameter to define the service to replicate -statements to and accepts a number of optional parameters. - -### Match - -An optional parameter used to limit the queries that will be replicated by the -tee filter. The parameter value is a regular expression that is used to match -against the SQL text. Only SQL statements that matches the text passed as the -value of this parameter will be sent to the service defined in the filter -section. - -``` -match=insert.*into.*order* -``` - -All regular expressions are evaluated with the option to ignore the case of the -text, therefore a match option of select will match both insert, INSERT and any -form of the word with upper or lowercase characters. - -### Exclude - -An optional parameter used to limit the queries that will be replicated by the -tee filter. The parameter value is a regular expression that is used to match -against the SQL text. SQL statements that match the text passed as the value of -this parameter will be excluded from the replication stream. - -``` -exclude=select -``` - -All regular expressions are evaluated with the option to ignore the case of the -text, therefore an exclude option of select will exclude statements that contain -both select, SELECT or any form of the word with upper or lowercase characters. - -### Source +### `source` The optional source parameter defines an address that is used to match against the address from which the client connection to MariaDB MaxScale originates. @@ -93,7 +89,7 @@ Only sessions that originate from this address will be replicated. source=127.0.0.1 ``` -### User +### `user` The optional user parameter defines a user name that is used to match against the user from which the client connection to MariaDB MaxScale originates. Only diff --git a/Documentation/Getting-Started/Building-MaxScale-from-Source-Code.md b/Documentation/Getting-Started/Building-MaxScale-from-Source-Code.md index f66d87770..7304020cf 100644 --- a/Documentation/Getting-Started/Building-MaxScale-from-Source-Code.md +++ b/Documentation/Getting-Started/Building-MaxScale-from-Source-Code.md @@ -34,7 +34,7 @@ Ubuntu 16.04: sudo apt-get update sudo apt-get install git build-essential libssl-dev ncurses-dev bison flex \ cmake perl libtool libcurl4-openssl-dev libpcre3-dev tcl tcl-dev uuid \ - uuid-dev libsqlite3-dev libgnutls30 libgcrypt20 + uuid-dev libsqlite3-dev gnutls-dev libgcrypt20-dev ``` ### Build and Install MaxScale diff --git a/Documentation/Getting-Started/Configuration-Guide.md b/Documentation/Getting-Started/Configuration-Guide.md index 7b2f478e3..8e58b3e3b 100644 --- a/Documentation/Getting-Started/Configuration-Guide.md +++ b/Documentation/Getting-Started/Configuration-Guide.md @@ -9,7 +9,9 @@ plugin modules that tailor the behavior of the program. # Table of Contents +* [Glossary](#glossary) * [Configuration](#configuration) + * [Special Parameter Types](#special-parameter-types) * [Global Settings](#global-settings) * [Service](#service) * [Server](#server) @@ -79,7 +81,9 @@ The values of the parameter that are not on the first line need to have at least one whitespace character before them in order for them to be recognized as a part of the multi-line parameter. -### Sizes +### Special Parameter Types + +#### Sizes Where _specifically noted_, a number denoting a size can be suffixed by a subset of the IEC binary prefixes or the SI prefixes. In the former case the number @@ -104,6 +108,16 @@ max_size=1000000M max_size=1000G max_size=1T ``` +#### Regular Expressions + +When a regular expression (regex) parameter is accepted, the pattern string +should be enclosed in slashes e.g. `match=/^select/` defines the pattern +`^select`. The slashes allow whitespace to be read from the ends of the regex +string contrary to a normal string parameter and are removed before compiling +the pattern. For backwards compatibility, the slashes are not yet mandatory. +Omitting them is, however, deprecated and will be rejected in the next release +of MaxScale. Currently, *QLAFilter* accepts parameters in regular expression +form. ### Global Settings diff --git a/Documentation/REST-API/Resources-Server.md b/Documentation/REST-API/Resources-Server.md index c50f186ca..02b64891d 100644 --- a/Documentation/REST-API/Resources-Server.md +++ b/Documentation/REST-API/Resources-Server.md @@ -72,7 +72,7 @@ Status: 200 OK "slaves": [ // List of slave server IDs 3001 ], - "statictics": { // Server statistics + "statistics": { // Server statistics "connections": 0, "total_connections": 0, "active_operations": 0 @@ -156,7 +156,7 @@ Status: 200 OK "slaves": [ 3001 ], - "statictics": { + "statistics": { "connections": 0, "total_connections": 0, "active_operations": 0 @@ -205,7 +205,7 @@ Status: 200 OK "master_id": 3000, "replication_depth": 1, "slaves": [], - "statictics": { + "statistics": { "connections": 0, "total_connections": 0, "active_operations": 0 @@ -401,7 +401,7 @@ Response to `GET /v1/server/server1`: 3001, 3002 ], - "statictics": { + "statistics": { "connections": 0, "total_connections": 0, "active_operations": 0 diff --git a/Documentation/Release-Notes/MaxScale-2.2.0-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.2.0-Release-Notes.md index 3d4640569..9e2ef5826 100644 --- a/Documentation/Release-Notes/MaxScale-2.2.0-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.2.0-Release-Notes.md @@ -17,11 +17,26 @@ Significant whitespace in object names is now deprecated. All object names squeezing repeating whitespace and replacing it with hyphens. If any object name conversions take place, a warning will be logged. +### Regular Expression Parameters + +Modules may now use a built-in regular expression (regex) string parameter type +instead of a normal string when accepting patterns. The regex parameters are +checked by the config file loader to compile using the PCRE2 library embedded +within MaxScale. The only module using the new regex parameter type is currently +*QLAFilter*. + +The only action users should take is enclose their regular expressions in +slashes, e.g. `match=/^select/` defines the pattern `^select`. The slashes allow +whitespace to be read from the ends of the regex string contrary to a normal +string parameter and are removed before compiling the pattern. For backwards +compatibility, the slashes are not yet mandatory. Omitting them is, however, +deprecated and will be rejected in the next release of MaxScale. + ### NamedServerFilter -This filter now uses the PCRE2-libarary to match queries. Previously, it used -the POSIX-version of PCRE2. The filter also accepts multiple match-server pairs. -Please see the NamedServerFilter documentation for details. +The filter now accepts multiple match-server pairs. Please see the +[NamedServerFilter](../Filters/Named-Server-Filter.md) documentation for +details. ### Tee Filter @@ -35,6 +50,10 @@ In addition to the aforementioned requirements, a failure to create a branched session no longer causes the actual client session to be closed. In most cases, this is desired behavior. +The `match` and `exclude` parameters were changed to use PCRE2 syntax for the +regular expressions. The regular expression should be enclosed by slashes +e.g. `match=/select.*from.*test/`. + ## Dropped Features ### MaxAdmin diff --git a/Documentation/Tutorials/MaxScale-Tutorial.md b/Documentation/Tutorials/MaxScale-Tutorial.md index 8596af5bd..0d98e8d30 100644 --- a/Documentation/Tutorials/MaxScale-Tutorial.md +++ b/Documentation/Tutorials/MaxScale-Tutorial.md @@ -101,11 +101,14 @@ two steps from above. ## Creating additional grants for users -Because MariaDB MaxScale sits between the clients and the backend databases, -the backend databases will see all clients as if they were connecting from -MariaDB MaxScale's address. -This usually requires users to create additional grants for MariaDB MaxScale's hostname. -The best way to describe this process is with an example. +**Note:** The client host and MaxScale host must have the same username and + password for both client and MaxScale hosts. + +Because MariaDB MaxScale sits between the clients and the backend databases, the +backend databases will see all clients as if they were connecting from MariaDB +MaxScale's address. This usually requires users to create additional grants for +MariaDB MaxScale's hostname. The best way to describe this process is with an +example. User `'jdoe'@'192.168.0.200` has the following grant on the cluster: `GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'jdoe'@'192.168.0.200'`. @@ -134,18 +137,22 @@ MariaDB [(none)]> SHOW GRANTS FOR 'jdoe'@'192.168.0.200'; ``` Then creating the user `'jdoe'@'192.168.0.101'` and giving it the same grants: ``` -MariaDB [(none)]> CREATE USER 'jdoe'@'192.168.0.101'; +MariaDB [(none)]> CREATE USER 'jdoe'@'192.168.0.101' IDENTIFIED BY 'secret_password'; Query OK, 0 rows affected (0.00 sec) MariaDB [(none)]> GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'jdoe'@'192.168.0.101'; Query OK, 0 rows affected (0.00 sec) ``` -The other option is to use a wildcard grant like -`GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'jdoe'@'%'`. -This is more convenient but also less secure than having specific grants -for both the client's address and MariaDB MaxScale's address. +The other option is to use a wildcard grant like the following: +``` +GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'jdoe'@'%' IDENTIFIED BY 'secret_password' +``` + +This is more convenient but less secure than having specific grants for both the +client's address and MariaDB MaxScale's address as it allows access from all +hosts. ## Creating the configuration file diff --git a/Documentation/Upgrading/Upgrading-To-MaxScale-2.2.md b/Documentation/Upgrading/Upgrading-To-MaxScale-2.2.md new file mode 100644 index 000000000..1eee3012f --- /dev/null +++ b/Documentation/Upgrading/Upgrading-To-MaxScale-2.2.md @@ -0,0 +1,20 @@ +# Upgrading MariaDB MaxScale from 2.1 to 2.2 + +This document describes possible issues upgrading MariaDB MaxScale from version +2.1 to 2.2. + +For more information about MariaDB MaxScale 2.2, please refer to the +[ChangeLog](../Changelog.md). + +For a complete list of changes in MaxScale 2.2.0, refer to the +[MaxScale 2.2.0 Release Notes](../Release-Notes/MaxScale-2.2.0-Release-Notes.md). + +Before starting the upgrade, we recommend you back up your current configuration +file. + +### Regular Expression Parameters + +Modules may now use a built-in regular expression string parameter type instead +of a normal string when accepting patterns. The modules that use the new regex +parameter type are *qlafilter* and *tee*. When inputting pattern, enclose the +string in slashes, e.g. `match=/^select/` defines the pattern `^select`. diff --git a/VERSION.cmake b/VERSION20.cmake similarity index 100% rename from VERSION.cmake rename to VERSION20.cmake diff --git a/VERSION21.cmake b/VERSION21.cmake new file mode 100644 index 000000000..ba1a23389 --- /dev/null +++ b/VERSION21.cmake @@ -0,0 +1,14 @@ +# MaxScale version for CMake +# +# This file contains cache values for CMake which control MaxScale's version +# number. + +set(MAXSCALE_VERSION_MAJOR "2" CACHE STRING "Major version") +set(MAXSCALE_VERSION_MINOR "1" CACHE STRING "Minor version") +set(MAXSCALE_VERSION_PATCH "4" CACHE STRING "Patch version") + +# This should only be incremented if a package is rebuilt +set(MAXSCALE_BUILD_NUMBER 1 CACHE STRING "Release number") + +set(MAXSCALE_VERSION_NUMERIC "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}") +set(MAXSCALE_VERSION "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}") diff --git a/VERSION22.cmake b/VERSION22.cmake new file mode 100644 index 000000000..0764519b3 --- /dev/null +++ b/VERSION22.cmake @@ -0,0 +1,14 @@ +# MaxScale version for CMake +# +# This file contains cache values for CMake which control MaxScale's version +# number. + +set(MAXSCALE_VERSION_MAJOR "2" CACHE STRING "Major version") +set(MAXSCALE_VERSION_MINOR "2" CACHE STRING "Minor version") +set(MAXSCALE_VERSION_PATCH "0" CACHE STRING "Patch version") + +# This should only be incremented if a package is rebuilt +set(MAXSCALE_BUILD_NUMBER 1 CACHE STRING "Release number") + +set(MAXSCALE_VERSION_NUMERIC "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}") +set(MAXSCALE_VERSION "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}") diff --git a/cmake/BuildJansson.cmake b/cmake/BuildJansson.cmake index 91fa99799..78e038a01 100644 --- a/cmake/BuildJansson.cmake +++ b/cmake/BuildJansson.cmake @@ -8,7 +8,7 @@ set(JANSSON_TAG "v2.9" CACHE STRING "Jansson Git tag") ExternalProject_Add(jansson GIT_REPOSITORY ${JANSSON_REPO} GIT_TAG ${JANSSON_TAG} - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/jansson/install -DCMAKE_C_FLAGS=-fPIC + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/jansson/install -DCMAKE_C_FLAGS=-fPIC -DJANSSON_BUILD_DOCS=OFF BINARY_DIR ${CMAKE_BINARY_DIR}/jansson INSTALL_DIR ${CMAKE_BINARY_DIR}/jansson/install UPDATE_COMMAND "") diff --git a/include/maxscale/atomic.h b/include/maxscale/atomic.h index 509509103..edcf503ff 100644 --- a/include/maxscale/atomic.h +++ b/include/maxscale/atomic.h @@ -57,10 +57,10 @@ uint64_t atomic_add_uint64(uint64_t *variable, int64_t value); * @param variable Pointer the the variable to load from * @return The stored value */ -int atomic_load_int32(int *variable); -int64_t atomic_load_int64(int64_t *variable); -uint64_t atomic_load_uint64(uint64_t *variable); -void* atomic_load_ptr(void **variable); +int atomic_load_int32(const int *variable); +int64_t atomic_load_int64(const int64_t *variable); +uint64_t atomic_load_uint64(const uint64_t *variable); +void* atomic_load_ptr(void * const *variable); /** * Implementation of an atomic store operation for the GCC environment. diff --git a/include/maxscale/backend.hh b/include/maxscale/backend.hh new file mode 100644 index 000000000..a7c84911c --- /dev/null +++ b/include/maxscale/backend.hh @@ -0,0 +1,308 @@ +#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 +#include + +#include +#include + + +namespace maxscale +{ + + +class Backend +{ + Backend(const Backend&); + Backend& operator =(const Backend&); +public: + + /** + * How is the backend being closed + */ + enum close_type + { + CLOSE_NORMAL, + CLOSE_FATAL + }; + + /** + * What type of a response we expect from the backend + */ + enum response_type + { + EXPECT_RESPONSE, + NO_RESPONSE + }; + + /** + * @brief Create new Backend + * + * @param ref Server reference used by this backend + */ + Backend(SERVER_REF* ref); + + virtual ~Backend(); + + /** + * @brief Execute the next session command in the queue + * + * @return True if the command was executed successfully + */ + virtual bool execute_session_command(); + + /** + * @brief Add a new session command to the tail of the command queue + * + * @param buffer Session command to add + * @param sequence Sequence identifier of this session command, returned when + * the session command is completed + */ + void append_session_command(GWBUF* buffer, uint64_t sequence); + void append_session_command(const SSessionCommand& sescmd); + void append_session_command(const SessionCommandList& sescmdlist); + + /** + * @brief Mark the current session command as successfully executed + * + * This should be called when the response to the command is received + * + * @return The sequence identifier for this session command + */ + uint64_t complete_session_command(); + + /** + * @brief Check if backend has session commands + * + * @return Number of session commands + */ + size_t session_command_count() const; + + /** + * @brief Get the first session command + * + * Returns the first session command in the list of session commands + * to be executed. + * + * This should only be called when at least one session command has been + * added to the backend. If no session commands have been added, behavior + * is undefined. + * + * @return The first session command + */ + const SSessionCommand& next_session_command() const; + + /** + * @brief Get pointer to server reference + * + * @return Pointer to server reference + */ + SERVER_REF* backend() const; + + /** + * @brief Get pointer to server + * + * @return Pointer to server + */ + SERVER* server() const; + + /** + * @brief Check if a connection to this backend can be made + * + * @return True if the backend has not failed and a connection can be attempted + */ + bool can_connect() const; + + /** + * @brief Create a new connection + * + * @param session The session to which the connection is linked + * + * @return True if connection was successfully created + */ + bool connect(MXS_SESSION* session); + + /** + * @brief Close the backend + * + * This will close all active connections created by the backend. + */ + void close(close_type type = CLOSE_NORMAL); + + /** + * @brief Get a pointer to the internal DCB + * + * @return Pointer to internal DCB + */ + DCB* dcb() const; + + /** + * @brief Write data to the backend server + * + * @param buffer Buffer containing the data to write + * @param expect_response Whether to expect a response to the query + * + * @return True if data was written successfully + */ + bool write(GWBUF* buffer, response_type type = EXPECT_RESPONSE); + + /** + * @brief Write an authentication switch request to the backend server + * + * @param buffer Buffer containing the authentication switch request + * + * @return True if request was successfully written + */ + bool auth(GWBUF* buffer); + + /** + * @brief Mark that a reply to a query was received and processed + */ + void ack_write(); + + /** + * @brief Store a command + * + * The command is stored and executed once the session can execute + * the next command. + * + * @param buffer Buffer to store + */ + void store_command(GWBUF* buffer); + + /** + * @brief Write the stored command to the backend server + * + * @return True if command was written successfully + */ + bool write_stored_command(); + + /** + * @brief Check if backend is in use + * + * @return True if backend is in use + */ + bool in_use() const; + + /** + * @brief Check if the backend server reference is active + * + * @return True if the server reference is active + */ + bool is_active() const; + + /** + * @brief Check if backend is waiting for a result + * + * @return True if backend is waiting for a result + */ + bool is_waiting_result() const; + + /** + * @brief Check if the backend is closed + * + * @return True if the backend is closed + */ + bool is_closed() const; + + /** + * @brief Check if the server is a master + * + * @return True if server is a master + */ + bool is_master() const; + + /** + * @brief Check if the server is a slave + * + * @return True if the server is a slave + */ + bool is_slave() const; + + /** + * @brief Check if the server is a relay server + * + * @return True if the server is a relay server + */ + bool is_relay() const; + + /** + * @brief Check if the backend has failed fatally + * + * When a fatal failure occurs in a backend, the backend server can no longer + * be used by this session. Fatal failures can occur when the execution of + * a session command fails on the backend but the expected result is different. + * + * @return True if a fatal failure has occurred in the backend server + */ + bool has_failed() const; + + + /** + * @brief Get the object name of this server + * + * @return The unique object name of this server + */ + const char* name() const; + + /** + * @brief Get the address and port as a string + * + * @return The address and port combined into one string + */ + const char* uri() const; + +private: + /** + * Internal state of the backend + */ + enum backend_state + { + IN_USE = 0x01, /**< Backend has been taken into use */ + WAITING_RESULT = 0x02, /**< Waiting for a reply */ + FATAL_FAILURE = 0x04 /**< Backend references that should be dropped */ + }; + + /** + * @brief Clear state + * + * @param state State to clear + */ + void clear_state(backend_state state); + + /** + * @brief Set state + * + * @param state State to set + */ + void set_state(backend_state state); + + + bool m_closed; /**< True if a connection has been opened and closed */ + SERVER_REF* m_backend; /**< Backend server */ + DCB* m_dcb; /**< Backend DCB */ + mxs::Buffer m_pending_cmd; /**< Pending commands */ + int m_state; /**< State of the backend */ + SessionCommandList m_session_commands; /**< List of session commands that are + * to be executed on this backend server */ + std::string m_uri; /**< The combined address and port */ +}; + +typedef std::tr1::shared_ptr SBackend; +typedef std::list BackendList; +} diff --git a/include/maxscale/config.h b/include/maxscale/config.h index aad311a2f..157e695fe 100644 --- a/include/maxscale/config.h +++ b/include/maxscale/config.h @@ -369,6 +369,28 @@ pcre2_code* config_get_compiled_regex(const MXS_CONFIG_PARAMETER *params, const char *key, uint32_t options, uint32_t* output_ovec_size); +/** + * Get multiple compiled regular expressions and the maximum ovector size of + * the patterns. The returned regex codes should be freed by the caller. + * + * @param params List of configuration parameters + * @param keys An array of parameter names. If an element is not found in @c params, + * the corresponding spot in @c out_codes is set to NULL. + * @param keys_size Size of both @c keys and @c out_arr + * @param options PCRE2 compilation options + * @param out_ovec_size If not NULL, the maximum ovector size of successfully + * compiled patterns is written here. + * @param out_codes An array of handles to compiled codes. The referenced pointers + * will be set to point to the compiled codes. The array size must be equal to + * @c keys array size and it must contain valid pointers. + * + * @return True, if all patterns given by @c keys were successfully compiled. + * False otherwise. + */ +bool config_get_compiled_regexes(const MXS_CONFIG_PARAMETER *params, + const char* keys[], int keys_size, + uint32_t options, uint32_t* out_ovec_size, + pcre2_code** out_codes[]); /** * Parse a list of server names and write the results in an array of strings * with one server name in each. The output array and its elements should be diff --git a/include/maxscale/mysql_utils.h b/include/maxscale/mysql_utils.h index 4eac68fba..cc47c754c 100644 --- a/include/maxscale/mysql_utils.h +++ b/include/maxscale/mysql_utils.h @@ -83,4 +83,12 @@ mxs_mysql_name_kind_t mxs_mysql_name_to_pcre(char *pcre, const char *mysql, mxs_pcre_quote_approach_t approach); +/** + * Set the server information + * + * @param mysql A MySQL handle to the server. + * @param server The server whose version information should be updated. + */ +void mxs_mysql_set_server_version(MYSQL* mysql, SERVER* server); + MXS_END_DECLS diff --git a/include/maxscale/pcre2.h b/include/maxscale/pcre2.h index 0696a3f4c..70be6a8e5 100644 --- a/include/maxscale/pcre2.h +++ b/include/maxscale/pcre2.h @@ -55,4 +55,26 @@ mxs_pcre2_result_t mxs_pcre2_simple_match(const char* pattern, const char* subje void mxs_pcre2_print_error(int errorcode, const char *module_name, const char *filename, int line_num, const char* func_name); +/** + * Check that @c subject is valid. A valid subject matches @c re_match yet does + * not match @c re_exclude. If an error occurs, an error code is written to + * @c match_error_out. + * + * @param re_match If not NULL, the subject must match this to be valid. If NULL, + * all inputs are considered valid. + * @param re_exclude If not NULL, will invalidate a matching subject. Even subjects + * validated by @c re_match can be invalidated. If NULL, invalidates nothing. + * @param md PCRE2 match data block + * @param subject Subject string. Should NOT be an empty string. + * @param length Length of subject. Can be zero for 0-terminated strings. + * @param calling_module Which module the function was called from. Can be NULL. + * Used for log messages. + * + * @return True, if subject is considered valid. False if subject is not valid or + * an error occurred. + */ +bool mxs_pcre2_check_match_exclude(pcre2_code* re_match, pcre2_code* re_exclude, + pcre2_match_data* md, const char* subject, + int length, const char* calling_module); + MXS_END_DECLS diff --git a/include/maxscale/protocol/mysql.h b/include/maxscale/protocol/mysql.h index 18760dc9b..24d86aed1 100644 --- a/include/maxscale/protocol/mysql.h +++ b/include/maxscale/protocol/mysql.h @@ -40,6 +40,7 @@ #include #include #include +#include MXS_BEGIN_DECLS @@ -484,4 +485,23 @@ mysql_server_cmd_t mxs_mysql_current_command(MXS_SESSION* session); void mysql_num_response_packets(GWBUF *buf, uint8_t cmd, int* npackets, size_t *nbytes); +/** + * @brief Return current database of the session + * + * If no active database is in use, the database is an empty string. + * + * @param session Session to inspect + * + * @return The current database + */ +const char* mxs_mysql_get_current_db(MXS_SESSION* session); + +/** + * @brief Set the currently active database for a session + * + * @param session Session to modify + * @param db The new database + */ +void mxs_mysql_set_current_db(MXS_SESSION* session, const char* db); + MXS_END_DECLS diff --git a/include/maxscale/query_classifier.h b/include/maxscale/query_classifier.h index f10ff0213..d64ddf824 100644 --- a/include/maxscale/query_classifier.h +++ b/include/maxscale/query_classifier.h @@ -82,19 +82,20 @@ typedef enum qc_query_type */ typedef enum qc_query_op { - QUERY_OP_UNDEFINED = 0, - QUERY_OP_SELECT = (1 << 0), - QUERY_OP_UPDATE = (1 << 1), - QUERY_OP_INSERT = (1 << 2), - QUERY_OP_DELETE = (1 << 3), - QUERY_OP_TRUNCATE = (1 << 4), - QUERY_OP_ALTER = (1 << 5), - QUERY_OP_CREATE = (1 << 6), - QUERY_OP_DROP = (1 << 7), - QUERY_OP_CHANGE_DB = (1 << 8), - QUERY_OP_LOAD = (1 << 9), - QUERY_OP_GRANT = (1 << 10), - QUERY_OP_REVOKE = (1 << 11) + QUERY_OP_UNDEFINED = 0, + QUERY_OP_SELECT, + QUERY_OP_UPDATE, + QUERY_OP_INSERT, + QUERY_OP_DELETE, + QUERY_OP_TRUNCATE, + QUERY_OP_ALTER, + QUERY_OP_CREATE, + QUERY_OP_DROP, + QUERY_OP_CHANGE_DB, + QUERY_OP_LOAD, + QUERY_OP_GRANT, + QUERY_OP_REVOKE, + QUERY_OP_EXECUTE, } qc_query_op_t; /** @@ -373,6 +374,25 @@ typedef struct query_classifier * exhaustion or equivalent. */ int32_t (*qc_get_preparable_stmt)(GWBUF* stmt, GWBUF** preparable_stmt); + + /** + * Set the version of the server. The version may affect how a statement + * is classified. Note that the server version is maintained separately + * for each thread. + * + * @param version Version encoded as MariaDB encodes the version, i.e.: + * version = major * 10000 + minor * 100 + patch + */ + void (*qc_set_server_version)(uint64_t version); + + /** + * Get the thread specific version assumed of the server. If the version has + * not been set, all values are 0. + * + * @param version The version encoded as MariaDB encodes the version, i.e.: + * version = major * 10000 + minor * 100 + patch + */ + void (*qc_get_server_version)(uint64_t* version); } QUERY_CLASSIFIER; /** @@ -758,4 +778,23 @@ const char* qc_type_to_string(qc_query_type_t type); */ char* qc_typemask_to_string(uint32_t typemask); +/** + * Set the version of the server. The version may affect how a statement + * is classified. Note that the server version is maintained separately + * for each thread. + * + * @param version Version encoded as MariaDB encodes the version, i.e.: + * version = major * 10000 + minor * 100 + patch + */ +void qc_set_server_version(uint64_t version); + +/** + * Get the thread specific version assumed of the server. If the version has + * not been set, all values are 0. + * + * @return The version as MariaDB encodes the version, i.e: + * version = major * 10000 + minor * 100 + patch + */ +uint64_t qc_get_server_version(); + MXS_END_DECLS diff --git a/include/maxscale/server.h b/include/maxscale/server.h index 1409213f0..635958e10 100644 --- a/include/maxscale/server.h +++ b/include/maxscale/server.h @@ -25,9 +25,11 @@ MXS_BEGIN_DECLS -#define MAX_SERVER_NAME_LEN 1024 +#define MAX_SERVER_ADDRESS_LEN 1024 #define MAX_SERVER_MONUSER_LEN 1024 -#define MAX_SERVER_MONPW_LEN 1024 +#define MAX_SERVER_MONPW_LEN 1024 +#define MAX_SERVER_VERSION_LEN 256 + #define MAX_NUM_SLAVES 128 /**< Maximum number of slaves under a single server*/ /** @@ -61,6 +63,32 @@ typedef struct int n_persistent; /**< Current persistent pool */ } SERVER_STATS; +/** + * The server version. + */ +typedef struct server_version +{ + uint32_t major; + uint32_t minor; + uint32_t patch; +} SERVER_VERSION; + +static inline void server_decode_version(uint64_t version, SERVER_VERSION* server_version) +{ + uint32_t major = version / 10000; + uint32_t minor = (version - major * 10000) / 100; + uint32_t patch = version - major * 10000 - minor * 100; + + server_version->major = major; + server_version->minor = minor; + server_version->patch = patch; +} + +static uint64_t server_encode_version(const SERVER_VERSION* server_version) +{ + return server_version->major * 10000 + server_version->minor * 100 + server_version->patch; +} + /** * The SERVER structure defines a backend server. Each server has a name * or IP address for the server, a port that the server listens on and @@ -74,7 +102,7 @@ typedef struct server #endif SPINLOCK lock; /**< Common access lock */ char *unique_name; /**< Unique name for the server */ - char name[MAX_SERVER_NAME_LEN]; /**< Server name/IP address*/ + char name[MAX_SERVER_ADDRESS_LEN]; /**< Server name/IP address*/ unsigned short port; /**< Port to listen on */ char *protocol; /**< Protocol module to use */ char *authenticator; /**< Authenticator module name */ @@ -88,7 +116,8 @@ typedef struct server SERVER_STATS stats; /**< The server statistics */ struct server *next; /**< Next server */ struct server *nextdb; /**< Next server in list attached to a service */ - char *server_string; /**< Server version string, i.e. MySQL server version */ + char version_string[MAX_SERVER_VERSION_LEN]; /**< Server version string, i.e. MySQL server version */ + uint64_t version; /**< Server version */ long node_id; /**< Node id, server_id for M/S or local_index for Galera */ int rlag; /**< Replication Lag for Master / Slave replication */ unsigned long node_ts; /**< Last timestamp set from M/S monitor module */ @@ -308,7 +337,9 @@ extern DCB *server_get_persistent(SERVER *server, const char *user, const char extern void server_update_address(SERVER *server, const char *address); extern void server_update_port(SERVER *server, unsigned short port); extern unsigned int server_map_status(const char *str); -extern bool server_set_version_string(SERVER* server, const char* string); +extern void server_set_version_string(SERVER* server, const char* version_string); +extern void server_set_version(SERVER* server, const char* version_string, uint64_t version); +extern uint64_t server_get_version(const SERVER* server); extern void server_set_status(SERVER *server, int bit); extern void server_clear_status(SERVER *server, int bit); diff --git a/include/maxscale/service.h b/include/maxscale/service.h index b641ebe2d..81464e1fa 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -355,4 +355,28 @@ static inline uint64_t service_get_capabilities(const SERVICE *service) return service->capabilities; } +typedef enum service_version_which_t +{ + SERVICE_VERSION_ANY, /*< Any version of the servers of a service. */ + SERVICE_VERSION_MIN, /*< The minimum version. */ + SERVICE_VERSION_MAX, /*< The maximum version. */ +} service_version_which_t; + +/** + * Return the version of the service. The returned version can be + * + * - the version of any (in practice the first) server associated + * with the service, + * - the smallest version of any of the servers associated with + * the service, or + * - the largest version of any of the servers associated with + * the service. + * + * @param service The service. + * @param which Which version. + * + * @return The version of the service. + */ +uint64_t service_get_version(const SERVICE *service, service_version_which_t which); + MXS_END_DECLS diff --git a/server/modules/routing/schemarouter/session_command.hh b/include/maxscale/session_command.hh similarity index 73% rename from server/modules/routing/schemarouter/session_command.hh rename to include/maxscale/session_command.hh index 7fa37e754..59c198f43 100644 --- a/server/modules/routing/schemarouter/session_command.hh +++ b/include/maxscale/session_command.hh @@ -12,18 +12,21 @@ * Public License. */ +#include + +#include #include #include #include -using namespace maxscale; - -class SessionCommand; -typedef std::list SessionCommandList; +namespace maxscale +{ class SessionCommand { + SessionCommand(const SessionCommand&); + SessionCommand& operator=(const SessionCommand&); public: /** * @brief Mark reply as received @@ -54,7 +57,7 @@ public: * @brief Creates a copy of the internal buffer * @return A copy of the internal buffer */ - Buffer copy_buffer() const; + mxs::Buffer copy_buffer() const; /** * @brief Create a new session command @@ -75,8 +78,13 @@ public: std::string to_string(); private: - Buffer m_buffer; /**< The buffer containing the command */ - uint8_t m_command; /**< The command being executed */ - uint64_t m_pos; /**< Unique position identifier */ - bool m_reply_sent; /**< Whether the session command reply has been sent */ + mxs::Buffer m_buffer; /**< The buffer containing the command */ + uint8_t m_command; /**< The command being executed */ + uint64_t m_pos; /**< Unique position identifier */ + bool m_reply_sent; /**< Whether the session command reply has been sent */ }; + +typedef std::tr1::shared_ptr SSessionCommand; +typedef std::list SessionCommandList; + +} diff --git a/maxscale-system-test/CMakeLists.txt b/maxscale-system-test/CMakeLists.txt index 5c343efeb..4bb650376 100644 --- a/maxscale-system-test/CMakeLists.txt +++ b/maxscale-system-test/CMakeLists.txt @@ -607,6 +607,7 @@ add_test_executable(test_hints.cpp test_hints hints2 LABELS hintfilter LIGHT REP # Binlogrouter tests, these heavily alter the replication so they are run last add_test_executable(avro.cpp avro avro LABELS avrorouter binlogrouter LIGHT BREAKS_REPL) +add_test_executable(avro_alter.cpp avro_alter avro LABELS avrorouter binlogrouter LIGHT BREAKS_REPL) # Test avrorouter file compression #add_test_script(avro_compression avro avro_compression LABELS avrorouter binlogrouter LIGHT BREAKS_REPL) diff --git a/maxscale-system-test/avro.cpp b/maxscale-system-test/avro.cpp index fa80f5019..40f268e58 100644 --- a/maxscale-system-test/avro.cpp +++ b/maxscale-system-test/avro.cpp @@ -87,6 +87,7 @@ int main(int argc, char *argv[]) } execute_query(test.repl->nodes[0], "DROP TABLE test.t1;RESET MASTER"); + test.stop_timeout(); test.repl->fix_replication(); return test.global_result; diff --git a/maxscale-system-test/avro_alter.cpp b/maxscale-system-test/avro_alter.cpp new file mode 100644 index 000000000..3ce3f90b5 --- /dev/null +++ b/maxscale-system-test/avro_alter.cpp @@ -0,0 +1,57 @@ +/** + * @file avro_alter.cpp Test ALTER TABLE handling of avrorouter + */ + +#include "testconnections.h" +#include + +int main(int argc, char *argv[]) +{ + + TestConnections test(argc, argv); + test.set_timeout(600); + test.ssh_maxscale(true, (char *) "rm -rf /var/lib/maxscale/avro"); + + /** Start master to binlogrouter replication */ + if (!test.replicate_from_master()) + { + return 1; + } + + test.set_timeout(120); + test.repl->connect(); + + execute_query_silent(test.repl->nodes[0], "DROP TABLE test.t1"); + execute_query(test.repl->nodes[0], "CREATE TABLE test.t1(id INT)"); + execute_query(test.repl->nodes[0], "INSERT INTO test.t1 VALUES (1)"); + execute_query(test.repl->nodes[0], "ALTER TABLE test.t1 ADD COLUMN a VARCHAR(100)"); + execute_query(test.repl->nodes[0], "INSERT INTO test.t1 VALUES (2, \"a\")"); + execute_query(test.repl->nodes[0], "ALTER TABLE test.t1 ADD COLUMN b FLOAT"); + execute_query(test.repl->nodes[0], "INSERT INTO test.t1 VALUES (3, \"b\", 3.0)"); + execute_query(test.repl->nodes[0], "ALTER TABLE test.t1 CHANGE COLUMN b c DATETIME(3)"); + execute_query(test.repl->nodes[0], "INSERT INTO test.t1 VALUES (4, \"c\", NOW())"); + execute_query(test.repl->nodes[0], "ALTER TABLE test.t1 DROP COLUMN c"); + execute_query(test.repl->nodes[0], "INSERT INTO test.t1 VALUES (5, \"d\")"); + + test.repl->close_connections(); + + /** Give avrorouter some time to process the events */ + test.stop_timeout(); + sleep(10); + test.set_timeout(120); + + for (int i = 1; i <=5; i++) + { + std::stringstream cmd; + cmd << "maxavrocheck -d /var/lib/maxscale/avro/test.t1.00000" << i << ".avro|wc -l"; + char* rows = test.ssh_maxscale_output(true, cmd.str().c_str()); + int nrows = atoi(rows); + free(rows); + test.add_result(nrows != 1, "Expected 1 line in file number %d, got %d", i, nrows); + } + + execute_query(test.repl->nodes[0], "DROP TABLE test.t1;RESET MASTER"); + test.repl->fix_replication(); + + return test.global_result; +} diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.session_limits b/maxscale-system-test/cnf/maxscale.cnf.template.session_limits index 68f9fac21..c3a91c42d 100755 --- a/maxscale-system-test/cnf/maxscale.cnf.template.session_limits +++ b/maxscale-system-test/cnf/maxscale.cnf.template.session_limits @@ -11,7 +11,7 @@ user=maxskysql passwd= skysql [RW Split Router] -connection_timeout=30 +connection_timeout=10 type=service router= readwritesplit servers=server1, server2, server3,server4 diff --git a/maxscale-system-test/fwf_prepared_stmt.cpp b/maxscale-system-test/fwf_prepared_stmt.cpp index 11b884a38..c2d013cb8 100644 --- a/maxscale-system-test/fwf_prepared_stmt.cpp +++ b/maxscale-system-test/fwf_prepared_stmt.cpp @@ -47,7 +47,8 @@ int main(int argc, char** argv) test.add_result(!mysql_stmt_prepare(stmt, query, strlen(query)), "Binary protocol preparation should fail"); mysql_stmt_close(stmt); - test.try_query(test.conn_rwsplit, "DROP TABLE test.t1"); + test.repl->connect(); + test.try_query(test.repl->nodes[0], "DROP TABLE test.t1"); return test.global_result; } diff --git a/maxscale-system-test/fwf_reload.cpp b/maxscale-system-test/fwf_reload.cpp index ebec2dfa8..fa6833731 100644 --- a/maxscale-system-test/fwf_reload.cpp +++ b/maxscale-system-test/fwf_reload.cpp @@ -36,7 +36,7 @@ int main(int argc, char *argv[]) sprintf(str, "rules%d", i); Test->set_timeout(180); copy_rules(Test, str, rules_dir); - Test->ssh_maxscale(true, "maxadmin call command dbfwfilter rules/reload \"Database Firewall\""); + Test->ssh_maxscale(true, "maxadmin call command dbfwfilter rules/reload Database-Firewall"); int local_result = 0; sprintf(pass_file, "%s/fw/pass%d", test_dir, i); diff --git a/maxscale-system-test/mariadb_nodes.cpp b/maxscale-system-test/mariadb_nodes.cpp index 43fe57237..85860f4db 100644 --- a/maxscale-system-test/mariadb_nodes.cpp +++ b/maxscale-system-test/mariadb_nodes.cpp @@ -432,6 +432,7 @@ int Mariadb_nodes::start_replication() "mysql -u root %s -e \"STOP SLAVE; RESET SLAVE; RESET SLAVE ALL; RESET MASTER; SET GLOBAL read_only=OFF;\"", socket_cmd[i]); ssh_node(i, str, true); + ssh_node(i, "sudo rm -f /etc/my.cnf.d/kerb.cnf", true); } sprintf(str, "%s/create_user.sh", test_dir); diff --git a/maxscale-system-test/masking/masking_mysqltest/masking_rules.json b/maxscale-system-test/masking/masking_mysqltest/masking_rules.json index 76ee2f24a..d010b5358 100644 --- a/maxscale-system-test/masking/masking_mysqltest/masking_rules.json +++ b/maxscale-system-test/masking/masking_mysqltest/masking_rules.json @@ -5,7 +5,7 @@ "column": "a" }, "with": { - "fill": "X" + "fill": "Y" } }, { @@ -22,7 +22,7 @@ }, "with": { "value": "012345-ABCD", - "fill": "X" + "fill": "Y" } } ] diff --git a/maxscale-system-test/masking/masking_mysqltest/r/masking_column.result b/maxscale-system-test/masking/masking_mysqltest/r/masking_column.result index fb9cfd59f..9fd91f52b 100644 --- a/maxscale-system-test/masking/masking_mysqltest/r/masking_column.result +++ b/maxscale-system-test/masking/masking_mysqltest/r/masking_column.result @@ -11,12 +11,12 @@ insert into masking values ("hello", NULL, NULL); insert into masking values (NULL, NULL, NULL); select * from masking; a x y -XXXXX hello hello +YYYYY hello hello NULL hello hello -XXXXX NULL hello -XXXXX hello NULL +YYYYY NULL hello +YYYYY hello NULL NULL NULL hello -XXXXX NULL NULL +YYYYY NULL NULL NULL NULL NULL drop table masking; create table masking (x TEXT, a TEXT, y TEXT); @@ -29,10 +29,10 @@ insert into masking values ("hello", NULL, NULL); insert into masking values (NULL, NULL, NULL); select * from masking; x a y -hello XXXXX hello -NULL XXXXX hello +hello YYYYY hello +NULL YYYYY hello hello NULL hello -hello XXXXX NULL +hello YYYYY NULL NULL NULL hello hello NULL NULL NULL NULL NULL @@ -47,11 +47,11 @@ insert into masking values ("hello", NULL, NULL); insert into masking values (NULL, NULL, NULL); select * from masking; x y a -hello hello XXXXX -NULL hello XXXXX -hello NULL XXXXX +hello hello YYYYY +NULL hello YYYYY +hello NULL YYYYY hello hello NULL -NULL NULL XXXXX +NULL NULL YYYYY hello NULL NULL NULL NULL NULL drop table masking; diff --git a/maxscale-system-test/masking/masking_mysqltest/r/masking_replace.result b/maxscale-system-test/masking/masking_mysqltest/r/masking_replace.result index afbd3a9dd..0fca68826 100644 --- a/maxscale-system-test/masking/masking_mysqltest/r/masking_replace.result +++ b/maxscale-system-test/masking/masking_mysqltest/r/masking_replace.result @@ -5,15 +5,15 @@ create table masking (a TEXT, b TEXT, c TEXT); insert into masking values ("blah", "012345-ABC", "012345-ABC"); select * from masking; a b c -XXXX 012345-ABC XXXXXXXXXX +YYYY XXXXXXXXXX YYYYYYYYYY delete from masking; insert into masking values ("blahblah", "221073-01AB", "012345-ABC"); select * from masking; a b c -XXXXXXXX 012345-ABCD XXXXXXXXXX +YYYYYYYY 012345-ABCD YYYYYYYYYY delete from masking; insert into masking values ("221073-01AB", "221073-01AB", "221073-01AB"); select * from masking; a b c -XXXXXXXXXXX 012345-ABCD 012345-ABCD +YYYYYYYYYYY 012345-ABCD 012345-ABCD delete from masking; diff --git a/maxscale-system-test/masking/masking_mysqltest/r/masking_smoke.result b/maxscale-system-test/masking/masking_mysqltest/r/masking_smoke.result index ae84e1d18..6a454fe21 100644 --- a/maxscale-system-test/masking/masking_mysqltest/r/masking_smoke.result +++ b/maxscale-system-test/masking/masking_mysqltest/r/masking_smoke.result @@ -31,46 +31,46 @@ insert into masking_ENUM values ("aaa"); insert into masking_SET values ("aaa"); select * from masking_BINARY; a -XXX +YYY select * from masking_VARBINARY; a -XXX +YYY select * from masking_CHAR; a -XXX +YYY select * from masking_VARCHAR; a -XXX +YYY select * from masking_BLOB; a -XXX +YYY select * from masking_TINYBLOB; a -XXX +YYY select * from masking_MEDIUMBLOB; a -XXX +YYY select * from masking_LONGBLOB; a -XXX +YYY select * from masking_TEXT; a -XXX +YYY select * from masking_TINYTEXT; a -XXX +YYY select * from masking_MEDIUMTEXT; a -XXX +YYY select * from masking_LONGTEXT; a -XXX +YYY select * from masking_ENUM; a -XXX +YYY select * from masking_SET; a -XXX +YYY create table masking_INT (a INT); create table masking_REAL (a REAL(3, 2)); create table masking_DECIMAL (a DECIMAL(3, 2)); diff --git a/maxscale-system-test/masking/masking_mysqltest/t/masking_replace.test b/maxscale-system-test/masking/masking_mysqltest/t/masking_replace.test index e95534f53..331ecc37f 100644 --- a/maxscale-system-test/masking/masking_mysqltest/t/masking_replace.test +++ b/maxscale-system-test/masking/masking_mysqltest/t/masking_replace.test @@ -10,7 +10,7 @@ # "column": "a" # }, # "with": { -# "fill": "X" +# "fill": "Y" # } # }, # { @@ -27,7 +27,7 @@ # }, # "with": { # "value": "012345-ABCD", -# "fill": "X" +# "fill": "Y" # } # } # ] @@ -46,38 +46,38 @@ use maskingdb; # create table masking (a TEXT, b TEXT, c TEXT); -# - a should be just "X...", -# - b should be unchanged as the length does not match the string of "value", and -# there is no catch all "fill". -# - c should be just "X..." as the length does not match, so "value" is not applied +# - a should be just "Y...", +# - b should be changed into "X..." as the length does not match and there is no +# specific fill value, so the default "X" is used. +# - c should be just "Y..." as the length does not match, so "value" is not applied # and has "fill", which is applied. # #a b c -#XXXX 012345-ABC XXXXXXXXXX +#YYYY XXXXXXXXXX YYYYYYYYYY insert into masking values ("blah", "012345-ABC", "012345-ABC"); select * from masking; delete from masking; -# - a should be just "X...", +# - a should be just "Y...", # - b should be changed as the length matches the length of the string of "value" -# - c should be just "X..." as the length does not match, so "value" is not applied +# - c should be just "Y..." as the length does not match, so "value" is not applied # and has "fill", which is applied. # #a b c -#XXXXXXXX 012345-ABCD XXXXXXXXXX +#YYYYYYYY 012345-ABCD YYYYYYYYYY insert into masking values ("blahblah", "221073-01AB", "012345-ABC"); select * from masking; delete from masking; -# - a should be just "X...", +# - a should be just "Y...", # - b should be changed as the length matches the length of the string of "value" # - c should be chanched into a specific string as the length matches the string of # "value" # #a b c #a b c -#XXXXXXXXXXX 012345-ABCD 012345-ABCD -# a should still be just "X", b should be "012345-ABCD" and c should be "012345-ABCD" +#YYYYYYYYYYY 012345-ABCD 012345-ABCD +# a should still be just "Y", b should be "012345-ABCD" and c should be "012345-ABCD" insert into masking values ("221073-01AB", "221073-01AB", "221073-01AB"); select * from masking; delete from masking; diff --git a/maxscale-system-test/mm_mysqlmon.cpp b/maxscale-system-test/mm_mysqlmon.cpp index 0c2677c31..e5e251808 100644 --- a/maxscale-system-test/mm_mysqlmon.cpp +++ b/maxscale-system-test/mm_mysqlmon.cpp @@ -42,7 +42,7 @@ void check_status(TestConnections *Test, const char *server, const char *status) void check_group(TestConnections *Test, const char *server, const char *group) { - char *output = Test->ssh_maxscale_output(true, "maxadmin show monitor \"MySQL Monitor\""); + char *output = Test->ssh_maxscale_output(true, "maxadmin show monitor MySQL-Monitor"); if (output == NULL) { diff --git a/maxscale-system-test/mxs722.cpp b/maxscale-system-test/mxs722.cpp index 332f4d35c..bf114efb5 100644 --- a/maxscale-system-test/mxs722.cpp +++ b/maxscale-system-test/mxs722.cpp @@ -34,10 +34,9 @@ int main(int argc, char *argv[]) test->ssh_maxscale(true, "cp /etc/maxscale.cnf.backup /etc/maxscale.cnf"); /** Set router_options to a bad value */ - // Disabled for 2.0 - //test->ssh_maxscale(true, "sed -i -e 's/router_options.*/router_options=bad_option=true/' /etc/maxscale.cnf"); - //test->add_result(baseline == test->ssh_maxscale(true, "maxscale -c --user=maxscale"), - // "Bad router_options should be detected.\n"); + test->ssh_maxscale(true, "sed -i -e 's/router_options.*/router_options=bad_option=true/' /etc/maxscale.cnf"); + test->add_result(baseline == test->ssh_maxscale(true, "maxscale -c --user=maxscale"), + "Bad router_options should be detected.\n"); test->ssh_maxscale(true, "cp /etc/maxscale.cnf.backup /etc/maxscale.cnf"); diff --git a/maxscale-system-test/rw_select_insert.cpp b/maxscale-system-test/rw_select_insert.cpp index 11de0eb1c..fd31594a6 100644 --- a/maxscale-system-test/rw_select_insert.cpp +++ b/maxscale-system-test/rw_select_insert.cpp @@ -132,7 +132,7 @@ int main(int argc, char *argv[]) Test->tprintf("Connecting to RWSplit %s\n", Test->maxscale_IP); Test->connect_rwsplit(); - Test->execute_maxadmin_command((char *) "shutdown monitor \"MySQL Monitor\""); + Test->execute_maxadmin_command((char *) "shutdown monitor MySQL-Monitor"); get_global_status_allnodes(&selects[0], &inserts[0], Test->repl, silent); diff --git a/maxscale-system-test/session_limits.cpp b/maxscale-system-test/session_limits.cpp index 3ed6574a4..165d2a6e5 100644 --- a/maxscale-system-test/session_limits.cpp +++ b/maxscale-system-test/session_limits.cpp @@ -14,49 +14,40 @@ router_options=max_sescmd_history=10 * - execute one more session commad, excpect failure */ - - -#include #include "testconnections.h" int main(int argc, char *argv[]) { - TestConnections * Test = new TestConnections(argc, argv); - Test->set_timeout(200); - int i; - char sql[256]; + TestConnections test(argc, argv); + int first_sleep = 5; + int second_sleep = 12; - Test->tprintf("Open session and wait 20 seconds\n"); - Test->connect_maxscale(); - sleep(20); - Test->tprintf("Execute query to check session\n"); - Test->try_query(Test->conn_rwsplit, "SELECT 1"); + test.set_timeout(200); - Test->tprintf("Wait 35 seconds more and try quiry again expecting failure\n"); - sleep(35); - if (execute_query(Test->conn_rwsplit, "SELECT 1") == 0) + test.tprintf("Open session, wait %d seconds and execute a query", first_sleep); + test.connect_maxscale(); + sleep(first_sleep); + test.try_query(test.conn_rwsplit, "SELECT 1"); + + test.tprintf("Wait %d seconds and execute query, expecting failure", second_sleep); + sleep(second_sleep); + test.add_result(execute_query(test.conn_rwsplit, "SELECT 1") == 0, + "Session was not closed after %d seconds", + second_sleep); + test.close_maxscale_connections(); + + test.tprintf("Open session and execute 10 session commands"); + test.connect_maxscale(); + for (int i = 0; i < 10; i++) { - Test->add_result(1, "Session was not closed after 40 seconds\n"); + test.try_query(test.conn_rwsplit, "set @test=1"); } - Test->close_maxscale_connections(); - Test->tprintf("Open session and execute 10 session commands\n"); - fflush(stdout); - Test->connect_maxscale(); - for (i = 0; i < 10; i++) - { - sprintf(sql, "set @test=%d", i); - Test->try_query(Test->conn_rwsplit, sql); - } - Test->tprintf("done!\n"); + test.tprintf("Execute one more session command and expect message in error log"); + execute_query(test.conn_rwsplit, "set @test=1"); + sleep(1); + test.check_log_err("Router session exceeded session command history limit", true); + test.close_maxscale_connections(); - Test->tprintf("Execute one more session command and expect message in error log\n"); - execute_query(Test->conn_rwsplit, "set @test=11"); - sleep(5); - Test->check_log_err((char *) "Router session exceeded session command history limit", true); - Test->close_maxscale_connections(); - - int rval = Test->global_result; - delete Test; - return rval; + return test.global_result; } diff --git a/maxscale-system-test/sql_t1.cpp b/maxscale-system-test/sql_t1.cpp index d3967dba1..222402b4e 100644 --- a/maxscale-system-test/sql_t1.cpp +++ b/maxscale-system-test/sql_t1.cpp @@ -6,7 +6,7 @@ pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static char** sql = NULL; static size_t sql_size = 0; -int execute_select_query_and_check(MYSQL *conn, char *sql, unsigned long long int rows) +int execute_select_query_and_check(MYSQL *conn, const char *sql, unsigned long long int rows) { MYSQL_RES *res; MYSQL_ROW row; diff --git a/maxscale-system-test/sql_t1.h b/maxscale-system-test/sql_t1.h index 512243f66..ba2d3bea9 100644 --- a/maxscale-system-test/sql_t1.h +++ b/maxscale-system-test/sql_t1.h @@ -11,7 +11,7 @@ * @param rows Expected number of rows * @return 0 in case of success */ -int execute_select_query_and_check(MYSQL *conn, char *sql, unsigned long long int rows); +int execute_select_query_and_check(MYSQL *conn, const char *sql, unsigned long long int rows); /** * @brief create_t1 Create t1 table, fileds: (x1 int, fl int) diff --git a/maxscale-system-test/temporal_tables.cpp b/maxscale-system-test/temporal_tables.cpp index f0773adf9..79cf1f81c 100644 --- a/maxscale-system-test/temporal_tables.cpp +++ b/maxscale-system-test/temporal_tables.cpp @@ -1,16 +1,16 @@ /** - * @file temporal_tables.cpp Check temporal tables commands functionality (relates to bug 430) + * Check temporary tables commands functionality (relates to bug 430) + * * - create t1 table and put some data into it - * - create tempral table t1 + * - create temporary table t1 * - insert different data into t1 - * - check that SELECT FROM t1 gives data from tempral table - * - create other connections using all Maxscale services and check that SELECT via these connections gives data from main t1, not temporal - * - dropping tempral t1 + * - check that SELECT FROM t1 gives data from temporary table + * - create other connections using all MaxScale services and check that SELECT + * via these connections gives data from main t1, not temporary + * - dropping temporary t1 * - check that data from main t1 is not affected */ - -#include #include "testconnections.h" #include "sql_t1.h" @@ -18,82 +18,45 @@ using namespace std; int main(int argc, char *argv[]) { + TestConnections test(argc, argv); + test.connect_maxscale(); - TestConnections * Test = new TestConnections(argc, argv); + test.tprintf("Create a table and insert two rows into it"); + test.set_timeout(30); - Test->repl->connect(); + execute_query(test.conn_rwsplit, "USE test"); + create_t1(test.conn_rwsplit); + execute_query(test.conn_rwsplit, "INSERT INTO t1 (x1, fl) VALUES(0, 1)"); + execute_query(test.conn_rwsplit, "INSERT INTO t1 (x1, fl) VALUES(1, 1)"); - MYSQL * conn; - char sql[100]; + test.tprintf("Create temporary table and insert one row"); + test.set_timeout(30); - Test->set_timeout(40); - conn = Test->open_rwsplit_connection(); + execute_query(test.conn_rwsplit, "create temporary table t1 as (SELECT * FROM t1 WHERE fl=3)"); + execute_query(test.conn_rwsplit, "INSERT INTO t1 (x1, fl) VALUES(0, 1)"); - Test->tprintf("Cleaning up DB\n"); - execute_query(conn, (char *) "DROP DATABASE IF EXISTS test"); - execute_query(conn, (char *) "CREATE DATABASE test"); - execute_query(conn, (char *) "USE test"); + test.tprintf("Check that the temporary table has one row"); + test.set_timeout(90); - Test->tprintf("creating table t1\n"); - Test->set_timeout(40); - create_t1(conn); + test.add_result(execute_select_query_and_check(test.conn_rwsplit, "SELECT * FROM t1", 1), + "Current connection should show one row"); + test.add_result(execute_select_query_and_check(test.conn_master, "SELECT * FROM t1", 2), + "New connection should show two rows"); + test.add_result(execute_select_query_and_check(test.conn_slave, "SELECT * FROM t1", 2), + "New connection should show two rows"); - Test->tprintf("Inserting two rows into t1\n"); - Test->set_timeout(40); - execute_query(conn, "INSERT INTO t1 (x1, fl) VALUES(0, 1);"); - execute_query(conn, "INSERT INTO t1 (x1, fl) VALUES(1, 1);"); + printf("Drop temporary table and check that the real table has two rows"); + test.set_timeout(90); - Test->tprintf("Creating temporal table t1\n"); - execute_query(conn, "create temporary table t1 as (SELECT * FROM t1 WHERE fl=3);"); + execute_query(test.conn_rwsplit, "DROP TABLE t1"); + test.add_result(execute_select_query_and_check(test.conn_rwsplit, "SELECT * FROM t1", 2), + "check failed"); + test.add_result(execute_select_query_and_check(test.conn_master, "SELECT * FROM t1", 2), + "check failed"); + test.add_result(execute_select_query_and_check(test.conn_slave, "SELECT * FROM t1", 2), + "check failed"); - Test->tprintf("Inserting one row into temporal table\n"); - execute_query(conn, "INSERT INTO t1 (x1, fl) VALUES(0, 1);"); + test.close_maxscale_connections(); - Test->tprintf("Checking t1 temporal table\n"); - Test->set_timeout(240); - Test->add_result(execute_select_query_and_check(conn, (char *) "SELECT * FROM t1;", 1), "check failed\n"); - - - Test->tprintf("Connecting to all MaxScale routers and checking main t1 table (not temporal)\n"); - Test->set_timeout(240); - Test->add_result(Test->connect_maxscale(), "Connectiong to Maxscale failed\n"); - Test->tprintf("Checking t1 table using RWSplit router\n"); - Test->set_timeout(240); - Test->add_result(execute_select_query_and_check(Test->conn_rwsplit, (char *) "SELECT * FROM t1;", 2), - "check failed\n"); - Test->tprintf("Checking t1 table using ReadConn router in master mode\n"); - Test->set_timeout(240); - Test->add_result(execute_select_query_and_check(Test->conn_master, (char *) "SELECT * FROM t1;", 2), - "check failed\n"); - Test->tprintf("Checking t1 table using ReadConn router in slave mode\n"); - Test->set_timeout(240); - Test->add_result(execute_select_query_and_check(Test->conn_slave, (char *) "SELECT * FROM t1;", 2), - "check failed\n"); - Test->close_maxscale_connections(); - - - printf("Dropping temparal table and check main table again\n"); - execute_query(conn, "DROP TABLE t1;"); - - printf("Connecting to all MaxScale routers and checking main t1 table (not temporal)\n"); - Test->add_result(Test->connect_maxscale(), "Connectiong to Maxscale failed\n"); - Test->tprintf("Checking t1 table using RWSplit router\n"); - Test->set_timeout(240); - Test->add_result(execute_select_query_and_check(Test->conn_rwsplit, (char *) "SELECT * FROM t1;", 2), - "check failed\n"); - Test->tprintf("Checking t1 table using ReadConn router in master mode\n"); - Test->set_timeout(240); - Test->add_result(execute_select_query_and_check(Test->conn_master, (char *) "SELECT * FROM t1;", 2), - "check failed\n"); - Test->tprintf("Checking t1 table using ReadConn router in slave mode\n"); - Test->set_timeout(240); - Test->add_result(execute_select_query_and_check(Test->conn_slave, (char *) "SELECT * FROM t1;", 2), - "check failed\n"); - Test->close_maxscale_connections(); - - mysql_close(conn); - - int rval = Test->global_result; - delete Test; - return rval; + return test.global_result; } diff --git a/maxscale-system-test/testconnections.cpp b/maxscale-system-test/testconnections.cpp index 9d6a2e446..bb112901f 100644 --- a/maxscale-system-test/testconnections.cpp +++ b/maxscale-system-test/testconnections.cpp @@ -1933,7 +1933,7 @@ int TestConnections::check_maxadmin_param(const char *command, const char *param return rval; } -int TestConnections::get_maxadmin_param(char *command, char *param, char *result) +int TestConnections::get_maxadmin_param(const char *command, const char *param, char *result) { char * buf; diff --git a/maxscale-system-test/testconnections.h b/maxscale-system-test/testconnections.h index a79c01a93..a7cb095d0 100644 --- a/maxscale-system-test/testconnections.h +++ b/maxscale-system-test/testconnections.h @@ -647,7 +647,7 @@ public: int execute_maxadmin_command(char * cmd); int execute_maxadmin_command_print(char * cmd); int check_maxadmin_param(const char *command, const char *param, const char *value); - int get_maxadmin_param(char *command, char *param, char *result); + int get_maxadmin_param(const char *command, const char *param, char *result); void check_current_operations(int value); void check_current_connections(int value); diff --git a/query_classifier/CMakeLists.txt b/query_classifier/CMakeLists.txt index 5a9d73100..dabba63db 100644 --- a/query_classifier/CMakeLists.txt +++ b/query_classifier/CMakeLists.txt @@ -1,6 +1,5 @@ add_subdirectory(qc_mysqlembedded) add_subdirectory(qc_sqlite) -add_subdirectory(qc_dummy) if(BUILD_TESTS) add_subdirectory(test) diff --git a/query_classifier/qc_dummy/CMakeLists.txt b/query_classifier/qc_dummy/CMakeLists.txt deleted file mode 100644 index b7712a67b..000000000 --- a/query_classifier/qc_dummy/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_library(qc_dummy SHARED qc_dummy.cc) -set_target_properties(qc_dummy PROPERTIES VERSION "1.0.0") -set_target_properties(qc_dummy PROPERTIES LINK_FLAGS -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/qc_dummy.map) -install_module(qc_dummy core) diff --git a/query_classifier/qc_dummy/qc_dummy.cc b/query_classifier/qc_dummy/qc_dummy.cc deleted file mode 100644 index 358411b30..000000000 --- a/query_classifier/qc_dummy/qc_dummy.cc +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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. - */ - -#define MXS_MODULE_NAME "qc_sqlite" -#include - -#include "../../server/core/maxscale/config.h" - -int32_t qc_dummy_parse(GWBUF* querybuf, uint32_t collect, int32_t* pResult) -{ - *pResult = QC_QUERY_INVALID; - return QC_RESULT_OK; -} - -int32_t qc_dummy_get_type_mask(GWBUF* querybuf, uint32_t* pType_mask) -{ - *pType_mask = QUERY_TYPE_UNKNOWN; - return QC_RESULT_OK; -} - -int32_t qc_dummy_get_table_names(GWBUF* querybuf, int32_t fullnames, char*** ppzNames, int32_t* pSize) -{ - *ppzNames = NULL; - *pSize = 0; - return QC_RESULT_OK; -} - -int32_t qc_dummy_get_created_table_name(GWBUF* querybuf, char** pzName) -{ - *pzName = NULL; - return QC_RESULT_OK; -} - -int32_t qc_dummy_is_drop_table_query(GWBUF* querybuf, int32_t* pIs_drop_table) -{ - *pIs_drop_table = 0; - return QC_RESULT_OK; -} - -int32_t qc_dummy_query_has_clause(GWBUF* buf, int32_t *pHas_clause) -{ - *pHas_clause = 0; - return QC_RESULT_OK; -} - -int32_t qc_dummy_get_database_names(GWBUF* querybuf, char*** ppzNames, int32_t* pSize) -{ - *ppzNames = NULL; - *pSize = 0; - return QC_RESULT_OK; -} - -int32_t qc_dummy_get_operation(GWBUF* querybuf, int32_t* pOp) -{ - *pOp = QUERY_OP_UNDEFINED; - return QC_RESULT_OK; -} - -int32_t qc_dummy_get_prepare_name(GWBUF* query, char** pzName) -{ - *pzName = NULL; - return QC_RESULT_OK; -} - -int32_t qc_dummy_get_field_info(GWBUF* query, const QC_FIELD_INFO** ppInfos, uint32_t* nInfos) -{ - *ppInfos = NULL; - *nInfos = 0; - return QC_RESULT_OK; -} - -int32_t qc_dummy_get_function_info(GWBUF* query, const QC_FUNCTION_INFO** ppInfos, uint32_t* nInfos) -{ - *ppInfos = NULL; - *nInfos = 0; - return QC_RESULT_OK; -} - -int32_t qc_dummy_setup(const char* args) -{ - return QC_RESULT_OK; -} - -int32_t qc_dummy_process_init(void) -{ - return QC_RESULT_OK; -} - -void qc_dummy_process_end(void) -{ -} - -int32_t qc_dummy_thread_init(void) -{ - return QC_RESULT_OK; -} - -void qc_dummy_thread_end(void) -{ -} - -int32_t qc_dummy_get_preparable_stmt(GWBUF* stmt, GWBUF** preparable_stmt) -{ - *preparable_stmt = NULL; - return QC_RESULT_OK; -} - -extern "C" -{ - MXS_MODULE* MXS_CREATE_MODULE() - { - static QUERY_CLASSIFIER qc = - { - qc_dummy_setup, - qc_dummy_process_init, - qc_dummy_process_end, - qc_dummy_thread_init, - qc_dummy_thread_end, - qc_dummy_parse, - qc_dummy_get_type_mask, - qc_dummy_get_operation, - qc_dummy_get_created_table_name, - qc_dummy_is_drop_table_query, - qc_dummy_get_table_names, - NULL, - qc_dummy_query_has_clause, - qc_dummy_get_database_names, - qc_dummy_get_prepare_name, - qc_dummy_get_field_info, - qc_dummy_get_function_info, - qc_dummy_get_preparable_stmt, - }; - - static MXS_MODULE info = - { - MXS_MODULE_API_QUERY_CLASSIFIER, - MXS_MODULE_IN_DEVELOPMENT, - QUERY_CLASSIFIER_VERSION, - "Dummy Query Classifier", - "V1.0.0", - MXS_NO_MODULE_CAPABILITIES, - &qc, - qc_dummy_process_init, - qc_dummy_process_end, - qc_dummy_thread_init, - qc_dummy_thread_end, - { - {MXS_END_MODULE_PARAMS} - } - }; - - return &info; - } -} diff --git a/query_classifier/qc_dummy/qc_dummy.map b/query_classifier/qc_dummy/qc_dummy.map deleted file mode 100644 index f86f46a00..000000000 --- a/query_classifier/qc_dummy/qc_dummy.map +++ /dev/null @@ -1,6 +0,0 @@ -{ - global: - mxs_get_module_object; - local: - *; -}; diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index df11b1b01..467456079 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -94,6 +94,14 @@ typedef struct parsing_info_st #endif } parsing_info_t; +static thread_local struct +{ + // The version information is not used; the embedded library parses according + // to the version of the embedded library it has been linked with. However, we + // need to store the information so that qc_[get|set]_server_version will work. + uint64_t version; +} this_thread; + #define QTYPE_LESS_RESTRICTIVE_THAN_WRITE(t) (t 10) || + ((major == 10) && (minor > 2)) || + ((major == 10) && (minor == 2) && (patch >= 3))) + { + value = bsearch(key, BUILTIN_10_2_3_FUNCTIONS, N_BUILTIN_10_2_3_FUNCTIONS, + sizeof(char*), search_compare); + } + } + return value ? true : false; } diff --git a/query_classifier/qc_sqlite/builtin_functions.h b/query_classifier/qc_sqlite/builtin_functions.h index 9c0da9e47..49768aee8 100644 --- a/query_classifier/qc_sqlite/builtin_functions.h +++ b/query_classifier/qc_sqlite/builtin_functions.h @@ -1,11 +1,19 @@ -#ifndef BUILTIN_FUNCTIONS_H -#define BUILTIN_FUNCTIONS_H -/** - * @LICENCE@ +#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 #ifdef __cplusplus extern "C" { @@ -14,10 +22,8 @@ extern "C" { void init_builtin_functions(); void finish_builtin_functions(); -bool is_builtin_readonly_function(const char* zToken); +bool is_builtin_readonly_function(const char* zToken, uint32_t major, uint32_t minor, uint32_t patch); #ifdef __cplusplus } #endif - -#endif diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index 2f561d1d0..f9dfa289a 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -114,6 +114,9 @@ static thread_local struct bool initialized; sqlite3* db; // Thread specific database handle. QC_SQLITE_INFO* info; + uint32_t version_major; + uint32_t version_minor; + uint32_t version_patch; } this_thread; /** @@ -1102,7 +1105,10 @@ static void update_field_infos(QC_SQLITE_INFO* info, { info->type_mask |= (QUERY_TYPE_READ | QUERY_TYPE_MASTER_READ); } - else if (!is_builtin_readonly_function(zToken)) + else if (!is_builtin_readonly_function(zToken, + this_thread.version_major, + this_thread.version_minor, + this_thread.version_patch)) { info->type_mask |= QUERY_TYPE_WRITE; } @@ -2026,6 +2032,7 @@ void maxscaleExecute(Parse* pParse, Token* pName) info->status = QC_QUERY_PARSED; info->type_mask = QUERY_TYPE_WRITE; + info->operation = QUERY_OP_EXECUTE; // If information is collected in several passes, then we may // this information already. @@ -2907,6 +2914,8 @@ static int32_t qc_sqlite_get_canonical(GWBUF* query, char** canonical); static int32_t qc_sqlite_query_has_clause(GWBUF* query, int32_t* has_clause); static int32_t qc_sqlite_get_database_names(GWBUF* query, char*** names, int* sizep); static int32_t qc_sqlite_get_preparable_stmt(GWBUF* stmt, GWBUF** preparable_stmt); +static void qc_sqlite_set_server_version(uint64_t version); +static void qc_sqlite_get_server_version(uint64_t* version); static bool get_key_and_value(char* arg, const char** pkey, const char** pvalue) { @@ -3080,6 +3089,9 @@ static int32_t qc_sqlite_thread_init(void) this_thread.info = NULL; this_thread.initialized = true; + this_thread.version_major = 0; + this_thread.version_minor = 0; + this_thread.version_patch = 0; } else { @@ -3522,6 +3534,26 @@ int32_t qc_sqlite_get_preparable_stmt(GWBUF* stmt, GWBUF** preparable_stmt) return rv; } +static void qc_sqlite_set_server_version(uint64_t version) +{ + QC_TRACE(); + + uint32_t major = version / 10000; + uint32_t minor = (version - major * 10000) / 100; + uint32_t patch = version - major * 10000 - minor * 100; + + this_thread.version_major = major; + this_thread.version_minor = minor; + this_thread.version_patch = patch; +} + +static void qc_sqlite_get_server_version(uint64_t* version) +{ + QC_TRACE(); + + *version = this_thread.version_major * 10000 + this_thread.version_minor * 100 + this_thread.version_patch; +} + /** * EXPORTS */ @@ -3548,6 +3580,8 @@ MXS_MODULE* MXS_CREATE_MODULE() qc_sqlite_get_field_info, qc_sqlite_get_function_info, qc_sqlite_get_preparable_stmt, + qc_sqlite_set_server_version, + qc_sqlite_get_server_version, }; static MXS_MODULE info = diff --git a/query_classifier/test/CMakeLists.txt b/query_classifier/test/CMakeLists.txt index 1311ad572..a67ea4da6 100644 --- a/query_classifier/test/CMakeLists.txt +++ b/query_classifier/test/CMakeLists.txt @@ -22,6 +22,9 @@ if (BUILD_QC_MYSQLEMBEDDED) add_executable(compare compare.cc testreader.cc) target_link_libraries(compare maxscale-common) + add_executable(version_sensitivity version_sensitivity.cc) + target_link_libraries(version_sensitivity maxscale-common) + add_executable(crash_qc_sqlite crash_qc_sqlite.c) target_link_libraries(crash_qc_sqlite maxscale-common) @@ -39,6 +42,8 @@ if (BUILD_QC_MYSQLEMBEDDED) add_test(TestQC_CompareUpdate compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/update.test) add_test(TestQC_CompareMaxScale compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/maxscale.test) add_test(TestQC_CompareWhiteSpace compare -v 2 -S -s "select user from mysql.user; ") + + add_test(TestQC_version_sensitivity version_sensitivity) endif() add_subdirectory(canonical_tests) diff --git a/query_classifier/test/version_sensitivity.cc b/query_classifier/test/version_sensitivity.cc new file mode 100644 index 000000000..3e43963ad --- /dev/null +++ b/query_classifier/test/version_sensitivity.cc @@ -0,0 +1,161 @@ +/* + * 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 +#define MYSQL_COM_QUIT COM_QUIT +#define MYSQL_COM_INIT_DB COM_INIT_DB +#define MYSQL_COM_CHANGE_USER COM_CHANGE_USER +#include +#include +#include +#include +#include + +using namespace std; + +namespace +{ + +GWBUF* create_gwbuf(const string& s) +{ + size_t len = s.length(); + size_t payload_len = len + 1; + size_t gwbuf_len = MYSQL_HEADER_LEN + payload_len; + + GWBUF* gwbuf = gwbuf_alloc(gwbuf_len); + + *((unsigned char*)((char*)GWBUF_DATA(gwbuf))) = payload_len; + *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 1)) = (payload_len >> 8); + *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 2)) = (payload_len >> 16); + *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 3)) = 0x00; + *((unsigned char*)((char*)GWBUF_DATA(gwbuf) + 4)) = 0x03; + memcpy((char*)GWBUF_DATA(gwbuf) + 5, s.c_str(), len); + + return gwbuf; +} + +} + +namespace +{ + +bool test(const string& s, uint32_t expected) +{ + GWBUF* pBuf = create_gwbuf(s); + + uint32_t type_mask = qc_get_type_mask(pBuf); + + gwbuf_free(pBuf); + + bool success = (type_mask == expected); + + if (!success) + { + cout << "error: " << s << " classified wrong." << endl; + } + + return success; +} + +int test() +{ + int rc = EXIT_SUCCESS; + + string valid_json("SELECT Json_Array(56, 3.1416, 'My name is \"Foo\"', NULL)"); + string invalid_json("SELECT Json_Foo(56, 3.1416, 'My name is \"Foo\"', NULL)"); + + SERVER_VERSION sv; + + // pre-Json + sv.major = 10; + sv.minor = 0; + sv.patch = 0; + + cout << "Testing pre-Json server." << endl; + + qc_set_server_version(server_encode_version(&sv)); + + if (!test(valid_json, QUERY_TYPE_READ | QUERY_TYPE_WRITE)) + { + rc = EXIT_FAILURE; + } + + if (!test(invalid_json, QUERY_TYPE_READ | QUERY_TYPE_WRITE)) + { + rc = EXIT_FAILURE; + } + + cout << "Testing post-Json server." << endl; + + // post-Json + sv.major = 10; + sv.minor = 2; + sv.patch = 3; + + qc_set_server_version(server_encode_version(&sv)); + + if (!test(valid_json, QUERY_TYPE_READ)) + { + rc = EXIT_FAILURE; + } + + if (!test(invalid_json, QUERY_TYPE_READ | QUERY_TYPE_WRITE)) + { + rc = EXIT_FAILURE; + } + + return rc; +} + +} + +int main(int argc, char* argv[]) +{ + int rc = EXIT_FAILURE; + + set_datadir(strdup("/tmp")); + set_langdir(strdup(".")); + set_process_datadir(strdup("/tmp")); + + if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT)) + { + const char QC_LIB[] = "qc_sqlite"; + const char LIBDIR[] = "../qc_sqlite"; + + set_libdir(strdup(LIBDIR)); + + if (qc_setup(QC_LIB, NULL)) + { + if (qc_process_init(QC_INIT_BOTH)) + { + rc = test(); + } + else + { + cerr << "error: Could not perform process initialization for " << QC_LIB << "." << endl; + } + } + else + { + cerr << "error: Could not setup " << QC_LIB << "." << endl; + } + + mxs_log_finish(); + } + else + { + cerr << "error: Could not initialize log." << endl; + } + + return rc; +} diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index 7ef6a6f32..371e550ec 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -4,6 +4,7 @@ add_library(maxscale-common SHARED alloc.cc atomic.cc authenticator.cc + backend.cc buffer.cc config.cc config_runtime.cc @@ -41,6 +42,7 @@ add_library(maxscale-common SHARED server.cc service.cc session.cc + session_command.cc skygw_utils.cc spinlock.cc ssl.cc diff --git a/server/core/atomic.cc b/server/core/atomic.cc index e2a8119dd..1fc810572 100644 --- a/server/core/atomic.cc +++ b/server/core/atomic.cc @@ -53,7 +53,7 @@ uint64_t atomic_add_uint64(uint64_t *variable, int64_t value) #endif } -int atomic_load_int32(int *variable) +int atomic_load_int32(const int *variable) { #ifdef MXS_USE_ATOMIC_BUILTINS return __atomic_load_n(variable, __ATOMIC_SEQ_CST); @@ -62,7 +62,7 @@ int atomic_load_int32(int *variable) #endif } -int64_t atomic_load_int64(int64_t *variable) +int64_t atomic_load_int64(const int64_t *variable) { #ifdef MXS_USE_ATOMIC_BUILTINS return __atomic_load_n(variable, __ATOMIC_SEQ_CST); @@ -71,7 +71,7 @@ int64_t atomic_load_int64(int64_t *variable) #endif } -uint64_t atomic_load_uint64(uint64_t *variable) +uint64_t atomic_load_uint64(const uint64_t *variable) { #ifdef MXS_USE_ATOMIC_BUILTINS return __atomic_load_n(variable, __ATOMIC_SEQ_CST); @@ -80,7 +80,7 @@ uint64_t atomic_load_uint64(uint64_t *variable) #endif } -void* atomic_load_ptr(void **variable) +void* atomic_load_ptr(void * const *variable) { #ifdef MXS_USE_ATOMIC_BUILTINS return __atomic_load_n(variable, __ATOMIC_SEQ_CST); diff --git a/server/core/backend.cc b/server/core/backend.cc new file mode 100644 index 000000000..133ceefc6 --- /dev/null +++ b/server/core/backend.cc @@ -0,0 +1,316 @@ +/* + * 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 +#include + +using namespace maxscale; + +Backend::Backend(SERVER_REF *ref): + m_closed(false), + m_backend(ref), + m_dcb(NULL), + m_state(0) +{ + std::stringstream ss; + ss << "[" << server()->name << "]:" << server()->port; + m_uri = ss.str(); +} + +Backend::~Backend() +{ + ss_dassert(m_closed || !in_use()); + + if (in_use()) + { + close(); + } +} + +void Backend::close(close_type type) +{ + if (!m_closed) + { + m_closed = true; + + if (in_use()) + { + CHK_DCB(m_dcb); + + /** Clean operation counter in bref and in SERVER */ + if (is_waiting_result()) + { + clear_state(WAITING_RESULT); + } + clear_state(IN_USE); + + if (type == CLOSE_FATAL) + { + set_state(FATAL_FAILURE); + } + + dcb_close(m_dcb); + + /** decrease server current connection counters */ + atomic_add(&m_backend->connections, -1); + } + } + else + { + ss_dassert(false); + } +} + +bool Backend::execute_session_command() +{ + if (is_closed() || !session_command_count()) + { + return false; + } + + CHK_DCB(m_dcb); + + SessionCommandList::iterator iter = m_session_commands.begin(); + SessionCommand& sescmd = *(*iter); + GWBUF *buffer = sescmd.copy_buffer().release(); + bool rval = false; + + switch (sescmd.get_command()) + { + case MYSQL_COM_QUIT: + case MYSQL_COM_STMT_CLOSE: + rval = write(buffer, NO_RESPONSE); + break; + + case MYSQL_COM_CHANGE_USER: + /** This makes it possible to handle replies correctly */ + gwbuf_set_type(buffer, GWBUF_TYPE_SESCMD); + rval = auth(buffer); + break; + + case MYSQL_COM_QUERY: + default: + /** + * Mark session command buffer, it triggers writing + * MySQL command to protocol + */ + gwbuf_set_type(buffer, GWBUF_TYPE_SESCMD); + rval = write(buffer); + break; + } + + return rval; +} + +void Backend::append_session_command(GWBUF* buffer, uint64_t sequence) +{ + m_session_commands.push_back(SSessionCommand(new SessionCommand(buffer, sequence))); +} + +void Backend::append_session_command(const SSessionCommand& sescmd) +{ + m_session_commands.push_back(sescmd); +} + +void Backend::append_session_command(const SessionCommandList& sescmdlist) +{ + m_session_commands.insert(m_session_commands.end(), sescmdlist.begin(), sescmdlist.end()); +} + +uint64_t Backend::complete_session_command() +{ + uint64_t rval = m_session_commands.front()->get_position(); + m_session_commands.pop_front(); + return rval; +} + +size_t Backend::session_command_count() const +{ + return m_session_commands.size(); +} + +const SSessionCommand& Backend::next_session_command() const +{ + ss_dassert(session_command_count() > 0); + return m_session_commands.front(); +} + +void Backend::clear_state(backend_state state) +{ + if ((state & WAITING_RESULT) && (m_state & WAITING_RESULT)) + { + ss_debug(int prev2 = )atomic_add(&m_backend->server->stats.n_current_ops, -1); + ss_dassert(prev2 > 0); + } + + m_state &= ~state; +} + +void Backend::set_state(backend_state state) +{ + if ((state & WAITING_RESULT) && (m_state & WAITING_RESULT) == 0) + { + ss_debug(int prev2 = )atomic_add(&m_backend->server->stats.n_current_ops, 1); + ss_dassert(prev2 >= 0); + } + + m_state |= state; +} + +SERVER_REF* Backend::backend() const +{ + ss_dassert(m_backend); + return m_backend; +} + +SERVER* Backend::server() const +{ + ss_dassert(m_backend); + return m_backend->server; +} + +bool Backend::can_connect() const +{ + return !has_failed() && SERVER_IS_RUNNING(m_backend->server); +} + +bool Backend::connect(MXS_SESSION* session) +{ + bool rval = false; + + if ((m_dcb = dcb_connect(m_backend->server, session, m_backend->server->protocol))) + { + m_closed = false; + m_state = IN_USE; + atomic_add(&m_backend->connections, 1); + rval = true; + } + else + { + m_state = FATAL_FAILURE; + } + + return rval; +} + +DCB* Backend::dcb() const +{ + return m_dcb; +} + +bool Backend::write(GWBUF* buffer, response_type type) +{ + bool rval = m_dcb->func.write(m_dcb, buffer) != 0; + + if (rval && type == EXPECT_RESPONSE) + { + set_state(WAITING_RESULT); + } + + return rval; +} + +bool Backend::auth(GWBUF* buffer) +{ + bool rval = false; + + if (m_dcb->func.auth(m_dcb, NULL, m_dcb->session, buffer) == 1) + { + set_state(WAITING_RESULT); + rval = true; + } + + return rval; +} + +void Backend::ack_write() +{ + ss_dassert(is_waiting_result()); + clear_state(WAITING_RESULT); +} + +void Backend::store_command(GWBUF* buffer) +{ + m_pending_cmd.reset(buffer); +} + +bool Backend::write_stored_command() +{ + bool rval = false; + + if (m_pending_cmd.length()) + { + rval = write(m_pending_cmd.release()); + + if (!rval) + { + MXS_ERROR("Routing of pending query failed."); + } + } + + return rval; +} + +bool Backend::in_use() const +{ + return m_state & IN_USE; +} + +bool Backend::is_active() const +{ + return SERVER_REF_IS_ACTIVE(m_backend); +} + +bool Backend::is_waiting_result() const +{ + return m_state & WAITING_RESULT; +} + +bool Backend::is_closed() const +{ + return m_closed; +} + +bool Backend::is_master() const +{ + return SERVER_IS_MASTER(m_backend->server); +} + +bool Backend::is_slave() const +{ + return SERVER_IS_SLAVE(m_backend->server); +} + +bool Backend::is_relay() const +{ + return SERVER_IS_RELAY_SERVER(m_backend->server); +} + +bool Backend::has_failed() const +{ + return m_state & FATAL_FAILURE; +} + +const char* Backend::name() const +{ + return m_backend->server->unique_name; +} + +const char* Backend::uri() const +{ + return m_uri.c_str(); +} diff --git a/server/core/config.cc b/server/core/config.cc index 17fd24048..9682d1794 100644 --- a/server/core/config.cc +++ b/server/core/config.cc @@ -1244,9 +1244,50 @@ pcre2_code* config_get_compiled_regex(const MXS_CONFIG_PARAMETER *params, uint32_t* output_ovec_size) { const char* regex_string = config_get_string(params, key); - uint32_t jit_available = 0; - pcre2_config(PCRE2_CONFIG_JIT, &jit_available); - return compile_regex_string(regex_string, jit_available, options, output_ovec_size); + pcre2_code* code = NULL; + + if (*regex_string) + { + uint32_t jit_available = 0; + pcre2_config(PCRE2_CONFIG_JIT, &jit_available); + code = compile_regex_string(regex_string, jit_available, options, output_ovec_size); + } + + return code; +} + +bool config_get_compiled_regexes(const MXS_CONFIG_PARAMETER *params, + const char* keys[], int keys_size, + uint32_t options, uint32_t* out_ovec_size, + pcre2_code** out_codes[]) +{ + bool rval = true; + uint32_t max_ovec_size = 0; + uint32_t ovec_size_temp = 0; + for (int i = 0; i < keys_size; i++) + { + ss_dassert(out_codes[i]); + *out_codes[i] = config_get_compiled_regex(params, keys[i], options, + &ovec_size_temp); + if (*out_codes[i]) + { + if (ovec_size_temp > max_ovec_size) + { + max_ovec_size = ovec_size_temp; + } + } + /* config_get_compiled_regex() returns null also if the config setting + * didn't exist. Check that before setting error state. */ + else if (*(config_get_value_string(params, keys[i]))) + { + rval = false; + } + } + if (out_ovec_size) + { + *out_ovec_size = max_ovec_size; + } + return rval; } MXS_CONFIG_PARAMETER* config_clone_param(const MXS_CONFIG_PARAMETER* param) diff --git a/server/core/dcb.cc b/server/core/dcb.cc index 72c45cae8..0bcfc6f91 100644 --- a/server/core/dcb.cc +++ b/server/core/dcb.cc @@ -3401,7 +3401,7 @@ int poll_remove_dcb(DCB *dcb) * Only positive fds can be removed from epoll set. */ dcbfd = dcb->fd; - ss_dassert(dcbfd > 0); + ss_dassert(dcbfd > 0 || dcb->dcb_role == DCB_ROLE_INTERNAL); if (dcbfd > 0) { diff --git a/server/core/gateway.cc b/server/core/gateway.cc index 58fe215bc..4d9409b24 100644 --- a/server/core/gateway.cc +++ b/server/core/gateway.cc @@ -125,12 +125,15 @@ static struct option long_options[] = {"version-full", no_argument, 0, 'V'}, {"help", no_argument, 0, '?'}, {"connector_plugindir", required_argument, 0, 'H'}, + {"debug", required_argument, 0, 'g'}, {0, 0, 0, 0} }; + static bool syslog_configured = false; static bool maxlog_configured = false; static bool log_to_shm_configured = false; static volatile sig_atomic_t last_signal = 0; +static bool unload_modules_at_exit = true; static int cnf_preparser(void* data, const char* section, const char* name, const char* value); static void log_flush_shutdown(void); @@ -145,6 +148,7 @@ static int ntfw_cb(const char*, const struct stat*, int, struct FTW*); static bool file_is_readable(const char* absolute_pathname); static bool file_is_writable(const char* absolute_pathname); bool handle_path_arg(char** dest, const char* path, const char* arg, bool rd, bool wr); +static bool handle_debug_args(char* args); static void set_log_augmentation(const char* value); static void usage(void); static char* get_expanded_pathname( @@ -172,6 +176,32 @@ static bool daemonize(); static bool sniff_configuration(const char* filepath); static bool modules_process_init(); static void modules_process_finish(); +static void disable_module_unloading(); +static void enable_module_unloading(); + +struct DEBUG_ARGUMENT +{ + const char* name; /**< The name of the debug argument */ + void (*action)(); /**< The function implementing the argument */ + const char* description; /**< Help text */ +}; + +#define SPACER " " + +const DEBUG_ARGUMENT debug_arguments[] = +{ + { + "disable-module-unloading", disable_module_unloading, + "disable module unloading at exit. Will produce better\n" + SPACER "Valgring leak reports if leaked memory was allocated in\n" + SPACER "a shared library" + }, + { + "enable-module-unloading", enable_module_unloading, + "cancels disable-module-unloading" + }, + {NULL, NULL, NULL} +}; /** SSL multi-threading functions and structures */ @@ -916,6 +946,14 @@ static void usage(void) " -S, --maxlog=[yes|no] log messages to MaxScale log (default: yes)\n" " -G, --log_augmentation=0|1 augment messages with the name of the function\n" " where the message was logged (default: 0)\n" + " -g, --debug=arg1,arg2,... enable or disable debug features. Supported arguments:\n", + progname); + for (int i = 0; debug_arguments[i].action != NULL; i++) + { + fprintf(stderr, + " %-24s %s\n", debug_arguments[i].name, debug_arguments[i].description); + } + fprintf(stderr, " -v, --version print version info and exit\n" " -V, --version-full print full version info and exit\n" " -?, --help show this help\n" @@ -940,7 +978,6 @@ static void usage(void) "dir will be '/path/maxscale/var/log/maxscale', the config dir will be\n" "'/path/maxscale/etc' and the default config file will be\n" "'/path/maxscale/etc/maxscale.cnf'.\n", - progname, get_configdir(), default_cnf_fname, get_configdir(), get_logdir(), get_cachedir(), get_libdir(), get_datadir(), get_execdir(), get_langdir(), get_piddir(), @@ -1299,7 +1336,7 @@ int main(int argc, char **argv) } } - while ((opt = getopt_long(argc, argv, "dcf:l:vVs:S:?L:D:C:B:U:A:P:G:N:E:F:M:H:", + while ((opt = getopt_long(argc, argv, "dcf:g:l:vVs:S:?L:D:C:B:U:A:P:G:N:E:F:M:H:", long_options, &option_index)) != -1) { bool succp = true; @@ -1565,6 +1602,13 @@ int main(int argc, char **argv) config_check = true; break; + case 'g': + if (!handle_debug_args(optarg)) + { + succp = false; + } + break; + default: usage(); succp = false; @@ -2067,8 +2111,10 @@ int main(int argc, char **argv) utils_end(); cleanup_process_datadir(); MXS_NOTICE("MaxScale shutdown completed."); - - unload_all_modules(); + if (unload_modules_at_exit) + { + unload_all_modules(); + } /* Remove Pidfile */ unlock_pidfile(); unlink_pidfile(); @@ -2899,3 +2945,88 @@ static void modules_process_finish() } } } + +static void enable_module_unloading() +{ + unload_modules_at_exit = true; +} + +static void disable_module_unloading() +{ + unload_modules_at_exit = false; +} + +/** + * Process command line debug arguments + * + * @param args The debug argument list + * @return True on success, false on error + */ +static bool handle_debug_args(char* args) +{ + bool arg_error = false; + int args_found = 0; + char *endptr = NULL; + char* token = strtok_r(args, ",", &endptr); + while (token) + { + bool found = false; + for (int i = 0; debug_arguments[i].action != NULL; i++) + { + // Debug features are activated by running functions in the struct-array. + if (strcmp(token, debug_arguments[i].name) == 0) + { + found = true; + args_found++; + debug_arguments[i].action(); + break; + } + } + if (!found) + { + const char UNRECOG_P1[] = "Unrecognized debug setting: '"; + const char UNRECOG_P2[] = "'."; + size_t unrecog_msg_len = sizeof(UNRECOG_P1) + strlen(token) + sizeof(UNRECOG_P2); + char unrecog_msg[unrecog_msg_len]; + snprintf(unrecog_msg, unrecog_msg_len, "%s%s%s", UNRECOG_P1, token, UNRECOG_P2); + print_log_n_stderr(true, true, unrecog_msg, unrecog_msg, 0); + arg_error = true; + } + token = strtok_r(NULL, ",", &endptr); + } + if (args_found == 0) + { + arg_error = true; + } + if (arg_error) + { + // Form a string with all debug argument names listed. + size_t total_len = 1; + for (int i = 0; debug_arguments[i].action != NULL; i++) + { + total_len += strlen(debug_arguments[i].name) + 1; + } + char arglist[total_len]; + arglist[0] = '\0'; + for (int i = 0; debug_arguments[i].action != NULL; i++) + { + strcat(arglist, debug_arguments[i].name); + // If not the last element, add a comma + if (debug_arguments[i + 1].action != NULL) + { + strcat(arglist, ", "); + } + } + const char DEBUG_ERROR_P1[] = + "Debug argument identifier '-g' or '--debug' was specified " + "but no arguments were found or one of them was invalid. Supported " + "arguments are: "; + const char DEBUG_ERROR_P2[] = "."; + size_t arg_error_msg_len = sizeof(DEBUG_ERROR_P1) + total_len + sizeof(DEBUG_ERROR_P2); + char arg_error_msg[arg_error_msg_len]; + snprintf(arg_error_msg, arg_error_msg_len, "%s%s%s", DEBUG_ERROR_P1, arglist, + DEBUG_ERROR_P2); + print_log_n_stderr(true, true, arg_error_msg, arg_error_msg, 0); + } + return !arg_error; +} diff --git a/server/core/maxscale_pcre2.cc b/server/core/maxscale_pcre2.cc index b1391e615..ec0b3d640 100644 --- a/server/core/maxscale_pcre2.cc +++ b/server/core/maxscale_pcre2.cc @@ -141,7 +141,6 @@ mxs_pcre2_result_t mxs_pcre2_simple_match(const char* pattern, const char* subje void mxs_pcre2_print_error(int errorcode, const char *module_name, const char *filename, int line_num, const char* func_name) { - ss_dassert(module_name); ss_dassert(filename); ss_dassert(func_name); @@ -158,3 +157,53 @@ void mxs_pcre2_print_error(int errorcode, const char *module_name, const char *f "message."); } } + +bool mxs_pcre2_check_match_exclude(pcre2_code* re_match, pcre2_code* re_exclude, + pcre2_match_data* md, const char* subject, + int length, const char* calling_module) +{ + ss_dassert((!re_match && !re_exclude) || (md && subject)); + bool rval = true; + int string_len = ((size_t)length == PCRE2_ZERO_TERMINATED) ? strlen(subject) : length; + if (re_match) + { + int result = pcre2_match(re_match, (PCRE2_SPTR)subject, string_len, 0, 0, md, NULL); + if (result == PCRE2_ERROR_NOMATCH) + { + rval = false; // Didn't match the "match"-regex + if (mxs_log_priority_is_enabled(LOG_INFO)) + { + mxs_log_message(LOG_INFO, calling_module, __FILE__, __LINE__, __func__, + "Subject does not match the 'match' pattern: %.*s", + string_len, subject); + } + } + else if (result < 0) + { + rval = false; + /* The __FILE__ etc macros here do not match calling_module, but + * the values are only used for throttling messages. */ + mxs_pcre2_print_error(result, calling_module, __FILE__, __LINE__, __func__); + } + } + if (rval && re_exclude) + { + int result = pcre2_match(re_exclude, (PCRE2_SPTR)subject, string_len, 0, 0, md, NULL); + if (result >= 0) + { + rval = false; // Matched the "exclude"-regex + if (mxs_log_priority_is_enabled(LOG_INFO)) + { + mxs_log_message(LOG_INFO, calling_module, __FILE__, __LINE__, __func__, + "Query matches the 'exclude' pattern: %.*s", + string_len, subject); + } + } + else if (result != PCRE2_ERROR_NOMATCH) + { + rval = false; + mxs_pcre2_print_error(result, calling_module, __FILE__, __LINE__, __func__); + } + } + return rval; +} diff --git a/server/core/modutil.cc b/server/core/modutil.cc index 2ca4108cc..8b5f7558a 100644 --- a/server/core/modutil.cc +++ b/server/core/modutil.cc @@ -1310,7 +1310,7 @@ bool modutil_ignorable_ping(DCB *dcb) return rval; } -const char format_str[] = "COM_UNKNOWN(%02x)"; +const char format_str[] = "COM_UNKNOWN(%02hhx)"; // The message always fits inside the buffer thread_local char unknow_type[sizeof(format_str)] = ""; diff --git a/server/core/monitor.cc b/server/core/monitor.cc index 57abaf2d7..46dbeda51 100644 --- a/server/core/monitor.cc +++ b/server/core/monitor.cc @@ -972,7 +972,7 @@ static const char* mon_get_event_name(MXS_MONITOR_SERVERS* node) static void mon_append_node_names(MXS_MONITOR_SERVERS* servers, char* dest, int len, int status) { const char *separator = ""; - char arr[MAX_SERVER_NAME_LEN + 64]; // Some extra space for port and separator + char arr[MAX_SERVER_ADDRESS_LEN + 64]; // Some extra space for port and separator dest[0] = '\0'; while (servers && len) diff --git a/server/core/mysql_utils.cc b/server/core/mysql_utils.cc index 94f87bc3e..8b74ab714 100644 --- a/server/core/mysql_utils.cc +++ b/server/core/mysql_utils.cc @@ -285,3 +285,15 @@ mxs_mysql_name_kind_t mxs_mysql_name_to_pcre(char *pcre, return rv; } + +void mxs_mysql_set_server_version(MYSQL* mysql, SERVER* server) +{ + const char* version_string = mysql_get_server_info(mysql); + + if (version_string) + { + unsigned long version = mysql_get_server_version(mysql); + + server_set_version(server, version_string, version); + } +} diff --git a/server/core/query_classifier.cc b/server/core/query_classifier.cc index aa69e2ddf..47cf32905 100644 --- a/server/core/query_classifier.cc +++ b/server/core/query_classifier.cc @@ -901,3 +901,23 @@ uint32_t qc_get_trx_type_mask(GWBUF* stmt) { return qc_get_trx_type_mask_using(stmt, qc_trx_parse_using); } + +void qc_set_server_version(uint64_t version) +{ + QC_TRACE(); + ss_dassert(classifier); + + classifier->qc_set_server_version(version); +} + +uint64_t qc_get_server_version() +{ + QC_TRACE(); + ss_dassert(classifier); + + uint64_t version; + + classifier->qc_get_server_version(&version); + + return version; +} diff --git a/server/core/server.cc b/server/core/server.cc index d5be37d62..1eaec0f04 100644 --- a/server/core/server.cc +++ b/server/core/server.cc @@ -131,7 +131,6 @@ SERVER* server_alloc(const char *name, const char *address, unsigned short port, server->master_id = -1; server->depth = -1; server->parameters = NULL; - server->server_string = NULL; spinlock_init(&server->lock); server->persistent = persistent; server->persistmax = 0; @@ -187,7 +186,6 @@ server_free(SERVER *tofreeserver) /* Clean up session and free the memory */ MXS_FREE(tofreeserver->protocol); MXS_FREE(tofreeserver->unique_name); - MXS_FREE(tofreeserver->server_string); server_parameter_free(tofreeserver->parameters); if (tofreeserver->persistent) @@ -496,10 +494,7 @@ dprintServer(DCB *dcb, const SERVER *server) MXS_FREE(stat); dcb_printf(dcb, "\tProtocol: %s\n", server->protocol); dcb_printf(dcb, "\tPort: %d\n", server->port); - if (server->server_string) - { - dcb_printf(dcb, "\tServer Version: %s\n", server->server_string); - } + dcb_printf(dcb, "\tServer Version: %s\n", server->version_string); dcb_printf(dcb, "\tNode Id: %ld\n", server->node_id); dcb_printf(dcb, "\tMaster Id: %ld\n", server->master_id); if (server->slaves) @@ -1080,28 +1075,55 @@ server_map_status(const char *str) /** * Set the version string of the server. - * @param server Server to update - * @param string Version string - * @return True if the assignment of the version string was successful, false if - * memory allocation failed. + * + * @param server Server to update + * @param version_string Version string */ -bool server_set_version_string(SERVER* server, const char* string) +void server_set_version_string(SERVER* server, const char* version_string) { - bool rval = true; - string = MXS_STRDUP(string); + // There is a race here. The string may be accessed, while we are + // updating it. Thus we take some precautions to ensure that the + // string cannot be completely garbled at any point. - if (string) + size_t old_len = strlen(server->version_string); + size_t new_len = strlen(version_string); + + if (new_len >= MAX_SERVER_VERSION_LEN) { - char* old = server->server_string; - server->server_string = (char*)string; - MXS_FREE(old); - } - else - { - rval = false; + new_len = MAX_SERVER_VERSION_LEN - 1; } - return rval; + if (new_len < old_len) + { + // If the new string is shorter, we start by nulling out the + // excess data. + memset(server->version_string + new_len, 0, old_len - new_len); + } + + strncpy(server->version_string, version_string, new_len); + // No null-byte needs to be set. The array starts out as all zeros + // and the above memset adds the necessary null, should the new string + // be shorter than the old. +} + +/** + * Set the version of the server. + * + * @param server Server to update + * @param version_string Human readable version string. + * @param version Version encoded as MariaDB encodes the version, i.e.: + * version = major * 10000 + minor * 100 + patch + */ +void server_set_version(SERVER* server, const char* version_string, uint64_t version) +{ + server_set_version_string(server, version_string); + + atomic_store_uint64(&server->version, version); +} + +uint64_t server_get_version(const SERVER* server) +{ + return atomic_load_uint64(&server->version); } /** @@ -1396,10 +1418,7 @@ static json_t* server_json_attributes(const SERVER* server) json_object_set_new(attr, CN_STATUS, json_string(stat)); MXS_FREE(stat); - if (server->server_string) - { - json_object_set_new(attr, CN_VERSION_STRING, json_string(server->server_string)); - } + json_object_set_new(attr, CN_VERSION_STRING, json_string(server->version_string)); json_object_set_new(attr, "node_id", json_integer(server->node_id)); json_object_set_new(attr, "master_id", json_integer(server->master_id)); @@ -1440,7 +1459,7 @@ static json_t* server_json_attributes(const SERVER* server) json_object_set_new(stats, "total_connections", json_integer(server->stats.n_connections)); json_object_set_new(stats, "active_operations", json_integer(server->stats.n_current_ops)); - json_object_set_new(attr, "statictics", stats); + json_object_set_new(attr, "statistics", stats); return attr; } diff --git a/server/core/service.cc b/server/core/service.cc index e7ced7df0..29d2bf49a 100644 --- a/server/core/service.cc +++ b/server/core/service.cc @@ -2673,3 +2673,81 @@ json_t* service_relations_to_server(const SERVER* server, const char* host) return rel; } + +uint64_t service_get_version(const SERVICE *service, service_version_which_t which) +{ + uint64_t version = 0; + + if (which == SERVICE_VERSION_ANY) + { + SERVER_REF* sref = service->dbref; + + while (sref && !sref->active) + { + sref = sref->next; + } + + if (sref) + { + version = server_get_version(sref->server); + } + } + else + { + size_t n = 0; + + uint64_t v; + + if (which == SERVICE_VERSION_MIN) + { + v = UINT64_MAX; + } + else + { + ss_dassert(which == SERVICE_VERSION_MAX); + + v = 0; + } + + SERVER_REF* sref = service->dbref; + + while (sref) + { + if (sref->active) + { + ++n; + + SERVER* s = sref->server; + uint64_t server_version = server_get_version(s); + + if (which == SERVICE_VERSION_MIN) + { + if (server_version < v) + { + v = server_version; + } + } + else + { + ss_dassert(which == SERVICE_VERSION_MAX); + + if (server_version > v) + { + v = server_version; + } + } + } + + sref = sref->next; + } + + if (n == 0) + { + v = 0; + } + + version = v; + } + + return version; +} diff --git a/server/core/session.cc b/server/core/session.cc index e96946adb..c348af3a1 100644 --- a/server/core/session.cc +++ b/server/core/session.cc @@ -1156,8 +1156,12 @@ struct SessionListData bool seslist_cb(DCB* dcb, void* data) { - SessionListData* d = (SessionListData*)data; - json_array_append_new(d->json, session_json_data(dcb->session, d->host)); + if (dcb->dcb_role == DCB_ROLE_CLIENT_HANDLER) + { + SessionListData* d = (SessionListData*)data; + json_array_append_new(d->json, session_json_data(dcb->session, d->host)); + } + return true; } diff --git a/server/modules/routing/schemarouter/session_command.cc b/server/core/session_command.cc similarity index 95% rename from server/modules/routing/schemarouter/session_command.cc rename to server/core/session_command.cc index a5d7c520c..c663ee0ff 100644 --- a/server/modules/routing/schemarouter/session_command.cc +++ b/server/core/session_command.cc @@ -11,10 +11,13 @@ * Public License. */ -#include "session_command.hh" +#include + #include #include +using namespace maxscale; + void SessionCommand::mark_reply_received() { m_reply_sent = true; diff --git a/server/core/test/testdcb.cc b/server/core/test/testdcb.cc index 011700645..dbda88def 100644 --- a/server/core/test/testdcb.cc +++ b/server/core/test/testdcb.cc @@ -48,14 +48,12 @@ test1() DCB *dcb; SERV_LISTENER dummy; /* Single buffer tests */ - ss_dfprintf(stderr, - "testdcb : creating buffer with type DCB_ROLE_SERVICE_LISTENER"); - dcb = dcb_alloc(DCB_ROLE_SERVICE_LISTENER, &dummy); + ss_dfprintf(stderr, "testdcb : creating buffer with type DCB_ROLE_SERVICE_LISTENER"); + dcb = dcb_alloc(DCB_ROLE_INTERNAL, &dummy); printDCB(dcb); ss_info_dassert(dcb_isvalid(dcb), "New DCB must be valid"); ss_dfprintf(stderr, "\t..done\nAllocated dcb."); printAllDCBs(); - ss_info_dassert(true, "Something is true"); ss_dfprintf(stderr, "\t..done\n"); dcb->state = DCB_STATE_POLLING; dcb_close(dcb); diff --git a/server/core/test/testjson.cc b/server/core/test/testjson.cc index 50e33a1b2..474cde3f0 100644 --- a/server/core/test/testjson.cc +++ b/server/core/test/testjson.cc @@ -82,7 +82,7 @@ const char* test1_json = " 3002," " 3003" " ]," - " \"statictics\": {" + " \"statistics\": {" " \"connections\": 0," " \"total_connections\": 0," " \"active_operations\": 0" @@ -136,7 +136,7 @@ const char* test1_json = " \"master_id\": 3000," " \"replication_depth\": 1," " \"slaves\": []," - " \"statictics\": {" + " \"statistics\": {" " \"connections\": 0," " \"total_connections\": 0," " \"active_operations\": 0" @@ -190,7 +190,7 @@ const char* test1_json = " \"master_id\": 3000," " \"replication_depth\": 1," " \"slaves\": []," - " \"statictics\": {" + " \"statistics\": {" " \"connections\": 0," " \"total_connections\": 0," " \"active_operations\": 0" @@ -244,7 +244,7 @@ const char* test1_json = " \"master_id\": 3000," " \"replication_depth\": 1," " \"slaves\": []," - " \"statictics\": {" + " \"statistics\": {" " \"connections\": 0," " \"total_connections\": 0," " \"active_operations\": 0" diff --git a/server/core/test/testtrxtracking.cc b/server/core/test/testtrxtracking.cc index 88b43732a..e6b49ada2 100644 --- a/server/core/test/testtrxtracking.cc +++ b/server/core/test/testtrxtracking.cc @@ -12,6 +12,7 @@ */ #include +#include #include #include #include @@ -69,6 +70,7 @@ struct test_case uint32_t type_mask; } test_cases[] = { + // Keep these all uppercase, lowercase are tested programmatically. { "BEGIN", QUERY_TYPE_BEGIN_TRX }, { "BEGIN WORK", QUERY_TYPE_BEGIN_TRX }, @@ -299,14 +301,23 @@ bool test(uint32_t (*getter)(GWBUF*), bool dont_bail_out) string base(pTest->zStmt); cout << base << endl; - string s; - - s = base; - if (!test(getter, s.c_str(), pTest->type_mask)) + if (!test(getter, base.c_str(), pTest->type_mask)) { rc = false; } + if (dont_bail_out || rc) + { + // Test all lowercase. + string lc(base); + transform(lc.begin(), lc.end(), lc.begin(), ::tolower); + + if (!test(getter, lc.c_str(), pTest->type_mask)) + { + rc = false; + } + } + if (dont_bail_out || rc) { if (!test_with_prefixes(getter, base, pTest->type_mask)) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 9228edfe7..25719fa70 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -522,14 +522,13 @@ static bool check_server_permissions(SERVICE *service, SERVER* server, mysql_get_character_set_info(mysql, &cs_info); server->charset = cs_info.number; - if (server->server_string == NULL) + if (server->version_string[0] == 0) { - const char *server_string = mysql_get_server_info(mysql); - server_set_version_string(server, server_string); + mxs_mysql_set_server_version(mysql, server); } const char *template = "SELECT user, host, %s, Select_priv FROM mysql.user limit 1"; - const char* query_pw = strstr(server->server_string, "5.7.") ? + const char* query_pw = strstr(server->version_string, "5.7.") ? MYSQL57_PASSWORD : MYSQL_PASSWORD; char query[strlen(template) + strlen(query_pw) + 1]; bool rval = true; @@ -726,18 +725,14 @@ void commit_sqlite_transaction(sqlite3 *handle) } } -int get_users_from_server(MYSQL *con, SERVER_REF *server, SERVICE *service, SERV_LISTENER *listener) +int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service, SERV_LISTENER *listener) { - if (server->server->server_string == NULL) + if (server_ref->server->version_string[0] == 0) { - const char *server_string = mysql_get_server_info(con); - if (!server_set_version_string(server->server, server_string)) - { - return -1; - } + mxs_mysql_set_server_version(con, server_ref->server); } - char *query = get_new_users_query(server->server->server_string, service->enable_root); + char *query = get_new_users_query(server_ref->server->version_string, service->enable_root); MYSQL_AUTH *instance = (MYSQL_AUTH*)listener->auth_instance; bool anon_user = false; int users = 0; diff --git a/server/modules/filter/cache/CMakeLists.txt b/server/modules/filter/cache/CMakeLists.txt index 6c767beb7..83a04bb9a 100644 --- a/server/modules/filter/cache/CMakeLists.txt +++ b/server/modules/filter/cache/CMakeLists.txt @@ -17,7 +17,7 @@ if (JANSSON_FOUND) storagefactory.cc storagereal.cc ) - target_link_libraries(cache maxscale-common ${JANSSON_LIBRARIES}) + target_link_libraries(cache maxscale-common ${JANSSON_LIBRARIES} MySQLCommon) set_target_properties(cache PROPERTIES VERSION "1.0.0") set_target_properties(cache PROPERTIES LINK_FLAGS -Wl,-z,defs) install_module(cache core) diff --git a/server/modules/filter/cache/cachefiltersession.cc b/server/modules/filter/cache/cachefiltersession.cc index 5eb5f5d4f..46aeb24a4 100644 --- a/server/modules/filter/cache/cachefiltersession.cc +++ b/server/modules/filter/cache/cachefiltersession.cc @@ -205,15 +205,15 @@ CacheFilterSession* CacheFilterSession::Create(Cache* pCache, MXS_SESSION* pSess ss_dassert(pSession->client_dcb); ss_dassert(pSession->client_dcb->data); - MYSQL_session *pMysqlSession = (MYSQL_session*)pSession->client_dcb->data; + const char* zDb = mxs_mysql_get_current_db(pSession); char* zDefaultDb = NULL; - if (pMysqlSession->db[0] != 0) + if (zDb[0] != 0) { - zDefaultDb = MXS_STRDUP(pMysqlSession->db); + zDefaultDb = MXS_STRDUP(zDb); } - if ((pMysqlSession->db[0] == 0) || zDefaultDb) + if ((zDb[0] == 0) || zDefaultDb) { pCacheFilterSession = new (std::nothrow) CacheFilterSession(pSession, pCache, zDefaultDb); diff --git a/server/modules/filter/ccrfilter/ccrfilter.c b/server/modules/filter/ccrfilter/ccrfilter.c index d85b66e3b..5cd221689 100644 --- a/server/modules/filter/ccrfilter/ccrfilter.c +++ b/server/modules/filter/ccrfilter/ccrfilter.c @@ -13,16 +13,18 @@ #define MXS_MODULE_NAME "ccrfilter" +#include + #include +#include +#include #include +#include +#include #include #include -#include -#include -#include +#include #include -#include -#include /** * @file ccrfilter.c - a very simple filter designed to send queries to the @@ -81,8 +83,9 @@ typedef struct int count; /*< Number of hints to add after each operation * that modifies data. */ LAGSTATS stats; - regex_t re; /* Compiled regex text of match */ - regex_t nore; /* Compiled regex text of ignore */ + pcre2_code* re; /* Compiled regex text of match */ + pcre2_code* nore; /* Compiled regex text of ignore */ + uint32_t ovector_size; /* PCRE2 match data ovector size */ } CCR_INSTANCE; /** @@ -93,16 +96,20 @@ typedef struct MXS_DOWNSTREAM down; /*< The downstream filter */ int hints_left; /*< Number of hints left to add to queries*/ time_t last_modification; /*< Time of the last data modifying operation */ + pcre2_match_data* md; /*< PCRE2 match data */ } CCR_SESSION; static const MXS_ENUM_VALUE option_values[] = { - {"ignorecase", REG_ICASE}, + {"ignorecase", PCRE2_CASELESS}, {"case", 0}, - {"extended", REG_EXTENDED}, + {"extended", PCRE2_EXTENDED}, {NULL} }; +static const char PARAM_MATCH[] = "match"; +static const char PARAM_IGNORE[] = "ignore"; + typedef enum ccr_hint_value_t { CCR_HINT_NONE, @@ -154,8 +161,8 @@ MXS_MODULE* MXS_CREATE_MODULE() { {"count", MXS_MODULE_PARAM_COUNT, "0"}, {"time", MXS_MODULE_PARAM_COUNT, CCR_DEFAULT_TIME}, - {"match", MXS_MODULE_PARAM_STRING}, - {"ignore", MXS_MODULE_PARAM_STRING}, + {PARAM_MATCH, MXS_MODULE_PARAM_REGEX}, + {PARAM_IGNORE, MXS_MODULE_PARAM_REGEX}, { "options", MXS_MODULE_PARAM_ENUM, @@ -192,23 +199,26 @@ createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params) my_instance->stats.n_add_count = 0; my_instance->stats.n_add_time = 0; my_instance->stats.n_modified = 0; + my_instance->ovector_size = 0; + my_instance->re = NULL; + my_instance->nore = NULL; int cflags = config_get_enum(params, "options", option_values); + my_instance->match = config_copy_string(params, PARAM_MATCH); + my_instance->nomatch = config_copy_string(params, PARAM_IGNORE); + const char* keys[] = {PARAM_MATCH, PARAM_IGNORE}; + pcre2_code** code_arr[] = {&my_instance->re, &my_instance->nore}; - if ((my_instance->match = config_copy_string(params, "match"))) + if (!config_get_compiled_regexes(params, keys, sizeof(keys)/sizeof(char*), + cflags, &my_instance->ovector_size, + code_arr)) { - if (regcomp(&my_instance->re, my_instance->match, cflags)) - { - MXS_ERROR("Failed to compile regex '%s'.", my_instance->match); - } - } - - if ((my_instance->nomatch = config_copy_string(params, "ignore"))) - { - if (regcomp(&my_instance->nore, my_instance->nomatch, cflags)) - { - MXS_ERROR("Failed to compile regex '%s'.", my_instance->nomatch); - } + MXS_FREE(my_instance->match); + MXS_FREE(my_instance->nomatch); + pcre2_code_free(my_instance->re); + pcre2_code_free(my_instance->nore); + MXS_FREE(my_instance); + my_instance = NULL; } } @@ -226,12 +236,23 @@ createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params) static MXS_FILTER_SESSION * newSession(MXS_FILTER *instance, MXS_SESSION *session) { + CCR_INSTANCE *my_instance = (CCR_INSTANCE *)instance; CCR_SESSION *my_session = MXS_MALLOC(sizeof(CCR_SESSION)); if (my_session) { + bool error = false; my_session->hints_left = 0; my_session->last_modification = 0; + if (my_instance->ovector_size) + { + my_session->md = pcre2_match_data_create(my_instance->ovector_size, NULL); + if (!my_session->md) + { + MXS_FREE(my_session); + my_session = NULL; + } + } } return (MXS_FILTER_SESSION*)my_session; @@ -296,6 +317,7 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue) CCR_INSTANCE *my_instance = (CCR_INSTANCE *)instance; CCR_SESSION *my_session = (CCR_SESSION *)session; char *sql; + int length; time_t now = time(NULL); if (modutil_is_SQL(queue)) @@ -306,7 +328,7 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue) */ if (qc_query_is_type(qc_get_type_mask(queue), QUERY_TYPE_WRITE)) { - if ((sql = modutil_get_SQL(queue)) != NULL) + if (modutil_extract_SQL(queue, &sql, &length)) { bool trigger_ccr = true; bool decided = false; // Set by hints to take precedence. @@ -322,18 +344,10 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue) } if (!decided) { - if (my_instance->nomatch && - regexec(&my_instance->nore, sql, 0, NULL, 0) == 0) - { - // Nomatch was present and sql matched it. - trigger_ccr = false; - } - else if (my_instance->match && - (regexec(&my_instance->re, sql, 0, NULL, 0) != 0)) - { - // Match was present but sql did *not* match it. - trigger_ccr = false; - } + trigger_ccr = + mxs_pcre2_check_match_exclude(my_instance->re, my_instance->nore, + my_session->md, sql, length, + MXS_MODULE_NAME); } if (trigger_ccr) { @@ -351,7 +365,6 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue) my_instance->stats.n_modified++; } - MXS_FREE(sql); } } else if (my_session->hints_left > 0) @@ -435,7 +448,7 @@ static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESS if (my_instance->match) { - json_object_set_new(rval, "match", json_string(my_instance->match)); + json_object_set_new(rval, PARAM_MATCH, json_string(my_instance->match)); } if (my_instance->nomatch) diff --git a/server/modules/filter/dbfwfilter/CMakeLists.txt b/server/modules/filter/dbfwfilter/CMakeLists.txt index 8f03ad2b0..21b0b0872 100644 --- a/server/modules/filter/dbfwfilter/CMakeLists.txt +++ b/server/modules/filter/dbfwfilter/CMakeLists.txt @@ -7,13 +7,13 @@ if(BISON_FOUND AND FLEX_FOUND) include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_library(dbfwfilter SHARED dbfwfilter.c ${BISON_ruleparser_OUTPUTS} ${FLEX_token_OUTPUTS}) - target_link_libraries(dbfwfilter maxscale-common) + target_link_libraries(dbfwfilter maxscale-common MySQLCommon) set_target_properties(dbfwfilter PROPERTIES VERSION "1.0.0") install_module(dbfwfilter core) # The offline rule check utility add_executable(dbfwchk dbfw_rule_check.c ${BISON_ruleparser_OUTPUTS} ${FLEX_token_OUTPUTS}) - target_link_libraries(dbfwchk maxscale-common) + target_link_libraries(dbfwchk maxscale-common MySQLCommon) install_executable(dbfwchk core) else() diff --git a/server/modules/filter/dbfwfilter/dbfwfilter.c b/server/modules/filter/dbfwfilter/dbfwfilter.c index dc545695f..9035364c6 100644 --- a/server/modules/filter/dbfwfilter/dbfwfilter.c +++ b/server/modules/filter/dbfwfilter/dbfwfilter.c @@ -1745,7 +1745,7 @@ GWBUF* gen_dummy_error(FW_SESSION* session, char* msg) } dcb = session->session->client_dcb; - mysql_session = (MYSQL_session*) dcb->data; + const char* db = mxs_mysql_get_current_db(session->session); errlen = msg != NULL ? strlen(msg) : 0; errmsg = (char*) MXS_MALLOC((512 + errlen) * sizeof(char)); @@ -1755,14 +1755,14 @@ GWBUF* gen_dummy_error(FW_SESSION* session, char* msg) } - if (mysql_session->db[0] == '\0') + if (db[0] == '\0') { sprintf(errmsg, "Access denied for user '%s'@'%s'", dcb->user, dcb->remote); } else { sprintf(errmsg, "Access denied for user '%s'@'%s' to database '%s'", - dcb->user, dcb->remote, mysql_session->db); + dcb->user, dcb->remote, db); } if (msg != NULL) diff --git a/server/modules/filter/hintfilter/hintparser.c b/server/modules/filter/hintfilter/hintparser.c index 3881df8bb..f0dd24712 100644 --- a/server/modules/filter/hintfilter/hintparser.c +++ b/server/modules/filter/hintfilter/hintparser.c @@ -96,7 +96,11 @@ static const char* token_get_keyword( { switch (token->token) { - case TOK_EOL: + case TOK_END: + return "End of hint"; + break; + + case TOK_LINEBRK: return "End of line"; break; @@ -107,14 +111,14 @@ static const char* token_get_keyword( default: { int i = 0; - while (i < TOK_EOL && keywords[i].token != token->token) + while (i < TOK_LINEBRK && keywords[i].token != token->token) { i++; } - ss_dassert(i != TOK_EOL); + ss_dassert(i != TOK_LINEBRK); - if (i == TOK_EOL) + if (i == TOK_LINEBRK) { return "Unknown token"; } @@ -147,7 +151,7 @@ hint_parser(HINT_SESSION *session, GWBUF *request) GWBUF *buf; HINT_TOKEN *tok; HINT_MODE mode = HM_EXECUTE; - + bool multiline_comment = false; /* First look for any comment in the SQL */ modutil_MySQL_Query(request, &ptr, &len, &residual); buf = request; @@ -180,7 +184,9 @@ hint_parser(HINT_SESSION *session, GWBUF *request) squoted = 0; } else if (quoted || squoted) + { ; + } else if (escape) { escape = 0; @@ -197,6 +203,7 @@ hint_parser(HINT_SESSION *session, GWBUF *request) else if (*ptr == '*' && lastch == '/') { found = 1; + multiline_comment = true; break; } else if (*ptr == '-' && lastch == '-') @@ -271,9 +278,24 @@ hint_parser(HINT_SESSION *session, GWBUF *request) state = HS_INIT; - while ((tok = hint_next_token(&buf, &ptr)) != NULL - && tok->token != TOK_EOL) + while (((tok = hint_next_token(&buf, &ptr)) != NULL) && + (tok->token != TOK_END)) { + if (tok->token == TOK_LINEBRK) + { + if (multiline_comment) + { + // Skip token + token_free(tok); + continue; + } + else + { + // Treat as TOK_END + tok->token = TOK_END; + break; + } + } switch (state) { case HS_INIT: @@ -365,6 +387,7 @@ hint_parser(HINT_SESSION *session, GWBUF *request) { case TOK_EQUAL: pname = lvalue; + lvalue = NULL; state = HS_PVALUE; break; case TOK_PREPARE: @@ -390,6 +413,8 @@ hint_parser(HINT_SESSION *session, GWBUF *request) case HS_PVALUE: /* Action: pname = tok->value */ rval = hint_create_parameter(rval, pname, tok->value); + MXS_FREE(pname); + pname = NULL; state = HS_INIT; break; case HS_PREPARE: @@ -418,7 +443,7 @@ hint_parser(HINT_SESSION *session, GWBUF *request) token_free(tok); } /*< while */ - if (tok && tok->token == TOK_EOL) + if (tok && tok->token == TOK_END) { token_free(tok); } @@ -536,8 +561,8 @@ hint_next_token(GWBUF **buf, char **ptr) inword = 0; break; } - /** found '=', move ptr and return with '=' */ - else if (!inword && inquote == '\0' && **ptr == '=') + /** found '=' or '\n', move ptr and return with the char */ + else if (!inword && inquote == '\0' && ((**ptr == '=') || (**ptr == '\n'))) { *dest = **ptr; dest++; @@ -590,7 +615,12 @@ hint_next_token(GWBUF **buf, char **ptr) */ if (word[0] == '\0' || (word[0] == '*' && word[1] == '/')) { - tok->token = TOK_EOL; + tok->token = TOK_END; + return tok; + } + if (word[0] == '\n') + { + tok->token = TOK_LINEBRK; return tok; } found = 0; diff --git a/server/modules/filter/hintfilter/mysqlhint.h b/server/modules/filter/hintfilter/mysqlhint.h index 38004e6ef..7c0321510 100644 --- a/server/modules/filter/hintfilter/mysqlhint.h +++ b/server/modules/filter/hintfilter/mysqlhint.h @@ -40,7 +40,8 @@ typedef enum TOK_MASTER, TOK_SLAVE, TOK_SERVER, - TOK_EOL + TOK_LINEBRK, + TOK_END } TOKEN_VALUE; /* The tokenising return type */ diff --git a/server/modules/filter/mqfilter/mqfilter.c b/server/modules/filter/mqfilter/mqfilter.c index 71622b4d3..779759deb 100644 --- a/server/modules/filter/mqfilter/mqfilter.c +++ b/server/modules/filter/mqfilter/mqfilter.c @@ -807,21 +807,15 @@ void pushMessage(MQ_INSTANCE *instance, amqp_basic_properties_t* prop, char* msg static MXS_FILTER_SESSION * newSession(MXS_FILTER *instance, MXS_SESSION *session) { - MYSQL_session *sessauth = session->client_dcb->data; - char *db = sessauth->db; - if (db) + const char *db = mxs_mysql_get_current_db(session); + char* my_db = NULL; + + if (*db) { - if (strnlen(db, 128) > 0) + my_db = MXS_STRDUP(my_db); + if (!my_db) { - db = MXS_STRDUP(db); - if (!db) - { - return NULL; - } - } - else - { - db = NULL; + return NULL; } } @@ -832,11 +826,11 @@ newSession(MXS_FILTER *instance, MXS_SESSION *session) my_session->was_query = false; my_session->uid = NULL; my_session->session = session; - my_session->db = db; + my_session->db = my_db; } else { - MXS_FREE(db); + MXS_FREE(my_db); } return (MXS_FILTER_SESSION*)my_session; diff --git a/server/modules/filter/qlafilter/CMakeLists.txt b/server/modules/filter/qlafilter/CMakeLists.txt index 6304d0301..85246106f 100644 --- a/server/modules/filter/qlafilter/CMakeLists.txt +++ b/server/modules/filter/qlafilter/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(qlafilter SHARED qlafilter.c) +add_library(qlafilter SHARED qlafilter.cc) target_link_libraries(qlafilter maxscale-common) set_target_properties(qlafilter PROPERTIES VERSION "1.1.1") install_module(qlafilter core) diff --git a/server/modules/filter/qlafilter/qlafilter.c b/server/modules/filter/qlafilter/qlafilter.cc similarity index 91% rename from server/modules/filter/qlafilter/qlafilter.c rename to server/modules/filter/qlafilter/qlafilter.cc index 978ea8320..3ebe17f9a 100644 --- a/server/modules/filter/qlafilter/qlafilter.c +++ b/server/modules/filter/qlafilter/qlafilter.cc @@ -23,6 +23,8 @@ #define MXS_MODULE_NAME "qlafilter" +#include + #include #include #include @@ -99,8 +101,6 @@ typedef struct /* Avoid repeatedly printing some errors/warnings. */ bool write_warning_given; - bool match_error_printed; - bool exclude_error_printed; } QLA_INSTANCE; /* The session structure for this QLA filter. */ @@ -120,8 +120,6 @@ typedef struct static FILE* open_log_file(uint32_t, QLA_INSTANCE *, const char *); static int write_log_entry(uint32_t, FILE*, QLA_INSTANCE*, QLA_SESSION*, const char*, const char*, size_t); -static bool regex_check(QLA_INSTANCE* my_instance, QLA_SESSION* my_session, - const char* ptr, int length); static const MXS_ENUM_VALUE option_values[] = { @@ -159,6 +157,8 @@ static const char PARAM_LOG_DATA[] = "log_data"; static const char PARAM_FLUSH[] = "flush"; static const char PARAM_APPEND[] = "append"; +MXS_BEGIN_DECLS + /** * The module entry point routine. * @@ -256,6 +256,8 @@ MXS_MODULE* MXS_CREATE_MODULE() return &info; } +MXS_END_DECLS + /** * Create an instance of the filter for a particular service within MaxScale. * @@ -277,8 +279,6 @@ createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params) my_instance->ovec_size = 0; my_instance->unified_fp = NULL; my_instance->write_warning_given = false; - my_instance->match_error_printed = false; - my_instance->exclude_error_printed = false; my_instance->source = config_copy_string(params, PARAM_SOURCE); my_instance->user_name = config_copy_string(params, PARAM_USER); @@ -296,29 +296,13 @@ createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params) bool error = false; int cflags = config_get_enum(params, PARAM_OPTIONS, option_values); - if (my_instance->match) - { - my_instance->re_match = - config_get_compiled_regex(params, PARAM_MATCH, cflags, &my_instance->ovec_size); - if (!my_instance->re_match) - { - error = true; - } - } - if (my_instance->exclude) + const char* keys[] = {PARAM_MATCH, PARAM_EXCLUDE}; + pcre2_code** code_arr[] = {&my_instance->re_match, &my_instance->re_exclude}; + if (!config_get_compiled_regexes(params, keys, sizeof(keys) / sizeof(char*), + cflags, &my_instance->ovec_size, code_arr)) { - uint32_t ovec_size_temp = 0; - my_instance->re_exclude = - config_get_compiled_regex(params, PARAM_EXCLUDE, cflags, &ovec_size_temp); - if (ovec_size_temp > my_instance->ovec_size) - { - my_instance->ovec_size = ovec_size_temp; - } - if (!my_instance->re_exclude) - { - error = true; - } + error = true; } // Try to open the unified log file @@ -328,7 +312,7 @@ createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params) const char UNIFIED[] = ".unified"; int namelen = strlen(my_instance->filebase) + sizeof(UNIFIED); char *filename = NULL; - if ((filename = MXS_CALLOC(namelen, sizeof(char))) != NULL) + if ((filename = (char*)MXS_CALLOC(namelen, sizeof(char))) != NULL) { snprintf(filename, namelen, "%s.unified", my_instance->filebase); // Open the file. It is only closed at program exit @@ -386,7 +370,7 @@ newSession(MXS_FILTER *instance, MXS_SESSION *session) QLA_SESSION *my_session; const char *remote, *userName; - if ((my_session = MXS_CALLOC(1, sizeof(QLA_SESSION))) != NULL) + if ((my_session = (QLA_SESSION*)MXS_CALLOC(1, sizeof(QLA_SESSION))) != NULL) { my_session->fp = NULL; my_session->match_data = NULL; @@ -525,7 +509,8 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue) if (my_session->active && modutil_extract_SQL(queue, &ptr, &length) && - regex_check(my_instance, my_session, ptr, length)) + mxs_pcre2_check_match_exclude(my_instance->re_match, my_instance->re_exclude, + my_session->match_data, ptr, length, MXS_MODULE_NAME)) { char buffer[QLA_DATE_BUFFER_SIZE]; gettimeofday(&tv, NULL); @@ -830,7 +815,7 @@ static int write_log_entry(uint32_t data_flags, FILE *logfile, QLA_INSTANCE *ins cause garbled printing if several threads write simultaneously, so we have to first print to a string. */ char *print_str = NULL; - if ((print_str = MXS_CALLOC(print_len, sizeof(char))) == NULL) + if ((print_str = (char*)MXS_CALLOC(print_len, sizeof(char))) == NULL) { return -1; } @@ -918,46 +903,3 @@ static int write_log_entry(uint32_t data_flags, FILE *logfile, QLA_INSTANCE *ins return rval; } } - -static bool regex_check(QLA_INSTANCE* my_instance, QLA_SESSION* my_session, - const char* ptr, int length) -{ - bool rval = true; - if (my_instance->re_match) - { - int result = pcre2_match(my_instance->re_match, (PCRE2_SPTR)ptr, - length, 0, 0, my_session->match_data, NULL); - if (result == PCRE2_ERROR_NOMATCH) - { - rval = false; // Didn't match the "match"-regex - } - else if (result < 0) - { - rval = false; - if (!my_instance->match_error_printed) - { - MXS_PCRE2_PRINT_ERROR(result); - my_instance->match_error_printed = true; - } - } - } - if (rval && my_instance->re_exclude) - { - int result = pcre2_match(my_instance->re_exclude, (PCRE2_SPTR)ptr, - length, 0, 0, my_session->match_data, NULL); - if (result >= 0) - { - rval = false; // Matched the "exclude"-regex - } - else if (result != PCRE2_ERROR_NOMATCH) - { - rval = false; - if (!my_instance->exclude_error_printed) - { - MXS_PCRE2_PRINT_ERROR(result); - my_instance->exclude_error_printed = true; - } - } - } - return rval; -} diff --git a/server/modules/filter/tee/tee.cc b/server/modules/filter/tee/tee.cc index d92fb2c70..5aa508c25 100644 --- a/server/modules/filter/tee/tee.cc +++ b/server/modules/filter/tee/tee.cc @@ -29,31 +29,23 @@ static const MXS_ENUM_VALUE option_values[] = { - {"ignorecase", REG_ICASE}, - {"case", 0}, - {"extended", REG_EXTENDED}, + {"ignorecase", PCRE2_CASELESS}, + {"case", 0}, + {"extended", PCRE2_EXTENDED}, {NULL} }; -Tee::Tee(SERVICE* service, const char* user, const char* remote, - const char* match, const char* nomatch, int cflags): +Tee::Tee(SERVICE* service, std::string user, std::string remote, + pcre2_code* match, std::string match_string, + pcre2_code* exclude, std::string exclude_string): m_service(service), m_user(user), m_source(remote), - m_match(match), - m_nomatch(nomatch) + m_match_code(match), + m_exclude_code(exclude), + m_match(match_string), + m_exclude(exclude_string) { - if (*match) - { - ss_debug(int rc = )regcomp(&m_re, match, cflags); - ss_dassert(rc == 0); - } - - if (*nomatch) - { - ss_debug(int rc = )regcomp(&m_nore, nomatch, cflags); - ss_dassert(rc == 0); - } } /** @@ -68,34 +60,22 @@ Tee::Tee(SERVICE* service, const char* user, const char* remote, */ Tee* Tee::create(const char *name, char **options, MXS_CONFIG_PARAMETER *params) { - Tee *my_instance = NULL; - SERVICE* service = config_get_service(params, "service"); const char* source = config_get_string(params, "source"); const char* user = config_get_string(params, "user"); - const char* match = config_get_string(params, "match"); - const char* nomatch = config_get_string(params, "exclude"); + uint32_t cflags = config_get_enum(params, "options", option_values); + pcre2_code* match = config_get_compiled_regex(params, "match", cflags, NULL); + pcre2_code* exclude = config_get_compiled_regex(params, "exclude", cflags, NULL); + const char* match_str = config_get_string(params, "match"); + const char* exclude_str = config_get_string(params, "exclude"); - int cflags = config_get_enum(params, "options", option_values); - regex_t re; - regex_t nore; + Tee* my_instance = new (std::nothrow) Tee(service, source, user, match, + match_str, exclude, exclude_str); - if (*match && regcomp(&re, match, cflags) != 0) + if (my_instance == NULL) { - MXS_ERROR("Invalid regular expression '%s' for the match parameter.", match); - } - else if (*nomatch && regcomp(&nore, nomatch, cflags) != 0) - { - MXS_ERROR("Invalid regular expression '%s' for the nomatch parameter.", nomatch); - - if (*match) - { - regfree(&re); - } - } - else - { - my_instance = new (std::nothrow) Tee(service, source, user, match, nomatch, cflags); + pcre2_code_free(match); + pcre2_code_free(exclude); } return my_instance; @@ -136,10 +116,10 @@ void Tee::diagnostics(DCB *dcb) dcb_printf(dcb, "\t\tInclude queries that match %s\n", m_match.c_str()); } - if (m_nomatch.c_str()) + if (m_exclude.c_str()) { dcb_printf(dcb, "\t\tExclude queries that match %s\n", - m_nomatch.c_str()); + m_exclude.c_str()); } } @@ -174,9 +154,9 @@ json_t* Tee::diagnostics_json() const json_object_set_new(rval, "match", json_string(m_match.c_str())); } - if (m_nomatch.length()) + if (m_exclude.length()) { - json_object_set_new(rval, "exclude", json_string(m_nomatch.c_str())); + json_object_set_new(rval, "exclude", json_string(m_exclude.c_str())); } return rval; @@ -210,8 +190,8 @@ MXS_MODULE* MXS_CREATE_MODULE() NULL, /* Thread finish. */ { {"service", MXS_MODULE_PARAM_SERVICE, NULL, MXS_MODULE_OPT_REQUIRED}, - {"match", MXS_MODULE_PARAM_STRING}, - {"exclude", MXS_MODULE_PARAM_STRING}, + {"match", MXS_MODULE_PARAM_REGEX}, + {"exclude", MXS_MODULE_PARAM_REGEX}, {"source", MXS_MODULE_PARAM_STRING}, {"user", MXS_MODULE_PARAM_STRING}, { diff --git a/server/modules/filter/tee/tee.hh b/server/modules/filter/tee/tee.hh index df096583c..a17e65309 100644 --- a/server/modules/filter/tee/tee.hh +++ b/server/modules/filter/tee/tee.hh @@ -57,15 +57,26 @@ public: return m_service; } + pcre2_code* get_match() const + { + return m_match_code; + } + + pcre2_code* get_exclude() const + { + return m_exclude_code; + } + private: - Tee(SERVICE* service, const char* user, const char* remote, - const char* match, const char* nomatch, int cflags); + Tee(SERVICE* service, std::string user, std::string remote, + pcre2_code* match, std::string match_string, + pcre2_code* exclude, std::string exclude_string); SERVICE* m_service; - std::string m_user; /* The user name to filter on */ - std::string m_source; /* The source of the client connection */ - std::string m_match; /* Optional text to match against */ - std::string m_nomatch; /* Optional text to match against for exclusion */ - regex_t m_re; /* Compiled regex text */ - regex_t m_nore; /* Compiled regex nomatch text */ + std::string m_user; /* The user name to filter on */ + std::string m_source; /* The source of the client connection */ + pcre2_code* m_match_code; /* Compiled match pattern */ + pcre2_code* m_exclude_code; /* Compiled exclude pattern*/ + std::string m_match; /* Pattern for matching queries */ + std::string m_exclude; /* Pattern for excluding queries */ }; diff --git a/server/modules/filter/tee/teesession.cc b/server/modules/filter/tee/teesession.cc index 59a8bef78..0274ddfc5 100644 --- a/server/modules/filter/tee/teesession.cc +++ b/server/modules/filter/tee/teesession.cc @@ -17,6 +17,8 @@ #include #include +#include + /** * Detect loops in the filter chain. */ @@ -57,9 +59,15 @@ bool recursive_tee_usage(std::set& services, SERVICE* service) return false; } -TeeSession::TeeSession(MXS_SESSION* session, LocalClient* client): +TeeSession::TeeSession(MXS_SESSION* session, LocalClient* client, + pcre2_code* match, pcre2_match_data* md_match, + pcre2_code* exclude, pcre2_match_data* md_exclude): mxs::FilterSession(session), - m_client(client) + m_client(client), + m_match(match), + m_md_match(md_match), + m_exclude(exclude), + m_md_exclude(md_exclude) { } @@ -75,17 +83,43 @@ TeeSession* TeeSession::create(Tee* my_instance, MXS_SESSION* session) } LocalClient* client = NULL; + pcre2_code* match = NULL; + pcre2_code* exclude = NULL; + pcre2_match_data* md_match = NULL; + pcre2_match_data* md_exclude = NULL; if (my_instance->user_matches(session_get_user(session)) && my_instance->remote_matches(session_get_remote(session))) { + match = my_instance->get_match(); + exclude = my_instance->get_exclude(); + + if ((match && (md_match = pcre2_match_data_create_from_pattern(match, NULL)) == NULL) || + (exclude && (md_exclude = pcre2_match_data_create_from_pattern(exclude, NULL)) == NULL)) + { + return NULL; + } + if ((client = LocalClient::create(session, my_instance->get_service())) == NULL) { return NULL; } } - return new (std::nothrow) TeeSession(session, client); + TeeSession* tee = new (std::nothrow) TeeSession(session, client, match, md_match, exclude, md_exclude); + + if (!tee) + { + pcre2_match_data_free(md_match); + pcre2_match_data_free(md_exclude); + + if (client) + { + delete client; + } + } + + return tee; } TeeSession::~TeeSession() @@ -99,7 +133,7 @@ void TeeSession::close() int TeeSession::routeQuery(GWBUF* queue) { - if (m_client) + if (m_client && query_matches(queue)) { m_client->queue_query(queue); } @@ -115,3 +149,32 @@ json_t* TeeSession::diagnostics_json() const { return NULL; } + +bool TeeSession::query_matches(GWBUF* buffer) +{ + bool rval = true; + + if (m_match || m_exclude) + { + char* sql; + int len; + + if (modutil_extract_SQL(buffer, &sql, &len)) + { + if (m_match && pcre2_match_8(m_match, (PCRE2_SPTR)sql, len, 0, 0, + m_md_match, NULL) < 0) + { + MXS_INFO("Query does not match the 'match' pattern: %.*s", len, sql); + rval = false; + } + else if (m_exclude && pcre2_match_8(m_exclude, (PCRE2_SPTR)sql, len, + 0, 0, m_md_exclude, NULL) >= 0) + { + MXS_INFO("Query matches the 'exclude' pattern: %.*s", len, sql); + rval = false; + } + } + } + + return rval; +} diff --git a/server/modules/filter/tee/teesession.hh b/server/modules/filter/tee/teesession.hh index 9d731b0b7..5c9580d52 100644 --- a/server/modules/filter/tee/teesession.hh +++ b/server/modules/filter/tee/teesession.hh @@ -38,6 +38,14 @@ public: json_t* diagnostics_json() const; private: - TeeSession(MXS_SESSION* session, LocalClient* client); - LocalClient* m_client; /**< The client connection to the local service */ + TeeSession(MXS_SESSION* session, LocalClient* client, + pcre2_code* match, pcre2_match_data* md_match, + pcre2_code* exclude, pcre2_match_data* md_exclude); + bool query_matches(GWBUF* buffer); + + LocalClient* m_client; /**< The client connection to the local service */ + pcre2_code* m_match; + pcre2_match_data* m_md_match; + pcre2_code* m_exclude; + pcre2_match_data* m_md_exclude; }; diff --git a/server/modules/monitor/galeramon/galeramon.c b/server/modules/monitor/galeramon/galeramon.c index 93d1d8d3d..6cbda4144 100644 --- a/server/modules/monitor/galeramon/galeramon.c +++ b/server/modules/monitor/galeramon/galeramon.c @@ -13,31 +13,14 @@ /** * @file galera_mon.c - A MySQL Galera cluster monitor - * - * @verbatim - * Revision History - * - * Date Who Description - * 22/07/13 Mark Riddoch Initial implementation - * 21/05/14 Massimiliano Pinto Monitor sets a master server that has the lowest value of wsrep_local_index - * 23/05/14 Massimiliano Pinto Added 1 configuration option (setInterval). Interval is printed in diagnostics. - * 03/06/14 Mark Riddoch Add support for maintenance mode - * 24/06/14 Massimiliano Pinto Added depth level 0 for each node - * 30/10/14 Massimiliano Pinto Added disableMasterFailback feature - * 10/11/14 Massimiliano Pinto Added setNetworkTimeout for connect,read,write - * 20/04/15 Guillaume Lefranc Added availableWhenDonor feature - * 22/04/15 Martin Brampton Addition of disableMasterRoleSetting - * 08/05/15 Markus Makela Addition of launchable scripts - * 17/10/15 Martin Brampton Change DCB callback to hangup - * - * @endverbatim */ #define MXS_MODULE_NAME "galeramon" #include "galeramon.h" -#include #include +#include +#include #define DONOR_NODE_NAME_MAX_LEN 60 #define DONOR_LIST_SET_VAR "SET GLOBAL wsrep_sst_donor = \"" @@ -331,11 +314,8 @@ monitorDatabase(MXS_MONITOR *mon, MXS_MONITOR_SERVERS *database) server_set_status_nolock(database->server, SERVER_RUNNING); /* get server version string */ - server_string = (char *) mysql_get_server_info(database->con); - if (server_string) - { - server_set_version_string(database->server, server_string); - } + mxs_mysql_set_server_version(database->con, database->server); + server_string = database->server->version_string; /* Check if the the Galera FSM shows this node is joined to the cluster */ char *cluster_member = "SHOW STATUS WHERE Variable_name IN" diff --git a/server/modules/monitor/mmmon/mmmon.c b/server/modules/monitor/mmmon/mmmon.c index 3253e164e..20448f759 100644 --- a/server/modules/monitor/mmmon/mmmon.c +++ b/server/modules/monitor/mmmon/mmmon.c @@ -20,6 +20,7 @@ #include "mmmon.h" #include #include +#include static void monitorMain(void *); @@ -261,11 +262,8 @@ monitorDatabase(MXS_MONITOR* mon, MXS_MONITOR_SERVERS *database) server_version = mysql_get_server_version(database->con); /* get server version string */ - server_string = (char *) mysql_get_server_info(database->con); - if (server_string) - { - server_set_version_string(database->server, server_string); - } + mxs_mysql_set_server_version(database->con, database->server); + server_string = database->server->version_string; /* get server_id form current node */ if (mysql_query(database->con, "SELECT @@server_id") == 0 diff --git a/server/modules/monitor/mysqlmon/mysql_mon.c b/server/modules/monitor/mysqlmon/mysql_mon.c index c8c00e498..4f1a8baaf 100644 --- a/server/modules/monitor/mysqlmon/mysql_mon.c +++ b/server/modules/monitor/mysqlmon/mysql_mon.c @@ -18,10 +18,11 @@ #define MXS_MODULE_NAME "mysqlmon" #include "../mysqlmon.h" -#include -#include #include +#include #include +#include +#include #define DEFAULT_JOURNAL_MAX_AGE "28800" @@ -640,7 +641,7 @@ static MXS_MONITOR_SERVERS *build_mysql51_replication_tree(MXS_MONITOR *mon) (database->server->master_id <= 0 || database->server->master_id != handle->master->server->node_id)) { - monitor_clear_pending_status(database, SERVER_SLAVE); + monitor_set_pending_status(database, SERVER_SLAVE); monitor_set_pending_status(database, SERVER_SLAVE_OF_EXTERNAL_MASTER); } database = database->next; @@ -726,11 +727,8 @@ monitorDatabase(MXS_MONITOR *mon, MXS_MONITOR_SERVERS *database) server_version = mysql_get_server_version(database->con); /* get server version string */ - server_string = (char *) mysql_get_server_info(database->con); - if (server_string) - { - server_set_version_string(database->server, server_string); - } + mxs_mysql_set_server_version(database->con, database->server); + server_string = database->server->version_string; MYSQL_SERVER_INFO *serv_info = hashtable_fetch(handle->server_info, database->server->unique_name); ss_dassert(serv_info); @@ -1853,7 +1851,7 @@ static MXS_MONITOR_SERVERS *get_replication_tree(MXS_MONITOR *mon, int num_serve /* this server is slave of another server not in MaxScale configuration * we cannot use it as a real slave. */ - monitor_clear_pending_status(ptr, SERVER_SLAVE); + monitor_set_pending_status(ptr, SERVER_SLAVE); monitor_set_pending_status(ptr, SERVER_SLAVE_OF_EXTERNAL_MASTER); } } diff --git a/server/modules/monitor/ndbclustermon/ndbclustermon.c b/server/modules/monitor/ndbclustermon/ndbclustermon.c index 7840fe5ac..1bbd345fb 100644 --- a/server/modules/monitor/ndbclustermon/ndbclustermon.c +++ b/server/modules/monitor/ndbclustermon/ndbclustermon.c @@ -13,22 +13,13 @@ /** * @file ndbcluster_mon.c - A MySQL cluster SQL node monitor - * - * @verbatim - * Revision History - * - * Date Who Description - * 25/07/14 Massimiliano Pinto Initial implementation - * 10/11/14 Massimiliano Pinto Added setNetworkTimeout for connect,read,write - * 08/05/15 Markus Makela Addition of launchable scripts - * - * @endverbatim */ #define MXS_MODULE_NAME "ndbclustermon" #include "../mysqlmon.h" #include +#include static void monitorMain(void *); @@ -234,11 +225,8 @@ monitorDatabase(MXS_MONITOR_SERVERS *database, char *defaultUser, char *defaultP server_set_status_nolock(database->server, SERVER_RUNNING); /* get server version string */ - server_string = (char *) mysql_get_server_info(database->con); - if (server_string) - { - server_set_version_string(database->server, server_string); - } + mxs_mysql_set_server_version(database->con, database->server); + server_string = database->server->version_string; /* Check if the the SQL node is able to contact one or more data nodes */ if (mysql_query(database->con, "SHOW STATUS LIKE 'Ndb_number_of_ready_data_nodes'") == 0 diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index 2e8fc4a59..622c4e0f5 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -808,18 +808,27 @@ gw_read_and_write(DCB *dcb) */ if (protocol_get_srv_command((MySQLProtocol *)dcb->protocol, true) != MYSQL_COM_UNDEFINED) { - stmt = process_response_data(dcb, &read_buffer, gwbuf_length(read_buffer)); - /** - * Received incomplete response to session command. - * Store it to readqueue and return. - */ - if (!sescmd_response_complete(dcb)) + if (result_collected) { - stmt = gwbuf_append(stmt, read_buffer); - dcb->dcb_readqueue = gwbuf_append(stmt, dcb->dcb_readqueue); - return 0; + /** The result set or PS response was collected, we know it's complete */ + stmt = read_buffer; + read_buffer = NULL; + gwbuf_set_type(stmt, GWBUF_TYPE_RESPONSE_END | GWBUF_TYPE_SESCMD_RESPONSE); + } + else + { + stmt = process_response_data(dcb, &read_buffer, gwbuf_length(read_buffer)); + /** + * Received incomplete response to session command. + * Store it to readqueue and return. + */ + if (!sescmd_response_complete(dcb)) + { + stmt = gwbuf_append(stmt, read_buffer); + dcb->dcb_readqueue = gwbuf_append(stmt, dcb->dcb_readqueue); + return 0; + } } - if (!stmt) { MXS_ERROR("Read buffer unexpectedly null, even though response " diff --git a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c index 3421256d4..24580e31a 100644 --- a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c +++ b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c @@ -224,8 +224,7 @@ int MySQLSendHandshake(DCB* dcb) { mysql_server_language = dcb->service->dbref->server->charset; - if (dcb->service->dbref->server->server_string && - strstr(dcb->service->dbref->server->server_string, "10.2.")) + if (strstr(dcb->service->dbref->server->version_string, "10.2.")) { /** The backend servers support the extended capabilities */ is_maria = true; @@ -929,6 +928,10 @@ gw_read_normal_data(DCB *dcb, GWBUF *read_buffer, int nbytes_read) } } + /** The query classifier classifies according to the service's server that has + * the smallest version number. */ + qc_set_server_version(service_get_version(session->service, SERVICE_VERSION_MIN)); + spec_com_res_t res = process_special_commands(dcb, read_buffer, nbytes_read); int rval = 1; switch (res) diff --git a/server/modules/protocol/MySQL/mysql_common.c b/server/modules/protocol/MySQL/mysql_common.c index d0b67c604..3ea5ec1cc 100644 --- a/server/modules/protocol/MySQL/mysql_common.c +++ b/server/modules/protocol/MySQL/mysql_common.c @@ -1587,3 +1587,15 @@ mysql_server_cmd_t mxs_mysql_current_command(MXS_SESSION* session) MySQLProtocol* proto = (MySQLProtocol*)session->client_dcb->protocol; return proto->current_command; } + +const char* mxs_mysql_get_current_db(MXS_SESSION* session) +{ + MYSQL_session* data = (MYSQL_session*)session->client_dcb->data; + return data->db; +} + +void mxs_mysql_set_current_db(MXS_SESSION* session, const char* db) +{ + MYSQL_session* data = (MYSQL_session*)session->client_dcb->data; + snprintf(data->db, sizeof(data->db), "%s", db); +} diff --git a/server/modules/protocol/examples/cdc.py b/server/modules/protocol/examples/cdc.py index 420f5546e..2b3f8e22d 100755 --- a/server/modules/protocol/examples/cdc.py +++ b/server/modules/protocol/examples/cdc.py @@ -20,7 +20,10 @@ import selectors import binascii import os +schema_read = False + def read_data(): + global schema_read sel = selectors.DefaultSelector() sel.register(sock, selectors.EVENT_READ) @@ -29,8 +32,17 @@ def read_data(): events = sel.select(timeout=int(opts.read_timeout) if int(opts.read_timeout) > 0 else None) buf = sock.recv(4096, socket.MSG_DONTWAIT) if len(buf) > 0: + # If the request for data is rejected, an error will be sent instead of the table schema + if not schema_read: + if "err" in buf.decode().lower(): + print(buf.decode(), file=sys.stderr) + exit(1) + else: + schema_read = True + os.write(sys.stdout.fileno(), buf) sys.stdout.flush() + else: raise Exception('Socket was closed') @@ -40,6 +52,13 @@ def read_data(): print(ex, file=sys.stderr) break + +def check_for_err(err): + if "err" in err.lower().strip(): + print(err.strip(), file=sys.stderr) + exit(1) + + parser = argparse.ArgumentParser(description = "CDC Binary consumer", conflict_handler="resolve") parser.add_argument("-h", "--host", dest="host", help="Network address where the connection is made", default="localhost") parser.add_argument("-P", "--port", dest="port", help="Port where the connection is made", default="4001") @@ -60,13 +79,17 @@ auth_string += bytes(hashlib.sha1(opts.password.encode("utf_8")).hexdigest().enc sock.send(auth_string) # Discard the response -response = str(sock.recv(1024)).encode('utf_8') +response = sock.recv(1024).decode() + +check_for_err(response) # Register as a client as request Avro format data sock.send(bytes(("REGISTER UUID=XXX-YYY_YYY, TYPE=" + opts.format).encode())) # Discard the response again -response = str(sock.recv(1024)).encode('utf_8') +response = sock.recv(1024).decode() + +check_for_err(response) # Request a data stream sock.send(bytes(("REQUEST-DATA " + opts.FILE + (" " + opts.GTID if opts.GTID else "")).encode())) diff --git a/server/modules/protocol/examples/cdc_kafka_producer.py b/server/modules/protocol/examples/cdc_kafka_producer.py index 8e78a1c42..f660ea781 100755 --- a/server/modules/protocol/examples/cdc_kafka_producer.py +++ b/server/modules/protocol/examples/cdc_kafka_producer.py @@ -38,7 +38,7 @@ while True: if len(buf) == 0: break - data = buf.encode().strip() + data = buf[:-1] producer.send(topic=opts.kafka_topic, value=data) producer.flush() diff --git a/server/modules/routing/avrorouter/avro_schema.c b/server/modules/routing/avrorouter/avro_schema.c index a82c37c38..fe2c69185 100644 --- a/server/modules/routing/avrorouter/avro_schema.c +++ b/server/modules/routing/avrorouter/avro_schema.c @@ -575,9 +575,12 @@ int extract_type_length(const char* ptr, char *dest) } /** Store type */ - int typelen = ptr - start; - memcpy(dest, start, typelen); - dest[typelen] = '\0'; + for (const char* c = start; c < ptr; c++) + { + *dest++ = tolower(*c); + } + + *dest++ = '\0'; /** Skip whitespace */ while (*ptr && isspace(*ptr)) @@ -880,7 +883,7 @@ void read_alter_identifier(const char *sql, const char *end, char *dest, int siz void make_avro_token(char* dest, const char* src, int length) { - while (*src == '(' || *src == ')' || *src == '`' || isspace(*src)) + while (length > 0 && (*src == '(' || *src == ')' || *src == '`' || isspace(*src))) { src++; length--; @@ -902,16 +905,17 @@ void make_avro_token(char* dest, const char* src, int length) fix_reserved_word(dest); } -int get_column_index(TABLE_CREATE *create, const char *tok) +int get_column_index(TABLE_CREATE *create, const char *tok, int len) { int idx = -1; - char safe_tok[strlen(tok) + 2]; - strcpy(safe_tok, tok); + char safe_tok[len + 2]; + memcpy(safe_tok, tok, len); + safe_tok[len] = '\0'; fix_reserved_word(safe_tok); for (int x = 0; x < create->columns; x++) { - if (strcasecmp(create->column_names[x], tok) == 0) + if (strcasecmp(create->column_names[x], safe_tok) == 0) { idx = x; break; @@ -950,18 +954,17 @@ bool table_create_alter(TABLE_CREATE *create, const char *sql, const char *end) { tok = get_tok(tok + len, &len, end); - char ** tmp = MXS_REALLOC(create->column_names, sizeof(char*) * create->columns + 1); - ss_dassert(tmp); + create->column_names = MXS_REALLOC(create->column_names, sizeof(char*) * create->columns + 1); + create->column_types = MXS_REALLOC(create->column_types, sizeof(char*) * create->columns + 1); + create->column_lengths = MXS_REALLOC(create->column_lengths, sizeof(int) * create->columns + 1); - if (tmp == NULL) - { - return false; - } - - create->column_names = tmp; char avro_token[len + 1]; make_avro_token(avro_token, tok, len); + char field_type[200] = ""; // Enough to hold all types + int field_length = extract_type_length(tok + len, field_type); create->column_names[create->columns] = MXS_STRDUP_A(avro_token); + create->column_types[create->columns] = MXS_STRDUP_A(field_type); + create->column_lengths[create->columns] = field_length; create->columns++; updates++; tok = get_next_def(tok, end); @@ -971,25 +974,22 @@ bool table_create_alter(TABLE_CREATE *create, const char *sql, const char *end) { tok = get_tok(tok + len, &len, end); - int idx = get_column_index(create, tok); + int idx = get_column_index(create, tok, len); if (idx != -1) { MXS_FREE(create->column_names[idx]); + MXS_FREE(create->column_types[idx]); for (int i = idx; i < (int)create->columns - 1; i++) { create->column_names[i] = create->column_names[i + 1]; + create->column_types[i] = create->column_types[i + 1]; + create->column_lengths[i] = create->column_lengths[i + 1]; } - char ** tmp = realloc(create->column_names, sizeof(char*) * create->columns - 1); - ss_dassert(tmp); - - if (tmp == NULL) - { - return false; - } - - create->column_names = tmp; + create->column_names = MXS_REALLOC(create->column_names, sizeof(char*) * create->columns - 1); + create->column_types = MXS_REALLOC(create->column_types, sizeof(char*) * create->columns - 1); + create->column_lengths = MXS_REALLOC(create->column_lengths, sizeof(int) * create->columns - 1); create->columns--; updates++; } @@ -1001,12 +1001,19 @@ bool table_create_alter(TABLE_CREATE *create, const char *sql, const char *end) { tok = get_tok(tok + len, &len, end); - int idx = get_column_index(create, tok); + int idx = get_column_index(create, tok, len); - if (idx != -1) + if (idx != -1 && (tok = get_tok(tok + len, &len, end))) { MXS_FREE(create->column_names[idx]); - create->column_names[idx] = strndup(tok, len); + MXS_FREE(create->column_types[idx]); + char avro_token[len + 1]; + make_avro_token(avro_token, tok, len); + char field_type[200] = ""; // Enough to hold all types + int field_length = extract_type_length(tok + len, field_type); + create->column_names[idx] = MXS_STRDUP_A(avro_token); + create->column_types[idx] = MXS_STRDUP_A(field_type); + create->column_lengths[idx] = field_length; updates++; } @@ -1021,7 +1028,7 @@ bool table_create_alter(TABLE_CREATE *create, const char *sql, const char *end) } /** Only increment the create version if it has an associated .avro - * file. The .avro file is only created if it is acutally used. */ + * file. The .avro file is only created if it is actually used. */ if (updates > 0 && create->was_used) { create->version++; diff --git a/server/modules/routing/readconnroute/readconnroute.c b/server/modules/routing/readconnroute/readconnroute.c index 43d51cc98..36ffe5095 100644 --- a/server/modules/routing/readconnroute/readconnroute.c +++ b/server/modules/routing/readconnroute/readconnroute.c @@ -505,7 +505,7 @@ closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session) static void log_closed_session(mysql_server_cmd_t mysql_command, bool is_closed, SERVER_REF *ref) { - char msg[MAX_SERVER_NAME_LEN + 200] = ""; // Extra space for message + char msg[MAX_SERVER_ADDRESS_LEN + 200] = ""; // Extra space for message if (is_closed) { diff --git a/server/modules/routing/readwritesplit/CMakeLists.txt b/server/modules/routing/readwritesplit/CMakeLists.txt index ac14488b4..c970f3c63 100644 --- a/server/modules/routing/readwritesplit/CMakeLists.txt +++ b/server/modules/routing/readwritesplit/CMakeLists.txt @@ -1,4 +1,11 @@ -add_library(readwritesplit SHARED readwritesplit.c rwsplit_mysql.c rwsplit_route_stmt.c rwsplit_select_backends.c rwsplit_session_cmd.c rwsplit_tmp_table_multi.c) +add_library(readwritesplit SHARED +readwritesplit.cc +rwsplit_mysql.cc +rwsplit_route_stmt.cc +rwsplit_select_backends.cc +rwsplit_session_cmd.cc +rwsplit_tmp_table_multi.cc +rwsplit_ps.cc) target_link_libraries(readwritesplit maxscale-common MySQLCommon) set_target_properties(readwritesplit PROPERTIES VERSION "1.0.2") install_module(readwritesplit core) diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.cc similarity index 65% rename from server/modules/routing/readwritesplit/readwritesplit.c rename to server/modules/routing/readwritesplit/readwritesplit.cc index 982b773d0..fcb253af8 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.cc @@ -11,7 +11,8 @@ * Public License. */ -#include "readwritesplit.h" +#include "readwritesplit.hh" +#include "rwsplit_internal.hh" #include #include @@ -19,10 +20,10 @@ #include #include #include +#include + #include -#include "rwsplit_internal.h" - #include #include #include @@ -32,34 +33,13 @@ #include /** - * @file readwritesplit.c The entry points for the read/write query splitting - * router module. + * The entry points for the read/write query splitting router module. * - * This file contains the entry points that comprise the API to the read write - * query splitting router. It also contains functions that are directly called - * by the entry point functions. Some of these are used by functions in other - * modules of the read write split router, others are used only within this - * module. - * - * @verbatim - * Revision History - * - * Date Who Description - * 01/07/2013 Vilho Raatikka Initial implementation - * 15/07/2013 Massimiliano Pinto Added clientReply from master only in case - * of session change - * 17/07/2013 Massimiliano Pinto clientReply is now used by mysql_backend - * for all reply situations - * 18/07/2013 Massimiliano Pinto routeQuery now handles COM_QUIT - * as QUERY_TYPE_SESSION_WRITE - * 17/07/2014 Massimiliano Pinto Server connection counter is updated in - * closeSession - * 09/09/2015 Martin Brampton Modify error handler - * 25/09/2015 Martin Brampton Block callback processing when no router - * session in the DCB - * 03/08/2016 Martin Brampton Extract the API functions, move the rest - * - * @endverbatim + * This file contains the entry points that comprise the API to the read + * write query splitting router. It also contains functions that are + * directly called by the entry point functions. Some of these are used by + * functions in other modules of the read write split router, others are + * used only within this module. */ /** Maximum number of slaves */ @@ -69,33 +49,8 @@ * The functions that implement the router module API */ -static MXS_ROUTER *createInstance(SERVICE *service, char **options); -static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session); -static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session); -static void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session); static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *session, GWBUF *queue); -static void diagnostics(MXS_ROUTER *instance, DCB *dcb); -static json_t* diagnostics_json(const MXS_ROUTER *instance); -static void clientReply(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue, - DCB *backend_dcb); -static void handleError(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, - GWBUF *errmsgbuf, DCB *backend_dcb, - mxs_error_action_t action, bool *succp); -static uint64_t getCapabilities(MXS_ROUTER* instance); -/* - * End of the API functions; now the module structure that links to them. - * Note that the function names are chosen to exactly match the names used in - * the definition of ROUTER_OBJECT. This is not obligatory, but is done to - * make it easier to track the connection between calls and functions. - */ - -/* - * Declaration of functions that are used only within this module, and are - * not part of the API. - */ - -static void free_rwsplit_instance(ROUTER_INSTANCE *router); static bool rwsplit_process_router_options(ROUTER_INSTANCE *router, char **options); static void handle_error_reply_client(MXS_SESSION *ses, ROUTER_CLIENT_SES *rses, @@ -105,7 +60,6 @@ static bool handle_error_new_connection(ROUTER_INSTANCE *inst, DCB *backend_dcb, GWBUF *errmsg); static bool have_enough_servers(ROUTER_CLIENT_SES *rses, const int min_nsrv, int router_nsrv, ROUTER_INSTANCE *router); -static bool create_backends(ROUTER_CLIENT_SES *rses, backend_ref_t** dest, int* n_backend); /** * Enum values for router parameters @@ -135,993 +89,8 @@ static const MXS_ENUM_VALUE master_failure_mode_values[] = }; /** - * The module entry point routine. It is this routine that - * must return the structure that is referred to as the - * "module object", this is a structure with the set of - * external entry points for this module. - * - * @return The module object + * Internal functions */ -MXS_MODULE *MXS_CREATE_MODULE() -{ - static MXS_ROUTER_OBJECT MyObject = - { - createInstance, - newSession, - closeSession, - freeSession, - routeQuery, - diagnostics, - diagnostics_json, - clientReply, - handleError, - getCapabilities, - NULL - }; - - static MXS_MODULE info = - { - MXS_MODULE_API_ROUTER, MXS_MODULE_GA, MXS_ROUTER_VERSION, - "A Read/Write splitting router for enhancement read scalability", - "V1.1.0", - RCAP_TYPE_STMT_INPUT | RCAP_TYPE_TRANSACTION_TRACKING | RCAP_TYPE_STMT_OUTPUT, - &MyObject, - NULL, /* Process init. */ - NULL, /* Process finish. */ - NULL, /* Thread init. */ - NULL, /* Thread finish. */ - { - { - "use_sql_variables_in", - MXS_MODULE_PARAM_ENUM, - "all", - MXS_MODULE_OPT_NONE, - use_sql_variables_in_values - }, - { - "slave_selection_criteria", - MXS_MODULE_PARAM_ENUM, - "LEAST_CURRENT_OPERATIONS", - MXS_MODULE_OPT_NONE, - slave_selection_criteria_values - }, - { - "master_failure_mode", - MXS_MODULE_PARAM_ENUM, - "fail_instantly", - MXS_MODULE_OPT_NONE, - master_failure_mode_values - }, - {"max_slave_replication_lag", MXS_MODULE_PARAM_INT, "-1"}, - {"max_slave_connections", MXS_MODULE_PARAM_STRING, MAX_SLAVE_COUNT}, - {"retry_failed_reads", MXS_MODULE_PARAM_BOOL, "true"}, - {"disable_sescmd_history", MXS_MODULE_PARAM_BOOL, "true"}, - {"max_sescmd_history", MXS_MODULE_PARAM_COUNT, "0"}, - {"strict_multi_stmt", MXS_MODULE_PARAM_BOOL, "true"}, - {"master_accept_reads", MXS_MODULE_PARAM_BOOL, "false"}, - {"connection_keepalive", MXS_MODULE_PARAM_COUNT, "0"}, - {MXS_END_MODULE_PARAMS} - } - }; - - MXS_NOTICE("Initializing statement-based read/write split router module."); - return &info; -} - -// TODO: Don't process parameters in readwritesplit -static bool handle_max_slaves(ROUTER_INSTANCE *router, const char *str) -{ - bool rval = true; - char *endptr; - int val = strtol(str, &endptr, 10); - - if (*endptr == '%' && *(endptr + 1) == '\0') - { - router->rwsplit_config.rw_max_slave_conn_percent = val; - router->rwsplit_config.max_slave_connections = 0; - } - else if (*endptr == '\0') - { - router->rwsplit_config.max_slave_connections = val; - router->rwsplit_config.rw_max_slave_conn_percent = 0; - } - else - { - MXS_ERROR("Invalid value for 'max_slave_connections': %s", str); - rval = false; - } - - return rval; -} - -/** - * @brief Create an instance of the read/write router (API). - * - * Create an instance of read/write statement router within the MaxScale. One - * instance of the router is required for each service that is defined in the - * configuration as using this router. One instance of the router will handle - * multiple connections (or router sessions). - * - * @param service The service this router is being create for - * @param options The options for this query router - * @return NULL in failure, pointer to router in success. - */ -static MXS_ROUTER *createInstance(SERVICE *service, char **options) -{ - ROUTER_INSTANCE *router; - - if ((router = MXS_CALLOC(1, sizeof(ROUTER_INSTANCE))) == NULL) - { - return NULL; - } - router->service = service; - - /* - * Until we know otherwise assume we have some available slaves. - */ - router->available_slaves = true; - - /** By default, the client connection is closed immediately when a master - * failure is detected */ - router->rwsplit_config.master_failure_mode = RW_FAIL_INSTANTLY; - - MXS_CONFIG_PARAMETER *params = service->svc_config_param; - - router->rwsplit_config.use_sql_variables_in = config_get_enum(params, "use_sql_variables_in", - use_sql_variables_in_values); - - router->rwsplit_config.slave_selection_criteria = config_get_enum(params, "slave_selection_criteria", - slave_selection_criteria_values); - - router->rwsplit_config.master_failure_mode = config_get_enum(params, "master_failure_mode", - master_failure_mode_values); - - router->rwsplit_config.max_slave_replication_lag = config_get_integer(params, "max_slave_replication_lag"); - router->rwsplit_config.retry_failed_reads = config_get_bool(params, "retry_failed_reads"); - router->rwsplit_config.strict_multi_stmt = config_get_bool(params, "strict_multi_stmt"); - router->rwsplit_config.disable_sescmd_history = config_get_bool(params, "disable_sescmd_history"); - router->rwsplit_config.max_sescmd_history = config_get_integer(params, "max_sescmd_history"); - router->rwsplit_config.master_accept_reads = config_get_bool(params, "master_accept_reads"); - router->rwsplit_config.connection_keepalive = config_get_integer(params, "connection_keepalive"); - - if (!handle_max_slaves(router, config_get_string(params, "max_slave_connections")) || - (options && !rwsplit_process_router_options(router, options))) - { - free_rwsplit_instance(router); - return NULL; - } - - /** These options cancel each other out */ - if (router->rwsplit_config.disable_sescmd_history && - router->rwsplit_config.max_sescmd_history > 0) - { - router->rwsplit_config.max_sescmd_history = 0; - } - - return (MXS_ROUTER *)router; -} - -/** - * @brief Associate a new session with this instance of the router (API). - * - * The session is used to store all the data required by the router for a - * particular client connection. The instance of the router that relates to a - * particular service is passed as the first parameter. The second parameter is - * the session that has been created in response to the request from a client - * for a connection. The passed session contains generic information; this - * function creates the session structure that holds router specific data. - * There is often a one to one relationship between sessions and router - * sessions, although it is possible to create configurations where a - * connection is handled by multiple routers, one after another. - * - * @param instance The router instance data - * @param session The MaxScale session (generic connection data) - * @return Session specific data for this session, i.e. a router session - */ -static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *router_inst, MXS_SESSION *session) -{ - ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)router_inst; - ROUTER_CLIENT_SES *client_rses = (ROUTER_CLIENT_SES *)MXS_CALLOC(1, sizeof(ROUTER_CLIENT_SES)); - - if (client_rses == NULL) - { - return NULL; - } -#if defined(SS_DEBUG) - client_rses->rses_chk_top = CHK_NUM_ROUTER_SES; - client_rses->rses_chk_tail = CHK_NUM_ROUTER_SES; -#endif - - client_rses->router = router; - client_rses->client_dcb = session->client_dcb; - client_rses->have_tmp_tables = false; - client_rses->forced_node = NULL; - client_rses->expected_responses = 0; - client_rses->query_queue = NULL; - client_rses->load_data_state = LOAD_DATA_INACTIVE; - memcpy(&client_rses->rses_config, &router->rwsplit_config, sizeof(client_rses->rses_config)); - - int router_nservers = router->service->n_dbref; - const int min_nservers = 1; /*< hard-coded for now */ - - if (!have_enough_servers(client_rses, min_nservers, router_nservers, router)) - { - MXS_FREE(client_rses); - return NULL; - } - - /** - * Create backend reference objects for this session. - */ - backend_ref_t *backend_ref; - - if (!create_backends(client_rses, &backend_ref, &router_nservers)) - { - MXS_FREE(client_rses); - return NULL; - } - - int max_nslaves = rses_get_max_slavecount(client_rses, router_nservers); - int max_slave_rlag = rses_get_max_replication_lag(client_rses); - - client_rses->rses_backend_ref = backend_ref; - client_rses->rses_nbackends = router_nservers; /*< # of backend servers */ - - backend_ref_t *master_ref = NULL; /*< pointer to selected master */ - if (!select_connect_backend_servers(&master_ref, backend_ref, router_nservers, - max_nslaves, max_slave_rlag, - client_rses->rses_config.slave_selection_criteria, - session, router, false)) - { - /** - * Master and at least slaves must be found if the router is - * in the strict mode. If sessions without master are allowed, only - * slaves must be found. - */ - MXS_FREE(client_rses->rses_backend_ref); - MXS_FREE(client_rses); - return NULL; - } - - /** Copy backend pointers to router session. */ - client_rses->rses_master_ref = master_ref; - - if (client_rses->rses_config.rw_max_slave_conn_percent) - { - int n_conn = 0; - double pct = (double)client_rses->rses_config.rw_max_slave_conn_percent / 100.0; - n_conn = MXS_MAX(floor((double)client_rses->rses_nbackends * pct), 1); - client_rses->rses_config.max_slave_connections = n_conn; - } - - router->stats.n_sessions += 1; - - return (void *)client_rses; -} - -/** - * @brief Close a router session (API). - * - * Close a session with the router, this is the mechanism by which a router - * may cleanup data structure etc. The instance of the router that relates to - * the relevant service is passed, along with the router session that is to - * be closed. Typically the function is used in conjunction with freeSession - * which will release the resources used by a router session (see below). - * - * @param instance The router instance data - * @param session The router session being closed - */ -static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session) -{ - ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_session; - CHK_CLIENT_RSES(router_cli_ses); - - if (!router_cli_ses->rses_closed) - { - /** - * Mark router session as closed. @c rses_closed is checked at the start - * of every API function to quickly stop the processing of closed sessions. - */ - router_cli_ses->rses_closed = true; - - for (int i = 0; i < router_cli_ses->rses_nbackends; i++) - { - backend_ref_t *bref = &router_cli_ses->rses_backend_ref[i]; - - if (BREF_IS_IN_USE(bref)) - { - /** This backend is in use and it needs to be closed */ - DCB *dcb = bref->bref_dcb; - CHK_DCB(dcb); - ss_dassert(dcb->session->state == SESSION_STATE_STOPPING); - - if (BREF_IS_WAITING_RESULT(bref)) - { - /** This backend was executing a query when the session was closed */ - bref_clear_state(bref, BREF_WAITING_RESULT); - } - bref_clear_state(bref, BREF_IN_USE); - bref_set_state(bref, BREF_CLOSED); - - RW_CHK_DCB(bref, dcb); - - /** MXS-956: This will prevent closed DCBs from being closed twice. - * It should not happen but for currently unknown reasons, a DCB - * gets closed twice; first in handleError and a second time here. */ - if (dcb && dcb->state == DCB_STATE_POLLING) - { - dcb_close(dcb); - } - - RW_CLOSE_BREF(bref); - - /** decrease server current connection counters */ - atomic_add(&bref->ref->connections, -1); - } - else - { - ss_dassert(!BREF_IS_WAITING_RESULT(bref)); - - /** This should never be true unless a backend reference is taken - * out of use before clearing the BREF_WAITING_RESULT state */ - if (BREF_IS_WAITING_RESULT(bref)) - { - MXS_WARNING("A closed backend was expecting a result, this should not be possible. " - "Decrementing active operation counter for this backend."); - bref_clear_state(bref, BREF_WAITING_RESULT); - } - } - } - } -} - -/** - * @brief Free a router session (API). - * - * When a router session has been closed, freeSession can be called to free - * allocated resources. - * - * @param router_instance The router instance the session belongs to - * @param router_client_session Client session - * - */ -static void freeSession(MXS_ROUTER *router_instance, MXS_ROUTER_SESSION *router_client_session) -{ - ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session; - - /** - * For each property type, walk through the list, finalize properties - * and free the allocated memory. - */ - for (int i = RSES_PROP_TYPE_FIRST; i < RSES_PROP_TYPE_COUNT; i++) - { - rses_property_t *p = router_cli_ses->rses_properties[i]; - rses_property_t *q = p; - - while (p != NULL) - { - q = p->rses_prop_next; - rses_property_done(p); - p = q; - } - } - - MXS_FREE(router_cli_ses->rses_backend_ref); - MXS_FREE(router_cli_ses); - return; -} - -/** - * @brief Mark a backend reference as failed - * - * @param bref Backend reference to close - * @param fatal Whether the failure was fatal - */ -void close_failed_bref(backend_ref_t *bref, bool fatal) -{ - if (BREF_IS_WAITING_RESULT(bref)) - { - bref_clear_state(bref, BREF_WAITING_RESULT); - } - - bref_clear_state(bref, BREF_QUERY_ACTIVE); - bref_clear_state(bref, BREF_IN_USE); - bref_set_state(bref, BREF_CLOSED); - - if (fatal) - { - bref_set_state(bref, BREF_FATAL_FAILURE); - } - - if (sescmd_cursor_is_active(&bref->bref_sescmd_cur)) - { - sescmd_cursor_set_active(&bref->bref_sescmd_cur, false); - } -} - -bool route_stored_query(ROUTER_CLIENT_SES *rses) -{ - bool rval = true; - - if (rses->query_queue) - { - GWBUF* query_queue = modutil_get_next_MySQL_packet(&rses->query_queue); - query_queue = gwbuf_make_contiguous(query_queue); - - /** Store the query queue locally for the duration of the routeQuery call. - * This prevents recursive calls into this function. */ - GWBUF *temp_storage = rses->query_queue; - rses->query_queue = NULL; - - if (!routeQuery((MXS_ROUTER*)rses->router, (MXS_ROUTER_SESSION*)rses, query_queue)) - { - rval = false; - char* sql = modutil_get_SQL(query_queue); - - if (sql) - { - MXS_ERROR("Routing query \"%s\" failed.", sql); - MXS_FREE(sql); - } - else - { - MXS_ERROR("Failed to route query."); - } - gwbuf_free(query_queue); - } - - ss_dassert(rses->query_queue == NULL); - rses->query_queue = temp_storage; - } - - return rval; -} - -/** - * @brief The main routing entry point for a query (API) - * - * The routeQuery function will make the routing decision based on the contents - * of the instance, session and the query itself. The query always represents - * a complete MariaDB/MySQL packet because we define the RCAP_TYPE_STMT_INPUT in - * getCapabilities(). - * - * @param instance Router instance - * @param router_session Router session associated with the client - * @param querybuf Buffer containing the query - * @return 1 on success, 0 on error - */ -static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *querybuf) -{ - ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *) instance; - ROUTER_CLIENT_SES *rses = (ROUTER_CLIENT_SES *) router_session; - int rval = 0; - - CHK_CLIENT_RSES(rses); - - if (rses->rses_closed) - { - closed_session_reply(querybuf); - } - else - { - if ((rses->expected_responses == 0 && rses->query_queue == NULL) || - rses->load_data_state == LOAD_DATA_ACTIVE) - { - /** No active or pending queries */ - if (route_single_stmt(inst, rses, querybuf)) - { - rval = 1; - } - } - else - { - ss_dassert(rses->expected_responses || rses->query_queue); - /** We are already processing a request from the client. Store the - * new query and wait for the previous one to complete. */ - MXS_DEBUG("Storing query (len: %d cmd: %0x), expecting %d replies", - gwbuf_length(querybuf), GWBUF_DATA(querybuf)[4], - rses->expected_responses); - rses->query_queue = gwbuf_append(rses->query_queue, querybuf); - querybuf = NULL; - rval = 1; - - if (rses->expected_responses == 0 && !route_stored_query(rses)) - { - rval = 0; - } - } - } - - if (querybuf != NULL) - { - gwbuf_free(querybuf); - } - - return rval; -} - -/** - * @brief Diagnostics routine (API) - * - * Print query router statistics to the DCB passed in - * - * @param instance The router instance - * @param dcb The DCB for diagnostic output - */ -static void diagnostics(MXS_ROUTER *instance, DCB *dcb) -{ - ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; - const char *weightby = serviceGetWeightingParameter(router->service); - double master_pct = 0.0, slave_pct = 0.0, all_pct = 0.0; - - dcb_printf(dcb, "\n"); - dcb_printf(dcb, "\tuse_sql_variables_in: %s\n", - mxs_target_to_str(router->rwsplit_config.use_sql_variables_in)); - dcb_printf(dcb, "\tslave_selection_criteria: %s\n", - select_criteria_to_str(router->rwsplit_config.slave_selection_criteria)); - dcb_printf(dcb, "\tmaster_failure_mode: %s\n", - failure_mode_to_str(router->rwsplit_config.master_failure_mode)); - dcb_printf(dcb, "\tmax_slave_replication_lag: %d\n", - router->rwsplit_config.max_slave_replication_lag); - dcb_printf(dcb, "\tretry_failed_reads: %s\n", - router->rwsplit_config.retry_failed_reads ? "true" : "false"); - dcb_printf(dcb, "\tstrict_multi_stmt: %s\n", - router->rwsplit_config.strict_multi_stmt ? "true" : "false"); - dcb_printf(dcb, "\tdisable_sescmd_history: %s\n", - router->rwsplit_config.disable_sescmd_history ? "true" : "false"); - dcb_printf(dcb, "\tmax_sescmd_history: %d\n", - router->rwsplit_config.max_sescmd_history); - dcb_printf(dcb, "\tmaster_accept_reads: %s\n", - router->rwsplit_config.master_accept_reads ? "true" : "false"); - dcb_printf(dcb, "\n"); - - if (router->stats.n_queries > 0) - { - master_pct = ((double)router->stats.n_master / (double)router->stats.n_queries) * 100.0; - slave_pct = ((double)router->stats.n_slave / (double)router->stats.n_queries) * 100.0; - all_pct = ((double)router->stats.n_all / (double)router->stats.n_queries) * 100.0; - } - - dcb_printf(dcb, "\tNumber of router sessions: %" PRIu64 "\n", - router->stats.n_sessions); - dcb_printf(dcb, "\tCurrent no. of router sessions: %d\n", - router->service->stats.n_current); - dcb_printf(dcb, "\tNumber of queries forwarded: %" PRIu64 "\n", - router->stats.n_queries); - dcb_printf(dcb, "\tNumber of queries forwarded to master: %" PRIu64 " (%.2f%%)\n", - router->stats.n_master, master_pct); - dcb_printf(dcb, "\tNumber of queries forwarded to slave: %" PRIu64 " (%.2f%%)\n", - router->stats.n_slave, slave_pct); - dcb_printf(dcb, "\tNumber of queries forwarded to all: %" PRIu64 " (%.2f%%)\n", - router->stats.n_all, all_pct); - - if (*weightby) - { - dcb_printf(dcb, "\tConnection distribution based on %s " - "server parameter.\n", - weightby); - dcb_printf(dcb, "\t\tServer Target %% Connections " - "Operations\n"); - dcb_printf(dcb, "\t\t Global Router\n"); - for (SERVER_REF *ref = router->service->dbref; ref; ref = ref->next) - { - dcb_printf(dcb, "\t\t%-20s %3.1f%% %-6d %-6d %d\n", - ref->server->unique_name, (float)ref->weight / 10, - ref->server->stats.n_current, ref->connections, - ref->server->stats.n_current_ops); - } - } -} - -/** - * @brief Diagnostics routine (API) - * - * Print query router statistics to the DCB passed in - * - * @param instance The router instance - * @param dcb The DCB for diagnostic output - */ -static json_t* diagnostics_json(const MXS_ROUTER *instance) -{ - ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; - - json_t* rval = json_object(); - - json_object_set_new(rval, "use_sql_variables_in", - json_string(mxs_target_to_str(router->rwsplit_config.use_sql_variables_in))); - json_object_set_new(rval, "slave_selection_criteria", - json_string(select_criteria_to_str(router->rwsplit_config.slave_selection_criteria))); - json_object_set_new(rval, "master_failure_mode", - json_string(failure_mode_to_str(router->rwsplit_config.master_failure_mode))); - json_object_set_new(rval, "max_slave_replication_lag", - json_integer(router->rwsplit_config.max_slave_replication_lag)); - json_object_set_new(rval, "retry_failed_reads", - json_boolean(router->rwsplit_config.retry_failed_reads)); - json_object_set_new(rval, "strict_multi_stmt", - json_boolean(router->rwsplit_config.strict_multi_stmt)); - json_object_set_new(rval, "disable_sescmd_history", - json_boolean(router->rwsplit_config.disable_sescmd_history)); - json_object_set_new(rval, "max_sescmd_history", - json_integer(router->rwsplit_config.max_sescmd_history)); - json_object_set_new(rval, "master_accept_reads", - json_boolean(router->rwsplit_config.master_accept_reads)); - - - json_object_set_new(rval, "connections", json_integer(router->stats.n_sessions)); - json_object_set_new(rval, "current_connections", json_integer(router->service->stats.n_current)); - json_object_set_new(rval, "queries", json_integer(router->stats.n_queries)); - json_object_set_new(rval, "route_master", json_integer(router->stats.n_master)); - json_object_set_new(rval, "route_slave", json_integer(router->stats.n_slave)); - json_object_set_new(rval, "route_all", json_integer(router->stats.n_all)); - - const char *weightby = serviceGetWeightingParameter(router->service); - - if (*weightby) - { - json_object_set_new(rval, "weightby", json_string(weightby)); - } - - return rval; -} - -/** - * @brief Check if we have received a complete reply from the backend - * - * @param bref Backend reference - * @param buffer Buffer containing the response - * - * @return True if the complete response has been received - */ -bool reply_is_complete(backend_ref_t* bref, GWBUF *buffer) -{ - mysql_server_cmd_t cmd = mxs_mysql_current_command(bref->bref_dcb->session); - - if (bref->reply_state == REPLY_STATE_START && !mxs_mysql_is_result_set(buffer)) - { - if (cmd == MYSQL_COM_STMT_PREPARE || !mxs_mysql_more_results_after_ok(buffer)) - { - /** Not a result set, we have the complete response */ - LOG_RS(bref, REPLY_STATE_DONE); - bref->reply_state = REPLY_STATE_DONE; - } - } - else - { - bool more = false; - int old_eof = bref->reply_state == REPLY_STATE_RSET_ROWS ? 1 : 0; - int n_eof = modutil_count_signal_packets(buffer, old_eof, &more); - - if (n_eof == 0) - { - /** Waiting for the EOF packet after the column definitions */ - LOG_RS(bref, REPLY_STATE_RSET_COLDEF); - bref->reply_state = REPLY_STATE_RSET_COLDEF; - } - else if (n_eof == 1 && cmd != MYSQL_COM_FIELD_LIST) - { - /** Waiting for the EOF packet after the rows */ - LOG_RS(bref, REPLY_STATE_RSET_ROWS); - bref->reply_state = REPLY_STATE_RSET_ROWS; - } - 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 && cmd == MYSQL_COM_FIELD_LIST)); - LOG_RS(bref, REPLY_STATE_DONE); - bref->reply_state = REPLY_STATE_DONE; - - if (more) - { - /** The server will send more resultsets */ - LOG_RS(bref, REPLY_STATE_START); - bref->reply_state = REPLY_STATE_START; - } - } - } - - return bref->reply_state == REPLY_STATE_DONE; -} - -/** - * @brief Client Reply routine (API) - * - * The routine will reply to client for session change with master server data - * - * @param instance The router instance - * @param router_session The router session - * @param backend_dcb The backend DCB - * @param queue The GWBUF with reply data - */ -static void clientReply(MXS_ROUTER *instance, - MXS_ROUTER_SESSION *router_session, - GWBUF *writebuf, - DCB *backend_dcb) -{ - ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_session; - ROUTER_INSTANCE *router_inst = (ROUTER_INSTANCE *)instance; - DCB *client_dcb = backend_dcb->session->client_dcb; - - CHK_CLIENT_RSES(router_cli_ses); - - /** - * Lock router client session for secure read of router session members. - * Note that this could be done without lock by using version # - */ - if (router_cli_ses->rses_closed) - { - gwbuf_free(writebuf); - return; - } - - /** - * 1. Check if backend received reply to sescmd. - * 2. Check sescmd's state whether OK_PACKET has been - * sent to client already and if not, lock property cursor, - * reply to client, and move property cursor forward. Finally - * release the lock. - * 3. If reply for this sescmd is sent, lock property cursor - * and - */ - - backend_ref_t *bref = get_bref_from_dcb(router_cli_ses, backend_dcb); - CHK_BACKEND_REF(bref); - sescmd_cursor_t *scur = &bref->bref_sescmd_cur; - - /** Statement was successfully executed, free the stored statement */ - session_clear_stmt(backend_dcb->session); - ss_dassert(bref->reply_state != REPLY_STATE_DONE); - - if (reply_is_complete(bref, writebuf)) - { - /** Got a complete reply, decrement expected response count */ - router_cli_ses->expected_responses--; - ss_dassert(router_cli_ses->expected_responses >= 0); - ss_dassert(bref->reply_state == REPLY_STATE_DONE); - } - else - { - MXS_DEBUG("Reply not yet complete, waiting for %d replies", router_cli_ses->expected_responses); - } - - /** - * Active cursor means that reply is from session command - * execution. - */ - if (sescmd_cursor_is_active(scur)) - { - check_session_command_reply(writebuf, scur, bref); - - if (GWBUF_IS_TYPE_SESCMD_RESPONSE(writebuf)) - { - /** - * Discard all those responses that have already been sent to - * the client. Return with buffer including response that - * needs to be sent to client or NULL. - */ - bool rconn = false; - writebuf = sescmd_cursor_process_replies(writebuf, bref, &rconn); - - if (rconn && !router_inst->rwsplit_config.disable_sescmd_history) - { - select_connect_backend_servers( - &router_cli_ses->rses_master_ref, router_cli_ses->rses_backend_ref, - router_cli_ses->rses_nbackends, - router_cli_ses->rses_config.max_slave_connections, - router_cli_ses->rses_config.max_slave_replication_lag, - router_cli_ses->rses_config.slave_selection_criteria, - router_cli_ses->rses_master_ref->bref_dcb->session, - router_cli_ses->router, - true); - } - } - /** - * If response will be sent to client, decrease waiter count. - * This applies to session commands only. Counter decrement - * for other type of queries is done outside this block. - */ - - /** Set response status as replied */ - bref_clear_state(bref, BREF_WAITING_RESULT); - } - /** - * Clear BREF_QUERY_ACTIVE flag and decrease waiter counter. - * This applies for queries other than session commands. - */ - else if (BREF_IS_QUERY_ACTIVE(bref)) - { - bref_clear_state(bref, BREF_QUERY_ACTIVE); - /** Set response status as replied */ - bref_clear_state(bref, BREF_WAITING_RESULT); - } - - bool queue_routed = false; - - if (router_cli_ses->expected_responses == 0) - { - for (int i = 0; i < router_cli_ses->rses_nbackends; i++) - { - ss_dassert(router_cli_ses->rses_backend_ref[i].reply_state == REPLY_STATE_DONE); - } - - queue_routed = router_cli_ses->query_queue != NULL; - route_stored_query(router_cli_ses); - } - else - { - ss_dassert(router_cli_ses->expected_responses > 0); - } - if (writebuf != NULL && client_dcb != NULL) - { - /** Write reply to client DCB */ - MXS_SESSION_ROUTE_REPLY(backend_dcb->session, writebuf); - } - /** There is one pending session command to be executed. */ - else if (!queue_routed && sescmd_cursor_is_active(scur)) - { - MXS_INFO("Backend [%s]:%d processed reply and starts to execute active cursor.", - bref->ref->server->name, bref->ref->server->port); - - if (execute_sescmd_in_backend(bref)) - { - router_cli_ses->expected_responses++; - } - } -} - - -/** - * @brief Get router capabilities (API) - * - * Return a bit map indicating the characteristics of this particular router. - * In this case, the only bit set indicates that the router wants to receive - * data for routing as whole SQL statements. - * - * @return RCAP_TYPE_STMT_INPUT. - */ -static uint64_t getCapabilities(MXS_ROUTER* instance) -{ - return RCAP_TYPE_STMT_INPUT | RCAP_TYPE_TRANSACTION_TRACKING | RCAP_TYPE_STMT_OUTPUT; -} - -/* - * This is the end of the API functions, and the start of functions that are - * used by the API functions and also used in other modules of the router - * code. Their prototypes are included in rwsplit_internal.h since these - * functions are not intended for use outside the read write split router. - */ - -/* - * @brief Clear one or more bits in the backend reference state - * - * The router session holds details of the backend servers that are - * involved in the routing for this particular service. Each backend - * server has a state bit string, and this function (along with - * bref_set_state) is used to manage the state. - * - * @param bref The backend reference to be modified - * @param state A bit string where the 1 bits indicate bits that should - * be turned off in the bref state. - */ -void bref_clear_state(backend_ref_t *bref, bref_state_t state) -{ - if (bref == NULL) - { - MXS_ERROR("[%s] Error: NULL parameter.", __FUNCTION__); - return; - } - - if ((state & BREF_WAITING_RESULT) && (bref->bref_state & BREF_WAITING_RESULT)) - { - int prev1; - int prev2; - - /** Decrease waiter count */ - prev1 = atomic_add(&bref->bref_num_result_wait, -1); - - if (prev1 <= 0) - { - atomic_add(&bref->bref_num_result_wait, 1); - } - else - { - /** Decrease global operation count */ - prev2 = atomic_add(&bref->ref->server->stats.n_current_ops, -1); - ss_dassert(prev2 > 0); - if (prev2 <= 0) - { - MXS_ERROR("[%s] Error: negative current operation count in backend %s:%u", - __FUNCTION__, bref->ref->server->name, - bref->ref->server->port); - } - } - } - - bref->bref_state &= ~state; -} - -/* - * @brief Set one or more bits in the backend reference state - * - * The router session holds details of the backend servers that are - * involved in the routing for this particular service. Each backend - * server has a state bit string, and this function (along with - * bref_clear_state) is used to manage the state. - * - * @param bref The backend reference to be modified - * @param state A bit string where the 1 bits indicate bits that should - * be turned on in the bref state. - */ -void bref_set_state(backend_ref_t *bref, bref_state_t state) -{ - if (bref == NULL) - { - MXS_ERROR("[%s] Error: NULL parameter.", __FUNCTION__); - return; - } - - if ((state & BREF_WAITING_RESULT) && (bref->bref_state & BREF_WAITING_RESULT) == 0) - { - int prev1; - int prev2; - - /** Increase waiter count */ - prev1 = atomic_add(&bref->bref_num_result_wait, 1); - ss_dassert(prev1 >= 0); - if (prev1 < 0) - { - MXS_ERROR("[%s] Error: negative number of connections waiting for " - "results in backend %s:%u", __FUNCTION__, - bref->ref->server->name, bref->ref->server->port); - } - /** Increase global operation count */ - prev2 = atomic_add(&bref->ref->server->stats.n_current_ops, 1); - ss_dassert(prev2 >= 0); - if (prev2 < 0) - { - MXS_ERROR("[%s] Error: negative current operation count in backend %s:%u", - __FUNCTION__, bref->ref->server->name, bref->ref->server->port); - } - } - - bref->bref_state |= state; -} - -/** - * @brief Free resources belonging to a property - * - * Property is freed at the end of router client session. - * - * @param prop The property whose resources are to be released - */ -void rses_property_done(rses_property_t *prop) -{ - if (prop == NULL) - { - MXS_ERROR("[%s] Error: NULL parameter.", __FUNCTION__); - return; - } - CHK_RSES_PROP(prop); - - switch (prop->rses_prop_type) - { - case RSES_PROP_TYPE_SESCMD: - mysql_sescmd_done(&prop->rses_prop_data.sescmd); - break; - - case RSES_PROP_TYPE_TMPTABLES: - hashtable_free(prop->rses_prop_data.temp_tables); - break; - - default: - MXS_DEBUG("%lu [rses_property_done] Unknown property type %d " - "in property %p", pthread_self(), prop->rses_prop_type, prop); - - ss_dassert(false); - break; - } - MXS_FREE(prop); -} /** * @brief Get count of backend servers that are slaves. @@ -1133,11 +102,10 @@ void rses_property_done(rses_property_t *prop) * @param rses Router client session * @param router_nservers The number of backend servers in total */ -int rses_get_max_slavecount(ROUTER_CLIENT_SES *rses, - int router_nservers) +int rses_get_max_slavecount(ROUTER_CLIENT_SES *rses) { int conf_max_nslaves; - int max_nslaves; + int router_nservers = rses->rses_nbackends; CHK_CLIENT_RSES(rses); @@ -1149,9 +117,8 @@ int rses_get_max_slavecount(ROUTER_CLIENT_SES *rses, { conf_max_nslaves = (router_nservers * rses->rses_config.rw_max_slave_conn_percent) / 100; } - max_nslaves = MXS_MIN(router_nservers - 1, MXS_MAX(1, conf_max_nslaves)); - return max_nslaves; + return MXS_MIN(router_nservers - 1, MXS_MAX(1, conf_max_nslaves)); } /* @@ -1190,23 +157,26 @@ int rses_get_max_replication_lag(ROUTER_CLIENT_SES *rses) * * @return backend reference pointer if succeed or NULL */ -backend_ref_t *get_bref_from_dcb(ROUTER_CLIENT_SES *rses, DCB *dcb) +SRWBackend get_backend_from_dcb(ROUTER_CLIENT_SES *rses, DCB *dcb) { ss_dassert(dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER); CHK_DCB(dcb); CHK_CLIENT_RSES(rses); - for (int i = 0; i < rses->rses_nbackends; i++) + for (SRWBackendList::iterator it = rses->backends.begin(); + it != rses->backends.end(); it++) { - if (rses->rses_backend_ref[i].bref_dcb == dcb) + SRWBackend& backend = *it; + + if (backend->dcb() == dcb) { - return &rses->rses_backend_ref[i]; + return backend; } } /** We should always have a valid backend reference */ ss_dassert(false); - return NULL; + return SRWBackend(); } /** @@ -1316,158 +286,30 @@ static bool rwsplit_process_router_options(ROUTER_INSTANCE *router, return success; } -/** - * @brief Router error handling routine (API) - * - * Error Handler routine to resolve _backend_ failures. If it succeeds then - * there are enough operative backends available and connected. Otherwise it - * fails, and session is terminated. - * - * @param instance The router instance - * @param router_session The router session - * @param errmsgbuf The error message to reply - * @param backend_dcb The backend DCB - * @param action The action: ERRACT_NEW_CONNECTION or - * ERRACT_REPLY_CLIENT - * @param succp Result of action: true iff router can continue - * - * Even if succp == true connecting to new slave may have failed. succp is to - * tell whether router has enough master/slave connections to continue work. - */ -static void handleError(MXS_ROUTER *instance, - MXS_ROUTER_SESSION *router_session, - GWBUF *errmsgbuf, - DCB *problem_dcb, - mxs_error_action_t action, - bool *succp) +// TODO: Don't process parameters in readwritesplit +static bool handle_max_slaves(ROUTER_INSTANCE *router, const char *str) { - ss_dassert(problem_dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER); - ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance; - ROUTER_CLIENT_SES *rses = (ROUTER_CLIENT_SES *)router_session; - CHK_CLIENT_RSES(rses); - CHK_DCB(problem_dcb); + bool rval = true; + char *endptr; + int val = strtol(str, &endptr, 10); - if (rses->rses_closed) + if (*endptr == '%' && *(endptr + 1) == '\0') { - *succp = false; - return; + router->rwsplit_config.rw_max_slave_conn_percent = val; + router->rwsplit_config.max_slave_connections = 0; + } + else if (*endptr == '\0') + { + router->rwsplit_config.max_slave_connections = val; + router->rwsplit_config.rw_max_slave_conn_percent = 0; + } + else + { + MXS_ERROR("Invalid value for 'max_slave_connections': %s", str); + rval = false; } - MXS_SESSION *session = problem_dcb->session; - ss_dassert(session); - - backend_ref_t *bref = get_bref_from_dcb(rses, problem_dcb); - - switch (action) - { - case ERRACT_NEW_CONNECTION: - { - /** - * If master has lost its Master status error can't be - * handled so that session could continue. - */ - if (rses->rses_master_ref && rses->rses_master_ref->bref_dcb == problem_dcb) - { - SERVER *srv = rses->rses_master_ref->ref->server; - bool can_continue = false; - - if (rses->rses_config.master_failure_mode != RW_FAIL_INSTANTLY && - (bref == NULL || !BREF_IS_WAITING_RESULT(bref))) - { - /** The failure of a master is not considered a critical - * failure as partial functionality still remains. Reads - * are allowed as long as slave servers are available - * and writes will cause an error to be returned. - * - * If we were waiting for a response from the master, we - * can't be sure whether it was executed or not. In this - * case the safest thing to do is to close the client - * connection. */ - can_continue = true; - } - else if (!SERVER_IS_MASTER(srv) && !srv->master_err_is_logged) - { - MXS_ERROR("Server %s:%d lost the master status. Readwritesplit " - "service can't locate the master. Client sessions " - "will be closed.", srv->name, srv->port); - srv->master_err_is_logged = true; - } - - *succp = can_continue; - - if (bref != NULL) - { - CHK_BACKEND_REF(bref); - RW_CHK_DCB(bref, problem_dcb); - dcb_close(problem_dcb); - RW_CLOSE_BREF(bref); - close_failed_bref(bref, true); - } - else - { - MXS_ERROR("Server %s:%d lost the master status but could not locate the " - "corresponding backend ref.", srv->name, srv->port); - } - } - else if (bref) - { - /** Check whether problem_dcb is same as dcb of rses->forced_node - * and within READ ONLY transaction: - * if true reset rses->forced_node and close session - */ - if (rses->forced_node && - (rses->forced_node->bref_dcb == problem_dcb && - session_trx_is_read_only(problem_dcb->session))) - { - MXS_ERROR("forced_node SLAVE %s in opened READ ONLY transaction has failed:" - " closing session", - problem_dcb->server->unique_name); - - rses->forced_node = NULL; - *succp = false; - break; - } - - /** We should reconnect only if we find a backend for this - * DCB. If this DCB is an older DCB that has been closed, - * we can ignore it. */ - *succp = handle_error_new_connection(inst, &rses, problem_dcb, errmsgbuf); - } - - if (bref) - { - /** This is a valid DCB for a backend ref */ - if (BREF_IS_IN_USE(bref) && bref->bref_dcb == problem_dcb) - { - ss_dassert(false); - MXS_ERROR("Backend '%s' is still in use and points to the problem DCB.", - bref->ref->server->unique_name); - } - } - else - { - const char *remote = problem_dcb->state == DCB_STATE_POLLING && - problem_dcb->server ? problem_dcb->server->unique_name : "CLOSED"; - - MXS_ERROR("DCB connected to '%s' is not in use by the router " - "session, not closing it. DCB is in state '%s'", - remote, STRDCBSTATE(problem_dcb->state)); - } - break; - } - - case ERRACT_REPLY_CLIENT: - { - handle_error_reply_client(session, rses, problem_dcb, errmsgbuf); - *succp = false; /*< no new backend servers were made available */ - break; - } - - default: - ss_dassert(!true); - *succp = false; - break; - } + return rval; } /** @@ -1481,30 +323,13 @@ static void handleError(MXS_ROUTER *instance, static void handle_error_reply_client(MXS_SESSION *ses, ROUTER_CLIENT_SES *rses, DCB *backend_dcb, GWBUF *errmsg) { - mxs_session_state_t sesstate; - DCB *client_dcb; - backend_ref_t *bref; - sesstate = ses->state; - client_dcb = ses->client_dcb; + mxs_session_state_t sesstate = ses->state; + DCB *client_dcb = ses->client_dcb; - if ((bref = get_bref_from_dcb(rses, backend_dcb)) != NULL) - { - CHK_BACKEND_REF(bref); + SRWBackend backend = get_backend_from_dcb(rses, backend_dcb); - if (BREF_IS_IN_USE(bref)) - { - close_failed_bref(bref, false); - RW_CHK_DCB(bref, backend_dcb); - dcb_close(backend_dcb); - RW_CLOSE_BREF(bref); - } - } - else - { - // All dcbs should be associated with a backend reference. - ss_dassert(!true); - } + backend->close(); if (sesstate == SESSION_STATE_ROUTER_READY) { @@ -1513,7 +338,7 @@ static void handle_error_reply_client(MXS_SESSION *ses, ROUTER_CLIENT_SES *rses, } } -static bool reroute_stored_statement(ROUTER_CLIENT_SES *rses, backend_ref_t *old, GWBUF *stored) +static bool reroute_stored_statement(ROUTER_CLIENT_SES *rses, const SRWBackend& old, GWBUF *stored) { bool success = false; @@ -1523,21 +348,22 @@ static bool reroute_stored_statement(ROUTER_CLIENT_SES *rses, backend_ref_t *old * Only try to retry the read if autocommit is enabled and we are * outside of a transaction */ - for (int i = 0; i < rses->rses_nbackends; i++) + for (SRWBackendList::iterator it = rses->backends.begin(); + it != rses->backends.end(); it++) { - backend_ref_t *bref = &rses->rses_backend_ref[i]; + SRWBackend& backend = *it; - if (BREF_IS_IN_USE(bref) && bref != old && - !SERVER_IS_MASTER(bref->ref->server) && - SERVER_IS_SLAVE(bref->ref->server)) + if (backend->in_use() && backend != old && + !backend->is_master() && + backend->is_slave()) { /** Found a valid candidate; a non-master slave that's in use */ - if (bref->bref_dcb->func.write(bref->bref_dcb, stored)) + if (backend->write(stored)) { - MXS_INFO("Retrying failed read at '%s'.", bref->ref->server->unique_name); - ss_dassert(bref->reply_state == REPLY_STATE_DONE); - LOG_RS(bref, REPLY_STATE_START); - bref->reply_state = REPLY_STATE_START; + MXS_INFO("Retrying failed read at '%s'.", backend->name()); + ss_dassert(backend->get_reply_state() == REPLY_STATE_DONE); + LOG_RS(backend, REPLY_STATE_START); + backend->set_reply_state(REPLY_STATE_START); rses->expected_responses++; success = true; break; @@ -1545,20 +371,18 @@ static bool reroute_stored_statement(ROUTER_CLIENT_SES *rses, backend_ref_t *old } } - if (!success && rses->rses_master_ref && BREF_IS_IN_USE(rses->rses_master_ref)) + if (!success && rses->current_master && rses->current_master->in_use()) { /** * Either we failed to write to the slave or no valid slave was found. * Try to retry the read on the master. */ - backend_ref_t *bref = rses->rses_master_ref; - - if (bref->bref_dcb->func.write(bref->bref_dcb, stored)) + if (rses->current_master->write(stored)) { - MXS_INFO("Retrying failed read at '%s'.", bref->ref->server->unique_name); - LOG_RS(bref, REPLY_STATE_START); - ss_dassert(bref->reply_state == REPLY_STATE_DONE); - bref->reply_state = REPLY_STATE_START; + MXS_INFO("Retrying failed read at '%s'.", rses->current_master->name()); + LOG_RS(rses->current_master, REPLY_STATE_START); + ss_dassert(rses->current_master->get_reply_state() == REPLY_STATE_DONE); + rses->current_master->set_reply_state(REPLY_STATE_START); rses->expected_responses++; success = true; } @@ -1587,30 +411,13 @@ static bool handle_error_new_connection(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES **rses, DCB *backend_dcb, GWBUF *errmsg) { - ROUTER_CLIENT_SES *myrses; - MXS_SESSION *ses; - int max_nslaves; - int max_slave_rlag; - backend_ref_t *bref; - bool succp; + ROUTER_CLIENT_SES *myrses = *rses; + SRWBackend backend = get_backend_from_dcb(myrses, backend_dcb); - myrses = *rses; - - ses = backend_dcb->session; + MXS_SESSION* ses = backend_dcb->session; CHK_SESSION(ses); - /** - * If bref == NULL it has been replaced already with another one. - * - * NOTE: This can never happen. - */ - if ((bref = get_bref_from_dcb(myrses, backend_dcb)) == NULL) - { - return true; - } - CHK_BACKEND_REF(bref); - - if (BREF_IS_WAITING_RESULT(bref)) + if (backend->is_waiting_result()) { /** * A query was sent through the backend and it is waiting for a reply. @@ -1621,8 +428,8 @@ static bool handle_error_new_connection(ROUTER_INSTANCE *inst, const SERVER *target; if (!session_take_stmt(backend_dcb->session, &stored, &target) || - target != bref->ref->server || - !reroute_stored_statement(*rses, bref, stored)) + target != backend->backend()->server || + !reroute_stored_statement(*rses, backend, stored)) { /** * We failed to route the stored statement or no statement was @@ -1632,7 +439,7 @@ static bool handle_error_new_connection(ROUTER_INSTANCE *inst, gwbuf_free(stored); myrses->expected_responses--; - if (!sescmd_cursor_is_active(&bref->bref_sescmd_cur)) + if (backend->session_command_count()) { /** * The backend was executing a command that requires a reply. @@ -1654,12 +461,11 @@ static bool handle_error_new_connection(ROUTER_INSTANCE *inst, } } - RW_CHK_DCB(bref, backend_dcb); - dcb_close(backend_dcb); - RW_CLOSE_BREF(bref); - close_failed_bref(bref, false); - max_nslaves = rses_get_max_slavecount(myrses, myrses->rses_nbackends); - max_slave_rlag = rses_get_max_replication_lag(myrses); + /** Close the current connection */ + backend->close(); + + int max_nslaves = rses_get_max_slavecount(myrses); + bool succp; /** * Try to get replacement slave or at least the minimum * number of slave connections for router session. @@ -1670,12 +476,10 @@ static bool handle_error_new_connection(ROUTER_INSTANCE *inst, } else { - succp = select_connect_backend_servers(&myrses->rses_master_ref, - myrses->rses_backend_ref, - myrses->rses_nbackends, - max_nslaves, max_slave_rlag, + succp = select_connect_backend_servers(myrses->rses_nbackends, max_nslaves, myrses->rses_config.slave_selection_criteria, - ses, inst, true); + ses, inst, myrses, + connection_type::SLAVE); } return succp; @@ -1694,7 +498,7 @@ static bool handle_error_new_connection(ROUTER_INSTANCE *inst, static bool have_enough_servers(ROUTER_CLIENT_SES *rses, const int min_nsrv, int router_nsrv, ROUTER_INSTANCE *router) { - bool succp; + bool succp = true; /** With too few servers session is not created */ if (router_nsrv < min_nsrv || @@ -1735,81 +539,901 @@ static bool have_enough_servers(ROUTER_CLIENT_SES *rses, const int min_nsrv, } succp = false; } - else - { - succp = true; - } + return succp; } -/* - * @brief Release resources when createInstance fails to complete - * - * Internal to createInstance - * - * @param router Router instance - * - */ -static void free_rwsplit_instance(ROUTER_INSTANCE *router) +bool route_stored_query(ROUTER_CLIENT_SES *rses) { - if (router) + bool rval = true; + + if (rses->query_queue) { - MXS_FREE(router); + GWBUF* query_queue = modutil_get_next_MySQL_packet(&rses->query_queue); + query_queue = gwbuf_make_contiguous(query_queue); + + /** Store the query queue locally for the duration of the routeQuery call. + * This prevents recursive calls into this function. */ + GWBUF *temp_storage = rses->query_queue; + rses->query_queue = NULL; + + if (!routeQuery((MXS_ROUTER*)rses->router, (MXS_ROUTER_SESSION*)rses, query_queue)) + { + rval = false; + char* sql = modutil_get_SQL(query_queue); + + if (sql) + { + MXS_ERROR("Routing query \"%s\" failed.", sql); + MXS_FREE(sql); + } + else + { + MXS_ERROR("Failed to route query."); + } + gwbuf_free(query_queue); + } + + ss_dassert(rses->query_queue == NULL); + rses->query_queue = temp_storage; + } + + return rval; +} + +/** + * @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 reply_is_complete(SRWBackend backend, GWBUF *buffer) +{ + mysql_server_cmd_t cmd = mxs_mysql_current_command(backend->dcb()->session); + + if (backend->get_reply_state() == REPLY_STATE_START && !mxs_mysql_is_result_set(buffer)) + { + if (cmd == MYSQL_COM_STMT_PREPARE || !mxs_mysql_more_results_after_ok(buffer)) + { + /** Not a result set, we have the complete response */ + LOG_RS(backend, REPLY_STATE_DONE); + backend->set_reply_state(REPLY_STATE_DONE); + } + } + else + { + bool more = false; + int old_eof = backend->get_reply_state() == REPLY_STATE_RSET_ROWS ? 1 : 0; + int n_eof = modutil_count_signal_packets(buffer, old_eof, &more); + + if (n_eof == 0) + { + /** Waiting for the EOF packet after the column definitions */ + LOG_RS(backend, REPLY_STATE_RSET_COLDEF); + backend->set_reply_state(REPLY_STATE_RSET_COLDEF); + } + else if (n_eof == 1 && cmd != MYSQL_COM_FIELD_LIST) + { + /** Waiting for the EOF packet after the rows */ + LOG_RS(backend, REPLY_STATE_RSET_ROWS); + backend->set_reply_state(REPLY_STATE_RSET_ROWS); + } + 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 && cmd == MYSQL_COM_FIELD_LIST)); + LOG_RS(backend, REPLY_STATE_DONE); + backend->set_reply_state(REPLY_STATE_DONE); + + if (more) + { + /** The server will send more resultsets */ + LOG_RS(backend, REPLY_STATE_START); + backend->set_reply_state(REPLY_STATE_START); + } + } + } + + return backend->get_reply_state() == REPLY_STATE_DONE; +} + +void close_all_connections(ROUTER_CLIENT_SES* rses) +{ + for (SRWBackendList::iterator it = rses->backends.begin(); + it != rses->backends.end(); it++) + { + SRWBackend& backend = *it; + + if (backend->in_use()) + { + backend->close(); + } } } /** - * @brief Create backend server references - * - * This creates a new set of backend references for the client session. Currently - * this is only used on startup but it could be used to dynamically change the - * set of used servers. - * - * @param rses Client router session - * @param dest Destination where the array of backens is stored - * @param n_backend Number of items in the array - * @return True on success, false on error + * API function definitions */ -static bool create_backends(ROUTER_CLIENT_SES *rses, backend_ref_t** dest, int* n_backend) -{ - backend_ref_t *backend_ref = (backend_ref_t *)MXS_CALLOC(1, *n_backend * sizeof(backend_ref_t)); - if (backend_ref == NULL) +/** + * @brief Create an instance of the read/write router (API). + * + * Create an instance of read/write statement router within the MaxScale. One + * instance of the router is required for each service that is defined in the + * configuration as using this router. One instance of the router will handle + * multiple connections (or router sessions). + * + * @param service The service this router is being create for + * @param options The options for this query router + * @return NULL in failure, pointer to router in success. + */ +static MXS_ROUTER *createInstance(SERVICE *service, char **options) +{ + ROUTER_INSTANCE* router = new (std::nothrow) ROUTER_INSTANCE; + + if (router == NULL) { - return false; + return NULL; } - int i = 0; + router->service = service; - for (SERVER_REF *sref = rses->router->service->dbref; sref && i < *n_backend; sref = sref->next) + /* + * Until we know otherwise assume we have some available slaves. + */ + router->available_slaves = true; + + /** By default, the client connection is closed immediately when a master + * failure is detected */ + router->rwsplit_config.master_failure_mode = RW_FAIL_INSTANTLY; + + MXS_CONFIG_PARAMETER *params = service->svc_config_param; + + router->rwsplit_config.use_sql_variables_in = + (mxs_target_t)config_get_enum(params, "use_sql_variables_in", + use_sql_variables_in_values); + + router->rwsplit_config.slave_selection_criteria = + (select_criteria_t)config_get_enum(params, "slave_selection_criteria", + slave_selection_criteria_values); + + router->rwsplit_config.master_failure_mode = + (enum failure_mode)config_get_enum(params, "master_failure_mode", + master_failure_mode_values); + + router->rwsplit_config.max_slave_replication_lag = config_get_integer(params, "max_slave_replication_lag"); + router->rwsplit_config.retry_failed_reads = config_get_bool(params, "retry_failed_reads"); + router->rwsplit_config.strict_multi_stmt = config_get_bool(params, "strict_multi_stmt"); + router->rwsplit_config.disable_sescmd_history = config_get_bool(params, "disable_sescmd_history"); + router->rwsplit_config.max_sescmd_history = config_get_integer(params, "max_sescmd_history"); + router->rwsplit_config.master_accept_reads = config_get_bool(params, "master_accept_reads"); + router->rwsplit_config.connection_keepalive = config_get_integer(params, "connection_keepalive"); + + if (!handle_max_slaves(router, config_get_string(params, "max_slave_connections")) || + (options && !rwsplit_process_router_options(router, options))) + { + delete router; + return NULL; + } + + /** These options cancel each other out */ + if (router->rwsplit_config.disable_sescmd_history && + router->rwsplit_config.max_sescmd_history > 0) + { + router->rwsplit_config.max_sescmd_history = 0; + } + + return (MXS_ROUTER*)router; +} + +/** + * @brief Associate a new session with this instance of the router (API). + * + * The session is used to store all the data required by the router for a + * particular client connection. The instance of the router that relates to a + * particular service is passed as the first parameter. The second parameter is + * the session that has been created in response to the request from a client + * for a connection. The passed session contains generic information; this + * function creates the session structure that holds router specific data. + * There is often a one to one relationship between sessions and router + * sessions, although it is possible to create configurations where a + * connection is handled by multiple routers, one after another. + * + * @param instance The router instance data + * @param session The MaxScale session (generic connection data) + * @return Session specific data for this session, i.e. a router session + */ +static MXS_ROUTER_SESSION *newSession(MXS_ROUTER *router_inst, MXS_SESSION *session) +{ + ROUTER_INSTANCE* router = (ROUTER_INSTANCE*)router_inst; + ROUTER_CLIENT_SES* client_rses = new (std::nothrow) ROUTER_CLIENT_SES; + + if (client_rses == NULL) + { + delete client_rses; + return NULL; + } + + client_rses->rses_chk_top = CHK_NUM_ROUTER_SES; + client_rses->rses_chk_tail = CHK_NUM_ROUTER_SES; + client_rses->rses_closed = false; + client_rses->router = router; + client_rses->client_dcb = session->client_dcb; + client_rses->have_tmp_tables = false; + client_rses->expected_responses = 0; + client_rses->query_queue = NULL; + client_rses->load_data_state = LOAD_DATA_INACTIVE; + client_rses->sent_sescmd = 0; + client_rses->recv_sescmd = 0; + client_rses->sescmd_count = 1; // Needs to be a positive number to work + memcpy(&client_rses->rses_config, &router->rwsplit_config, sizeof(client_rses->rses_config)); + + int router_nservers = router->service->n_dbref; + const int min_nservers = 1; /*< hard-coded for now */ + + if (!have_enough_servers(client_rses, min_nservers, router_nservers, router)) + { + delete client_rses; + return NULL; + } + + for (SERVER_REF *sref = router->service->dbref; sref; sref = sref->next) { if (sref->active) { -#if defined(SS_DEBUG) - backend_ref[i].bref_chk_top = CHK_NUM_BACKEND_REF; - backend_ref[i].bref_chk_tail = CHK_NUM_BACKEND_REF; - backend_ref[i].bref_sescmd_cur.scmd_cur_chk_top = CHK_NUM_SESCMD_CUR; - backend_ref[i].bref_sescmd_cur.scmd_cur_chk_tail = CHK_NUM_SESCMD_CUR; -#endif - backend_ref[i].bref_state = 0; - backend_ref[i].ref = sref; - backend_ref[i].reply_state = REPLY_STATE_DONE; - /** store pointers to sescmd list to both cursors */ - backend_ref[i].bref_sescmd_cur.scmd_cur_rses = rses; - backend_ref[i].bref_sescmd_cur.scmd_cur_active = false; - backend_ref[i].bref_sescmd_cur.scmd_cur_ptr_property = - &rses->rses_properties[RSES_PROP_TYPE_SESCMD]; - backend_ref[i].bref_sescmd_cur.scmd_cur_cmd = NULL; - i++; + client_rses->backends.push_back(SRWBackend(new RWBackend(sref))); } } - if (i < *n_backend) + client_rses->rses_nbackends = router_nservers; /*< # of backend servers */ + + int max_nslaves = rses_get_max_slavecount(client_rses); + + + if (!select_connect_backend_servers(router_nservers, max_nslaves, + client_rses->rses_config.slave_selection_criteria, + session, router, client_rses, + connection_type::ALL)) { - MXS_INFO("The service reported %d servers but only took %d into use.", *n_backend, i); - *n_backend = i; + /** + * Master and at least slaves must be found if the router is + * in the strict mode. If sessions without master are allowed, only + * slaves must be found. + */ + delete client_rses; + return NULL; } - *dest = backend_ref; - return true; + if (client_rses->rses_config.rw_max_slave_conn_percent) + { + int n_conn = 0; + double pct = (double)client_rses->rses_config.rw_max_slave_conn_percent / 100.0; + n_conn = MXS_MAX(floor((double)client_rses->rses_nbackends * pct), 1); + client_rses->rses_config.max_slave_connections = n_conn; + } + + router->stats.n_sessions += 1; + + return (MXS_ROUTER_SESSION*)client_rses; } + +/** + * @brief Close a router session (API). + * + * Close a session with the router, this is the mechanism by which a router + * may cleanup data structure etc. The instance of the router that relates to + * the relevant service is passed, along with the router session that is to + * be closed. Typically the function is used in conjunction with freeSession + * which will release the resources used by a router session (see below). + * + * @param instance The router instance data + * @param session The router session being closed + */ +static void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session) +{ + ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_session; + CHK_CLIENT_RSES(router_cli_ses); + + if (!router_cli_ses->rses_closed) + { + /** + * Mark router session as closed. @c rses_closed is checked at the start + * of every API function to quickly stop the processing of closed sessions. + */ + router_cli_ses->rses_closed = true; + close_all_connections(router_cli_ses); + + if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO) && + router_cli_ses->sescmd_list.size()) + { + std::string sescmdstr; + + for (mxs::SessionCommandList::iterator it = router_cli_ses->sescmd_list.begin(); + it != router_cli_ses->sescmd_list.end(); it++) + { + mxs::SSessionCommand& scmd = *it; + sescmdstr += scmd->to_string(); + sescmdstr += "\n"; + } + + MXS_INFO("Executed session commands:\n%s", sescmdstr.c_str()); + } + } +} + +/** + * @brief Free a router session (API). + * + * When a router session has been closed, freeSession can be called to free + * allocated resources. + * + * @param router_instance The router instance the session belongs to + * @param router_client_session Client session + * + */ +static void freeSession(MXS_ROUTER *router_instance, MXS_ROUTER_SESSION *router_client_session) +{ + ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session; + delete router_cli_ses; +} + +/** + * @brief The main routing entry point for a query (API) + * + * The routeQuery function will make the routing decision based on the contents + * of the instance, session and the query itself. The query always represents + * a complete MariaDB/MySQL packet because we define the RCAP_TYPE_STMT_INPUT in + * getCapabilities(). + * + * @param instance Router instance + * @param router_session Router session associated with the client + * @param querybuf Buffer containing the query + * @return 1 on success, 0 on error + */ +static int routeQuery(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *querybuf) +{ + ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *) instance; + ROUTER_CLIENT_SES *rses = (ROUTER_CLIENT_SES *) router_session; + int rval = 0; + + CHK_CLIENT_RSES(rses); + + if (rses->rses_closed) + { + closed_session_reply(querybuf); + } + else + { + if ((rses->expected_responses == 0 && rses->query_queue == NULL) || + rses->load_data_state == LOAD_DATA_ACTIVE) + { + /** No active or pending queries */ + if (route_single_stmt(inst, rses, querybuf)) + { + rval = 1; + } + } + else + { + ss_dassert(rses->expected_responses || rses->query_queue); + /** We are already processing a request from the client. Store the + * new query and wait for the previous one to complete. */ + MXS_DEBUG("Storing query (len: %d cmd: %0x), expecting %d replies", + gwbuf_length(querybuf), GWBUF_DATA(querybuf)[4], + rses->expected_responses); + rses->query_queue = gwbuf_append(rses->query_queue, querybuf); + querybuf = NULL; + rval = 1; + + if (rses->expected_responses == 0 && !route_stored_query(rses)) + { + rval = 0; + } + } + } + + if (querybuf != NULL) + { + gwbuf_free(querybuf); + } + + return rval; +} + +/** + * @brief Diagnostics routine (API) + * + * Print query router statistics to the DCB passed in + * + * @param instance The router instance + * @param dcb The DCB for diagnostic output + */ +static void diagnostics(MXS_ROUTER *instance, DCB *dcb) +{ + ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; + const char *weightby = serviceGetWeightingParameter(router->service); + double master_pct = 0.0, slave_pct = 0.0, all_pct = 0.0; + + dcb_printf(dcb, "\n"); + dcb_printf(dcb, "\tuse_sql_variables_in: %s\n", + mxs_target_to_str(router->rwsplit_config.use_sql_variables_in)); + dcb_printf(dcb, "\tslave_selection_criteria: %s\n", + select_criteria_to_str(router->rwsplit_config.slave_selection_criteria)); + dcb_printf(dcb, "\tmaster_failure_mode: %s\n", + failure_mode_to_str(router->rwsplit_config.master_failure_mode)); + dcb_printf(dcb, "\tmax_slave_replication_lag: %d\n", + router->rwsplit_config.max_slave_replication_lag); + dcb_printf(dcb, "\tretry_failed_reads: %s\n", + router->rwsplit_config.retry_failed_reads ? "true" : "false"); + dcb_printf(dcb, "\tstrict_multi_stmt: %s\n", + router->rwsplit_config.strict_multi_stmt ? "true" : "false"); + dcb_printf(dcb, "\tdisable_sescmd_history: %s\n", + router->rwsplit_config.disable_sescmd_history ? "true" : "false"); + dcb_printf(dcb, "\tmax_sescmd_history: %lu\n", + router->rwsplit_config.max_sescmd_history); + dcb_printf(dcb, "\tmaster_accept_reads: %s\n", + router->rwsplit_config.master_accept_reads ? "true" : "false"); + dcb_printf(dcb, "\n"); + + if (router->stats.n_queries > 0) + { + master_pct = ((double)router->stats.n_master / (double)router->stats.n_queries) * 100.0; + slave_pct = ((double)router->stats.n_slave / (double)router->stats.n_queries) * 100.0; + all_pct = ((double)router->stats.n_all / (double)router->stats.n_queries) * 100.0; + } + + dcb_printf(dcb, "\tNumber of router sessions: %" PRIu64 "\n", + router->stats.n_sessions); + dcb_printf(dcb, "\tCurrent no. of router sessions: %d\n", + router->service->stats.n_current); + dcb_printf(dcb, "\tNumber of queries forwarded: %" PRIu64 "\n", + router->stats.n_queries); + dcb_printf(dcb, "\tNumber of queries forwarded to master: %" PRIu64 " (%.2f%%)\n", + router->stats.n_master, master_pct); + dcb_printf(dcb, "\tNumber of queries forwarded to slave: %" PRIu64 " (%.2f%%)\n", + router->stats.n_slave, slave_pct); + dcb_printf(dcb, "\tNumber of queries forwarded to all: %" PRIu64 " (%.2f%%)\n", + router->stats.n_all, all_pct); + + if (*weightby) + { + dcb_printf(dcb, "\tConnection distribution based on %s " + "server parameter.\n", + weightby); + dcb_printf(dcb, "\t\tServer Target %% Connections " + "Operations\n"); + dcb_printf(dcb, "\t\t Global Router\n"); + for (SERVER_REF *ref = router->service->dbref; ref; ref = ref->next) + { + dcb_printf(dcb, "\t\t%-20s %3.1f%% %-6d %-6d %d\n", + ref->server->unique_name, (float)ref->weight / 10, + ref->server->stats.n_current, ref->connections, + ref->server->stats.n_current_ops); + } + } +} + +/** + * @brief Diagnostics routine (API) + * + * Print query router statistics to the DCB passed in + * + * @param instance The router instance + * @param dcb The DCB for diagnostic output + */ +static json_t* diagnostics_json(const MXS_ROUTER *instance) +{ + ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; + + json_t* rval = json_object(); + + json_object_set_new(rval, "use_sql_variables_in", + json_string(mxs_target_to_str(router->rwsplit_config.use_sql_variables_in))); + json_object_set_new(rval, "slave_selection_criteria", + json_string(select_criteria_to_str(router->rwsplit_config.slave_selection_criteria))); + json_object_set_new(rval, "master_failure_mode", + json_string(failure_mode_to_str(router->rwsplit_config.master_failure_mode))); + json_object_set_new(rval, "max_slave_replication_lag", + json_integer(router->rwsplit_config.max_slave_replication_lag)); + json_object_set_new(rval, "retry_failed_reads", + json_boolean(router->rwsplit_config.retry_failed_reads)); + json_object_set_new(rval, "strict_multi_stmt", + json_boolean(router->rwsplit_config.strict_multi_stmt)); + json_object_set_new(rval, "disable_sescmd_history", + json_boolean(router->rwsplit_config.disable_sescmd_history)); + json_object_set_new(rval, "max_sescmd_history", + json_integer(router->rwsplit_config.max_sescmd_history)); + json_object_set_new(rval, "master_accept_reads", + json_boolean(router->rwsplit_config.master_accept_reads)); + + + json_object_set_new(rval, "connections", json_integer(router->stats.n_sessions)); + json_object_set_new(rval, "current_connections", json_integer(router->service->stats.n_current)); + json_object_set_new(rval, "queries", json_integer(router->stats.n_queries)); + json_object_set_new(rval, "route_master", json_integer(router->stats.n_master)); + json_object_set_new(rval, "route_slave", json_integer(router->stats.n_slave)); + json_object_set_new(rval, "route_all", json_integer(router->stats.n_all)); + + const char *weightby = serviceGetWeightingParameter(router->service); + + if (*weightby) + { + json_object_set_new(rval, "weightby", json_string(weightby)); + } + + return rval; +} + +/** + * @brief Client Reply routine (API) + * + * The routine will reply to client for session change with master server data + * + * @param instance The router instance + * @param router_session The router session + * @param backend_dcb The backend DCB + * @param queue The GWBUF with reply data + */ +static void clientReply(MXS_ROUTER *instance, + MXS_ROUTER_SESSION *router_session, + GWBUF *writebuf, + DCB *backend_dcb) +{ + ROUTER_CLIENT_SES *router_cli_ses = (ROUTER_CLIENT_SES *)router_session; + ROUTER_INSTANCE *router_inst = (ROUTER_INSTANCE *)instance; + DCB *client_dcb = backend_dcb->session->client_dcb; + + CHK_CLIENT_RSES(router_cli_ses); + + /** + * Lock router client session for secure read of router session members. + * Note that this could be done without lock by using version # + */ + if (router_cli_ses->rses_closed) + { + gwbuf_free(writebuf); + return; + } + + /** + * 1. Check if backend received reply to sescmd. + * 2. Check sescmd's state whether OK_PACKET has been + * sent to client already and if not, lock property cursor, + * reply to client, and move property cursor forward. Finally + * release the lock. + * 3. If reply for this sescmd is sent, lock property cursor + * and + */ + + SRWBackend backend = get_backend_from_dcb(router_cli_ses, backend_dcb); + + /** Statement was successfully executed, free the stored statement */ + session_clear_stmt(backend_dcb->session); + ss_dassert(backend->get_reply_state() != REPLY_STATE_DONE); + + if (reply_is_complete(backend, writebuf)) + { + /** Got a complete reply, acknowledge the write decrement expected response count */ + backend->ack_write(); + router_cli_ses->expected_responses--; + ss_dassert(router_cli_ses->expected_responses >= 0); + ss_dassert(backend->get_reply_state() == REPLY_STATE_DONE); + } + else + { + MXS_DEBUG("Reply not yet complete, waiting for %d replies", router_cli_ses->expected_responses); + } + + /** + * Active cursor means that reply is from session command + * execution. + */ + if (backend->session_command_count()) + { + check_session_command_reply(writebuf, backend); + + /** This discards all responses that have already been sent to the client */ + bool rconn = false; + process_sescmd_response(router_cli_ses, backend, &writebuf, &rconn); + + if (rconn && !router_inst->rwsplit_config.disable_sescmd_history) + { + select_connect_backend_servers( + router_cli_ses->rses_nbackends, + router_cli_ses->rses_config.max_slave_connections, + router_cli_ses->rses_config.slave_selection_criteria, + router_cli_ses->client_dcb->session, + router_cli_ses->router, + router_cli_ses, + connection_type::SLAVE); + } + } + + bool queue_routed = false; + + if (router_cli_ses->expected_responses == 0) + { + for (SRWBackendList::iterator it = router_cli_ses->backends.begin(); + it != router_cli_ses->backends.end(); it++) + { + ss_dassert((*it)->get_reply_state() == REPLY_STATE_DONE); + } + + queue_routed = router_cli_ses->query_queue != NULL; + route_stored_query(router_cli_ses); + } + else + { + ss_dassert(router_cli_ses->expected_responses > 0); + } + + if (writebuf && client_dcb) + { + /** Write reply to client DCB */ + MXS_SESSION_ROUTE_REPLY(backend_dcb->session, writebuf); + } + /** Check pending session commands */ + else if (!queue_routed && backend->session_command_count()) + { + MXS_INFO("Backend %s processed reply and starts to execute active cursor.", + backend->uri()); + + if (backend->execute_session_command()) + { + router_cli_ses->expected_responses++; + } + } +} + + +/** + * @brief Get router capabilities (API) + * + * Return a bit map indicating the characteristics of this particular router. + * In this case, the only bit set indicates that the router wants to receive + * data for routing as whole SQL statements. + * + * @return RCAP_TYPE_STMT_INPUT. + */ +static uint64_t getCapabilities(MXS_ROUTER* instance) +{ + return RCAP_TYPE_STMT_INPUT | RCAP_TYPE_TRANSACTION_TRACKING | RCAP_TYPE_STMT_OUTPUT; +} + +/* + * This is the end of the API functions, and the start of functions that are + * used by the API functions and also used in other modules of the router + * code. Their prototypes are included in rwsplit_internal.h since these + * functions are not intended for use outside the read write split router. + */ + +/** + * @brief Router error handling routine (API) + * + * Error Handler routine to resolve _backend_ failures. If it succeeds then + * there are enough operative backends available and connected. Otherwise it + * fails, and session is terminated. + * + * @param instance The router instance + * @param router_session The router session + * @param errmsgbuf The error message to reply + * @param backend_dcb The backend DCB + * @param action The action: ERRACT_NEW_CONNECTION or + * ERRACT_REPLY_CLIENT + * @param succp Result of action: true iff router can continue + * + * Even if succp == true connecting to new slave may have failed. succp is to + * tell whether router has enough master/slave connections to continue work. + */ +static void handleError(MXS_ROUTER *instance, + MXS_ROUTER_SESSION *router_session, + GWBUF *errmsgbuf, + DCB *problem_dcb, + mxs_error_action_t action, + bool *succp) +{ + ss_dassert(problem_dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER); + ROUTER_INSTANCE *inst = (ROUTER_INSTANCE *)instance; + ROUTER_CLIENT_SES *rses = (ROUTER_CLIENT_SES *)router_session; + CHK_CLIENT_RSES(rses); + CHK_DCB(problem_dcb); + + if (rses->rses_closed) + { + *succp = false; + return; + } + + MXS_SESSION *session = problem_dcb->session; + ss_dassert(session); + + SRWBackend backend = get_backend_from_dcb(rses, problem_dcb); + + switch (action) + { + case ERRACT_NEW_CONNECTION: + { + /** + * If master has lost its Master status error can't be + * handled so that session could continue. + */ + if (rses->current_master && rses->current_master->dcb() == problem_dcb) + { + SERVER *srv = rses->current_master->server(); + bool can_continue = false; + + if (rses->rses_config.master_failure_mode != RW_FAIL_INSTANTLY && + (!backend || !backend->is_waiting_result())) + { + /** The failure of a master is not considered a critical + * failure as partial functionality still remains. Reads + * are allowed as long as slave servers are available + * and writes will cause an error to be returned. + * + * If we were waiting for a response from the master, we + * can't be sure whether it was executed or not. In this + * case the safest thing to do is to close the client + * connection. */ + can_continue = true; + } + else if (!SERVER_IS_MASTER(srv) && !srv->master_err_is_logged) + { + MXS_ERROR("Server %s:%d lost the master status. Readwritesplit " + "service can't locate the master. Client sessions " + "will be closed.", srv->name, srv->port); + srv->master_err_is_logged = true; + } + + *succp = can_continue; + + if (backend) + { + backend->close(mxs::Backend::CLOSE_FATAL); + } + else + { + MXS_ERROR("Server %s:%d lost the master status but could not locate the " + "corresponding backend ref.", srv->name, srv->port); + } + } + else if (backend) + { + /** Check whether problem_dcb is same as dcb of rses->target_node + * and within READ ONLY transaction: + * if true reset rses->target_node and close session + */ + if (rses->target_node && + (rses->target_node->dcb() == problem_dcb && + session_trx_is_read_only(problem_dcb->session))) + { + MXS_ERROR("forced_node SLAVE %s in opened READ ONLY transaction has failed:" + " closing session", + problem_dcb->server->unique_name); + + rses->target_node.reset(); + *succp = false; + break; + } + + /** We should reconnect only if we find a backend for this + * DCB. If this DCB is an older DCB that has been closed, + * we can ignore it. */ + *succp = handle_error_new_connection(inst, &rses, problem_dcb, errmsgbuf); + } + + if (backend) + { + /** This is a valid DCB for a backend ref */ + if (backend->in_use() && backend->dcb() == problem_dcb) + { + ss_dassert(false); + MXS_ERROR("Backend '%s' is still in use and points to the problem DCB.", + backend->name()); + } + } + else + { + const char *remote = problem_dcb->state == DCB_STATE_POLLING && + problem_dcb->server ? problem_dcb->server->unique_name : "CLOSED"; + + MXS_ERROR("DCB connected to '%s' is not in use by the router " + "session, not closing it. DCB is in state '%s'", + remote, STRDCBSTATE(problem_dcb->state)); + } + break; + } + + case ERRACT_REPLY_CLIENT: + { + handle_error_reply_client(session, rses, problem_dcb, errmsgbuf); + *succp = false; /*< no new backend servers were made available */ + break; + } + + default: + ss_dassert(!true); + *succp = false; + break; + } +} + +MXS_BEGIN_DECLS + +/** + * The module entry point routine. It is this routine that + * must return the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ +MXS_MODULE *MXS_CREATE_MODULE() +{ + static MXS_ROUTER_OBJECT MyObject = + { + createInstance, + newSession, + closeSession, + freeSession, + routeQuery, + diagnostics, + diagnostics_json, + clientReply, + handleError, + getCapabilities, + NULL + }; + + static MXS_MODULE info = + { + MXS_MODULE_API_ROUTER, MXS_MODULE_GA, MXS_ROUTER_VERSION, + "A Read/Write splitting router for enhancement read scalability", + "V1.1.0", + RCAP_TYPE_STMT_INPUT | RCAP_TYPE_TRANSACTION_TRACKING | RCAP_TYPE_STMT_OUTPUT, + &MyObject, + NULL, /* Process init. */ + NULL, /* Process finish. */ + NULL, /* Thread init. */ + NULL, /* Thread finish. */ + { + { + "use_sql_variables_in", + MXS_MODULE_PARAM_ENUM, + "all", + MXS_MODULE_OPT_NONE, + use_sql_variables_in_values + }, + { + "slave_selection_criteria", + MXS_MODULE_PARAM_ENUM, + "LEAST_CURRENT_OPERATIONS", + MXS_MODULE_OPT_NONE, + slave_selection_criteria_values + }, + { + "master_failure_mode", + MXS_MODULE_PARAM_ENUM, + "fail_instantly", + MXS_MODULE_OPT_NONE, + master_failure_mode_values + }, + {"max_slave_replication_lag", MXS_MODULE_PARAM_INT, "-1"}, + {"max_slave_connections", MXS_MODULE_PARAM_STRING, MAX_SLAVE_COUNT}, + {"retry_failed_reads", MXS_MODULE_PARAM_BOOL, "true"}, + {"disable_sescmd_history", MXS_MODULE_PARAM_BOOL, "true"}, + {"max_sescmd_history", MXS_MODULE_PARAM_COUNT, "0"}, + {"strict_multi_stmt", MXS_MODULE_PARAM_BOOL, "true"}, + {"master_accept_reads", MXS_MODULE_PARAM_BOOL, "false"}, + {"connection_keepalive", MXS_MODULE_PARAM_COUNT, "0"}, + {MXS_END_MODULE_PARAMS} + } + }; + + MXS_NOTICE("Initializing statement-based read/write split router module."); + return &info; +} + +MXS_END_DECLS diff --git a/server/modules/routing/readwritesplit/readwritesplit.h b/server/modules/routing/readwritesplit/readwritesplit.h deleted file mode 100644 index 276366e50..000000000 --- a/server/modules/routing/readwritesplit/readwritesplit.h +++ /dev/null @@ -1,424 +0,0 @@ -#pragma once -#ifndef _RWSPLITROUTER_H -#define _RWSPLITROUTER_H -/* - * 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. - */ - -/** - * @file router.h - The read write split router module heder file - * - * @verbatim - * Revision History - * - * See GitHub https://github.com/mariadb-corporation/MaxScale - * - * @endverbatim - */ - -#define MXS_MODULE_NAME "readwritesplit" - -#include - -#include - -#include -#include -#include -#include - -MXS_BEGIN_DECLS - -typedef enum bref_state -{ - BREF_IN_USE = 0x01, - BREF_WAITING_RESULT = 0x02, /*< for session commands only */ - BREF_QUERY_ACTIVE = 0x04, /*< for other queries */ - BREF_CLOSED = 0x08, - BREF_FATAL_FAILURE = 0x10 /*< Backend references that should be dropped */ -} bref_state_t; - -#define BREF_IS_NOT_USED(s) ((s)->bref_state & ~BREF_IN_USE) -#define BREF_IS_IN_USE(s) ((s)->bref_state & BREF_IN_USE) -#define BREF_IS_WAITING_RESULT(s) ((s)->bref_num_result_wait > 0) -#define BREF_IS_QUERY_ACTIVE(s) ((s)->bref_state & BREF_QUERY_ACTIVE) -#define BREF_IS_CLOSED(s) ((s)->bref_state & BREF_CLOSED) -#define BREF_HAS_FAILED(s) ((s)->bref_state & BREF_FATAL_FAILURE) - -typedef enum backend_type_t -{ - BE_UNDEFINED = -1, - BE_MASTER, - BE_JOINED = BE_MASTER, - BE_SLAVE, - BE_COUNT -} backend_type_t; - -struct router_instance; - -typedef enum -{ - TARGET_UNDEFINED = 0x00, - TARGET_MASTER = 0x01, - TARGET_SLAVE = 0x02, - TARGET_NAMED_SERVER = 0x04, - TARGET_ALL = 0x08, - TARGET_RLAG_MAX = 0x10 -} route_target_t; - -#define TARGET_IS_MASTER(t) (t & TARGET_MASTER) -#define TARGET_IS_SLAVE(t) (t & TARGET_SLAVE) -#define TARGET_IS_NAMED_SERVER(t) (t & TARGET_NAMED_SERVER) -#define TARGET_IS_ALL(t) (t & TARGET_ALL) -#define TARGET_IS_RLAG_MAX(t) (t & TARGET_RLAG_MAX) - -typedef struct rses_property_st rses_property_t; -typedef struct router_client_session ROUTER_CLIENT_SES; - -typedef enum rses_property_type_t -{ - RSES_PROP_TYPE_UNDEFINED = -1, - RSES_PROP_TYPE_SESCMD = 0, - RSES_PROP_TYPE_FIRST = RSES_PROP_TYPE_SESCMD, - RSES_PROP_TYPE_TMPTABLES, - RSES_PROP_TYPE_LAST = RSES_PROP_TYPE_TMPTABLES, - RSES_PROP_TYPE_COUNT = RSES_PROP_TYPE_LAST + 1 -} rses_property_type_t; - -/** - * This criteria is used when backends are chosen for a router session's use. - * Backend servers are sorted to ascending order according to the criteria - * and top N are chosen. - */ -typedef enum select_criteria -{ - UNDEFINED_CRITERIA = 0, - LEAST_GLOBAL_CONNECTIONS, /*< all connections established by MaxScale */ - LEAST_ROUTER_CONNECTIONS, /*< connections established by this router */ - LEAST_BEHIND_MASTER, - LEAST_CURRENT_OPERATIONS, - DEFAULT_CRITERIA = LEAST_CURRENT_OPERATIONS, - LAST_CRITERIA /*< not used except for an index */ -} select_criteria_t; - -static inline const char* select_criteria_to_str(select_criteria_t type) -{ - switch (type) - { - case LEAST_GLOBAL_CONNECTIONS: - return "LEAST_GLOBAL_CONNECTIONS"; - - case LEAST_ROUTER_CONNECTIONS: - return "LEAST_ROUTER_CONNECTIONS"; - - case LEAST_BEHIND_MASTER: - return "LEAST_BEHIND_MASTER"; - - case LEAST_CURRENT_OPERATIONS: - return "LEAST_CURRENT_OPERATIONS"; - - default: - return "UNDEFINED_CRITERIA"; - } -} - -/** - * Controls how master failure is handled - */ -enum failure_mode -{ - RW_FAIL_INSTANTLY, /**< Close the connection as soon as the master is lost */ - RW_FAIL_ON_WRITE, /**< Close the connection when the first write is received */ - RW_ERROR_ON_WRITE /**< Don't close the connection but send an error for writes */ -}; - -static inline const char* failure_mode_to_str(enum failure_mode type) -{ - switch (type) - { - case RW_FAIL_INSTANTLY: - return "fail_instantly"; - - case RW_FAIL_ON_WRITE: - return "fail_on_write"; - - case RW_ERROR_ON_WRITE: - return "error_on_write"; - - default: - ss_dassert(false); - return "UNDEFINED_MODE"; - } -} - -/** default values for rwsplit configuration parameters */ -#define CONFIG_MAX_SLAVE_CONN 1 -#define CONFIG_MAX_SLAVE_RLAG -1 /*< not used */ -#define CONFIG_SQL_VARIABLES_IN TYPE_ALL - -#define GET_SELECT_CRITERIA(s) \ - (strncmp(s,"LEAST_GLOBAL_CONNECTIONS", strlen("LEAST_GLOBAL_CONNECTIONS")) == 0 ? \ - LEAST_GLOBAL_CONNECTIONS : ( \ - strncmp(s,"LEAST_BEHIND_MASTER", strlen("LEAST_BEHIND_MASTER")) == 0 ? \ - LEAST_BEHIND_MASTER : ( \ - strncmp(s,"LEAST_ROUTER_CONNECTIONS", strlen("LEAST_ROUTER_CONNECTIONS")) == 0 ? \ - LEAST_ROUTER_CONNECTIONS : ( \ - strncmp(s,"LEAST_CURRENT_OPERATIONS", strlen("LEAST_CURRENT_OPERATIONS")) == 0 ? \ - LEAST_CURRENT_OPERATIONS : UNDEFINED_CRITERIA)))) - -/** - * Session variable command - */ -typedef struct mysql_sescmd_st -{ -#if defined(SS_DEBUG) - skygw_chk_t my_sescmd_chk_top; -#endif - rses_property_t* my_sescmd_prop; /*< parent property */ - GWBUF* my_sescmd_buf; /*< query buffer */ - unsigned char my_sescmd_packet_type; /*< packet type */ - bool my_sescmd_is_replied; /*< is cmd replied to client */ - unsigned char reply_cmd; /*< The reply command. One of OK, ERR, RESULTSET or - * LOCAL_INFILE. Slave servers are compared to this - * when they return session command replies.*/ - int position; /*< Position of this command */ -#if defined(SS_DEBUG) - skygw_chk_t my_sescmd_chk_tail; -#endif -} mysql_sescmd_t; - -/** - * Property structure - */ -struct rses_property_st -{ -#if defined(SS_DEBUG) - skygw_chk_t rses_prop_chk_top; -#endif - ROUTER_CLIENT_SES* rses_prop_rsession; /*< parent router session */ - int rses_prop_refcount; - rses_property_type_t rses_prop_type; - - union rses_prop_data - { - mysql_sescmd_t sescmd; - HASHTABLE* temp_tables; - } rses_prop_data; - rses_property_t* rses_prop_next; /*< next property of same type */ -#if defined(SS_DEBUG) - skygw_chk_t rses_prop_chk_tail; -#endif -} ; - -typedef struct sescmd_cursor_st -{ -#if defined(SS_DEBUG) - skygw_chk_t scmd_cur_chk_top; -#endif - ROUTER_CLIENT_SES* scmd_cur_rses; /*< pointer to owning router session */ - rses_property_t** scmd_cur_ptr_property; /*< address of pointer to owner property */ - mysql_sescmd_t* scmd_cur_cmd; /*< pointer to current session command */ - bool scmd_cur_active; /*< true if command is being executed */ - int position; /*< Position of this cursor */ -#if defined(SS_DEBUG) - skygw_chk_t scmd_cur_chk_tail; -#endif -} sescmd_cursor_t; - -/** Enum for tracking client reply state */ -typedef enum -{ - REPLY_STATE_START, /**< Query sent to backend */ - REPLY_STATE_DONE, /**< Complete reply received */ - REPLY_STATE_RSET_COLDEF, /**< Resultset response, waiting for column definitions */ - REPLY_STATE_RSET_ROWS /**< Resultset response, waiting for rows */ -} reply_state_t; - -/** - * Helper function to convert reply_state_t to string - */ -static inline const char* rstostr(reply_state_t state) -{ - switch (state) - { - case REPLY_STATE_START: - return "REPLY_STATE_START"; - - case REPLY_STATE_DONE: - return "REPLY_STATE_DONE"; - - case REPLY_STATE_RSET_COLDEF: - return "REPLY_STATE_RSET_COLDEF"; - - case REPLY_STATE_RSET_ROWS: - return "REPLY_STATE_RSET_ROWS"; - } - - ss_dassert(false); - return "UNKNOWN"; -} - -/** - * Reference to BACKEND. - * - * Owned by router client session. - */ -typedef struct backend_ref_st -{ -#if defined(SS_DEBUG) - skygw_chk_t bref_chk_top; -#endif - SERVER_REF* ref; - DCB* bref_dcb; - bref_state_t bref_state; - int bref_num_result_wait; - sescmd_cursor_t bref_sescmd_cur; - unsigned char reply_cmd; /**< The reply the backend server sent to a session command. - * Used to detect slaves that fail to execute session command. */ - reply_state_t reply_state; /**< Reply state of the current query */ -#if defined(SS_DEBUG) - skygw_chk_t bref_chk_tail; -#endif - int closed_at; /** DEBUG: Line number where this backend reference was closed */ -} backend_ref_t; - -typedef struct rwsplit_config_st -{ - int rw_max_slave_conn_percent; /**< Maximum percentage of slaves - * to use for each connection*/ - int max_slave_connections; /**< Maximum number of slaves for each connection*/ - select_criteria_t slave_selection_criteria; /**< The slave selection criteria */ - int max_slave_replication_lag; /**< Maximum replication lag */ - mxs_target_t use_sql_variables_in; /**< Whether to send user variables - * to master or all nodes */ - int max_sescmd_history; /**< Maximum amount of session commands to store */ - bool disable_sescmd_history; /**< Disable session command history */ - bool master_accept_reads; /**< Use master for reads */ - bool strict_multi_stmt; /**< Force non-multistatement queries to be routed - * to the master after a multistatement query. */ - enum failure_mode master_failure_mode; /**< Master server failure handling mode. - * @see enum failure_mode */ - bool retry_failed_reads; /**< Retry failed reads on other servers */ - int connection_keepalive; /**< Send pings to servers that have - * been idle for too long */ -} rwsplit_config_t; - -#if defined(PREP_STMT_CACHING) - -typedef struct prep_stmt_st -{ -#if defined(SS_DEBUG) - skygw_chk_t pstmt_chk_top; -#endif - - union id - { - int seq; - char* name; - } pstmt_id; - prep_stmt_state_t pstmt_state; - prep_stmt_type_t pstmt_type; -#if defined(SS_DEBUG) - skygw_chk_t pstmt_chk_tail; -#endif -} prep_stmt_t; - -#endif /*< PREP_STMT_CACHING */ - -/** States of a LOAD DATA LOCAL INFILE */ -enum ld_state -{ - LOAD_DATA_INACTIVE, /**< Not active */ - LOAD_DATA_START, /**< Current query starts a load */ - LOAD_DATA_ACTIVE, /**< Load is active */ - LOAD_DATA_END /**< Current query contains an empty packet that ends the load */ -}; - -/** - * The client session structure used within this router. - */ -struct router_client_session -{ -#if defined(SS_DEBUG) - skygw_chk_t rses_chk_top; -#endif - bool rses_closed; /*< true when closeSession is called */ - rses_property_t* rses_properties[RSES_PROP_TYPE_COUNT]; /*< Properties listed by their type */ - backend_ref_t* rses_master_ref; - backend_ref_t* rses_backend_ref; /*< Pointer to backend reference array */ - rwsplit_config_t rses_config; /*< copied config info from router instance */ - int rses_nbackends; - int rses_nsescmd; /*< Number of executed session commands */ - enum ld_state load_data_state; /*< Current load data state */ - bool have_tmp_tables; - uint64_t rses_load_data_sent; /*< How much data has been sent */ - DCB* client_dcb; - int pos_generator; - backend_ref_t *forced_node; /*< Current server where all queries should be sent */ - int expected_responses; /**< Number of expected responses to the current query */ - GWBUF* query_queue; /**< Queued commands waiting to be executed */ -#if defined(PREP_STMT_CACHING) - HASHTABLE* rses_prep_stmt[2]; -#endif - struct router_instance *router; /*< The router instance */ - struct router_client_session *next; -#if defined(SS_DEBUG) - skygw_chk_t rses_chk_tail; -#endif -} ; - -/** - * The statistics for this router instance - */ -typedef struct -{ - uint64_t n_sessions; /*< Number sessions created */ - uint64_t n_queries; /*< Number of queries forwarded */ - uint64_t n_master; /*< Number of stmts sent to master */ - uint64_t n_slave; /*< Number of stmts sent to slave */ - uint64_t n_all; /*< Number of stmts sent to all */ -} ROUTER_STATS; - -/** - * The per instance data for the router. - */ -typedef struct router_instance -{ - SERVICE* service; /*< Pointer to service */ - rwsplit_config_t rwsplit_config; /*< expanded config info from SERVICE */ - int rwsplit_version; /*< version number for router's config */ - ROUTER_STATS stats; /*< Statistics for this router */ - bool available_slaves; /*< The router has some slaves avialable */ -} ROUTER_INSTANCE; - -#define BACKEND_TYPE(b) (SERVER_IS_MASTER((b)->backend_server) ? BE_MASTER : \ - (SERVER_IS_SLAVE((b)->backend_server) ? BE_SLAVE : BE_UNDEFINED)); - -/** - * @brief Route a stored query - * - * When multiple queries are executed in a pipeline fashion, the readwritesplit - * stores the extra queries in a queue. This queue is emptied after reading a - * reply from the backend server. - * - * @param rses Router client session - * @return True if a stored query was routed successfully - */ -bool route_stored_query(ROUTER_CLIENT_SES *rses); - -/** Reply state change debug logging */ -#define LOG_RS(a, b) MXS_DEBUG("[%s]:%d %s -> %s", (a)->ref->server->name, \ - (a)->ref->server->port, rstostr((a)->reply_state), rstostr(b)); - -MXS_END_DECLS - -#endif /*< _RWSPLITROUTER_H */ diff --git a/server/modules/routing/readwritesplit/readwritesplit.hh b/server/modules/routing/readwritesplit/readwritesplit.hh new file mode 100644 index 000000000..0660dd28f --- /dev/null +++ b/server/modules/routing/readwritesplit/readwritesplit.hh @@ -0,0 +1,335 @@ +#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. + */ + +/** + * @file router.h - The read write split router module heder file + */ + +#define MXS_MODULE_NAME "readwritesplit" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +enum backend_type_t +{ + BE_UNDEFINED = -1, + BE_MASTER, + BE_JOINED = BE_MASTER, + BE_SLAVE, + BE_COUNT +}; + +enum route_target_t +{ + TARGET_UNDEFINED = 0x00, + TARGET_MASTER = 0x01, + TARGET_SLAVE = 0x02, + TARGET_NAMED_SERVER = 0x04, + TARGET_ALL = 0x08, + TARGET_RLAG_MAX = 0x10 +}; + +/** + * This criteria is used when backends are chosen for a router session's use. + * Backend servers are sorted to ascending order according to the criteria + * and top N are chosen. + */ +enum select_criteria_t +{ + UNDEFINED_CRITERIA = 0, + LEAST_GLOBAL_CONNECTIONS, /**< all connections established by MaxScale */ + LEAST_ROUTER_CONNECTIONS, /**< connections established by this router */ + LEAST_BEHIND_MASTER, + LEAST_CURRENT_OPERATIONS, + DEFAULT_CRITERIA = LEAST_CURRENT_OPERATIONS, + LAST_CRITERIA /**< not used except for an index */ +}; + +/** + * Controls how master failure is handled + */ +enum failure_mode +{ + RW_FAIL_INSTANTLY, /**< Close the connection as soon as the master is lost */ + RW_FAIL_ON_WRITE, /**< Close the connection when the first write is received */ + RW_ERROR_ON_WRITE /**< Don't close the connection but send an error for writes */ +}; + +/** States of a LOAD DATA LOCAL INFILE */ +enum ld_state +{ + LOAD_DATA_INACTIVE, /**< Not active */ + LOAD_DATA_START, /**< Current query starts a load */ + LOAD_DATA_ACTIVE, /**< Load is active */ + LOAD_DATA_END /**< Current query contains an empty packet that ends the load */ +}; + +#define TARGET_IS_MASTER(t) (t & TARGET_MASTER) +#define TARGET_IS_SLAVE(t) (t & TARGET_SLAVE) +#define TARGET_IS_NAMED_SERVER(t) (t & TARGET_NAMED_SERVER) +#define TARGET_IS_ALL(t) (t & TARGET_ALL) +#define TARGET_IS_RLAG_MAX(t) (t & TARGET_RLAG_MAX) +#define BREF_IS_NOT_USED(s) ((s)->bref_state & ~BREF_IN_USE) +#define BREF_IS_IN_USE(s) ((s)->bref_state & BREF_IN_USE) +#define BREF_IS_WAITING_RESULT(s) ((s)->bref_num_result_wait > 0) +#define BREF_IS_QUERY_ACTIVE(s) ((s)->bref_state & BREF_QUERY_ACTIVE) +#define BREF_IS_CLOSED(s) ((s)->bref_state & BREF_CLOSED) +#define BREF_HAS_FAILED(s) ((s)->bref_state & BREF_FATAL_FAILURE) + +/** default values for rwsplit configuration parameters */ +#define CONFIG_MAX_SLAVE_CONN 1 +#define CONFIG_MAX_SLAVE_RLAG -1 /**< not used */ +#define CONFIG_SQL_VARIABLES_IN TYPE_ALL + +#define GET_SELECT_CRITERIA(s) \ + (strncmp(s,"LEAST_GLOBAL_CONNECTIONS", strlen("LEAST_GLOBAL_CONNECTIONS")) == 0 ? \ + LEAST_GLOBAL_CONNECTIONS : ( \ + strncmp(s,"LEAST_BEHIND_MASTER", strlen("LEAST_BEHIND_MASTER")) == 0 ? \ + LEAST_BEHIND_MASTER : ( \ + strncmp(s,"LEAST_ROUTER_CONNECTIONS", strlen("LEAST_ROUTER_CONNECTIONS")) == 0 ? \ + LEAST_ROUTER_CONNECTIONS : ( \ + strncmp(s,"LEAST_CURRENT_OPERATIONS", strlen("LEAST_CURRENT_OPERATIONS")) == 0 ? \ + LEAST_CURRENT_OPERATIONS : UNDEFINED_CRITERIA)))) + +#define BACKEND_TYPE(b) (SERVER_IS_MASTER((b)->backend_server) ? BE_MASTER : \ + (SERVER_IS_SLAVE((b)->backend_server) ? BE_SLAVE : BE_UNDEFINED)); + +/** Reply state change debug logging */ +#define LOG_RS(a, b) MXS_DEBUG("[%s]:%d %s -> %s", (a)->server()->name, \ + (a)->server()->port, rstostr((a)->get_reply_state()), rstostr(b)); + +struct ROUTER_INSTANCE; +struct ROUTER_CLIENT_SES; + +/** Enum for tracking client reply state */ +enum reply_state_t +{ + REPLY_STATE_START, /**< Query sent to backend */ + REPLY_STATE_DONE, /**< Complete reply received */ + REPLY_STATE_RSET_COLDEF, /**< Resultset response, waiting for column definitions */ + REPLY_STATE_RSET_ROWS /**< Resultset response, waiting for rows */ +}; + +struct rwsplit_config_t +{ + int rw_max_slave_conn_percent; /**< Maximum percentage of slaves + * to use for each connection*/ + int max_slave_connections; /**< Maximum number of slaves for each connection*/ + select_criteria_t slave_selection_criteria; /**< The slave selection criteria */ + int max_slave_replication_lag; /**< Maximum replication lag */ + mxs_target_t use_sql_variables_in; /**< Whether to send user variables + * to master or all nodes */ + uint64_t max_sescmd_history; /**< Maximum amount of session commands to store */ + bool disable_sescmd_history; /**< Disable session command history */ + bool master_accept_reads; /**< Use master for reads */ + bool strict_multi_stmt; /**< Force non-multistatement queries to be routed + * to the master after a multistatement query. */ + enum failure_mode master_failure_mode; /**< Master server failure handling mode. + * @see enum failure_mode */ + bool retry_failed_reads; /**< Retry failed reads on other servers */ + int connection_keepalive; /**< Send pings to servers that have + * been idle for too long */ +}; + +class RWBackend: public mxs::Backend +{ + RWBackend(const RWBackend&); + RWBackend& operator=(const RWBackend&); + +public: + RWBackend(SERVER_REF* ref): + mxs::Backend(ref), + m_reply_state(REPLY_STATE_DONE) + { + } + + ~RWBackend() + { + } + + reply_state_t get_reply_state() const + { + return m_reply_state; + } + + void set_reply_state(reply_state_t state) + { + m_reply_state = state; + } + + bool execute_session_command() + { + bool rval = mxs::Backend::execute_session_command(); + + if (rval) + { + set_reply_state(REPLY_STATE_START); + } + + return rval; + } + +private: + reply_state_t m_reply_state; +}; + +typedef std::tr1::shared_ptr SRWBackend; +typedef std::list SRWBackendList; + +typedef std::tr1::unordered_set TableSet; +typedef std::map ResponseMap; + +/** Prepared statement ID to type maps for text and binary protocols */ +typedef std::tr1::unordered_map TextPSMap; + +/** + * The client session structure used within this router. + */ +struct ROUTER_CLIENT_SES +{ + skygw_chk_t rses_chk_top; + bool rses_closed; /**< true when closeSession is called */ + SRWBackendList backends; /**< List of backend servers */ + SRWBackend current_master; /**< Current master server */ + SRWBackend target_node; /**< The currently locked target node */ + rwsplit_config_t rses_config; /**< copied config info from router instance */ + int rses_nbackends; + enum ld_state load_data_state; /**< Current load data state */ + bool have_tmp_tables; + uint64_t rses_load_data_sent; /**< How much data has been sent */ + DCB* client_dcb; + uint64_t sescmd_count; + int expected_responses; /**< Number of expected responses to the current query */ + GWBUF* query_queue; /**< Queued commands waiting to be executed */ + struct ROUTER_INSTANCE *router; /**< The router instance */ + struct ROUTER_CLIENT_SES *next; + TableSet temp_tables; /**< Set of temporary tables */ + mxs::SessionCommandList sescmd_list; /**< List of executed session commands */ + ResponseMap sescmd_responses; /**< Response to each session command */ + uint64_t sent_sescmd; /**< ID of the last sent session command*/ + uint64_t recv_sescmd; /**< ID of the most recently completed session command */ + TextPSMap ps_text; /**< Text protocol prepared statements */ + skygw_chk_t rses_chk_tail; +}; + +/** + * The statistics for this router instance + */ +struct ROUTER_STATS +{ + uint64_t n_sessions; /**< Number sessions created */ + uint64_t n_queries; /**< Number of queries forwarded */ + uint64_t n_master; /**< Number of stmts sent to master */ + uint64_t n_slave; /**< Number of stmts sent to slave */ + uint64_t n_all; /**< Number of stmts sent to all */ +}; + +/** + * The per instance data for the router. + */ +struct ROUTER_INSTANCE +{ + SERVICE* service; /**< Pointer to service */ + rwsplit_config_t rwsplit_config; /**< expanded config info from SERVICE */ + int rwsplit_version; /**< version number for router's config */ + ROUTER_STATS stats; /**< Statistics for this router */ + bool available_slaves; /**< The router has some slaves avialable */ +}; + +/** + * @brief Route a stored query + * + * When multiple queries are executed in a pipeline fashion, the readwritesplit + * stores the extra queries in a queue. This queue is emptied after reading a + * reply from the backend server. + * + * @param rses Router client session + * @return True if a stored query was routed successfully + */ +bool route_stored_query(ROUTER_CLIENT_SES *rses); + +static inline const char* select_criteria_to_str(select_criteria_t type) +{ + switch (type) + { + case LEAST_GLOBAL_CONNECTIONS: + return "LEAST_GLOBAL_CONNECTIONS"; + + case LEAST_ROUTER_CONNECTIONS: + return "LEAST_ROUTER_CONNECTIONS"; + + case LEAST_BEHIND_MASTER: + return "LEAST_BEHIND_MASTER"; + + case LEAST_CURRENT_OPERATIONS: + return "LEAST_CURRENT_OPERATIONS"; + + default: + return "UNDEFINED_CRITERIA"; + } +} + +/** + * Helper function to convert reply_state_t to string + */ +static inline const char* rstostr(reply_state_t state) +{ + switch (state) + { + case REPLY_STATE_START: + return "REPLY_STATE_START"; + + case REPLY_STATE_DONE: + return "REPLY_STATE_DONE"; + + case REPLY_STATE_RSET_COLDEF: + return "REPLY_STATE_RSET_COLDEF"; + + case REPLY_STATE_RSET_ROWS: + return "REPLY_STATE_RSET_ROWS"; + } + + ss_dassert(false); + return "UNKNOWN"; +} + +static inline const char* failure_mode_to_str(enum failure_mode type) +{ + switch (type) + { + case RW_FAIL_INSTANTLY: + return "fail_instantly"; + + case RW_FAIL_ON_WRITE: + return "fail_on_write"; + + case RW_ERROR_ON_WRITE: + return "error_on_write"; + + default: + ss_dassert(false); + return "UNDEFINED_MODE"; + } +} diff --git a/server/modules/routing/readwritesplit/rwsplit_internal.h b/server/modules/routing/readwritesplit/rwsplit_internal.h deleted file mode 100644 index a61dc59e5..000000000 --- a/server/modules/routing/readwritesplit/rwsplit_internal.h +++ /dev/null @@ -1,158 +0,0 @@ -#pragma once -#ifndef _RWSPLIT_INTERNAL_H -#define _RWSPLIT_INTERNAL_H -/* - * 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. - */ - -/* - * File: rwsplit_internal.h - * Author: mbrampton - * - * Created on 08 August 2016, 11:54 - */ - -#include -#include - -MXS_BEGIN_DECLS - -/* This needs to be removed along with dependency on it - see the - * rwsplit_tmp_table_multi functions - */ -#include - -#define RW_CHK_DCB(b, d) \ -do{ \ - if(d->state == DCB_STATE_DISCONNECTED){ \ - MXS_NOTICE("DCB was closed on line %d and another attempt to close it is made on line %d." , \ - (b) ? (b)->closed_at : -1, __LINE__); \ - } \ -}while (false) - -#define RW_CLOSE_BREF(b) do{ if (b){ (b)->closed_at = __LINE__; } } while (false) - -/* - * The following are implemented in rwsplit_mysql.c - */ -bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, - GWBUF *querybuf); -void closed_session_reply(GWBUF *querybuf); -void print_error_packet(ROUTER_CLIENT_SES *rses, GWBUF *buf, DCB *dcb); -void check_session_command_reply(GWBUF *writebuf, sescmd_cursor_t *scur, backend_ref_t *bref); -bool execute_sescmd_in_backend(backend_ref_t *backend_ref); -bool handle_target_is_all(route_target_t route_target, - ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, - GWBUF *querybuf, int packet_type, qc_query_type_t qtype); -int determine_packet_type(GWBUF *querybuf, bool *non_empty_packet); -void log_transaction_status(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, qc_query_type_t qtype); -bool is_packet_a_one_way_message(int packet_type); -sescmd_cursor_t *backend_ref_get_sescmd_cursor(backend_ref_t *bref); -bool is_packet_a_query(int packet_type); -bool send_readonly_error(DCB *dcb); - -/* - * The following are implemented in readwritesplit.c - */ -void bref_clear_state(backend_ref_t *bref, bref_state_t state); -void bref_set_state(backend_ref_t *bref, bref_state_t state); -int router_handle_state_switch(DCB *dcb, DCB_REASON reason, void *data); -backend_ref_t *get_bref_from_dcb(ROUTER_CLIENT_SES *rses, DCB *dcb); -void rses_property_done(rses_property_t *prop); -int rses_get_max_slavecount(ROUTER_CLIENT_SES *rses, int router_nservers); -int rses_get_max_replication_lag(ROUTER_CLIENT_SES *rses); - -/* - * The following are implemented in rwsplit_route_stmt.c - */ - -bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, - GWBUF *querybuf); -int rwsplit_hashkeyfun(const void *key); -int rwsplit_hashcmpfun(const void *v1, const void *v2); -void *rwsplit_hstrdup(const void *fval); -void rwsplit_hfree(void *fval); -bool rwsplit_get_dcb(DCB **p_dcb, ROUTER_CLIENT_SES *rses, backend_type_t btype, - char *name, int max_rlag); -route_target_t get_route_target(ROUTER_CLIENT_SES *rses, - qc_query_type_t qtype, HINT *hint); -rses_property_t *rses_property_init(rses_property_type_t prop_type); -int rses_property_add(ROUTER_CLIENT_SES *rses, rses_property_t *prop); -void handle_multi_temp_and_load(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, - int packet_type, int *qtype); -bool handle_hinted_target(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, - route_target_t route_target, DCB **target_dcb); -bool handle_slave_is_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, - DCB **target_dcb); -bool handle_master_is_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, - DCB **target_dcb); -bool handle_got_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, - GWBUF *querybuf, DCB *target_dcb, bool store); -bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses, - GWBUF *querybuf, ROUTER_INSTANCE *inst, - int packet_type, - qc_query_type_t qtype); - -/* - * The following are implemented in rwsplit_session_cmd.c -*/ -mysql_sescmd_t *rses_property_get_sescmd(rses_property_t *prop); -mysql_sescmd_t *mysql_sescmd_init(rses_property_t *rses_prop, - GWBUF *sescmd_buf, - unsigned char packet_type, - ROUTER_CLIENT_SES *rses); -void mysql_sescmd_done(mysql_sescmd_t *sescmd); -mysql_sescmd_t *sescmd_cursor_get_command(sescmd_cursor_t *scur); -bool sescmd_cursor_is_active(sescmd_cursor_t *sescmd_cursor); -void sescmd_cursor_set_active(sescmd_cursor_t *sescmd_cursor, - bool value); -bool execute_sescmd_history(backend_ref_t *bref); -GWBUF *sescmd_cursor_clone_querybuf(sescmd_cursor_t *scur); -GWBUF *sescmd_cursor_process_replies(GWBUF *replybuf, - backend_ref_t *bref, - bool *reconnect); - -/* - * The following are implemented in rwsplit_select_backends.c - */ -bool select_connect_backend_servers(backend_ref_t **p_master_ref, - backend_ref_t *backend_ref, - int router_nservers, int max_nslaves, - int max_slave_rlag, - select_criteria_t select_criteria, - MXS_SESSION *session, - ROUTER_INSTANCE *router, - bool active_session); - -/* - * The following are implemented in rwsplit_tmp_table_multi.c - */ -void check_drop_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, - GWBUF *querybuf, - mysql_server_cmd_t packet_type); -bool is_read_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, - GWBUF *querybuf, - qc_query_type_t type); -void check_create_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, - GWBUF *querybuf, qc_query_type_t type); -bool check_for_multi_stmt(GWBUF *buf, void *protocol, mysql_server_cmd_t packet_type); -qc_query_type_t determine_query_type(GWBUF *querybuf, int packet_type, bool non_empty_packet); -void close_failed_bref(backend_ref_t *bref, bool fatal); - -#ifdef __cplusplus -} -#endif - -MXS_END_DECLS - -#endif /* RWSPLIT_INTERNAL_H */ - diff --git a/server/modules/routing/readwritesplit/rwsplit_internal.hh b/server/modules/routing/readwritesplit/rwsplit_internal.hh new file mode 100644 index 000000000..7cd3a7f00 --- /dev/null +++ b/server/modules/routing/readwritesplit/rwsplit_internal.hh @@ -0,0 +1,122 @@ +#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 +#include + +#include "readwritesplit.hh" + +#define RW_CHK_DCB(b, d) \ +do{ \ + if(d->state == DCB_STATE_DISCONNECTED){ \ + MXS_NOTICE("DCB was closed on line %d and another attempt to close it is made on line %d." , \ + (b) ? (b)->closed_at : -1, __LINE__); \ + } \ +}while (false) + +#define RW_CLOSE_BREF(b) do{ if (b){ (b)->closed_at = __LINE__; } } while (false) + +/* + * The following are implemented in rwsplit_mysql.c + */ +bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, + GWBUF *querybuf); +void closed_session_reply(GWBUF *querybuf); +void print_error_packet(ROUTER_CLIENT_SES *rses, GWBUF *buf, DCB *dcb); +void check_session_command_reply(GWBUF *writebuf, SRWBackend bref); +bool execute_sescmd_in_backend(SRWBackend& backend_ref); +bool handle_target_is_all(route_target_t route_target, + ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, + GWBUF *querybuf, int packet_type, uint32_t qtype); +uint8_t determine_packet_type(GWBUF *querybuf, bool *non_empty_packet); +void log_transaction_status(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, uint32_t qtype); +bool command_will_respond(uint8_t packet_type); +bool is_packet_a_query(int packet_type); +bool send_readonly_error(DCB *dcb); + +/* + * The following are implemented in readwritesplit.c + */ +int router_handle_state_switch(DCB *dcb, DCB_REASON reason, void *data); +SRWBackend get_backend_from_dcb(ROUTER_CLIENT_SES *rses, DCB *dcb); +int rses_get_max_slavecount(ROUTER_CLIENT_SES *rses); +int rses_get_max_replication_lag(ROUTER_CLIENT_SES *rses); + +/* + * The following are implemented in rwsplit_route_stmt.c + */ + +bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, + GWBUF *querybuf); +SRWBackend get_target_backend(ROUTER_CLIENT_SES *rses, backend_type_t btype, + char *name, int max_rlag); +route_target_t get_route_target(ROUTER_CLIENT_SES *rses, + uint32_t qtype, HINT *hint); +void handle_multi_temp_and_load(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, + uint8_t packet_type, uint32_t *qtype); +SRWBackend handle_hinted_target(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, + route_target_t route_target); +SRWBackend handle_slave_is_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses); +bool handle_master_is_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, + SRWBackend* dest); +bool handle_got_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, + GWBUF *querybuf, SRWBackend& target, bool store); +bool route_session_write(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, uint8_t command); + +void process_sescmd_response(ROUTER_CLIENT_SES* rses, SRWBackend& bref, + GWBUF** ppPacket, bool* reconnect); +/* + * The following are implemented in rwsplit_select_backends.c + */ + +/** What sort of connections should be create */ +enum connection_type +{ + ALL, + SLAVE +}; + +bool select_connect_backend_servers(int router_nservers, + int max_nslaves, + select_criteria_t select_criteria, + MXS_SESSION *session, + ROUTER_INSTANCE *router, + ROUTER_CLIENT_SES *rses, + connection_type type); + +/* + * The following are implemented in rwsplit_tmp_table_multi.c + */ +void check_drop_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, GWBUF *querybuf); +bool is_read_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, + GWBUF *querybuf, + uint32_t type); +void check_create_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, + GWBUF *querybuf, uint32_t type); +bool check_for_multi_stmt(GWBUF *buf, void *protocol, uint8_t packet_type); +uint32_t determine_query_type(GWBUF *querybuf, int packet_type, bool non_empty_packet); + +void close_all_connections(ROUTER_CLIENT_SES* rses); + +/** + * Functions for prepared statement handling + */ +std::string extract_text_ps_id(GWBUF* buffer); +void store_text_ps(ROUTER_CLIENT_SES* rses, std::string id, GWBUF* buffer); +void erase_text_ps(ROUTER_CLIENT_SES* rses, std::string id); +bool get_text_ps_type(ROUTER_CLIENT_SES* rses, GWBUF* buffer, uint32_t* out); diff --git a/server/modules/routing/readwritesplit/rwsplit_mysql.c b/server/modules/routing/readwritesplit/rwsplit_mysql.cc similarity index 56% rename from server/modules/routing/readwritesplit/rwsplit_mysql.c rename to server/modules/routing/readwritesplit/rwsplit_mysql.cc index d772cac5a..ecd894ea0 100644 --- a/server/modules/routing/readwritesplit/rwsplit_mysql.c +++ b/server/modules/routing/readwritesplit/rwsplit_mysql.cc @@ -11,18 +11,15 @@ * Public License. */ -#include "readwritesplit.h" +#include "readwritesplit.hh" +#include "rwsplit_internal.hh" -#include #include #include #include #include #include -#include "rwsplit_internal.h" - -#include #include #include #include @@ -30,32 +27,19 @@ #include #include #include -#include #include -#if defined(SS_DEBUG) -#include -#endif - #define RWSPLIT_TRACE_MSG_LEN 1000 /** - * @file rwsplit_mysql.c 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. - * - * @verbatim - * Revision History - * - * Date Who Description - * 08/08/2016 Martin Brampton Initial implementation - * - * @endverbatim + * 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.h. They are not intended to be called + * are defined in rwsplit_internal.hh. They are not intended to be called * from outside this router. */ @@ -80,24 +64,24 @@ * @param non_empty_packet bool indicating whether the packet is non-empty * @return The packet type, or MYSQL_COM_UNDEFINED; also the second parameter is set */ -int +uint8_t determine_packet_type(GWBUF *querybuf, bool *non_empty_packet) { - mysql_server_cmd_t packet_type; + uint8_t packet_type; uint8_t *packet = GWBUF_DATA(querybuf); if (gw_mysql_get_byte3(packet) == 0) { /** Empty packet signals end of LOAD DATA LOCAL INFILE, send it to master*/ *non_empty_packet = false; - packet_type = MYSQL_COM_UNDEFINED; + packet_type = (uint8_t)MYSQL_COM_UNDEFINED; } else { *non_empty_packet = true; packet_type = packet[4]; } - return (int)packet_type; + return packet_type; } /* @@ -134,11 +118,11 @@ is_packet_a_query(int packet_type) * @param packet_type Type of packet (integer) * @return bool indicating whether packet contains a one way message */ -bool -is_packet_a_one_way_message(int packet_type) +bool command_will_respond(uint8_t packet_type) { - return (packet_type == MYSQL_COM_STMT_SEND_LONG_DATA || - packet_type == MYSQL_COM_QUIT || packet_type == MYSQL_COM_STMT_CLOSE); + return packet_type != MYSQL_COM_STMT_SEND_LONG_DATA && + packet_type != MYSQL_COM_QUIT && + packet_type != MYSQL_COM_STMT_CLOSE; } /* @@ -157,7 +141,7 @@ is_packet_a_one_way_message(int packet_type) * @param qtype Query type */ void -log_transaction_status(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, qc_query_type_t qtype) +log_transaction_status(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, uint32_t qtype) { if (rses->load_data_state == LOAD_DATA_INACTIVE) { @@ -219,10 +203,19 @@ log_transaction_status(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, qc_query_type_t */ bool handle_target_is_all(route_target_t route_target, ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, GWBUF *querybuf, - int packet_type, qc_query_type_t qtype) + int packet_type, uint32_t qtype) { bool result = false; + if (qc_query_is_type(qtype, QUERY_TYPE_PREPARE_NAMED_STMT)) + { + store_text_ps(rses, extract_text_ps_id(querybuf), querybuf); + } + else if (qc_query_is_type(qtype, QUERY_TYPE_PREPARE_STMT)) + { + gwbuf_set_type(querybuf, GWBUF_TYPE_COLLECT_RESULT); + } + if (TARGET_IS_MASTER(route_target) || TARGET_IS_SLAVE(route_target)) { /** @@ -250,7 +243,7 @@ bool handle_target_is_all(route_target_t route_target, ROUTER_INSTANCE *inst, MXS_FREE(query_str); MXS_FREE(qtype_str); } - else if (route_session_write(rses, gwbuf_clone(querybuf), inst, packet_type, qtype)) + else if (route_session_write(rses, gwbuf_clone(querybuf), packet_type)) { result = true; @@ -291,202 +284,33 @@ void closed_session_reply(GWBUF *querybuf) /** * @brief Check the reply from a backend server to a session command * - * If the reply is an error, a message may be logged. + * If the reply is an error, a message is logged. * - * @param writebuf Query buffer containing reply data - * @param scur Session cursor - * @param bref Router session data for a backend server + * @param buffer Query buffer containing reply data + * @param backend Router session data for a backend server */ -void check_session_command_reply(GWBUF *writebuf, sescmd_cursor_t *scur, backend_ref_t *bref) +void check_session_command_reply(GWBUF *buffer, SRWBackend backend) { - if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_ERR) && - MYSQL_IS_ERROR_PACKET(((uint8_t *)GWBUF_DATA(writebuf)))) + if (MYSQL_IS_ERROR_PACKET(((uint8_t *)GWBUF_DATA(buffer)))) { - uint8_t *buf = (uint8_t *)GWBUF_DATA((scur->scmd_cur_cmd->my_sescmd_buf)); - uint8_t *replybuf = (uint8_t *)GWBUF_DATA(writebuf); - size_t len = MYSQL_GET_PAYLOAD_LEN(buf); - size_t replylen = MYSQL_GET_PAYLOAD_LEN(replybuf); - char *err = strndup(&((char *)replybuf)[8], 5); - char *replystr = strndup(&((char *)replybuf)[13], replylen - 4 - 5); + 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); - ss_dassert(len + 4 == GWBUF_LENGTH(scur->scmd_cur_cmd->my_sescmd_buf)); - - MXS_ERROR("Failed to execute session command in [%s]:%d. Error was: %s %s", - bref->ref->server->name, - bref->ref->server->port, err, replystr); - MXS_FREE(err); - MXS_FREE(replystr); + MXS_ERROR("Failed to execute session command in %s. Error was: %s %s", + backend->uri(), err.c_str(), msg.c_str()); } } /** - * @brief If session command cursor is passive, sends the command to backend for - * execution. + * @brief Send an error message to the client telling that the server is in read only mode * - * Returns true if command was sent or added successfully to the queue. - * Returns false if command sending failed or if there are no pending session - * commands. - * - * Router session must be locked. - * - * @param backend_ref Router session backend database data - * @return bool - true for success, false for failure - */ -/* - * Uses MySQL specific values in the large switch statement, although it - * may be possible to generalize them. - */ -bool execute_sescmd_in_backend(backend_ref_t *backend_ref) -{ - ss_dassert(backend_ref); - CHK_BACKEND_REF(backend_ref); - bool succp = false; - - if (BREF_IS_CLOSED(backend_ref)) - { - return succp; - } - - DCB *dcb = backend_ref->bref_dcb; - CHK_DCB(dcb); - - /** - * Get cursor pointer and copy of command buffer to cursor. - */ - sescmd_cursor_t *scur = &backend_ref->bref_sescmd_cur; - - /** Return if there are no pending ses commands */ - if (sescmd_cursor_get_command(scur) == NULL) - { - succp = true; - MXS_INFO("Cursor had no pending session commands."); - return succp; - } - - if (!sescmd_cursor_is_active(scur)) - { - /** Cursor is left active when function returns. */ - sescmd_cursor_set_active(scur, true); - } - - int rc = 0; - GWBUF *buf; - - switch (scur->scmd_cur_cmd->my_sescmd_packet_type) - { - case MYSQL_COM_CHANGE_USER: - /** This makes it possible to handle replies correctly */ - gwbuf_set_type(scur->scmd_cur_cmd->my_sescmd_buf, GWBUF_TYPE_SESCMD); - buf = sescmd_cursor_clone_querybuf(scur); - rc = dcb->func.auth(dcb, NULL, dcb->session, buf); - break; - - case MYSQL_COM_INIT_DB: - { - /** - * Record database name and store to session. - * - * TODO: Do this in the client protocol module - */ - GWBUF *tmpbuf; - MYSQL_session* data; - unsigned int qlen; - - data = (MYSQL_session*)dcb->session->client_dcb->data; - *data->db = 0; - tmpbuf = scur->scmd_cur_cmd->my_sescmd_buf; - qlen = MYSQL_GET_PAYLOAD_LEN((unsigned char *) GWBUF_DATA(tmpbuf)); - if (qlen) - { - --qlen; // The COM_INIT_DB byte - if (qlen > MYSQL_DATABASE_MAXLEN) - { - MXS_ERROR("Too long a database name received in COM_INIT_DB, " - "trailing data will be cut."); - qlen = MYSQL_DATABASE_MAXLEN; - } - - memcpy(data->db, (char*)GWBUF_DATA(tmpbuf) + 5, qlen); - data->db[qlen] = 0; - } - } - /** Fallthrough */ - case MYSQL_COM_QUERY: - default: - /** - * Mark session command buffer, it triggers writing - * MySQL command to protocol - */ - - gwbuf_set_type(scur->scmd_cur_cmd->my_sescmd_buf, GWBUF_TYPE_SESCMD); - buf = sescmd_cursor_clone_querybuf(scur); - rc = dcb->func.write(dcb, buf); - break; - } - - if (rc == 1) - { - succp = true; - ss_dassert(backend_ref->reply_state == REPLY_STATE_DONE); - LOG_RS(backend_ref, REPLY_STATE_START); - backend_ref->reply_state = REPLY_STATE_START; - } - - return succp; -} - -/* - * End of functions called from other router modules; start of functions that - * are internal to this module - */ - -/** - * Get client DCB pointer of the router client session. - * This routine must be protected by Router client session lock. - * - * APPEARS TO NEVER BE USED!! - * - * @param rses Router client session pointer - * - * @return Pointer to client DCB - */ -static DCB *rses_get_client_dcb(ROUTER_CLIENT_SES *rses) -{ - DCB *dcb = NULL; - int i; - - for (i = 0; i < rses->rses_nbackends; i++) - { - if ((dcb = rses->rses_backend_ref[i].bref_dcb) != NULL && - BREF_IS_IN_USE(&rses->rses_backend_ref[i]) && dcb->session != NULL && - dcb->session->client_dcb != NULL) - { - return dcb->session->client_dcb; - } - } - return NULL; -} - -/* - * The following are internal (directly or indirectly) to routing a statement - * and should be moved to rwsplit_route_cmd.c if the MySQL specific code can - * be removed. - */ - -sescmd_cursor_t *backend_ref_get_sescmd_cursor(backend_ref_t *bref) -{ - sescmd_cursor_t *scur; - CHK_BACKEND_REF(bref); - - scur = &bref->bref_sescmd_cur; - CHK_SESCMD_CUR(scur); - - return scur; -} - -/** - * 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) diff --git a/server/modules/routing/readwritesplit/rwsplit_ps.cc b/server/modules/routing/readwritesplit/rwsplit_ps.cc new file mode 100644 index 000000000..9e0a80613 --- /dev/null +++ b/server/modules/routing/readwritesplit/rwsplit_ps.cc @@ -0,0 +1,68 @@ +/* + * 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 +#include + +std::string extract_text_ps_id(GWBUF* buffer) +{ + std::string rval; + char* name = qc_get_prepare_name(buffer); + + if (name) + { + rval = name; + MXS_FREE(name); + } + + return rval; +} + +void store_text_ps(ROUTER_CLIENT_SES* rses, std::string id, GWBUF* buffer) +{ + GWBUF* stmt = qc_get_preparable_stmt(buffer); + ss_dassert(stmt); + + uint32_t type = qc_get_type_mask(stmt); + ss_dassert((type & (QUERY_TYPE_PREPARE_STMT | QUERY_TYPE_PREPARE_NAMED_STMT)) == 0); + + rses->ps_text[id] = type; +} + +void erase_text_ps(ROUTER_CLIENT_SES* rses, std::string id) +{ + rses->ps_text.erase(id); +} + +bool get_text_ps_type(ROUTER_CLIENT_SES* rses, GWBUF* buffer, uint32_t* out) +{ + bool rval = false; + char* name = qc_get_prepare_name(buffer); + + if (name) + { + TextPSMap::iterator it = rses->ps_text.find(name); + + if (it != rses->ps_text.end()) + { + *out = it->second; + rval = true; + } + + MXS_FREE(name); + } + + return rval; +} diff --git a/server/modules/routing/readwritesplit/rwsplit_route_stmt.c b/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc similarity index 52% rename from server/modules/routing/readwritesplit/rwsplit_route_stmt.c rename to server/modules/routing/readwritesplit/rwsplit_route_stmt.cc index 0eec6d8f8..9fc500c2b 100644 --- a/server/modules/routing/readwritesplit/rwsplit_route_stmt.c +++ b/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc @@ -11,67 +11,83 @@ * Public License. */ -#include "readwritesplit.h" +#include "readwritesplit.hh" +#include "rwsplit_internal.hh" -#include #include #include #include #include + #include #include #include -#include "rwsplit_internal.h" - /** - * @file rwsplit_route_stmt.c The functions that support the routing of - * queries to back end servers. All the functions in this module are internal - * to the read write split router, and not intended to be called from - * anywhere else. - * - * @verbatim - * Revision History - * - * Date Who Description - * 08/08/2016 Martin Brampton Initial implementation - * - * @endverbatim + * The functions that support the routing of queries to back end + * servers. All the functions in this module are internal to the read + * write split router, and not intended to be called from anywhere else. */ -extern int (*criteria_cmpfun[LAST_CRITERIA])(const void *, const void *); +extern int (*criteria_cmpfun[LAST_CRITERIA])(const SRWBackend&, const SRWBackend&); -static backend_ref_t *check_candidate_bref(backend_ref_t *cand, - backend_ref_t *new, - select_criteria_t sc); -static backend_ref_t *get_root_master_bref(ROUTER_CLIENT_SES *rses); +static SRWBackend get_root_master_backend(ROUTER_CLIENT_SES *rses); + +/** + * Find out which of the two backend servers has smaller value for select + * criteria property. + * + * @param cand previously selected candidate + * @param new challenger + * @param sc select criteria + * + * @return pointer to backend reference of that backend server which has smaller + * value in selection criteria. If either reference pointer is NULL then the + * other reference pointer value is returned. + */ +static SRWBackend compare_backends(SRWBackend a, SRWBackend b, select_criteria_t sc) +{ + int (*p)(const SRWBackend&, const SRWBackend&) = criteria_cmpfun[sc]; + + if (!a) + { + return b; + } + else if (!b) + { + return a; + } + + return p(a, b) < 0 ? a : b; +} void handle_connection_keepalive(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, - DCB *target_dcb) + SRWBackend& target) { - ss_dassert(target_dcb); + ss_dassert(target); ss_debug(int nserv = 0); + /** Each heartbeat is 1/10th of a second */ + int keepalive = inst->rwsplit_config.connection_keepalive * 10; - for (int i = 0; i < rses->rses_nbackends; i++) + for (SRWBackendList::iterator it = rses->backends.begin(); + it != rses->backends.end(); it++) { - /** Each heartbeat is 1/10th of a second */ - int keepalive = inst->rwsplit_config.connection_keepalive * 10; - backend_ref_t *bref = &rses->rses_backend_ref[i]; + SRWBackend backend = *it; - if (bref->bref_dcb != target_dcb && BREF_IS_IN_USE(bref) && - !BREF_IS_WAITING_RESULT(bref)) + if (backend->in_use() && backend != target && !backend->is_waiting_result()) { ss_debug(nserv++); - int diff = hkheartbeat - bref->bref_dcb->last_read; + int diff = hkheartbeat - backend->dcb()->last_read; if (diff > keepalive) { MXS_INFO("Pinging %s, idle for %d seconds", - bref->bref_dcb->server->unique_name, diff / 10); - modutil_ignorable_ping(bref->bref_dcb); + backend->name(), diff / 10); + modutil_ignorable_ping(backend->dcb()); } } } + ss_dassert(nserv < rses->rses_nbackends); } @@ -88,9 +104,6 @@ void handle_connection_keepalive(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, GWBUF *querybuf) { - qc_query_type_t qtype = QUERY_TYPE_UNKNOWN; - int packet_type; - DCB *target_dcb = NULL; route_target_t route_target; bool succp = false; bool non_empty_packet; @@ -98,12 +111,12 @@ bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, ss_dassert(querybuf->next == NULL); // The buffer must be contiguous. /* packet_type is a problem as it is MySQL specific */ - packet_type = determine_packet_type(querybuf, &non_empty_packet); - qtype = determine_query_type(querybuf, packet_type, non_empty_packet); + uint8_t command = determine_packet_type(querybuf, &non_empty_packet); + uint32_t qtype = determine_query_type(querybuf, command, non_empty_packet); if (non_empty_packet) { - handle_multi_temp_and_load(rses, querybuf, packet_type, (int *)&qtype); + handle_multi_temp_and_load(rses, querybuf, command, &qtype); if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) { @@ -126,6 +139,15 @@ bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, * - route primarily according to the hints and if they failed, * eventually to master */ + + uint32_t ps_type; + + if (qc_get_operation(querybuf) == QUERY_OP_EXECUTE && + get_text_ps_type(rses, querybuf, &ps_type)) + { + qtype = ps_type; + } + route_target = get_route_target(rses, qtype, querybuf->hint); } else @@ -137,13 +159,14 @@ bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, rses->rses_load_data_sent + gwbuf_length(querybuf)); } + SRWBackend target; + if (TARGET_IS_ALL(route_target)) { - succp = handle_target_is_all(route_target, inst, rses, querybuf, packet_type, qtype); + succp = handle_target_is_all(route_target, inst, rses, querybuf, command, qtype); } else { - /* Now we have a lock on the router session */ bool store_stmt = false; /** * There is a hint which either names the target backend or @@ -153,40 +176,46 @@ bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, if (TARGET_IS_NAMED_SERVER(route_target) || TARGET_IS_RLAG_MAX(route_target)) { - succp = handle_hinted_target(rses, querybuf, route_target, &target_dcb); + if ((target = handle_hinted_target(rses, querybuf, route_target))) + { + succp = true; + } } else if (TARGET_IS_SLAVE(route_target)) { - succp = handle_slave_is_target(inst, rses, &target_dcb); - store_stmt = rses->rses_config.retry_failed_reads; + if ((target = handle_slave_is_target(inst, rses))) + { + succp = true; + store_stmt = rses->rses_config.retry_failed_reads; + } } else if (TARGET_IS_MASTER(route_target)) { - succp = handle_master_is_target(inst, rses, &target_dcb); + succp = handle_master_is_target(inst, rses, &target); if (!rses->rses_config.strict_multi_stmt && - rses->forced_node == rses->rses_master_ref) + rses->target_node == rses->current_master) { /** Reset the forced node as we're in relaxed multi-statement mode */ - rses->forced_node = NULL; + rses->target_node.reset(); } } - if (target_dcb && succp) /*< Have DCB of the target backend */ + if (target && succp) /*< Have DCB of the target backend */ { ss_dassert(!store_stmt || TARGET_IS_SLAVE(route_target)); - handle_got_target(inst, rses, querybuf, target_dcb, store_stmt); + handle_got_target(inst, rses, querybuf, target, store_stmt); } } if (succp && inst->rwsplit_config.connection_keepalive && (TARGET_IS_SLAVE(route_target) || TARGET_IS_MASTER(route_target))) { - handle_connection_keepalive(inst, rses, target_dcb); + handle_connection_keepalive(inst, rses, target); } return succp; -} /* route_single_stmt */ +} /** * Execute in backends used by current router session. @@ -208,375 +237,155 @@ bool route_single_stmt(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, * backends being used, otherwise false. * */ -bool route_session_write(ROUTER_CLIENT_SES *router_cli_ses, - GWBUF *querybuf, ROUTER_INSTANCE *inst, - int packet_type, - qc_query_type_t qtype) +bool route_session_write(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, uint8_t command) { - bool succp; - rses_property_t *prop; - backend_ref_t *backend_ref; - int i; - int max_nslaves; - int nbackends; - int nsucc; + /** The SessionCommand takes ownership of the buffer */ + uint64_t id = rses->sescmd_count++; + mxs::SSessionCommand sescmd(new mxs::SessionCommand(querybuf, id)); + bool expecting_response = command_will_respond(command); + int nsucc = 0; + uint64_t lowest_pos = id; MXS_INFO("Session write, routing to all servers."); - /** Maximum number of slaves in this router client session */ - max_nslaves = - rses_get_max_slavecount(router_cli_ses, router_cli_ses->rses_nbackends); - nsucc = 0; - nbackends = 0; - backend_ref = router_cli_ses->rses_backend_ref; - /** - * These are one-way messages and server doesn't respond to them. - * Therefore reply processing is unnecessary and session - * command property is not needed. It is just routed to all available - * backends. - */ - if (is_packet_a_one_way_message(packet_type)) + for (SRWBackendList::iterator it = rses->backends.begin(); + it != rses->backends.end(); it++) { - int rc; + SRWBackend& backend = *it; - for (i = 0; i < router_cli_ses->rses_nbackends; i++) + if (backend->in_use()) { - DCB *dcb = backend_ref[i].bref_dcb; + backend->append_session_command(sescmd); - if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO) && - BREF_IS_IN_USE((&backend_ref[i]))) + uint64_t current_pos = backend->next_session_command()->get_position(); + + if (current_pos < lowest_pos) { - MXS_INFO("Route query to %s \t[%s]:%d%s", - (SERVER_IS_MASTER(backend_ref[i].ref->server) - ? "master" : "slave"), - backend_ref[i].ref->server->name, - backend_ref[i].ref->server->port, - (i + 1 == router_cli_ses->rses_nbackends ? " <" : " ")); + lowest_pos = current_pos; } - if (BREF_IS_IN_USE((&backend_ref[i]))) + if (backend->execute_session_command()) { - nbackends += 1; - if ((rc = dcb->func.write(dcb, gwbuf_clone(querybuf))) == 1) + nsucc += 1; + + if (expecting_response) { - nsucc += 1; + rses->expected_responses++; } + + MXS_INFO("Route query to %s \t%s", + backend->is_master() ? "master" : "slave", + backend->uri()); + } + else + { + MXS_ERROR("Failed to execute session command in %s", backend->uri()); } } - gwbuf_free(querybuf); - goto return_succp; } - if (router_cli_ses->rses_nbackends <= 0) - { - MXS_INFO("Router session doesn't have any backends in use. Routing failed. <"); - goto return_succp; - } - - if (router_cli_ses->rses_config.max_sescmd_history > 0 && - router_cli_ses->rses_nsescmd >= - router_cli_ses->rses_config.max_sescmd_history) + if (rses->rses_config.max_sescmd_history > 0 && + rses->sescmd_count >= rses->rses_config.max_sescmd_history) { MXS_WARNING("Router session exceeded session command history limit. " "Slave recovery is disabled and only slave servers with " "consistent session state are used " "for the duration of the session."); - router_cli_ses->rses_config.disable_sescmd_history = true; - router_cli_ses->rses_config.max_sescmd_history = 0; + rses->rses_config.disable_sescmd_history = true; + rses->rses_config.max_sescmd_history = 0; + rses->sescmd_list.clear(); } - if (router_cli_ses->rses_config.disable_sescmd_history) + if (rses->rses_config.disable_sescmd_history) { - rses_property_t *prop, *tmp; - backend_ref_t *bref; - bool conflict; + /** Prune stored responses */ + ResponseMap::iterator it = rses->sescmd_responses.lower_bound(lowest_pos); - prop = router_cli_ses->rses_properties[RSES_PROP_TYPE_SESCMD]; - while (prop) + if (it != rses->sescmd_responses.end()) { - conflict = false; - - for (i = 0; i < router_cli_ses->rses_nbackends; i++) - { - bref = &backend_ref[i]; - if (BREF_IS_IN_USE(bref)) - { - - if (bref->bref_sescmd_cur.position <= - prop->rses_prop_data.sescmd.position + 1) - { - conflict = true; - break; - } - } - } - - if (conflict) - { - break; - } - - tmp = prop; - router_cli_ses->rses_properties[RSES_PROP_TYPE_SESCMD] = prop->rses_prop_next; - rses_property_done(tmp); - prop = router_cli_ses->rses_properties[RSES_PROP_TYPE_SESCMD]; + rses->sescmd_responses.erase(rses->sescmd_responses.begin(), it); } } - - /** - * Additional reference is created to querybuf to - * prevent it from being released before properties - * are cleaned up as a part of router sessionclean-up. - */ - if ((prop = rses_property_init(RSES_PROP_TYPE_SESCMD)) == NULL) + else { - MXS_ERROR("Router session property initialization failed"); - return false; + rses->sescmd_list.push_back(sescmd); } - mysql_sescmd_init(prop, querybuf, packet_type, router_cli_ses); - - /** Add sescmd property to router client session */ - if (rses_property_add(router_cli_ses, prop) != 0) + if (nsucc) { - MXS_ERROR("Session property addition failed."); - return false; + rses->sent_sescmd = id; } - for (i = 0; i < router_cli_ses->rses_nbackends; i++) - { - if (BREF_IS_IN_USE((&backend_ref[i]))) - { - sescmd_cursor_t *scur; - - nbackends += 1; - - if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) - { - MXS_INFO("Route query to %s \t[%s]:%d%s", - (SERVER_IS_MASTER(backend_ref[i].ref->server) - ? "master" : "slave"), - backend_ref[i].ref->server->name, - backend_ref[i].ref->server->port, - (i + 1 == router_cli_ses->rses_nbackends ? " <" : " ")); - } - - scur = backend_ref_get_sescmd_cursor(&backend_ref[i]); - - /** - * Add one waiter to backend reference. - */ - bref_set_state(get_bref_from_dcb(router_cli_ses, backend_ref[i].bref_dcb), - BREF_WAITING_RESULT); - /** - * Start execution if cursor is not already executing or this is the - * master server. Otherwise, cursor will execute pending commands - * when it completes the previous command. - */ - if (sescmd_cursor_is_active(scur) && &backend_ref[i] != router_cli_ses->rses_master_ref) - { - nsucc += 1; - MXS_INFO("Backend [%s]:%d already executing sescmd.", - backend_ref[i].ref->server->name, - backend_ref[i].ref->server->port); - } - else - { - if (execute_sescmd_in_backend(&backend_ref[i])) - { - router_cli_ses->expected_responses++; - nsucc += 1; - } - else - { - MXS_ERROR("Failed to execute session command in [%s]:%d", - backend_ref[i].ref->server->name, - backend_ref[i].ref->server->port); - } - } - } - } - - atomic_add(&router_cli_ses->rses_nsescmd, 1); - -return_succp: - /** - * Routing must succeed to all backends that are used. - * There must be at least one and at most max_nslaves+1 backends. - */ - succp = (nbackends > 0 && nsucc == nbackends && nbackends <= max_nslaves + 1); - return succp; + return nsucc; } /** - * @brief Function to hash keys in read-write split router + * Provide the router with a reference to a suitable backend * - * Used to store information about temporary tables. + * @param rses Pointer to router client session + * @param btype Backend type + * @param name Name of the backend which is primarily searched. May be NULL. + * @param max_rlag Maximum replication lag + * @param target The target backend * - * @param key key to be hashed, actually a character string - * @result the hash value integer + * @return True if a backend was found */ -int rwsplit_hashkeyfun(const void *key) +SRWBackend get_target_backend(ROUTER_CLIENT_SES *rses, backend_type_t btype, + char *name, int max_rlag) { - if (key == NULL) - { - return 0; - } - - unsigned int hash = 0, c = 0; - const char *ptr = (const char *)key; - - while ((c = *ptr++)) - { - hash = c + (hash << 6) + (hash << 16) - hash; - } - return hash; -} - -/** - * @brief Function to compare hash keys in read-write split router - * - * Used to manage information about temporary tables. - * - * @param key first key to be compared, actually a character string - * @param v2 second key to be compared, actually a character string - * @result 1 if keys are equal, 0 otherwise - */ -int rwsplit_hashcmpfun(const void *v1, const void *v2) -{ - const char *i1 = (const char *)v1; - const char *i2 = (const char *)v2; - - return strcmp(i1, i2); -} - -/** - * @brief Function to duplicate a hash value in read-write split router - * - * Used to manage information about temporary tables. - * - * @param fval value to be duplicated, actually a character string - * @result the duplicated value, actually a character string - */ -void *rwsplit_hstrdup(const void *fval) -{ - char *str = (char *)fval; - return MXS_STRDUP(str); -} - -/** - * @brief Function to free hash values in read-write split router - * - * Used to manage information about temporary tables. - * - * @param key value to be freed - */ -void rwsplit_hfree(void *fval) -{ - MXS_FREE(fval); -} - -/** - * Provide the router with a pointer to a suitable backend dcb. - * - * Detect failures in server statuses and reselect backends if necessary. - * If name is specified, server name becomes primary selection criteria. - * Similarly, if max replication lag is specified, skip backends which lag too - * much. - * - * @param p_dcb Address of the pointer to the resulting DCB - * @param rses Pointer to router client session - * @param btype Backend type - * @param name Name of the backend which is primarily searched. May be NULL. - * - * @return True if proper DCB was found, false otherwise. - */ -bool rwsplit_get_dcb(DCB **p_dcb, ROUTER_CLIENT_SES *rses, backend_type_t btype, - char *name, int max_rlag) -{ - backend_ref_t *backend_ref; - backend_ref_t *master_bref; - int i; - bool succp = false; - CHK_CLIENT_RSES(rses); - ss_dassert(p_dcb != NULL && *(p_dcb) == NULL); - if (p_dcb == NULL) + /** Check whether using rses->target_node as target SLAVE */ + if (rses->target_node && session_trx_is_read_only(rses->client_dcb->session)) { - goto return_succp; - } - backend_ref = rses->rses_backend_ref; - - /** Check whether using rses->forced_node as target SLAVE */ - if (rses->forced_node && - session_trx_is_read_only(rses->client_dcb->session)) - { - *p_dcb = rses->forced_node->bref_dcb; - succp = true; - - MXS_DEBUG("force_node found in READ ONLY transaction: use slave %s", - (*p_dcb)->server->unique_name); - - goto return_succp; + MXS_DEBUG("In READ ONLY transaction, using server '%s'", + rses->target_node->name()); + return rses->target_node; } /** get root master from available servers */ - master_bref = get_root_master_bref(rses); + SRWBackend master = get_root_master_backend(rses); - if (name != NULL) /*< Choose backend by name from a hint */ + if (name) /*< Choose backend by name from a hint */ { ss_dassert(btype != BE_MASTER); /*< Master dominates and no name should be passed with it */ - for (i = 0; i < rses->rses_nbackends; i++) + for (SRWBackendList::iterator it = rses->backends.begin(); + it != rses->backends.end(); it++) { - SERVER_REF *b = backend_ref[i].ref; - SERVER server; - server.status = b->server->status; - /** - * To become chosen: - * backend must be in use, name must match, - * backend's role must be either slave, relay - * server, or master. - */ - if (BREF_IS_IN_USE((&backend_ref[i])) && - SERVER_REF_IS_ACTIVE(b) && - (strncasecmp(name, b->server->unique_name, PATH_MAX) == 0) && - (SERVER_IS_SLAVE(&server) || SERVER_IS_RELAY_SERVER(&server) || - SERVER_IS_MASTER(&server))) + SRWBackend& backend = *it; + + /** The server must be a valid slave, relay server, or master */ + + if (backend->in_use() && backend->is_active() && + (strcasecmp(name, backend->name()) == 0) && + (backend->is_slave() || + backend->is_relay() || + backend->is_master())) { - *p_dcb = backend_ref[i].bref_dcb; - succp = true; - ss_dassert(backend_ref[i].bref_dcb->state != DCB_STATE_ZOMBIE); - break; + return backend; } } - if (succp) - { - goto return_succp; - } - else - { - btype = BE_SLAVE; - } + + /** No server found, use a normal slave for it */ + btype = BE_SLAVE; } + SRWBackend rval; + if (btype == BE_SLAVE) { - backend_ref_t *candidate_bref = NULL; - - for (i = 0; i < rses->rses_nbackends; i++) + for (SRWBackendList::iterator it = rses->backends.begin(); + it != rses->backends.end(); it++) { - SERVER_REF *b = backend_ref[i].ref; - SERVER server; - SERVER candidate; - server.status = b->server->status; + SRWBackend& backend = *it; + /** * Unused backend or backend which is not master nor * slave can't be used */ - if (!BREF_IS_IN_USE(&backend_ref[i]) || !SERVER_REF_IS_ACTIVE(b) || - (!SERVER_IS_MASTER(&server) && !SERVER_IS_SLAVE(&server))) + if (!backend->in_use() || !backend->is_active() || + (!backend->is_master() && !backend->is_slave())) { continue; } @@ -584,18 +393,16 @@ bool rwsplit_get_dcb(DCB **p_dcb, ROUTER_CLIENT_SES *rses, backend_type_t btype, * If there are no candidates yet accept both master or * slave. */ - else if (candidate_bref == NULL) + else if (!rval) { /** - * Ensure that master has not changed dunring + * Ensure that master has not changed during * session and abort if it has. */ - if (SERVER_IS_MASTER(&server) && &backend_ref[i] == master_bref) + if (backend->is_master() && backend == rses->current_master) { /** found master */ - candidate_bref = &backend_ref[i]; - candidate.status = candidate_bref->ref->server->status; - succp = true; + rval = backend; } /** * Ensure that max replication lag is not set @@ -603,106 +410,87 @@ bool rwsplit_get_dcb(DCB **p_dcb, ROUTER_CLIENT_SES *rses, backend_type_t btype, * maximum allowed replication lag. */ else if (max_rlag == MAX_RLAG_UNDEFINED || - (b->server->rlag != MAX_RLAG_NOT_AVAILABLE && - b->server->rlag <= max_rlag)) + (backend->server()->rlag != MAX_RLAG_NOT_AVAILABLE && + backend->server()->rlag <= max_rlag)) { /** found slave */ - candidate_bref = &backend_ref[i]; - candidate.status = candidate_bref->ref->server->status; - succp = true; + rval = backend; } } /** * If candidate is master, any slave which doesn't break * replication lag limits replaces it. */ - else if (SERVER_IS_MASTER(&candidate) && SERVER_IS_SLAVE(&server) && + else if (rval->is_master() && backend->is_slave() && (max_rlag == MAX_RLAG_UNDEFINED || - (b->server->rlag != MAX_RLAG_NOT_AVAILABLE && - b->server->rlag <= max_rlag)) && + (backend->server()->rlag != MAX_RLAG_NOT_AVAILABLE && + backend->server()->rlag <= max_rlag)) && !rses->rses_config.master_accept_reads) { /** found slave */ - candidate_bref = &backend_ref[i]; - candidate.status = candidate_bref->ref->server->status; - succp = true; + rval = backend; } /** * When candidate exists, compare it against the current * backend and update assign it to new candidate if * necessary. */ - else if (SERVER_IS_SLAVE(&server) || - (rses->rses_config.master_accept_reads && SERVER_IS_MASTER(&server))) + else if (backend->is_slave() || + (rses->rses_config.master_accept_reads && + backend->is_master())) { if (max_rlag == MAX_RLAG_UNDEFINED || - (b->server->rlag != MAX_RLAG_NOT_AVAILABLE && - b->server->rlag <= max_rlag)) + (backend->server()->rlag != MAX_RLAG_NOT_AVAILABLE && + backend->server()->rlag <= max_rlag)) { - candidate_bref = check_candidate_bref(candidate_bref, &backend_ref[i], - rses->rses_config.slave_selection_criteria); - candidate.status = candidate_bref->ref->server->status; + rval = compare_backends(rval, backend, rses->rses_config.slave_selection_criteria); } else { - MXS_INFO("Server [%s]:%d is too much behind the master, %d s. and can't be chosen.", - b->server->name, b->server->port, b->server->rlag); + MXS_INFO("Server %s is too much behind the master " + "(%d seconds) and can't be chosen", + backend->uri(), backend->server()->rlag); } } } /*< for */ - - /** Assign selected DCB's pointer value */ - if (candidate_bref != NULL) - { - *p_dcb = candidate_bref->bref_dcb; - } - - goto return_succp; - } /*< if (btype == BE_SLAVE) */ + } /** * If target was originally master only then the execution jumps * directly here. */ - if (btype == BE_MASTER) + else if (btype == BE_MASTER) { - if (master_bref && SERVER_REF_IS_ACTIVE(master_bref->ref)) + if (master && master->is_active()) { /** It is possible for the server status to change at any point in time * so copying it locally will make possible error messages * easier to understand */ SERVER server; - server.status = master_bref->ref->server->status; + server.status = master->server()->status; - if (BREF_IS_IN_USE(master_bref)) + if (master->in_use()) { if (SERVER_IS_MASTER(&server)) { - *p_dcb = master_bref->bref_dcb; - succp = true; - /** if bref is in use DCB should not be closed */ - ss_dassert(master_bref->bref_dcb->state != DCB_STATE_ZOMBIE); + rval = master; } else { - MXS_ERROR("Server '%s' should be master but " - "is %s instead and can't be chosen as the master.", - master_bref->ref->server->unique_name, + MXS_ERROR("Server '%s' should be master but is %s instead " + "and can't be chosen as the master.", + master->name(), STRSRVSTATUS(&server)); - succp = false; } } else { - MXS_ERROR("Server '%s' is not in use and can't be " - "chosen as the master.", - master_bref->ref->server->unique_name); - succp = false; + MXS_ERROR("Server '%s' is not in use and can't be chosen as the master.", + master->name()); } } } -return_succp: - return succp; + return rval; } /** @@ -717,17 +505,22 @@ return_succp: * if the query would otherwise be routed to slave. */ route_target_t get_route_target(ROUTER_CLIENT_SES *rses, - qc_query_type_t qtype, HINT *hint) + uint32_t qtype, HINT *hint) { bool trx_active = session_trx_is_active(rses->client_dcb->session); bool load_active = rses->load_data_state != LOAD_DATA_INACTIVE; mxs_target_t use_sql_variables_in = rses->rses_config.use_sql_variables_in; - route_target_t target = TARGET_UNDEFINED; + int target = TARGET_UNDEFINED; - if (rses->forced_node && rses->forced_node == rses->rses_master_ref) + if (rses->target_node && rses->target_node == rses->current_master) { target = TARGET_MASTER; } + else if (qc_query_is_type(qtype, QUERY_TYPE_PREPARE_STMT) || + qc_query_is_type(qtype, QUERY_TYPE_PREPARE_NAMED_STMT)) + { + target = TARGET_ALL; + } /** * These queries are not affected by hints */ @@ -756,9 +549,7 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses, * 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) && - !(qc_query_is_type(qtype, QUERY_TYPE_PREPARE_STMT) || - qc_query_is_type(qtype, QUERY_TYPE_PREPARE_NAMED_STMT))) + 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 " @@ -778,8 +569,6 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses, else if (!trx_active && !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_PREPARE_STMT) && - !qc_query_is_type(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) && (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) || @@ -837,9 +626,7 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses, 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) || - qc_query_is_type(qtype, QUERY_TYPE_PREPARE_STMT) || - qc_query_is_type(qtype, QUERY_TYPE_PREPARE_NAMED_STMT)); + qc_query_is_type(qtype, QUERY_TYPE_EXEC_STMT)); target = TARGET_MASTER; } @@ -898,7 +685,7 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses, hint = hint->next; } /*< while (hint != NULL) */ - return target; + return (route_target_t)target; } /** @@ -913,7 +700,7 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses, */ void handle_multi_temp_and_load(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, - int packet_type, int *qtype) + uint8_t packet_type, uint32_t *qtype) { /** Check for multi-statement queries. If no master server is available * and a multi-statement is issued, an error is returned to the client @@ -923,12 +710,12 @@ handle_multi_temp_and_load(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, * 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 ((rses->forced_node == NULL || rses->forced_node != rses->rses_master_ref) && + if ((rses->target_node == NULL || rses->target_node != rses->current_master) && check_for_multi_stmt(querybuf, rses->client_dcb->protocol, packet_type)) { - if (rses->rses_master_ref) + if (rses->current_master) { - rses->forced_node = rses->rses_master_ref; + rses->target_node = rses->current_master; MXS_INFO("Multi-statement query, routing all future queries to master."); } else @@ -969,7 +756,7 @@ handle_multi_temp_and_load(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, */ if (rses->have_tmp_tables) { - check_drop_tmp_table(rses, querybuf, packet_type); + check_drop_tmp_table(rses, querybuf); if (is_packet_a_query(packet_type) && is_read_tmp_table(rses, querybuf, *qtype)) { *qtype |= QUERY_TYPE_MASTER_READ; @@ -1009,16 +796,13 @@ handle_multi_temp_and_load(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, * * @return bool - true if succeeded, false otherwise */ -bool handle_hinted_target(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, - route_target_t route_target, DCB **target_dcb) +SRWBackend handle_hinted_target(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, + route_target_t route_target) { - HINT *hint; char *named_server = NULL; - backend_type_t btype; /*< target backend type */ int rlag_max = MAX_RLAG_UNDEFINED; - bool succp; - hint = querybuf->hint; + HINT* hint = querybuf->hint; while (hint != NULL) { @@ -1028,7 +812,7 @@ bool handle_hinted_target(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, * Set the name of searched * backend server. */ - named_server = hint->data; + named_server = (char*)hint->data; MXS_INFO("Hint: route to server '%s'", named_server); } else if (hint->type == HINT_PARAMETER && @@ -1053,15 +837,15 @@ bool handle_hinted_target(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, } /** target may be master or slave */ - btype = route_target & TARGET_SLAVE ? BE_SLAVE : BE_MASTER; + backend_type_t btype = route_target & TARGET_SLAVE ? BE_SLAVE : BE_MASTER; /** * Search backend server by name or replication lag. * If it fails, then try to find valid slave or master. */ - succp = rwsplit_get_dcb(target_dcb, rses, btype, named_server, rlag_max); + SRWBackend target = get_target_backend(rses, btype, named_server, rlag_max); - if (!succp) + if (!target) { if (TARGET_IS_NAMED_SERVER(route_target)) { @@ -1076,7 +860,8 @@ bool handle_hinted_target(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, "find such a slave.", rlag_max); } } - return succp; + + return target; } /** @@ -1090,24 +875,21 @@ bool handle_hinted_target(ROUTER_CLIENT_SES *rses, GWBUF *querybuf, * * @return bool - true if succeeded, false otherwise */ -bool handle_slave_is_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, - DCB **target_dcb) +SRWBackend handle_slave_is_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses) { int rlag_max = rses_get_max_replication_lag(rses); + SRWBackend target = get_target_backend(rses, BE_SLAVE, NULL, rlag_max); - /** - * Search suitable backend server, get DCB in target_dcb - */ - if (rwsplit_get_dcb(target_dcb, rses, BE_SLAVE, NULL, rlag_max)) + if (target) { atomic_add_uint64(&inst->stats.n_slave, 1); - return true; } else { MXS_INFO("Was supposed to route to slave but finding suitable one failed."); - return false; } + + return target; } /** @@ -1116,36 +898,36 @@ bool handle_slave_is_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, * @param rses Router session */ static void log_master_routing_failure(ROUTER_CLIENT_SES *rses, bool found, - DCB *master_dcb, DCB *curr_master_dcb) + SRWBackend& old_master, SRWBackend& curr_master) { - char errmsg[MAX_SERVER_NAME_LEN * 2 + 100]; // Extra space for error message + char errmsg[MAX_SERVER_ADDRESS_LEN * 2 + 100]; // Extra space for error message if (!found) { sprintf(errmsg, "Could not find a valid master connection"); } - else if (master_dcb && curr_master_dcb) + else if (old_master && curr_master) { /** We found a master but it's not the same connection */ - ss_dassert(master_dcb != curr_master_dcb); - if (master_dcb->server != curr_master_dcb->server) + ss_dassert(old_master != curr_master); + if (old_master != curr_master) { sprintf(errmsg, "Master server changed from '%s' to '%s'", - master_dcb->server->unique_name, - curr_master_dcb->server->unique_name); + old_master->name(), + curr_master->name()); } else { ss_dassert(false); // Currently we don't reconnect to the master sprintf(errmsg, "Connection to master '%s' was recreated", - curr_master_dcb->server->unique_name); + curr_master->name()); } } - else if (master_dcb) + else if (old_master) { /** We have an original master connection but we couldn't find it */ sprintf(errmsg, "The connection to master server '%s' is not available", - master_dcb->server->unique_name); + old_master->name()); } else { @@ -1180,47 +962,35 @@ static void log_master_routing_failure(ROUTER_CLIENT_SES *rses, bool found, * @return bool - true if succeeded, false otherwise */ bool handle_master_is_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, - DCB **target_dcb) + SRWBackend* dest) { - DCB *master_dcb = rses->rses_master_ref ? rses->rses_master_ref->bref_dcb : NULL; - DCB *curr_master_dcb = NULL; - bool succp = rwsplit_get_dcb(&curr_master_dcb, rses, BE_MASTER, NULL, MAX_RLAG_UNDEFINED); + SRWBackend target = get_target_backend(rses, BE_MASTER, NULL, MAX_RLAG_UNDEFINED); + bool succp = true; - if (succp && master_dcb == curr_master_dcb) + if (target && target == rses->current_master) { atomic_add_uint64(&inst->stats.n_master, 1); - *target_dcb = master_dcb; } else { - if (succp && master_dcb == curr_master_dcb) + /** The original master is not available, we can't route the write */ + if (rses->rses_config.master_failure_mode == RW_ERROR_ON_WRITE) { - atomic_add_uint64(&inst->stats.n_master, 1); - *target_dcb = master_dcb; + succp = send_readonly_error(rses->client_dcb); + + if (rses->current_master && rses->current_master->in_use()) + { + rses->current_master->close(); + } } else { - /** The original master is not available, we can't route the write */ - if (rses->rses_config.master_failure_mode == RW_ERROR_ON_WRITE) - { - succp = send_readonly_error(rses->client_dcb); - - if (rses->rses_master_ref && BREF_IS_IN_USE(rses->rses_master_ref)) - { - close_failed_bref(rses->rses_master_ref, true); - RW_CHK_DCB(rses->rses_master_ref, rses->rses_master_ref->bref_dcb); - dcb_close(rses->rses_master_ref->bref_dcb); - RW_CLOSE_BREF(rses->rses_master_ref); - } - } - else - { - log_master_routing_failure(rses, succp, master_dcb, curr_master_dcb); - succp = false; - } + log_master_routing_failure(rses, succp, rses->current_master, target); + succp = false; } } + *dest = target; return succp; } @@ -1245,62 +1015,51 @@ static inline bool query_creates_reply(mysql_server_cmd_t cmd) */ bool handle_got_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, - GWBUF *querybuf, DCB *target_dcb, bool store) + GWBUF *querybuf, SRWBackend& target, bool store) { - backend_ref_t *bref; - - bref = get_bref_from_dcb(rses, target_dcb); - ss_dassert(bref->reply_state == REPLY_STATE_DONE); - /** - * If the transaction is READ ONLY set forced_node to bref - * That SLAVE backend will be used until COMMIT is seen + * If the transaction is READ ONLY set forced_node to this backend. + * This SLAVE backend will be used until the COMMIT is seen. */ - if (!rses->forced_node && + if (!rses->target_node && session_trx_is_read_only(rses->client_dcb->session)) { - rses->forced_node = bref; - MXS_DEBUG("Setting forced_node SLAVE to %s within an opened READ ONLY transaction\n", - target_dcb->server->unique_name); + rses->target_node = target; + MXS_DEBUG("Setting forced_node SLAVE to %s within an opened READ ONLY transaction", + target->name()); } - ss_dassert(target_dcb != NULL); - - MXS_INFO("Route query to %s \t[%s]:%d <", - (SERVER_IS_MASTER(bref->ref->server) ? "master" : "slave"), - bref->ref->server->name, bref->ref->server->port); + MXS_INFO("Route query to %s \t%s <", target->is_master() ? "master" : "slave", + target->uri()); /** The session command cursor must not be active */ - ss_dassert(!sescmd_cursor_is_active(&bref->bref_sescmd_cur)); + ss_dassert(target->session_command_count() == 0); - /** We only want the complete response to the preparation */ - if (MYSQL_GET_COMMAND(GWBUF_DATA(querybuf)) == MYSQL_COM_STMT_PREPARE) + mxs::Backend::response_type response = mxs::Backend::NO_RESPONSE; + mysql_server_cmd_t cmd = mxs_mysql_current_command(rses->client_dcb->session); + + if (rses->load_data_state != LOAD_DATA_ACTIVE && + query_creates_reply(cmd)) { - gwbuf_set_type(querybuf, GWBUF_TYPE_COLLECT_RESULT); + response = mxs::Backend::EXPECT_RESPONSE; } - if (target_dcb->func.write(target_dcb, gwbuf_clone(querybuf)) == 1) + if (target->write(gwbuf_clone(querybuf), response)) { - if (store && !session_store_stmt(rses->client_dcb->session, querybuf, target_dcb->server)) + if (store && !session_store_stmt(rses->client_dcb->session, querybuf, target->server())) { MXS_ERROR("Failed to store current statement, it won't be retried if it fails."); } atomic_add_uint64(&inst->stats.n_queries, 1); - mysql_server_cmd_t cmd = mxs_mysql_current_command(rses->client_dcb->session); - - if (rses->load_data_state != LOAD_DATA_ACTIVE && query_creates_reply(cmd)) + if (response == mxs::Backend::EXPECT_RESPONSE) { /** The server will reply to this command */ - ss_dassert(bref->reply_state == REPLY_STATE_DONE); + ss_dassert(target->get_reply_state() == REPLY_STATE_DONE); - bref = get_bref_from_dcb(rses, target_dcb); - bref_set_state(bref, BREF_QUERY_ACTIVE); - bref_set_state(bref, BREF_WAITING_RESULT); - - LOG_RS(bref, REPLY_STATE_START); - bref->reply_state = REPLY_STATE_START; + LOG_RS(target, REPLY_STATE_START); + target->set_reply_state(REPLY_STATE_START); rses->expected_responses++; if (rses->load_data_state == LOAD_DATA_START) @@ -1321,12 +1080,12 @@ handle_got_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, /** * If a READ ONLY transaction is ending set forced_node to NULL */ - if (rses->forced_node && + if (rses->target_node && session_trx_is_read_only(rses->client_dcb->session) && session_trx_is_ending(rses->client_dcb->session)) { MXS_DEBUG("An opened READ ONLY transaction ends: forced_node is set to NULL"); - rses->forced_node = NULL; + rses->target_node.reset(); } return true; } @@ -1338,161 +1097,50 @@ handle_got_target(ROUTER_INSTANCE *inst, ROUTER_CLIENT_SES *rses, } /** - * @brief Create a generic router session property structure. + * @brief Get the root master server from MySQL replication tree * - * @param prop_type Property type + * Finds the server with the lowest replication depth level which has the master + * status. Servers are checked even if they are in 'maintenance'. * - * @return property structure of requested type, or NULL if failed + * @param rses Router client session + * + * @return The backend that points to the master server or an empty reference + * if the master cannot be found */ -rses_property_t *rses_property_init(rses_property_type_t prop_type) +static SRWBackend get_root_master_backend(ROUTER_CLIENT_SES *rses) { - rses_property_t *prop; - - prop = (rses_property_t *)MXS_CALLOC(1, sizeof(rses_property_t)); - if (prop == NULL) - { - return NULL; - } - prop->rses_prop_type = prop_type; -#if defined(SS_DEBUG) - prop->rses_prop_chk_top = CHK_NUM_ROUTER_PROPERTY; - prop->rses_prop_chk_tail = CHK_NUM_ROUTER_PROPERTY; -#endif - - CHK_RSES_PROP(prop); - return prop; -} - -/** - * @brief Add property to the router client session - * - * Add property to the router_client_ses structure's rses_properties - * array. The slot is determined by the type of property. - * In each slot there is a list of properties of similar type. - * - * Router client session must be locked. - * - * @param rses Router session - * @param prop Router session property to be added - * - * @return -1 on failure, 0 on success - */ -int rses_property_add(ROUTER_CLIENT_SES *rses, rses_property_t *prop) -{ - if (rses == NULL) - { - MXS_ERROR("Router client session is NULL. (%s:%d)", __FILE__, __LINE__); - return -1; - } - if (prop == NULL) - { - MXS_ERROR("Router client session property is NULL. (%s:%d)", __FILE__, __LINE__); - return -1; - } - rses_property_t *p; - - CHK_CLIENT_RSES(rses); - CHK_RSES_PROP(prop); - - prop->rses_prop_rsession = rses; - p = rses->rses_properties[prop->rses_prop_type]; - - if (p == NULL) - { - rses->rses_properties[prop->rses_prop_type] = prop; - } - else - { - while (p->rses_prop_next != NULL) - { - p = p->rses_prop_next; - } - p->rses_prop_next = prop; - } - return 0; -} - -/** - * Find out which of the two backend servers has smaller value for select - * criteria property. - * - * @param cand previously selected candidate - * @param new challenger - * @param sc select criteria - * - * @return pointer to backend reference of that backend server which has smaller - * value in selection criteria. If either reference pointer is NULL then the - * other reference pointer value is returned. - */ -static backend_ref_t *check_candidate_bref(backend_ref_t *cand, - backend_ref_t *new, - select_criteria_t sc) -{ - int (*p)(const void *, const void *); - /** get compare function */ - p = criteria_cmpfun[sc]; - - if (new == NULL) - { - return cand; - } - else if (cand == NULL || (p((void *)cand, (void *)new) > 0)) - { - return new; - } - else - { - return cand; - } -} - -/******************************** - * This routine returns the root master server from MySQL replication tree - * Get the root Master rule: - * - * find server with the lowest replication depth level - * and the SERVER_MASTER bitval - * Servers are checked even if they are in 'maintenance' - * - * @param rses pointer to router session - * @return pointer to backend reference of the root master or NULL - * - */ -static backend_ref_t *get_root_master_bref(ROUTER_CLIENT_SES *rses) -{ - backend_ref_t *bref; - backend_ref_t *candidate_bref = NULL; + SRWBackend candidate; SERVER master = {}; - for (int i = 0; i < rses->rses_nbackends; i++) + for (SRWBackendList::iterator it = rses->backends.begin(); + it != rses->backends.end(); it++) { - bref = &rses->rses_backend_ref[i]; - if (bref && BREF_IS_IN_USE(bref)) + SRWBackend& backend = *it; + if (backend->in_use()) { - ss_dassert(!BREF_IS_CLOSED(bref) && !BREF_HAS_FAILED(bref)); - if (bref == rses->rses_master_ref) + if (backend == rses->current_master) { /** Store master state for better error reporting */ - master.status = bref->ref->server->status; + master.status = backend->server()->status; } - if (SERVER_IS_MASTER(bref->ref->server)) + if (backend->is_master()) { - if (candidate_bref == NULL || - (bref->ref->server->depth < candidate_bref->ref->server->depth)) + if (!candidate || + (backend->server()->depth < candidate->server()->depth)) { - candidate_bref = bref; + candidate = backend; } } } } - if (candidate_bref == NULL && rses->rses_config.master_failure_mode == RW_FAIL_INSTANTLY && - rses->rses_master_ref && BREF_IS_IN_USE(rses->rses_master_ref)) + if (!candidate && rses->rses_config.master_failure_mode == RW_FAIL_INSTANTLY && + rses->current_master && rses->current_master->in_use()) { MXS_ERROR("Could not find master among the backend servers. " "Previous master's state : %s", STRSRVSTATUS(&master)); } - return candidate_bref; + return candidate; } diff --git a/server/modules/routing/readwritesplit/rwsplit_select_backends.c b/server/modules/routing/readwritesplit/rwsplit_select_backends.c deleted file mode 100644 index ca3dc6b04..000000000 --- a/server/modules/routing/readwritesplit/rwsplit_select_backends.c +++ /dev/null @@ -1,557 +0,0 @@ -/* - * 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.h" - -#include -#include -#include -#include -#include - -#include -#include "rwsplit_internal.h" -/** - * @file rwsplit_select_backends.c The functions that implement back end - * selection for the read write split router. All of these functions are - * internal to that router and not intended to be called from elsewhere. - * - * @verbatim - * Revision History - * - * Date Who Description - * 08/08/2016 Martin Brampton Initial implementation - * - * @endverbatim - */ - -static bool connect_server(backend_ref_t *bref, MXS_SESSION *session, bool execute_history); - -static void log_server_connections(select_criteria_t select_criteria, - backend_ref_t *backend_ref, int router_nservers); - -static SERVER_REF *get_root_master(backend_ref_t *servers, int router_nservers); - -static int bref_cmp_global_conn(const void *bref1, const void *bref2); - -static int bref_cmp_router_conn(const void *bref1, const void *bref2); - -static int bref_cmp_behind_master(const void *bref1, const void *bref2); - -static int bref_cmp_current_load(const void *bref1, const void *bref2); - -/** - * The order of functions _must_ match with the order the select criteria are - * listed in select_criteria_t definition in readwritesplit.h - */ -int (*criteria_cmpfun[LAST_CRITERIA])(const void *, const void *) = -{ - NULL, - bref_cmp_global_conn, - bref_cmp_router_conn, - bref_cmp_behind_master, - bref_cmp_current_load -}; - -/** - * @brief Check whether it's possible to connect to this server - * - * @param bref Backend reference - * @return True if a connection to this server can be attempted - */ -static bool bref_valid_for_connect(const backend_ref_t *bref) -{ - return !BREF_HAS_FAILED(bref) && SERVER_IS_RUNNING(bref->ref->server); -} - -/** - * Check whether it's possible to use this server as a slave - * - * @param bref Backend reference - * @param master_host The master server - * @return True if this server is a valid slave candidate - */ -static bool bref_valid_for_slave(const backend_ref_t *bref, const SERVER *master_host) -{ - SERVER *server = bref->ref->server; - - return (SERVER_IS_SLAVE(server) || SERVER_IS_RELAY_SERVER(server)) && - (master_host == NULL || (server != master_host)); -} - -/** - * @brief Find the best slave candidate - * - * This function iterates through @c bref and tries to find the best backend - * reference that is not in use. @c cmpfun will be called to compare the backends. - * - * @param bref Backend reference - * @param n Size of @c bref - * @param master The master server - * @param cmpfun qsort() compatible comparison function - * @return The best slave backend reference or NULL if no candidates could be found - */ -backend_ref_t* get_slave_candidate(backend_ref_t *bref, int n, const SERVER *master, - int (*cmpfun)(const void *, const void *)) -{ - backend_ref_t *candidate = NULL; - - for (int i = 0; i < n; i++) - { - if (!BREF_IS_IN_USE(&bref[i]) && - bref_valid_for_connect(&bref[i]) && - bref_valid_for_slave(&bref[i], master)) - { - if (candidate) - { - if (cmpfun(candidate, &bref[i]) > 0) - { - candidate = &bref[i]; - } - } - else - { - candidate = &bref[i]; - } - } - } - - return candidate; -} - -/** - * @brief Search suitable backend servers from those of router instance - * - * It is assumed that there is only one master among servers of a router instance. - * As a result, the first master found is chosen. There will possibly be more - * backend references than connected backends because only those in correct state - * are connected to. - * - * @param p_master_ref Pointer to location where master's backend reference is to be stored - * @param backend_ref Pointer to backend server reference object array - * @param router_nservers Number of backend server pointers pointed to by @p backend_ref - * @param max_nslaves Upper limit for the number of slaves - * @param max_slave_rlag Maximum allowed replication lag for any slave - * @param select_criteria Slave selection criteria - * @param session Client session - * @param router Router instance - * @return true, if at least one master and one slave was found. - */ -bool select_connect_backend_servers(backend_ref_t **p_master_ref, - backend_ref_t *backend_ref, - int router_nservers, int max_nslaves, - int max_slave_rlag, - select_criteria_t select_criteria, - MXS_SESSION *session, - ROUTER_INSTANCE *router, - bool active_session) -{ - if (p_master_ref == NULL || backend_ref == NULL) - { - MXS_ERROR("Master reference (%p) or backend reference (%p) is NULL.", - p_master_ref, backend_ref); - ss_dassert(false); - return false; - } - - /* get the root Master */ - SERVER_REF *master_backend = get_root_master(backend_ref, router_nservers); - SERVER *master_host = master_backend ? master_backend->server : NULL; - - if (router->rwsplit_config.master_failure_mode == RW_FAIL_INSTANTLY && - (master_host == NULL || SERVER_IS_DOWN(master_host))) - { - MXS_ERROR("Couldn't find suitable Master from %d candidates.", router_nservers); - return false; - } - - /** - * New session: - * - * Connect to both master and slaves - * - * Existing session: - * - * Master is already connected or we don't have a master. The function was - * called because new slaves must be selected to replace failed ones. - */ - bool master_connected = active_session || *p_master_ref != NULL; - - /** Check slave selection criteria and set compare function */ - int (*p)(const void *, const void *) = criteria_cmpfun[select_criteria]; - ss_dassert(p); - - SERVER *old_master = *p_master_ref ? (*p_master_ref)->ref->server : NULL; - - if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) - { - log_server_connections(select_criteria, backend_ref, router_nservers); - } - - int slaves_found = 0; - int slaves_connected = 0; - const int min_nslaves = 0; /*< not configurable at the time */ - bool succp = false; - - if (!master_connected) - { - /** Find a master server */ - for (int i = 0; i < router_nservers; i++) - { - SERVER *serv = backend_ref[i].ref->server; - - if (bref_valid_for_connect(&backend_ref[i]) && - master_host && serv == master_host) - { - if (connect_server(&backend_ref[i], session, false)) - { - *p_master_ref = &backend_ref[i]; - break; - } - } - } - } - - /** Calculate how many connections we already have */ - for (int i = 0; i < router_nservers; i++) - { - if (bref_valid_for_connect(&backend_ref[i]) && - bref_valid_for_slave(&backend_ref[i], master_host)) - { - slaves_found += 1; - - if (BREF_IS_IN_USE(&backend_ref[i])) - { - slaves_connected += 1; - } - } - } - - ss_dassert(slaves_connected < max_nslaves || max_nslaves == 0); - - backend_ref_t *bref = get_slave_candidate(backend_ref, router_nservers, master_host, p); - - /** Connect to all possible slaves */ - while (bref && slaves_connected < max_nslaves) - { - if (connect_server(bref, session, true)) - { - slaves_connected += 1; - } - else - { - /** Failed to connect, mark server as failed */ - bref_set_state(bref, BREF_FATAL_FAILURE); - } - - bref = get_slave_candidate(backend_ref, router_nservers, master_host, p); - } - - /** - * Successful cases - */ - if (slaves_connected >= min_nslaves && slaves_connected <= max_nslaves) - { - succp = true; - - if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) - { - if (slaves_connected < max_nslaves) - { - MXS_INFO("Couldn't connect to maximum number of " - "slaves. Connected successfully to %d slaves " - "of %d of them.", slaves_connected, slaves_found); - } - - for (int i = 0; i < router_nservers; i++) - { - if (BREF_IS_IN_USE((&backend_ref[i]))) - { - MXS_INFO("Selected %s in \t[%s]:%d", - STRSRVSTATUS(backend_ref[i].ref->server), - backend_ref[i].ref->server->name, - backend_ref[i].ref->server->port); - } - } /* for */ - } - } - /** Failure cases */ - else - { - MXS_ERROR("Couldn't establish required amount of slave connections for " - "router session. Would need between %d and %d slaves but only have %d.", - min_nslaves, max_nslaves, slaves_connected); - - /** Clean up connections */ - for (int i = 0; i < router_nservers; i++) - { - if (BREF_IS_IN_USE((&backend_ref[i]))) - { - ss_dassert(backend_ref[i].ref->connections > 0); - - close_failed_bref(&backend_ref[i], true); - - /** Decrease backend's connection counter. */ - atomic_add(&backend_ref[i].ref->connections, -1); - RW_CHK_DCB(&backend_ref[i], backend_ref[i].bref_dcb); - dcb_close(backend_ref[i].bref_dcb); - RW_CLOSE_BREF(&backend_ref[i]); - } - } - } - - return succp; -} - -/** Compare number of connections from this router in backend servers */ -static int bref_cmp_router_conn(const void *bref1, const void *bref2) -{ - SERVER_REF *b1 = ((backend_ref_t *)bref1)->ref; - SERVER_REF *b2 = ((backend_ref_t *)bref2)->ref; - - if (b1->weight == 0 && b2->weight == 0) - { - return b1->connections - b2->connections; - } - else if (b1->weight == 0) - { - return 1; - } - else if (b2->weight == 0) - { - return -1; - } - - return ((1000 + 1000 * b1->connections) / b1->weight) - - ((1000 + 1000 * b2->connections) / b2->weight); -} - -/** Compare number of global connections in backend servers */ -static int bref_cmp_global_conn(const void *bref1, const void *bref2) -{ - SERVER_REF *b1 = ((backend_ref_t *)bref1)->ref; - SERVER_REF *b2 = ((backend_ref_t *)bref2)->ref; - - if (b1->weight == 0 && b2->weight == 0) - { - return b1->server->stats.n_current - - b2->server->stats.n_current; - } - else if (b1->weight == 0) - { - return 1; - } - else if (b2->weight == 0) - { - return -1; - } - - return ((1000 + 1000 * b1->server->stats.n_current) / b1->weight) - - ((1000 + 1000 * b2->server->stats.n_current) / b2->weight); -} - -/** Compare replication lag between backend servers */ -static int bref_cmp_behind_master(const void *bref1, const void *bref2) -{ - SERVER_REF *b1 = ((backend_ref_t *)bref1)->ref; - SERVER_REF *b2 = ((backend_ref_t *)bref2)->ref; - - if (b1->weight == 0 && b2->weight == 0) - { - return b1->server->rlag - - b2->server->rlag; - } - else if (b1->weight == 0) - { - return 1; - } - else if (b2->weight == 0) - { - return -1; - } - - return ((1000 + 1000 * b1->server->rlag) / b1->weight) - - ((1000 + 1000 * b2->server->rlag) / b2->weight); -} - -/** Compare number of current operations in backend servers */ -static int bref_cmp_current_load(const void *bref1, const void *bref2) -{ - SERVER_REF *b1 = ((backend_ref_t *)bref1)->ref; - SERVER_REF *b2 = ((backend_ref_t *)bref2)->ref; - - if (b1->weight == 0 && b2->weight == 0) - { - return b1->server->stats.n_current_ops - b2->server->stats.n_current_ops; - } - else if (b1->weight == 0) - { - return 1; - } - else if (b2->weight == 0) - { - return -1; - } - - return ((1000 + 1000 * b1->server->stats.n_current_ops) / b1->weight) - - ((1000 + 1000 * b2->server->stats.n_current_ops) / b2->weight); -} - -/** - * @brief Connect a server - * - * Connects to a server, adds callbacks to the created DCB and updates - * router statistics. If @p execute_history is true, the session command - * history will be executed on this server. - * - * @param b Router's backend structure for the server - * @param session Client's session object - * @param execute_history Execute session command history - * @return True if successful, false if an error occurred - */ -static bool connect_server(backend_ref_t *bref, MXS_SESSION *session, bool execute_history) -{ - SERVER *serv = bref->ref->server; - bool rval = false; - - bref->bref_dcb = dcb_connect(serv, session, serv->protocol); - - if (bref->bref_dcb != NULL) - { - bref_clear_state(bref, BREF_CLOSED); - bref->closed_at = 0; - - if (!execute_history || execute_sescmd_history(bref)) - { - bref->bref_state = 0; - bref_set_state(bref, BREF_IN_USE); - atomic_add(&bref->ref->connections, 1); - rval = true; - } - else - { - MXS_ERROR("Failed to execute session command in %s ([%s]:%d). See earlier " - "errors for more details.", - bref->ref->server->unique_name, - bref->ref->server->name, - bref->ref->server->port); - RW_CHK_DCB(bref, bref->bref_dcb); - dcb_close(bref->bref_dcb); - RW_CLOSE_BREF(bref); - bref->bref_dcb = NULL; - } - } - else - { - MXS_ERROR("Unable to establish connection with server [%s]:%d", - serv->name, serv->port); - } - - return rval; -} - -/** - * @brief Log server connections - * - * @param select_criteria Slave selection criteria - * @param backend_ref Backend reference array - * @param router_nservers Number of backends in @p backend_ref - */ -static void log_server_connections(select_criteria_t select_criteria, - backend_ref_t *backend_ref, int router_nservers) -{ - if (select_criteria == LEAST_GLOBAL_CONNECTIONS || - select_criteria == LEAST_ROUTER_CONNECTIONS || - select_criteria == LEAST_BEHIND_MASTER || - select_criteria == LEAST_CURRENT_OPERATIONS) - { - MXS_INFO("Servers and %s connection counts:", - select_criteria == LEAST_GLOBAL_CONNECTIONS ? "all MaxScale" - : "router"); - - for (int i = 0; i < router_nservers; i++) - { - SERVER_REF *b = backend_ref[i].ref; - - switch (select_criteria) - { - case LEAST_GLOBAL_CONNECTIONS: - MXS_INFO("MaxScale connections : %d in \t[%s]:%d %s", - b->server->stats.n_current, b->server->name, - b->server->port, STRSRVSTATUS(b->server)); - break; - - case LEAST_ROUTER_CONNECTIONS: - MXS_INFO("RWSplit connections : %d in \t[%s]:%d %s", - b->connections, b->server->name, - b->server->port, STRSRVSTATUS(b->server)); - break; - - case LEAST_CURRENT_OPERATIONS: - MXS_INFO("current operations : %d in \t[%s]:%d %s", - b->server->stats.n_current_ops, - b->server->name, b->server->port, - STRSRVSTATUS(b->server)); - break; - - case LEAST_BEHIND_MASTER: - MXS_INFO("replication lag : %d in \t[%s]:%d %s", - b->server->rlag, b->server->name, - b->server->port, STRSRVSTATUS(b->server)); - default: - break; - } - } - } -} - -/******************************** - * This routine returns the root master server from MySQL replication tree - * Get the root Master rule: - * - * find server with the lowest replication depth level - * and the SERVER_MASTER bitval - * Servers are checked even if they are in 'maintenance' - * - * @param servers The list of servers - * @param router_nservers The number of servers - * @return The Master found - * - */ -static SERVER_REF *get_root_master(backend_ref_t *servers, int router_nservers) -{ - int i = 0; - SERVER_REF *master_host = NULL; - - for (i = 0; i < router_nservers; i++) - { - if (servers[i].ref == NULL) - { - /** This should not happen */ - ss_dassert(false); - continue; - } - - SERVER_REF *b = servers[i].ref; - - if (SERVER_IS_MASTER(b->server)) - { - if (master_host == NULL || - (b->server->depth < master_host->server->depth)) - { - master_host = b; - } - } - } - return master_host; -} diff --git a/server/modules/routing/readwritesplit/rwsplit_select_backends.cc b/server/modules/routing/readwritesplit/rwsplit_select_backends.cc new file mode 100644 index 000000000..95a204303 --- /dev/null +++ b/server/modules/routing/readwritesplit/rwsplit_select_backends.cc @@ -0,0 +1,429 @@ +/* + * 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 +#include +#include +#include +#include + +#include + +/** + * The functions that implement back end selection for the read write + * split router. All of these functions are internal to that router and + * not intended to be called from elsewhere. + */ + +/** + * Check whether it's possible to use this server as a slave + * + * @param server The slave candidate + * @param master The master server or NULL if no master is available + * + * @return True if this server is a valid slave candidate + */ +static bool valid_for_slave(const SERVER *server, const SERVER *master) +{ + return (SERVER_IS_SLAVE(server) || SERVER_IS_RELAY_SERVER(server)) && + (master == NULL || (server != master)); +} + +/** + * @brief Find the best slave candidate + * + * This function iterates through @c backend and tries to find the best backend + * reference that is not in use. @c cmpfun will be called to compare the backends. + * + * @param rses Router client session + * @param master The master server + * @param cmpfun qsort() compatible comparison function + * + * @return The best slave backend reference or NULL if no candidates could be found + */ +SRWBackend get_slave_candidate(ROUTER_CLIENT_SES* rses, const SERVER *master, + int (*cmpfun)(const SRWBackend&, const SRWBackend&)) +{ + SRWBackend candidate; + + for (SRWBackendList::iterator it = rses->backends.begin(); + it != rses->backends.end(); it++) + { + SRWBackend& backend = *it; + + if (!backend->in_use() && backend->can_connect() && + valid_for_slave(backend->server(), master)) + { + if (candidate) + { + if (cmpfun(candidate, backend) > 0) + { + candidate = backend; + } + } + else + { + candidate = backend; + } + } + } + + return candidate; +} + +/** Compare number of connections from this router in backend servers */ +static int backend_cmp_router_conn(const SRWBackend& a, const SRWBackend& b) +{ + SERVER_REF *first = a->backend(); + SERVER_REF *second = b->backend(); + + if (first->weight == 0 && second->weight == 0) + { + return first->connections - second->connections; + } + else if (first->weight == 0) + { + return 1; + } + else if (second->weight == 0) + { + return -1; + } + + return ((1000 + 1000 * first->connections) / first->weight) - + ((1000 + 1000 * second->connections) / second->weight); +} + +/** Compare number of global connections in backend servers */ +static int backend_cmp_global_conn(const SRWBackend& a, const SRWBackend& b) +{ + SERVER_REF *first = a->backend(); + SERVER_REF *second = b->backend(); + + if (first->weight == 0 && second->weight == 0) + { + return first->server->stats.n_current - + second->server->stats.n_current; + } + else if (first->weight == 0) + { + return 1; + } + else if (second->weight == 0) + { + return -1; + } + + return ((1000 + 1000 * first->server->stats.n_current) / first->weight) - + ((1000 + 1000 * second->server->stats.n_current) / second->weight); +} + +/** Compare replication lag between backend servers */ +static int backend_cmp_behind_master(const SRWBackend& a, const SRWBackend& b) +{ + SERVER_REF *first = a->backend(); + SERVER_REF *second = b->backend(); + + if (first->weight == 0 && second->weight == 0) + { + return first->server->rlag - + second->server->rlag; + } + else if (first->weight == 0) + { + return 1; + } + else if (second->weight == 0) + { + return -1; + } + + return ((1000 + 1000 * first->server->rlag) / first->weight) - + ((1000 + 1000 * second->server->rlag) / second->weight); +} + +/** Compare number of current operations in backend servers */ +static int backend_cmp_current_load(const SRWBackend& a, const SRWBackend& b) +{ + SERVER_REF *first = a->backend(); + SERVER_REF *second = b->backend(); + + if (first->weight == 0 && second->weight == 0) + { + return first->server->stats.n_current_ops - second->server->stats.n_current_ops; + } + else if (first->weight == 0) + { + return 1; + } + else if (second->weight == 0) + { + return -1; + } + + return ((1000 + 1000 * first->server->stats.n_current_ops) / first->weight) - + ((1000 + 1000 * second->server->stats.n_current_ops) / second->weight); +} + +/** + * The order of functions _must_ match with the order the select criteria are + * listed in select_criteria_t definition in readwritesplit.h + */ +int (*criteria_cmpfun[LAST_CRITERIA])(const SRWBackend&, const SRWBackend&) = +{ + NULL, + backend_cmp_global_conn, + backend_cmp_router_conn, + backend_cmp_behind_master, + backend_cmp_current_load +}; + +/** + * @brief Log server connections + * + * @param criteria Slave selection criteria + * @param rses Router client session + */ +static void log_server_connections(select_criteria_t criteria, + ROUTER_CLIENT_SES* rses) +{ + MXS_INFO("Servers and %s connection counts:", + criteria == LEAST_GLOBAL_CONNECTIONS ? "all MaxScale" : "router"); + + for (SRWBackendList::iterator it = rses->backends.begin(); + it != rses->backends.end(); it++) + { + SERVER_REF* b = (*it)->backend(); + + switch (criteria) + { + case LEAST_GLOBAL_CONNECTIONS: + MXS_INFO("MaxScale connections : %d in \t[%s]:%d %s", + b->server->stats.n_current, b->server->name, + b->server->port, STRSRVSTATUS(b->server)); + break; + + case LEAST_ROUTER_CONNECTIONS: + MXS_INFO("RWSplit connections : %d in \t[%s]:%d %s", + b->connections, b->server->name, + b->server->port, STRSRVSTATUS(b->server)); + break; + + case LEAST_CURRENT_OPERATIONS: + MXS_INFO("current operations : %d in \t[%s]:%d %s", + b->server->stats.n_current_ops, + b->server->name, b->server->port, + STRSRVSTATUS(b->server)); + break; + + case LEAST_BEHIND_MASTER: + MXS_INFO("replication lag : %d in \t[%s]:%d %s", + b->server->rlag, b->server->name, + b->server->port, STRSRVSTATUS(b->server)); + default: + ss_dassert(!true); + break; + } + } +} +/** + * @brief Find the master server that is at the root of the replication tree + * + * @param rses Router client session + * + * @return The root master reference or NULL if no master is found + */ +static SERVER_REF* get_root_master(ROUTER_CLIENT_SES* rses) +{ + SERVER_REF *master_host = NULL; + + for (SRWBackendList::iterator it = rses->backends.begin(); + it != rses->backends.end(); it++) + { + SERVER_REF* b = (*it)->backend(); + + if (SERVER_IS_MASTER(b->server)) + { + if (master_host == NULL || + (b->server->depth < master_host->server->depth)) + { + master_host = b; + } + } + } + + return master_host; +} + +/** + * @brief Search suitable backend servers from those of router instance + * + * It is assumed that there is only one master among servers of a router instance. + * As a result, the first master found is chosen. There will possibly be more + * backend references than connected backends because only those in correct state + * are connected to. + * + * @param router_nservers Number of backend servers + * @param max_nslaves Upper limit for the number of slaves + * @param select_criteria Slave selection criteria + * @param session Client session + * @param router Router instance + * @param rses Router client session + * @param type Connection type, ALL for all types, SLAVE for slaves only + * + * @return True if at least one master and one slave was found + */ +bool select_connect_backend_servers(int router_nservers, + int max_nslaves, + select_criteria_t select_criteria, + MXS_SESSION *session, + ROUTER_INSTANCE *router, + ROUTER_CLIENT_SES *rses, + connection_type type) +{ + /* get the root Master */ + SERVER_REF *master_backend = get_root_master(rses); + SERVER *master_host = master_backend ? master_backend->server : NULL; + + if (router->rwsplit_config.master_failure_mode == RW_FAIL_INSTANTLY && + (master_host == NULL || SERVER_IS_DOWN(master_host))) + { + MXS_ERROR("Couldn't find suitable Master from %d candidates.", router_nservers); + return false; + } + + /** + * New session: + * + * Connect to both master and slaves + * + * Existing session: + * + * Master is already connected or we don't have a master. The function was + * called because new slaves must be selected to replace failed ones. + */ + bool master_connected = type == SLAVE || rses->current_master; + + /** Check slave selection criteria and set compare function */ + int (*cmpfun)(const SRWBackend&, const SRWBackend&) = criteria_cmpfun[select_criteria]; + ss_dassert(cmpfun); + + if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) + { + log_server_connections(select_criteria, rses); + } + + int slaves_found = 0; + int slaves_connected = 0; + const int min_nslaves = 0; /*< not configurable at the time */ + bool succp = false; + + if (!master_connected) + { + /** Find a master server */ + for (SRWBackendList::iterator it = rses->backends.begin(); + it != rses->backends.end(); it++) + { + SRWBackend& backend = *it; + + if (backend->can_connect() && master_host && backend->server() == master_host) + { + if (backend->connect(session)) + { + rses->current_master = backend; + } + } + } + } + + /** Calculate how many connections we already have */ + for (SRWBackendList::iterator it = rses->backends.begin(); + it != rses->backends.end(); it++) + { + SRWBackend& backend = *it; + + if (backend->can_connect() && valid_for_slave(backend->server(), master_host)) + { + slaves_found += 1; + + if (backend->in_use()) + { + slaves_connected += 1; + } + } + } + + ss_dassert(slaves_connected < max_nslaves || max_nslaves == 0); + + /** Connect to all possible slaves */ + for (SRWBackend backend(get_slave_candidate(rses, master_host, cmpfun)); + backend && slaves_connected < max_nslaves; + backend = get_slave_candidate(rses, master_host, cmpfun)) + { + if (backend->can_connect() && backend->connect(session)) + { + if (rses->sescmd_list.size()) + { + backend->append_session_command(rses->sescmd_list); + + if (backend->execute_session_command()) + { + rses->expected_responses++; + slaves_connected++; + } + } + else + { + slaves_connected++; + } + } + } + + if (slaves_connected >= min_nslaves && slaves_connected <= max_nslaves) + { + succp = true; + + if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) + { + if (slaves_connected < max_nslaves) + { + MXS_INFO("Couldn't connect to maximum number of " + "slaves. Connected successfully to %d slaves " + "of %d of them.", slaves_connected, slaves_found); + } + + for (SRWBackendList::iterator it = rses->backends.begin(); + it != rses->backends.end(); it++) + { + SRWBackend& backend = *it; + if (backend->in_use()) + { + MXS_INFO("Selected %s in \t%s", STRSRVSTATUS(backend->server()), + backend->uri()); + } + } + } + } + else + { + MXS_ERROR("Couldn't establish required amount of slave connections for " + "router session. Would need between %d and %d slaves but only have %d.", + min_nslaves, max_nslaves, slaves_connected); + close_all_connections(rses); + } + + return succp; +} diff --git a/server/modules/routing/readwritesplit/rwsplit_session_cmd.c b/server/modules/routing/readwritesplit/rwsplit_session_cmd.c deleted file mode 100644 index 82ccbddf1..000000000 --- a/server/modules/routing/readwritesplit/rwsplit_session_cmd.c +++ /dev/null @@ -1,462 +0,0 @@ -/* - * 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.h" - -#include -#include -#include -#include -#include - -#include -#include "rwsplit_internal.h" - -/** - * @file rwsplit_session_cmd.c The functions that provide session command - * handling for the read write split router. - * - * @verbatim - * Revision History - * - * Date Who Description - * 08/08/2016 Martin Brampton Initial implementation - * - * @endverbatim - */ - -static bool sescmd_cursor_history_empty(sescmd_cursor_t *scur); -static void sescmd_cursor_reset(sescmd_cursor_t *scur); -static bool sescmd_cursor_next(sescmd_cursor_t *scur); -static rses_property_t *mysql_sescmd_get_property(mysql_sescmd_t *scmd); - -/* - * The following functions, all to do with the handling of session commands, - * are called from other modules of the read write split router: - */ - -/** - * Router session must be locked. - * Return session command pointer if succeed, NULL if failed. - */ -mysql_sescmd_t *rses_property_get_sescmd(rses_property_t *prop) -{ - mysql_sescmd_t *sescmd; - - if (prop == NULL) - { - MXS_ERROR("[%s] Error: NULL parameter.", __FUNCTION__); - return NULL; - } - - CHK_RSES_PROP(prop); - - sescmd = &prop->rses_prop_data.sescmd; - - if (sescmd != NULL) - { - CHK_MYSQL_SESCMD(sescmd); - } - return sescmd; -} - -/** - * Create session command property. - */ -mysql_sescmd_t *mysql_sescmd_init(rses_property_t *rses_prop, - GWBUF *sescmd_buf, - unsigned char packet_type, - ROUTER_CLIENT_SES *rses) -{ - mysql_sescmd_t *sescmd; - - CHK_RSES_PROP(rses_prop); - /** Can't call rses_property_get_sescmd with uninitialized sescmd */ - sescmd = &rses_prop->rses_prop_data.sescmd; - sescmd->my_sescmd_prop = rses_prop; /*< reference to owning property */ -#if defined(SS_DEBUG) - sescmd->my_sescmd_chk_top = CHK_NUM_MY_SESCMD; - sescmd->my_sescmd_chk_tail = CHK_NUM_MY_SESCMD; -#endif - /** Set session command buffer */ - sescmd->my_sescmd_buf = sescmd_buf; - sescmd->my_sescmd_packet_type = packet_type; - sescmd->position = atomic_add(&rses->pos_generator, 1); - - return sescmd; -} - -void mysql_sescmd_done(mysql_sescmd_t *sescmd) -{ - if (sescmd == NULL) - { - MXS_ERROR("[%s] Error: NULL parameter.", __FUNCTION__); - return; - } - CHK_RSES_PROP(sescmd->my_sescmd_prop); - gwbuf_free(sescmd->my_sescmd_buf); - memset(sescmd, 0, sizeof(mysql_sescmd_t)); -} - -/** - * All cases where backend message starts at least with one response to session - * command are handled here. - * Read session commands from property list. If command is already replied, - * discard packet. Else send reply to client. In both cases move cursor forward - * until all session command replies are handled. - * - * Cases that are expected to happen and which are handled: - * s = response not yet replied to client, S = already replied response, - * q = query - * 1. q+ for example : select * from mysql.user - * 2. s+ for example : set autocommit=1 - * 3. S+ - * 4. sq+ - * 5. Sq+ - * 6. Ss+ - * 7. Ss+q+ - * 8. S+q+ - * 9. s+q+ - */ -GWBUF *sescmd_cursor_process_replies(GWBUF *replybuf, - backend_ref_t *bref, - bool *reconnect) -{ - sescmd_cursor_t *scur = &bref->bref_sescmd_cur; - mysql_sescmd_t *scmd = sescmd_cursor_get_command(scur); - ROUTER_CLIENT_SES *ses = (*scur->scmd_cur_ptr_property)->rses_prop_rsession; - CHK_GWBUF(replybuf); - - /** - * Walk through packets in the message and the list of session - * commands. - */ - while (scmd != NULL && replybuf != NULL) - { - bref->reply_cmd = *((unsigned char *)replybuf->start + 4); - scur->position = scmd->position; - /** Faster backend has already responded to client : discard */ - if (scmd->my_sescmd_is_replied) - { - bool last_packet = false; - - CHK_GWBUF(replybuf); - - while (!last_packet) - { - int buflen; - - buflen = GWBUF_LENGTH(replybuf); - last_packet = GWBUF_IS_TYPE_RESPONSE_END(replybuf); - /** discard packet */ - replybuf = gwbuf_consume(replybuf, buflen); - } - /** Set response status received */ - bref_clear_state(bref, BREF_WAITING_RESULT); - - if (bref->reply_cmd != scmd->reply_cmd && BREF_IS_IN_USE(bref)) - { - MXS_ERROR("Slave server '%s': response differs from master's response. " - "Closing connection due to inconsistent session state.", - bref->ref->server->unique_name); - close_failed_bref(bref, true); - - RW_CHK_DCB(bref, bref->bref_dcb); - dcb_close(bref->bref_dcb); - RW_CLOSE_BREF(bref); - *reconnect = true; - gwbuf_free(replybuf); - replybuf = NULL; - } - } - /** This is a response from the master and it is the "right" one. - * A slave server's response will be compared to this and if - * their response differs from the master server's response, they - * are dropped from the valid list of backend servers. - * Response is in the buffer and it will be sent to client. - * - * If we have no master server, the first slave's response is considered - * the "right" one. */ - else if (ses->rses_master_ref == NULL || - !BREF_IS_IN_USE(ses->rses_master_ref) || - ses->rses_master_ref->bref_dcb == bref->bref_dcb) - { - /** Mark the rest session commands as replied */ - scmd->my_sescmd_is_replied = true; - scmd->reply_cmd = *((unsigned char *)replybuf->start + 4); - - MXS_INFO("Server '%s' responded to a session command, sending the response " - "to the client.", bref->ref->server->unique_name); - - for (int i = 0; i < ses->rses_nbackends; i++) - { - if (!BREF_IS_WAITING_RESULT(&ses->rses_backend_ref[i])) - { - /** This backend has already received a response */ - if (ses->rses_backend_ref[i].reply_cmd != scmd->reply_cmd && - !BREF_IS_CLOSED(&ses->rses_backend_ref[i]) && - BREF_IS_IN_USE(&ses->rses_backend_ref[i])) - { - close_failed_bref(&ses->rses_backend_ref[i], true); - - if (ses->rses_backend_ref[i].bref_dcb) - { - RW_CHK_DCB(&ses->rses_backend_ref[i], ses->rses_backend_ref[i].bref_dcb); - dcb_close(ses->rses_backend_ref[i].bref_dcb); - RW_CLOSE_BREF(&ses->rses_backend_ref[i]); - } - *reconnect = true; - MXS_INFO("Disabling slave [%s]:%d, result differs from " - "master's result. Master: %d Slave: %d", - ses->rses_backend_ref[i].ref->server->name, - ses->rses_backend_ref[i].ref->server->port, - bref->reply_cmd, ses->rses_backend_ref[i].reply_cmd); - } - } - } - - } - else - { - MXS_INFO("Slave '%s' responded before master to a session command. Result: %d", - bref->ref->server->unique_name, - (int)bref->reply_cmd); - if (bref->reply_cmd == 0xff) - { - SERVER *serv = bref->ref->server; - MXS_ERROR("Slave '%s' (%s:%u) failed to execute session command.", - serv->unique_name, serv->name, serv->port); - } - - gwbuf_free(replybuf); - replybuf = NULL; - } - - if (sescmd_cursor_next(scur)) - { - scmd = sescmd_cursor_get_command(scur); - } - else - { - scmd = NULL; - /** All session commands are replied */ - scur->scmd_cur_active = false; - } - } - ss_dassert(replybuf == NULL || *scur->scmd_cur_ptr_property == NULL); - - return replybuf; -} - -/** - * Get the address of current session command. - * - * Router session must be locked */ -mysql_sescmd_t *sescmd_cursor_get_command(sescmd_cursor_t *scur) -{ - mysql_sescmd_t *scmd; - - scur->scmd_cur_cmd = rses_property_get_sescmd(*scur->scmd_cur_ptr_property); - - CHK_MYSQL_SESCMD(scur->scmd_cur_cmd); - - scmd = scur->scmd_cur_cmd; - - return scmd; -} - -/** router must be locked */ -bool sescmd_cursor_is_active(sescmd_cursor_t *sescmd_cursor) -{ - bool succp; - - if (sescmd_cursor == NULL) - { - MXS_ERROR("[%s] Error: NULL parameter.", __FUNCTION__); - return false; - } - - succp = sescmd_cursor->scmd_cur_active; - return succp; -} - -/** router must be locked */ -void sescmd_cursor_set_active(sescmd_cursor_t *sescmd_cursor, - bool value) -{ - /** avoid calling unnecessarily */ - ss_dassert(sescmd_cursor->scmd_cur_active != value); - sescmd_cursor->scmd_cur_active = value; -} - -/** - * Clone session command's command buffer. - * Router session must be locked - */ -GWBUF *sescmd_cursor_clone_querybuf(sescmd_cursor_t *scur) -{ - GWBUF *buf; - if (scur == NULL) - { - MXS_ERROR("[%s] Error: NULL parameter.", __FUNCTION__); - return NULL; - } - ss_dassert(scur->scmd_cur_cmd != NULL); - - buf = gwbuf_clone(scur->scmd_cur_cmd->my_sescmd_buf); - - CHK_GWBUF(buf); - return buf; -} - -bool execute_sescmd_history(backend_ref_t *bref) -{ - ss_dassert(bref); - CHK_BACKEND_REF(bref); - bool succp = true; - - sescmd_cursor_t *scur = &bref->bref_sescmd_cur; - CHK_SESCMD_CUR(scur); - - if (!sescmd_cursor_history_empty(scur)) - { - sescmd_cursor_reset(scur); - succp = execute_sescmd_in_backend(bref); - } - - return succp; -} - -static bool sescmd_cursor_history_empty(sescmd_cursor_t *scur) -{ - bool succp; - - if (scur == NULL) - { - MXS_ERROR("[%s] Error: NULL parameter.", __FUNCTION__); - return true; - } - CHK_SESCMD_CUR(scur); - - if (scur->scmd_cur_rses->rses_properties[RSES_PROP_TYPE_SESCMD] == NULL) - { - succp = true; - } - else - { - succp = false; - } - - return succp; -} - -/* - * End of functions called from other modules of the read write split router; - * start of functions that are internal to this module. - */ - -static void sescmd_cursor_reset(sescmd_cursor_t *scur) -{ - ROUTER_CLIENT_SES *rses; - if (scur == NULL) - { - MXS_ERROR("[%s] Error: NULL parameter.", __FUNCTION__); - return; - } - CHK_SESCMD_CUR(scur); - CHK_CLIENT_RSES(scur->scmd_cur_rses); - rses = scur->scmd_cur_rses; - - scur->scmd_cur_ptr_property = &rses->rses_properties[RSES_PROP_TYPE_SESCMD]; - - CHK_RSES_PROP((*scur->scmd_cur_ptr_property)); - scur->scmd_cur_active = false; - scur->scmd_cur_cmd = &(*scur->scmd_cur_ptr_property)->rses_prop_data.sescmd; -} - -/** - * Moves cursor to next property and copied address of its sescmd to cursor. - * Current propery must be non-null. - * If current property is the last on the list, *scur->scmd_ptr_property == NULL - * - * Router session must be locked - */ -static bool sescmd_cursor_next(sescmd_cursor_t *scur) -{ - bool succp = false; - rses_property_t *prop_curr; - rses_property_t *prop_next; - - if (scur == NULL) - { - MXS_ERROR("[%s] Error: NULL parameter.", __FUNCTION__); - return false; - } - - ss_dassert(scur != NULL); - ss_dassert(*(scur->scmd_cur_ptr_property) != NULL); - - /** Illegal situation */ - if (scur == NULL || *scur->scmd_cur_ptr_property == NULL || - scur->scmd_cur_cmd == NULL) - { - /** Log error */ - goto return_succp; - } - prop_curr = *(scur->scmd_cur_ptr_property); - - CHK_MYSQL_SESCMD(scur->scmd_cur_cmd); - ss_dassert(prop_curr == mysql_sescmd_get_property(scur->scmd_cur_cmd)); - CHK_RSES_PROP(prop_curr); - - /** Copy address of pointer to next property */ - scur->scmd_cur_ptr_property = &(prop_curr->rses_prop_next); - prop_next = *scur->scmd_cur_ptr_property; - ss_dassert(prop_next == *(scur->scmd_cur_ptr_property)); - - /** If there is a next property move forward */ - if (prop_next != NULL) - { - CHK_RSES_PROP(prop_next); - CHK_RSES_PROP((*(scur->scmd_cur_ptr_property))); - - /** Get pointer to next property's sescmd */ - scur->scmd_cur_cmd = rses_property_get_sescmd(prop_next); - - ss_dassert(prop_next == scur->scmd_cur_cmd->my_sescmd_prop); - CHK_MYSQL_SESCMD(scur->scmd_cur_cmd); - CHK_RSES_PROP(scur->scmd_cur_cmd->my_sescmd_prop); - } - else - { - /** No more properties, can't proceed. */ - goto return_succp; - } - - if (scur->scmd_cur_cmd != NULL) - { - succp = true; - } - else - { - ss_dassert(false); /*< Log error, sescmd shouldn't be NULL */ - } -return_succp: - return succp; -} - -static rses_property_t *mysql_sescmd_get_property(mysql_sescmd_t *scmd) -{ - CHK_MYSQL_SESCMD(scmd); - return scmd->my_sescmd_prop; -} diff --git a/server/modules/routing/readwritesplit/rwsplit_session_cmd.cc b/server/modules/routing/readwritesplit/rwsplit_session_cmd.cc new file mode 100644 index 000000000..9b7a79910 --- /dev/null +++ b/server/modules/routing/readwritesplit/rwsplit_session_cmd.cc @@ -0,0 +1,71 @@ +/* + * 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 +#include +#include +#include +#include + +#include + +/** + * Functions for session command handling + */ + +void process_sescmd_response(ROUTER_CLIENT_SES* rses, SRWBackend& backend, + GWBUF** ppPacket, bool* pReconnect) +{ + if (backend->session_command_count()) + { + /** We are executing a session command */ + if (GWBUF_IS_TYPE_SESCMD_RESPONSE((*ppPacket))) + { + uint8_t cmd; + gwbuf_copy_data(*ppPacket, MYSQL_HEADER_LEN, 1, &cmd); + uint64_t id = backend->complete_session_command(); + + if (rses->recv_sescmd < rses->sent_sescmd && + id == rses->recv_sescmd + 1 && + (!rses->current_master || // Session doesn't have a master + rses->current_master == backend)) // This is the master's response + { + /** First reply to this session command, route it to the client */ + ++rses->recv_sescmd; + + /** Store the master's response so that the slave responses can + * be compared to it */ + rses->sescmd_responses[id] = cmd; + } + else + { + /** The reply to this session command has already been sent to + * the client, discard it */ + gwbuf_free(*ppPacket); + *ppPacket = NULL; + + if (rses->sescmd_responses[id] != cmd) + { + MXS_ERROR("Slave server '%s': response differs from master's response. " + "Closing connection due to inconsistent session state.", + backend->name()); + backend->close(mxs::Backend::CLOSE_FATAL); + *pReconnect = true; + } + } + } + } +} diff --git a/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c b/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c deleted file mode 100644 index c9b9f0a44..000000000 --- a/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.c +++ /dev/null @@ -1,422 +0,0 @@ -/* - * 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.h" - -#include -#include -#include -#include -#include -#include - -/* Note that modutil contains much MySQL specific code */ -#include - -#include -#include "rwsplit_internal.h" -/** - * @file rwsplit_tmp_table.c The functions that carry out checks on - * statements to see if they involve various operations involving temporary - * tables or multi-statement queries. - * - * @verbatim - * Revision History - * - * Date Who Description - * 08/08/2016 Martin Brampton Initial implementation - * - * @endverbatim - */ - -/* - * The following are to do with checking whether the statement refers to - * temporary tables, or is a multi-statement request. Maybe they belong - * somewhere else, outside this router. Perhaps in the query classifier? - */ - -/** - * @brief Check for dropping of temporary tables - * - * Check if the query is a DROP TABLE... query and - * if it targets a temporary table, remove it from the hashtable. - * @param router_cli_ses Router client session - * @param querybuf GWBUF containing the query - * @param type The type of the query resolved so far - */ -void check_drop_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, GWBUF *querybuf, - mysql_server_cmd_t packet_type) -{ - if (packet_type != MYSQL_COM_QUERY && packet_type != MYSQL_COM_DROP_DB) - { - return; - } - - int tsize = 0, klen = 0, i; - char **tbl = NULL; - char *hkey, *dbname; - MYSQL_session *my_data; - rses_property_t *rses_prop_tmp; - MYSQL_session *data = (MYSQL_session *)router_cli_ses->client_dcb->data; - - rses_prop_tmp = router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES]; - dbname = (char *)data->db; - - if (qc_is_drop_table_query(querybuf)) - { - tbl = qc_get_table_names(querybuf, &tsize, false); - if (tbl != NULL) - { - for (i = 0; i < tsize; i++) - { - /* Not clear why the next six lines are outside the if block */ - klen = strlen(dbname) + strlen(tbl[i]) + 2; - hkey = MXS_CALLOC(klen, sizeof(char)); - MXS_ABORT_IF_NULL(hkey); - strcpy(hkey, dbname); - strcat(hkey, "."); - strcat(hkey, tbl[i]); - - if (rses_prop_tmp && rses_prop_tmp->rses_prop_data.temp_tables) - { - if (hashtable_delete(rses_prop_tmp->rses_prop_data.temp_tables, - (void *)hkey)) - { - MXS_INFO("Temporary table dropped: %s", hkey); - } - } - MXS_FREE(tbl[i]); - MXS_FREE(hkey); - } - - MXS_FREE(tbl); - } - } -} - -/** - * Check if the query targets a temporary table. - * @param router_cli_ses Router client session - * @param querybuf GWBUF containing the query - * @param type The type of the query resolved so far - * @return The type of the query - */ -bool is_read_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, - GWBUF *querybuf, - qc_query_type_t qtype) -{ - - bool target_tmp_table = false; - int tsize = 0, klen = 0, i; - char **tbl = NULL; - char *dbname; - char hkey[MYSQL_DATABASE_MAXLEN + MYSQL_TABLE_MAXLEN + 2]; - MYSQL_session *data; - bool rval = false; - rses_property_t *rses_prop_tmp; - - if (router_cli_ses == NULL || querybuf == NULL) - { - MXS_ERROR("[%s] Error: NULL parameters passed: %p %p", __FUNCTION__, - router_cli_ses, querybuf); - return false; - } - - if (router_cli_ses->client_dcb == NULL) - { - MXS_ERROR("[%s] Error: Client DCB is NULL.", __FUNCTION__); - return false; - } - - rses_prop_tmp = router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES]; - data = (MYSQL_session *)router_cli_ses->client_dcb->data; - - if (data == NULL) - { - MXS_ERROR("[%s] Error: User data in client DBC is NULL.", __FUNCTION__); - return false; - } - - dbname = (char *)data->db; - - if (qc_query_is_type(qtype, QUERY_TYPE_READ) || - qc_query_is_type(qtype, QUERY_TYPE_LOCAL_READ) || - 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)) - { - tbl = qc_get_table_names(querybuf, &tsize, false); - - if (tbl != NULL && tsize > 0) - { - /** Query targets at least one table */ - for (i = 0; i < tsize && !target_tmp_table && tbl[i]; i++) - { - sprintf(hkey, "%s.%s", dbname, tbl[i]); - if (rses_prop_tmp && rses_prop_tmp->rses_prop_data.temp_tables) - { - if (hashtable_fetch(rses_prop_tmp->rses_prop_data.temp_tables, hkey)) - { - /**Query target is a temporary table*/ - rval = true; - MXS_INFO("Query targets a temporary table: %s", hkey); - break; - } - } - } - } - } - - if (tbl != NULL) - { - for (i = 0; i < tsize; i++) - { - MXS_FREE(tbl[i]); - } - MXS_FREE(tbl); - } - - return rval; -} - -/** - * If query is of type QUERY_TYPE_CREATE_TMP_TABLE then find out - * the database and table name, create a hashvalue and - * add it to the router client session's property. If property - * doesn't exist then create it first. - * @param router_cli_ses Router client session - * @param querybuf GWBUF containing the query - * @param type The type of the query resolved so far - */ -void check_create_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, - GWBUF *querybuf, qc_query_type_t type) -{ - if (!qc_query_is_type(type, QUERY_TYPE_CREATE_TMP_TABLE)) - { - return; - } - - int klen = 0; - char *hkey, *dbname; - MYSQL_session *data; - rses_property_t *rses_prop_tmp; - HASHTABLE *h; - - if (router_cli_ses == NULL || querybuf == NULL) - { - MXS_ERROR("[%s] Error: NULL parameters passed: %p %p", __FUNCTION__, - router_cli_ses, querybuf); - return; - } - - if (router_cli_ses->client_dcb == NULL) - { - MXS_ERROR("[%s] Error: Client DCB is NULL.", __FUNCTION__); - return; - } - - router_cli_ses->have_tmp_tables = true; - rses_prop_tmp = router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES]; - data = (MYSQL_session *)router_cli_ses->client_dcb->data; - - if (data == NULL) - { - MXS_ERROR("[%s] Error: User data in master server DBC is NULL.", - __FUNCTION__); - return; - } - - dbname = (char *)data->db; - - bool is_temp = true; - char *tblname = NULL; - - tblname = qc_get_created_table_name(querybuf); - - if (tblname && strlen(tblname) > 0) - { - klen = strlen(dbname) + strlen(tblname) + 2; - hkey = MXS_CALLOC(klen, sizeof(char)); - MXS_ABORT_IF_NULL(hkey); - strcpy(hkey, dbname); - strcat(hkey, "."); - strcat(hkey, tblname); - } - else - { - hkey = NULL; - } - - if (rses_prop_tmp == NULL) - { - if ((rses_prop_tmp = (rses_property_t *)MXS_CALLOC(1, sizeof(rses_property_t)))) - { -#if defined(SS_DEBUG) - rses_prop_tmp->rses_prop_chk_top = CHK_NUM_ROUTER_PROPERTY; - rses_prop_tmp->rses_prop_chk_tail = CHK_NUM_ROUTER_PROPERTY; -#endif - rses_prop_tmp->rses_prop_rsession = router_cli_ses; - rses_prop_tmp->rses_prop_refcount = 1; - rses_prop_tmp->rses_prop_next = NULL; - rses_prop_tmp->rses_prop_type = RSES_PROP_TYPE_TMPTABLES; - router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES] = rses_prop_tmp; - } - } - if (rses_prop_tmp) - { - if (rses_prop_tmp->rses_prop_data.temp_tables == NULL) - { - h = hashtable_alloc(7, rwsplit_hashkeyfun, rwsplit_hashcmpfun); - hashtable_memory_fns(h, rwsplit_hstrdup, NULL, rwsplit_hfree, NULL); - if (h != NULL) - { - rses_prop_tmp->rses_prop_data.temp_tables = h; - } - else - { - MXS_ERROR("Failed to allocate a new hashtable."); - } - } - - if (hkey && rses_prop_tmp->rses_prop_data.temp_tables && - hashtable_add(rses_prop_tmp->rses_prop_data.temp_tables, (void *)hkey, - (void *)is_temp) == 0) /*< Conflict in hash table */ - { - MXS_INFO("Temporary table conflict in hashtable: %s", hkey); - } -#if defined(SS_DEBUG) - { - bool retkey = hashtable_fetch(rses_prop_tmp->rses_prop_data.temp_tables, hkey); - if (retkey) - { - MXS_INFO("Temporary table added: %s", hkey); - } - } -#endif - } - - MXS_FREE(hkey); - MXS_FREE(tblname); -} - -/** - * @brief Detect multi-statement queries - * - * It is possible that the session state is modified inside a multi-statement - * query which would leave any slave sessions in an inconsistent state. Due to - * this, for the duration of this session, all queries will be sent to the - * master - * if the current query contains a multi-statement query. - * @param rses Router client session - * @param buf Buffer containing the full query - * @return True if the query contains multiple statements - */ -bool check_for_multi_stmt(GWBUF *buf, void *protocol, mysql_server_cmd_t packet_type) -{ - MySQLProtocol *proto = (MySQLProtocol *)protocol; - bool rval = false; - - if (proto->client_capabilities & GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS && - packet_type == MYSQL_COM_QUERY) - { - char *ptr, *data = (char*)GWBUF_DATA(buf) + 5; - /** Payload size without command byte */ - int buflen = gw_mysql_get_byte3((uint8_t *)GWBUF_DATA(buf)) - 1; - - if ((ptr = strnchr_esc_mysql(data, ';', buflen))) - { - /** Skip stored procedures etc. */ - while (ptr && is_mysql_sp_end(ptr, buflen - (ptr - data))) - { - ptr = strnchr_esc_mysql(ptr + 1, ';', buflen - (ptr - data) - 1); - } - - if (ptr) - { - if (ptr < data + buflen && - !is_mysql_statement_end(ptr, buflen - (ptr - data))) - { - rval = true; - } - } - } - } - - return rval; -} - -/** - * @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 qc_query_type_t the query type; also the non_empty_packet bool is set - */ -qc_query_type_t -determine_query_type(GWBUF *querybuf, int packet_type, bool non_empty_packet) -{ - qc_query_type_t qtype = QUERY_TYPE_UNKNOWN; - - if (non_empty_packet) - { - mysql_server_cmd_t my_packet_type = (mysql_server_cmd_t)packet_type; - switch (my_packet_type) - { - case MYSQL_COM_QUIT: /*< 1 QUIT will close all sessions */ - case MYSQL_COM_INIT_DB: /*< 2 DDL must go to the master */ - case MYSQL_COM_REFRESH: /*< 7 - I guess this is session but not sure */ - case MYSQL_COM_DEBUG: /*< 0d all servers dump debug info to stdout */ - case MYSQL_COM_PING: /*< 0e all servers are pinged */ - case MYSQL_COM_CHANGE_USER: /*< 11 all servers change it accordingly */ - case MYSQL_COM_SET_OPTION: /*< 1b send options to all servers */ - qtype = QUERY_TYPE_SESSION_WRITE; - break; - - case MYSQL_COM_CREATE_DB: /**< 5 DDL must go to the master */ - case MYSQL_COM_DROP_DB: /**< 6 DDL must go to the master */ - case MYSQL_COM_STMT_CLOSE: /*< free prepared statement */ - case MYSQL_COM_STMT_SEND_LONG_DATA: /*< send data to column */ - case MYSQL_COM_STMT_RESET: /*< resets the data of a prepared statement */ - qtype = QUERY_TYPE_WRITE; - break; - - case MYSQL_COM_QUERY: - qtype = qc_get_type_mask(querybuf); - break; - - case MYSQL_COM_STMT_PREPARE: - qtype = qc_get_type_mask(querybuf); - qtype |= QUERY_TYPE_PREPARE_STMT; - break; - - case MYSQL_COM_STMT_EXECUTE: - /** Parsing is not needed for this type of packet */ - qtype = QUERY_TYPE_EXEC_STMT; - break; - - case MYSQL_COM_SHUTDOWN: /**< 8 where should shutdown be routed ? */ - case MYSQL_COM_STATISTICS: /**< 9 ? */ - case MYSQL_COM_PROCESS_INFO: /**< 0a ? */ - case MYSQL_COM_CONNECT: /**< 0b ? */ - case MYSQL_COM_PROCESS_KILL: /**< 0c ? */ - case MYSQL_COM_TIME: /**< 0f should this be run in gateway ? */ - case MYSQL_COM_DELAYED_INSERT: /**< 10 ? */ - case MYSQL_COM_DAEMON: /**< 1d ? */ - default: - break; - } /**< switch by packet type */ - } - return qtype; -} diff --git a/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.cc b/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.cc new file mode 100644 index 000000000..ddc8e9054 --- /dev/null +++ b/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.cc @@ -0,0 +1,265 @@ +/* + * 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 +#include +#include +#include +#include + +#include +#include +#include + +/** + * The functions that carry out checks on statements to see if they involve + * various operations involving temporary tables or multi-statement queries. + */ + +/* + * The following are to do with checking whether the statement refers to + * temporary tables, or is a multi-statement request. Maybe they belong + * somewhere else, outside this router. Perhaps in the query classifier? + */ + +/** + * @brief Check for dropping of temporary tables + * + * Check if the query is a DROP TABLE... query and + * if it targets a temporary table, remove it from the hashtable. + * @param router_cli_ses Router client session + * @param querybuf GWBUF containing the query + * @param type The type of the query resolved so far + */ +void check_drop_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, GWBUF *querybuf) +{ + if (qc_is_drop_table_query(querybuf)) + { + const QC_FIELD_INFO* info; + size_t n_infos; + qc_get_field_info(querybuf, &info, &n_infos); + + for (size_t i = 0; i < n_infos; i++) + { + const char* db = mxs_mysql_get_current_db(router_cli_ses->client_dcb->session); + std::string table = info[i].database ? info[i].database : db; + table += "."; + + if (info[i].table) + { + table += info[i].table; + } + + router_cli_ses->temp_tables.erase(table); + } + } +} + +/** + * Check if the query targets a temporary table. + * @param router_cli_ses Router client session + * @param querybuf GWBUF containing the query + * @param type The type of the query resolved so far + * @return The type of the query + */ +bool is_read_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, + GWBUF *querybuf, + uint32_t qtype) +{ + ss_dassert(router_cli_ses && querybuf && router_cli_ses->client_dcb); + bool rval = false; + + if (qtype & (QUERY_TYPE_READ | + QUERY_TYPE_LOCAL_READ | + QUERY_TYPE_USERVAR_READ | + QUERY_TYPE_SYSVAR_READ | + QUERY_TYPE_GSYSVAR_READ)) + { + const QC_FIELD_INFO* info; + size_t n_infos; + qc_get_field_info(querybuf, &info, &n_infos); + + for (size_t i = 0; i < n_infos; i++) + { + const char* db = mxs_mysql_get_current_db(router_cli_ses->client_dcb->session); + std::string table = info[i].database ? info[i].database : db; + table += "."; + + if (info[i].table) + { + table += info[i].table; + } + + if (router_cli_ses->temp_tables.find(table) != + router_cli_ses->temp_tables.end()) + { + rval = true; + MXS_INFO("Query targets a temporary table: %s", table.c_str()); + break; + } + } + } + + return rval; +} + +/** + * If query is of type QUERY_TYPE_CREATE_TMP_TABLE then find out + * the database and table name, create a hashvalue and + * add it to the router client session's property. If property + * doesn't exist then create it first. + * @param router_cli_ses Router client session + * @param querybuf GWBUF containing the query + * @param type The type of the query resolved so far + */ +void check_create_tmp_table(ROUTER_CLIENT_SES *router_cli_ses, + GWBUF *querybuf, uint32_t type) +{ + if (qc_query_is_type(type, QUERY_TYPE_CREATE_TMP_TABLE)) + { + ss_dassert(router_cli_ses && querybuf && router_cli_ses->client_dcb && + router_cli_ses->client_dcb->data); + + router_cli_ses->have_tmp_tables = true; + char* tblname = qc_get_created_table_name(querybuf); + std::string table; + + if (tblname && *tblname) + { + const char* db = mxs_mysql_get_current_db(router_cli_ses->client_dcb->session); + table += db; + table += "."; + table += tblname; + } + + /** Add the table to the set of temporary tables */ + router_cli_ses->temp_tables.insert(table); + + MXS_FREE(tblname); + } +} + +/** + * @brief Detect multi-statement queries + * + * It is possible that the session state is modified inside a multi-statement + * query which would leave any slave sessions in an inconsistent state. Due to + * this, for the duration of this session, all queries will be sent to the + * master + * if the current query contains a multi-statement query. + * @param rses Router client session + * @param buf Buffer containing the full query + * @return True if the query contains multiple statements + */ +bool check_for_multi_stmt(GWBUF *buf, void *protocol, uint8_t packet_type) +{ + MySQLProtocol *proto = (MySQLProtocol *)protocol; + bool rval = false; + + if (proto->client_capabilities & GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS && + packet_type == MYSQL_COM_QUERY) + { + char *ptr, *data = (char*)GWBUF_DATA(buf) + 5; + /** Payload size without command byte */ + int buflen = gw_mysql_get_byte3((uint8_t *)GWBUF_DATA(buf)) - 1; + + if ((ptr = strnchr_esc_mysql(data, ';', buflen))) + { + /** Skip stored procedures etc. */ + while (ptr && is_mysql_sp_end(ptr, buflen - (ptr - data))) + { + ptr = strnchr_esc_mysql(ptr + 1, ';', buflen - (ptr - data) - 1); + } + + if (ptr) + { + if (ptr < data + buflen && + !is_mysql_statement_end(ptr, buflen - (ptr - data))) + { + rval = true; + } + } + } + } + + return rval; +} + +/** + * @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 packet_type, bool non_empty_packet) +{ + uint32_t qtype = QUERY_TYPE_UNKNOWN; + + if (non_empty_packet) + { + uint8_t my_packet_type = (uint8_t)packet_type; + switch (my_packet_type) + { + case MYSQL_COM_QUIT: /*< 1 QUIT will close all sessions */ + case MYSQL_COM_INIT_DB: /*< 2 DDL must go to the master */ + case MYSQL_COM_REFRESH: /*< 7 - I guess this is session but not sure */ + case MYSQL_COM_DEBUG: /*< 0d all servers dump debug info to stdout */ + case MYSQL_COM_PING: /*< 0e all servers are pinged */ + case MYSQL_COM_CHANGE_USER: /*< 11 all servers change it accordingly */ + case MYSQL_COM_SET_OPTION: /*< 1b send options to all servers */ + qtype = QUERY_TYPE_SESSION_WRITE; + break; + + case MYSQL_COM_CREATE_DB: /**< 5 DDL must go to the master */ + case MYSQL_COM_DROP_DB: /**< 6 DDL must go to the master */ + case MYSQL_COM_STMT_CLOSE: /*< free prepared statement */ + case MYSQL_COM_STMT_SEND_LONG_DATA: /*< send data to column */ + case MYSQL_COM_STMT_RESET: /*< resets the data of a prepared statement */ + qtype = QUERY_TYPE_WRITE; + break; + + case MYSQL_COM_QUERY: + qtype = qc_get_type_mask(querybuf); + break; + + case MYSQL_COM_STMT_PREPARE: + qtype = qc_get_type_mask(querybuf); + qtype |= QUERY_TYPE_PREPARE_STMT; + break; + + case MYSQL_COM_STMT_EXECUTE: + /** Parsing is not needed for this type of packet */ + qtype = QUERY_TYPE_EXEC_STMT; + break; + + case MYSQL_COM_SHUTDOWN: /**< 8 where should shutdown be routed ? */ + case MYSQL_COM_STATISTICS: /**< 9 ? */ + case MYSQL_COM_PROCESS_INFO: /**< 0a ? */ + case MYSQL_COM_CONNECT: /**< 0b ? */ + case MYSQL_COM_PROCESS_KILL: /**< 0c ? */ + case MYSQL_COM_TIME: /**< 0f should this be run in gateway ? */ + case MYSQL_COM_DELAYED_INSERT: /**< 10 ? */ + case MYSQL_COM_DAEMON: /**< 1d ? */ + default: + break; + } /**< switch by packet type */ + } + return qtype; +} diff --git a/server/modules/routing/schemarouter/CMakeLists.txt b/server/modules/routing/schemarouter/CMakeLists.txt index ef81f9e57..c2d3dd5d6 100644 --- a/server/modules/routing/schemarouter/CMakeLists.txt +++ b/server/modules/routing/schemarouter/CMakeLists.txt @@ -1,5 +1,5 @@ -add_library(schemarouter SHARED schemarouter.cc schemarouterinstance.cc schemaroutersession.cc shard_map.cc session_command.cc) -target_link_libraries(schemarouter maxscale-common) +add_library(schemarouter SHARED schemarouter.cc schemarouterinstance.cc schemaroutersession.cc shard_map.cc) +target_link_libraries(schemarouter maxscale-common MySQLCommon) add_dependencies(schemarouter pcre2) set_target_properties(schemarouter PROPERTIES VERSION "1.0.0") install_module(schemarouter core) diff --git a/server/modules/routing/schemarouter/schemarouter.cc b/server/modules/routing/schemarouter/schemarouter.cc index dc61bdf68..899bb7750 100644 --- a/server/modules/routing/schemarouter/schemarouter.cc +++ b/server/modules/routing/schemarouter/schemarouter.cc @@ -13,218 +13,17 @@ #include "schemarouter.hh" -#include - -using namespace schemarouter; - -Backend::Backend(SERVER_REF *ref): - m_closed(false), - m_backend(ref), - m_dcb(NULL), - m_mapped(false), - m_num_result_wait(0), - m_state(0) +namespace schemarouter { -} -Backend::~Backend() -{ - ss_dassert(m_closed); - - if (!m_closed) - { - close(); - } -} - -void Backend::close() -{ - if (!m_closed) - { - m_closed = true; - - if (in_use()) - { - CHK_DCB(m_dcb); - - /** Clean operation counter in bref and in SERVER */ - while (is_waiting_result()) - { - clear_state(BREF_WAITING_RESULT); - } - clear_state(BREF_IN_USE); - set_state(BREF_CLOSED); - - dcb_close(m_dcb); - - /** decrease server current connection counters */ - atomic_add(&m_backend->connections, -1); - } - } - else - { - ss_dassert(false); - } -} - -bool Backend::execute_session_command() -{ - if (is_closed() || !session_command_count()) - { - return false; - } - - CHK_DCB(m_dcb); - - int rc = 0; - - SessionCommandList::iterator iter = m_session_commands.begin(); - GWBUF *buffer = iter->copy_buffer().release(); - - switch (iter->get_command()) - { - case MYSQL_COM_CHANGE_USER: - /** This makes it possible to handle replies correctly */ - gwbuf_set_type(buffer, GWBUF_TYPE_SESCMD); - rc = m_dcb->func.auth(m_dcb, NULL, m_dcb->session, buffer); - break; - - case MYSQL_COM_QUERY: - default: - /** - * Mark session command buffer, it triggers writing - * MySQL command to protocol - */ - gwbuf_set_type(buffer, GWBUF_TYPE_SESCMD); - rc = m_dcb->func.write(m_dcb, buffer); - break; - } - - return rc == 1; -} - -void Backend::add_session_command(GWBUF* buffer, uint64_t sequence) -{ - m_session_commands.push_back(SessionCommand(buffer, sequence)); -} - -uint64_t Backend::complete_session_command() -{ - uint64_t rval = m_session_commands.front().get_position(); - m_session_commands.pop_front(); - return rval; -} - -size_t Backend::session_command_count() const -{ - return m_session_commands.size(); -} - -void Backend::clear_state(enum bref_state state) -{ - if (state != BREF_WAITING_RESULT) - { - m_state &= ~state; - } - else - { - /** Decrease global operation count */ - ss_debug(int prev2 = )atomic_add(&m_backend->server->stats.n_current_ops, -1); - ss_dassert(prev2 > 0); - } -} - -void Backend::set_state(enum bref_state state) -{ - if (state != BREF_WAITING_RESULT) - { - m_state |= state; - } - else - { - /** Increase global operation count */ - ss_debug(int prev2 = )atomic_add(&m_backend->server->stats.n_current_ops, 1); - ss_dassert(prev2 >= 0); - } -} - -SERVER_REF* Backend::backend() const -{ - return m_backend; -} - -bool Backend::connect(MXS_SESSION* session) -{ - bool rval = false; - - if ((m_dcb = dcb_connect(m_backend->server, session, m_backend->server->protocol))) - { - m_state = BREF_IN_USE; - atomic_add(&m_backend->connections, 1); - rval = true; - } - - return rval; -} - -DCB* Backend::dcb() const -{ - return m_dcb; -} - -bool Backend::write(GWBUF* buffer) -{ - return m_dcb->func.write(m_dcb, buffer) != 0; -} - -void Backend::store_command(GWBUF* buffer) -{ - m_pending_cmd.reset(buffer); -} - -bool Backend::write_stored_command() -{ - bool rval = false; - - if (m_pending_cmd.length()) - { - rval = write(m_pending_cmd.release()); - - if (!rval) - { - MXS_ERROR("Routing of pending query failed."); - } - } - - return rval; -} - -bool Backend::in_use() const -{ - return m_state & BREF_IN_USE; -} - -bool Backend::is_waiting_result() const -{ - return m_num_result_wait > 0; -} - -bool Backend::is_query_active() const -{ - return m_state & BREF_QUERY_ACTIVE; -} - -bool Backend::is_closed() const -{ - return m_state & BREF_CLOSED; -} - -void Backend::set_mapped(bool value) +void SRBackend::set_mapped(bool value) { m_mapped = value; } -bool Backend::is_mapped() const +bool SRBackend::is_mapped() const { return m_mapped; } + +} \ No newline at end of file diff --git a/server/modules/routing/schemarouter/schemarouter.hh b/server/modules/routing/schemarouter/schemarouter.hh index b9ce7b66c..aa5ff84c9 100644 --- a/server/modules/routing/schemarouter/schemarouter.hh +++ b/server/modules/routing/schemarouter/schemarouter.hh @@ -29,27 +29,7 @@ #include #include #include - -#include "session_command.hh" - -using std::list; -using std::set; -using std::string; -using std::tr1::shared_ptr; - -using maxscale::Buffer; - -/** - * The state of the backend server reference - */ -enum bref_state -{ - BREF_IN_USE = 0x01, - BREF_WAITING_RESULT = 0x02, /**< for session commands only */ - BREF_QUERY_ACTIVE = 0x04, /**< for other queries */ - BREF_CLOSED = 0x08, - BREF_DB_MAPPED = 0x10 -}; +#include namespace schemarouter { @@ -65,7 +45,7 @@ struct Config bool debug; /**< Enable verbose debug messages to clients */ pcre2_code* ignore_regex; /**< Regular expression used to ignore databases */ pcre2_match_data* ignore_match_data; /**< Match data for @c ignore_regex */ - set ignored_dbs; /**< Set of ignored databases */ + std::set ignored_dbs; /**< Set of ignored databases */ Config(): refresh_min_interval(0.0), @@ -110,151 +90,23 @@ struct Stats }; /** - * Reference to BACKEND. + * Reference to a backend * * Owned by router client session. */ -class Backend +class SRBackend: public mxs::Backend { public: - /** - * @brief Create new Backend - * - * @param ref Server reference used by this backend - */ - Backend(SERVER_REF *ref); - ~Backend(); + SRBackend(SERVER_REF *ref): + mxs::Backend(ref), + m_mapped(false) + { + } - /** - * @brief Execute the next session command in the queue - * - * @return True if the command was executed successfully - */ - bool execute_session_command(); - - /** - * @brief Add a new session command to the tail of the command queue - * - * @param buffer Session command to add - * @param sequence Sequence identifier of this session command, returned when - * the session command is completed - */ - void add_session_command(GWBUF* buffer, uint64_t sequence); - - /** - * @brief Mark the current session command as successfully executed - * - * This should be called when the response to the command is received - * - * @return The sequence identifier for this session command - */ - uint64_t complete_session_command(); - - /** - * @brief Check if backend has session commands - * - * @return True if backend has session commands - */ - size_t session_command_count() const; - - /** - * @brief Clear state - * - * @param state State to clear - */ - void clear_state(enum bref_state state); - - /** - * @brief Set state - * - * @param state State to set - */ - void set_state(enum bref_state state); - - /** - * @brief Get pointer to server reference - * - * @return Pointer to server reference - */ - SERVER_REF* backend() const; - - /** - * @brief Create a new connection - * - * @param session The session to which the connection is linked - * - * @return True if connection was successfully created - */ - bool connect(MXS_SESSION* session); - - /** - * @brief Close the backend - * - * This will close all active connections created by the backend. - */ - void close(); - - /** - * @brief Get a pointer to the internal DCB - * - * @return Pointer to internal DCB - */ - DCB* dcb() const; - - /** - * @brief Write data to the backend server - * - * @param buffer Buffer containing the data to write - * - * @return True if data was written successfully - */ - bool write(GWBUF* buffer); - - /** - * @brief Store a command - * - * The command is stored and executed once the session can execute - * the next command. - * - * @param buffer Buffer to store - */ - void store_command(GWBUF* buffer); - - /** - * @brief Write the stored command to the backend server - * - * @return True if command was written successfully - */ - bool write_stored_command(); - - /** - * @brief Check if backend is in use - * - * @return True if backend is in use - */ - bool in_use() const; - - /** - * @brief Check if backend is waiting for a result - * - * @return True if backend is waiting for a result - */ - bool is_waiting_result() const; - - /** - * @brief Check if a query is active - * - * @return True if a query is active - */ - bool is_query_active() const; - - /** - * @brief Check if the backend is closed - * - * @return True if the backend is closed - */ - bool is_closed() const; + ~SRBackend() + { + } /** * @brief Set the mapping state of the backend @@ -271,18 +123,10 @@ public: bool is_mapped() const; private: - bool m_closed; /**< True if a connection has been opened and closed */ - SERVER_REF* m_backend; /**< Backend server */ - DCB* m_dcb; /**< Backend DCB */ - bool m_mapped; /**< Whether the backend has been mapped */ - int m_num_result_wait; /**< Number of not yet received results */ - Buffer m_pending_cmd; /**< Pending commands */ - int m_state; /**< State of the backend */ - SessionCommandList m_session_commands; /**< List of session commands that are - * to be executed on this backend server */ + bool m_mapped; /**< Whether the backend has been mapped */ }; -typedef shared_ptr SBackend; -typedef list BackendList; +typedef std::tr1::shared_ptr SSRBackend; +typedef std::list SSRBackendList; } diff --git a/server/modules/routing/schemarouter/schemarouterinstance.cc b/server/modules/routing/schemarouter/schemarouterinstance.cc index a488dccae..02cfd3ab2 100644 --- a/server/modules/routing/schemarouter/schemarouterinstance.cc +++ b/server/modules/routing/schemarouter/schemarouterinstance.cc @@ -34,6 +34,9 @@ using std::string; using std::map; +namespace schemarouter +{ + #define DEFAULT_REFRESH_INTERVAL "300" /** @@ -195,7 +198,7 @@ SchemaRouter* SchemaRouter::create(SERVICE* pService, char** pzOptions) * connections because all servers are supposed to be operational. It is, * however, possible that there are less available servers than expected. */ -bool connect_backend_servers(BackendList& backends, MXS_SESSION* session) +bool connect_backend_servers(SSRBackendList& backends, MXS_SESSION* session) { bool succp = false; int servers_found = 0; @@ -206,7 +209,7 @@ bool connect_backend_servers(BackendList& backends, MXS_SESSION* session) { MXS_INFO("Servers and connection counts:"); - for (BackendList::iterator it = backends.begin(); it != backends.end(); it++) + for (SSRBackendList::iterator it = backends.begin(); it != backends.end(); it++) { SERVER_REF* b = (*it)->backend(); @@ -222,7 +225,7 @@ bool connect_backend_servers(BackendList& backends, MXS_SESSION* session) * Scan server list and connect each of them. None should fail or session * can't be established. */ - for (BackendList::iterator it = backends.begin(); it != backends.end(); it++) + for (SSRBackendList::iterator it = backends.begin(); it != backends.end(); it++) { SERVER_REF* b = (*it)->backend(); @@ -262,7 +265,7 @@ bool connect_backend_servers(BackendList& backends, MXS_SESSION* session) if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) { - for (BackendList::iterator it = backends.begin(); it != backends.end(); it++) + for (SSRBackendList::iterator it = backends.begin(); it != backends.end(); it++) { SERVER_REF* b = (*it)->backend(); @@ -282,13 +285,13 @@ bool connect_backend_servers(BackendList& backends, MXS_SESSION* session) SchemaRouterSession* SchemaRouter::newSession(MXS_SESSION* pSession) { - BackendList backends; + SSRBackendList backends; for (SERVER_REF *ref = m_service->dbref; ref; ref = ref->next) { if (ref->active) { - backends.push_back(SBackend(new Backend(ref))); + backends.push_back(SSRBackend(new SRBackend(ref))); } } @@ -336,8 +339,8 @@ void SchemaRouter::diagnostics(DCB* dcb) json_t* SchemaRouter::diagnostics_json() const { double sescmd_pct = m_stats.n_sescmd != 0 ? - 100.0 * ((double)m_stats.n_sescmd / (double)m_stats.n_queries) : - 0.0; + 100.0 * ((double)m_stats.n_sescmd / (double)m_stats.n_queries) : + 0.0; json_t* rval = json_object(); json_object_set_new(rval, "queries", json_integer(m_stats.n_queries)); @@ -364,6 +367,8 @@ uint64_t SchemaRouter::getCapabilities() return RCAP_TYPE_NONE; } +} + MXS_BEGIN_DECLS /** @@ -384,7 +389,7 @@ MXS_MODULE* MXS_CREATE_MODULE() "A database sharding router for simple sharding", "V1.0.0", RCAP_TYPE_CONTIGUOUS_INPUT, - &SchemaRouter::s_object, + &schemarouter::SchemaRouter::s_object, NULL, /* Process init. */ NULL, /* Process finish. */ NULL, /* Thread init. */ diff --git a/server/modules/routing/schemarouter/schemarouterinstance.hh b/server/modules/routing/schemarouter/schemarouterinstance.hh index b3cb3ca8b..431b11474 100644 --- a/server/modules/routing/schemarouter/schemarouterinstance.hh +++ b/server/modules/routing/schemarouter/schemarouterinstance.hh @@ -22,9 +22,8 @@ #include "schemaroutersession.hh" -using std::string; -using std::set; -using namespace schemarouter; +namespace schemarouter +{ class SchemaRouterSession; @@ -54,3 +53,5 @@ private: SPINLOCK m_lock; /*< Lock for the instance data */ Stats m_stats; /*< Statistics for this router */ }; + +} \ No newline at end of file diff --git a/server/modules/routing/schemarouter/schemaroutersession.cc b/server/modules/routing/schemarouter/schemaroutersession.cc index e34415b6d..03abf5295 100644 --- a/server/modules/routing/schemarouter/schemaroutersession.cc +++ b/server/modules/routing/schemarouter/schemaroutersession.cc @@ -12,6 +12,8 @@ */ #include "schemarouter.hh" +#include "schemaroutersession.hh" +#include "schemarouterinstance.hh" #include @@ -19,10 +21,10 @@ #include #include -#include "schemaroutersession.hh" -#include "schemarouterinstance.hh" +namespace schemarouter +{ -bool connect_backend_servers(BackendList& backends, MXS_SESSION* session); +bool connect_backend_servers(SSRBackendList& backends, MXS_SESSION* session); enum route_target get_shard_route_target(uint32_t qtype); bool change_current_db(string& dest, Shard& shard, GWBUF* buf); @@ -30,7 +32,8 @@ bool extract_database(GWBUF* buf, char* str); bool detect_show_shards(GWBUF* query); void write_error_to_client(DCB* dcb, int errnum, const char* mysqlstate, const char* errmsg); -SchemaRouterSession::SchemaRouterSession(MXS_SESSION* session, SchemaRouter* router, BackendList& backends): +SchemaRouterSession::SchemaRouterSession(MXS_SESSION* session, SchemaRouter* router, + SSRBackendList& backends): mxs::RouterSession(session), m_closed(false), m_client(session->client_dcb), @@ -46,28 +49,23 @@ SchemaRouterSession::SchemaRouterSession(MXS_SESSION* session, SchemaRouter* rou { char db[MYSQL_DATABASE_MAXLEN + 1] = ""; MySQLProtocol* protocol = (MySQLProtocol*)session->client_dcb->protocol; - MYSQL_session* data = (MYSQL_session*)session->client_dcb->data; bool using_db = false; bool have_db = false; + const char* current_db = mxs_mysql_get_current_db(session); /* To enable connecting directly to a sharded database we first need * to disable it for the client DCB's protocol so that we can connect to them*/ if (protocol->client_capabilities & GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB && - (have_db = strnlen(data->db, MYSQL_DATABASE_MAXLEN) > 0)) + (have_db = *current_db)) { protocol->client_capabilities &= ~GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB; - strcpy(db, data->db); - *data->db = 0; + strcpy(db, current_db); + mxs_mysql_set_current_db(session, ""); using_db = true; MXS_INFO("Client logging in directly to a database '%s', " "postponing until databases have been mapped.", db); } - if (!have_db) - { - MXS_INFO("Client'%s' connecting with empty database.", data->user); - } - if (using_db) { m_state |= INIT_USE_DB; @@ -97,11 +95,15 @@ void SchemaRouterSession::close() { m_closed = true; - for (BackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) + for (SSRBackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) { + SSRBackend& bref = *it; /** The backends are closed here to trigger the shutdown of * the connected DCBs */ - (*it)->close(); + if (bref->in_use()) + { + bref->close(); + } } spinlock_acquire(&m_router->m_lock); @@ -230,7 +232,7 @@ SERVER* SchemaRouterSession::resolve_query_target(GWBUF* pPacket, if (TARGET_IS_ANY(route_target)) { - for (BackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) + for (SSRBackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) { SERVER *server = (*it)->backend()->server; if (SERVER_IS_RUNNING(server)) @@ -419,7 +421,7 @@ int32_t SchemaRouterSession::routeQuery(GWBUF* pPacket) get_shard_dcb(&target_dcb, target->unique_name)) { /** We know where to route this query */ - SBackend bref = get_bref_from_dcb(target_dcb); + SSRBackend bref = get_bref_from_dcb(target_dcb); if (op == QUERY_OP_LOAD) { @@ -436,23 +438,22 @@ int32_t SchemaRouterSession::routeQuery(GWBUF* pPacket) pPacket = NULL; ret = 1; } - else if ((ret = target_dcb->func.write(target_dcb, gwbuf_clone(pPacket))) == 1) + else if (bref->write(pPacket)) { /** Add one query response waiter to backend reference */ - bref->set_state(BREF_QUERY_ACTIVE); - bref->set_state(BREF_WAITING_RESULT); atomic_add(&m_router->m_stats.n_queries, 1); + ret = 1; } else { MXS_ERROR("Routing query failed."); + gwbuf_free(pPacket); } } - gwbuf_free(pPacket); return ret; } -void SchemaRouterSession::handle_mapping_reply(SBackend& bref, GWBUF** pPacket) +void SchemaRouterSession::handle_mapping_reply(SSRBackend& bref, GWBUF** pPacket) { int rc = inspect_mapping_states(bref, pPacket); @@ -486,7 +487,7 @@ void SchemaRouterSession::handle_mapping_reply(SBackend& bref, GWBUF** pPacket) } } -void SchemaRouterSession::process_sescmd_response(SBackend& bref, GWBUF** ppPacket) +void SchemaRouterSession::process_sescmd_response(SSRBackend& bref, GWBUF** ppPacket) { if (bref->session_command_count()) { @@ -508,23 +509,12 @@ void SchemaRouterSession::process_sescmd_response(SBackend& bref, GWBUF** ppPack *ppPacket = NULL; } } - - if (*ppPacket) - { - bref->clear_state(BREF_WAITING_RESULT); - } - } - else if (bref->is_query_active()) - { - bref->clear_state(BREF_QUERY_ACTIVE); - /** Set response status as replied */ - bref->clear_state(BREF_WAITING_RESULT); } } void SchemaRouterSession::clientReply(GWBUF* pPacket, DCB* pDcb) { - SBackend bref = get_bref_from_dcb(pDcb); + SSRBackend bref = get_bref_from_dcb(pDcb); if (m_closed || bref.get() == NULL) // The bref should always be valid { @@ -567,6 +557,10 @@ void SchemaRouterSession::clientReply(GWBUF* pPacket, DCB* pDcb) { process_sescmd_response(bref, &pPacket); + ss_dassert(bref->is_waiting_result()); + /** Set response status as replied */ + bref->ack_write(); + if (pPacket) { MXS_SESSION_ROUTE_REPLY(pDcb->session, pPacket); @@ -581,8 +575,6 @@ void SchemaRouterSession::clientReply(GWBUF* pPacket, DCB* pDcb) else if (bref->write_stored_command()) { atomic_add(&m_router->m_stats.n_queries, 1); - bref->set_state(BREF_QUERY_ACTIVE); - bref->set_state(BREF_WAITING_RESULT); } } @@ -596,7 +588,7 @@ void SchemaRouterSession::handleError(GWBUF* pMessage, { ss_dassert(pProblem->dcb_role == DCB_ROLE_BACKEND_HANDLER); CHK_DCB(pProblem); - SBackend bref = get_bref_from_dcb(pProblem); + SSRBackend bref = get_bref_from_dcb(pProblem); if (bref.get() == NULL) // Should never happen { @@ -728,13 +720,13 @@ bool SchemaRouterSession::route_session_write(GWBUF* querybuf, uint8_t command) /** Increment the session command count */ ++m_sent_sescmd; - for (BackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) + for (SSRBackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) { if ((*it)->in_use()) { GWBUF *buffer = gwbuf_clone(querybuf); - (*it)->add_session_command(buffer, m_sent_sescmd); + (*it)->append_session_command(buffer, m_sent_sescmd); if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) { @@ -746,19 +738,6 @@ bool SchemaRouterSession::route_session_write(GWBUF* querybuf, uint8_t command) if ((*it)->session_command_count() == 1) { - /** Only one command, execute it */ - switch (command) - { - /** These types of commands don't generate responses */ - case MYSQL_COM_QUIT: - case MYSQL_COM_STMT_CLOSE: - break; - - default: - (*it)->set_state(BREF_WAITING_RESULT); - break; - } - if ((*it)->execute_session_command()) { succp = true; @@ -783,6 +762,7 @@ bool SchemaRouterSession::route_session_write(GWBUF* querybuf, uint8_t command) } } + gwbuf_free(querybuf); return succp; } @@ -794,7 +774,7 @@ bool SchemaRouterSession::route_session_write(GWBUF* querybuf, uint8_t command) */ bool SchemaRouterSession::have_servers() { - for (BackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) + for (SSRBackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) { if ((*it)->in_use() && !(*it)->is_closed()) { @@ -813,11 +793,11 @@ bool SchemaRouterSession::have_servers() * * @return backend reference pointer if succeed or NULL */ -SBackend SchemaRouterSession::get_bref_from_dcb(DCB* dcb) +SSRBackend SchemaRouterSession::get_bref_from_dcb(DCB* dcb) { CHK_DCB(dcb); - for (BackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) + for (SSRBackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) { if ((*it)->dcb() == dcb) { @@ -827,7 +807,7 @@ SBackend SchemaRouterSession::get_bref_from_dcb(DCB* dcb) // This should not happen ss_dassert(false); - return SBackend(reinterpret_cast(NULL)); + return SSRBackend(reinterpret_cast(NULL)); } /** @@ -1024,13 +1004,13 @@ void SchemaRouterSession::route_queued_query() * @param router_cli_ses Router client session * @return 1 if mapping is done, 0 if it is still ongoing and -1 on error */ -int SchemaRouterSession::inspect_mapping_states(SBackend& bref, +int SchemaRouterSession::inspect_mapping_states(SSRBackend& bref, GWBUF** wbuf) { bool mapped = true; GWBUF* writebuf = *wbuf; - for (BackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) + for (SSRBackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) { if (bref->dcb() == (*it)->dcb() && !(*it)->is_mapped()) { @@ -1248,6 +1228,8 @@ bool SchemaRouterSession::ignore_duplicate_database(const char* data) { rval = true; } + + pcre2_match_data_free(match_data); } return rval; @@ -1264,7 +1246,7 @@ bool SchemaRouterSession::ignore_duplicate_database(const char* data) * @return 1 if a complete response was received, 0 if a partial response was received * and -1 if a database was found on more than one server. */ -enum showdb_response SchemaRouterSession::parse_mapping_response(SBackend& bref, GWBUF** buffer) +enum showdb_response SchemaRouterSession::parse_mapping_response(SSRBackend& bref, GWBUF** buffer) { unsigned char* ptr; SERVER* target = bref->backend()->server; @@ -1383,7 +1365,7 @@ enum showdb_response SchemaRouterSession::parse_mapping_response(SBackend& bref, void SchemaRouterSession::query_databases() { - for (BackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) + for (SSRBackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) { (*it)->set_mapped(false); } @@ -1394,7 +1376,7 @@ void SchemaRouterSession::query_databases() GWBUF *buffer = modutil_create_query("SHOW DATABASES"); gwbuf_set_type(buffer, GWBUF_TYPE_COLLECT_RESULT); - for (BackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) + for (SSRBackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) { if ((*it)->in_use() && !(*it)->is_closed() & SERVER_IS_RUNNING((*it)->backend()->server)) @@ -1502,7 +1484,7 @@ SERVER* SchemaRouterSession::get_shard_target(GWBUF* buffer, uint32_t qtype) } else if (buffer->hint && buffer->hint->type == HINT_ROUTE_TO_NAMED_SERVER) { - for (BackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) + for (SSRBackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) { char *srvnm = (*it)->backend()->server->unique_name; @@ -1551,7 +1533,7 @@ bool SchemaRouterSession::get_shard_dcb(DCB** p_dcb, char* name) bool succp = false; ss_dassert(p_dcb != NULL && *(p_dcb) == NULL); - for (BackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) + for (SSRBackendList::iterator it = m_backends.begin(); it != m_backends.end(); it++) { SERVER_REF* b = (*it)->backend(); /** @@ -1666,3 +1648,5 @@ bool SchemaRouterSession::send_databases() return rval; } + +} \ No newline at end of file diff --git a/server/modules/routing/schemarouter/schemaroutersession.hh b/server/modules/routing/schemarouter/schemaroutersession.hh index 43908e6a7..bbe277403 100644 --- a/server/modules/routing/schemarouter/schemaroutersession.hh +++ b/server/modules/routing/schemarouter/schemaroutersession.hh @@ -19,15 +19,12 @@ #include #include +#include #include "shard_map.hh" -#include "session_command.hh" - -using std::string; -using std::list; - -using namespace schemarouter; +namespace schemarouter +{ /** * Bitmask values for the router session's initialization. These values are used * to prevent responses from internal commands being forwarded to the client. @@ -80,7 +77,7 @@ class SchemaRouterSession: public mxs::RouterSession { public: - SchemaRouterSession(MXS_SESSION* session, SchemaRouter* router, BackendList& backends); + SchemaRouterSession(MXS_SESSION* session, SchemaRouter* router, SSRBackendList& backends); /** * The RouterSession instance will be deleted when a client session @@ -128,7 +125,7 @@ private: /** Helper functions */ SERVER* get_shard_target(GWBUF* buffer, uint32_t qtype); - SBackend get_bref_from_dcb(DCB* dcb); + SSRBackend get_bref_from_dcb(DCB* dcb); bool get_shard_dcb(DCB** dcb, char* name); bool have_servers(); bool handle_default_db(); @@ -136,7 +133,7 @@ private: /** Routing functions */ bool route_session_write(GWBUF* querybuf, uint8_t command); - void process_sescmd_response(SBackend& bref, GWBUF** ppPacket); + void process_sescmd_response(SSRBackend& bref, GWBUF** ppPacket); SERVER* resolve_query_target(GWBUF* pPacket, uint32_t type, uint8_t command, enum route_target& route_target); @@ -144,26 +141,27 @@ private: bool send_databases(); bool send_shards(); void query_databases(); - int inspect_mapping_states(SBackend& bref, GWBUF** wbuf); - enum showdb_response parse_mapping_response(SBackend& bref, GWBUF** buffer); + int inspect_mapping_states(SSRBackend& bref, GWBUF** wbuf); + enum showdb_response parse_mapping_response(SSRBackend& bref, GWBUF** buffer); void route_queued_query(); void synchronize_shards(); - void handle_mapping_reply(SBackend& bref, GWBUF** pPacket); + void handle_mapping_reply(SSRBackend& bref, GWBUF** pPacket); /** Member variables */ - bool m_closed; /**< True if session closed */ - DCB* m_client; /**< The client DCB */ - MYSQL_session* m_mysql_session; /**< Session client data (username, password, SHA1). */ - BackendList m_backends; /**< Backend references */ - Config* m_config; /**< Pointer to router config */ - SchemaRouter* m_router; /**< The router instance */ - Shard m_shard; /**< Database to server mapping */ - string m_connect_db; /**< Database the user was trying to connect to */ - string m_current_db; /**< Current active database */ - int m_state; /**< Initialization state bitmask */ - list m_queue; /**< Query that was received before the session was ready */ - Stats m_stats; /**< Statistics for this router */ - uint64_t m_sent_sescmd; /**< The latest session command being executed */ - uint64_t m_replied_sescmd; /**< The last session command reply that was sent to the client */ - SERVER* m_load_target; /**< Target for LOAD DATA LOCAL INFILE */ + bool m_closed; /**< True if session closed */ + DCB* m_client; /**< The client DCB */ + MYSQL_session* m_mysql_session; /**< Session client data (username, password, SHA1). */ + SSRBackendList m_backends; /**< Backend references */ + Config* m_config; /**< Pointer to router config */ + SchemaRouter* m_router; /**< The router instance */ + Shard m_shard; /**< Database to server mapping */ + std::string m_connect_db; /**< Database the user was trying to connect to */ + std::string m_current_db; /**< Current active database */ + int m_state; /**< Initialization state bitmask */ + std::list m_queue; /**< Query that was received before the session was ready */ + Stats m_stats; /**< Statistics for this router */ + uint64_t m_sent_sescmd; /**< The latest session command being executed */ + uint64_t m_replied_sescmd; /**< The last session command reply that was sent to the client */ + SERVER* m_load_target; /**< Target for LOAD DATA LOCAL INFILE */ }; +} \ No newline at end of file