diff --git a/include/maxscale/query_classifier.h b/include/maxscale/query_classifier.h index 9dfab54ad..38556a4a8 100644 --- a/include/maxscale/query_classifier.h +++ b/include/maxscale/query_classifier.h @@ -645,6 +645,30 @@ char** qc_get_table_names(GWBUF* stmt, int* size, bool fullnames); */ uint32_t qc_get_type_mask(GWBUF* stmt); +/** + * Returns the type bitmask of transaction related statements. + * + * If the statement starts a transaction, ends a transaction or + * changes the autocommit state, the returned bitmap will be a + * combination of: + * + * QUERY_TYPE_BEGIN_TRX + * QUERY_TYPE_COMMIT + * QUERY_TYPE_ROLLBACK + * QUERY_TYPE_ENABLE_AUTOCOMMIT + * QUERY_TYPE_DISABLE_AUTOCOMMIT + * QUERY_TYPE_READ (explicitly read only transaction) + * QUERY_TYPE_WRITE (explicitly read write transaction) + * + * Otherwise the result will be 0. + * + * @param stmt A COM_QUERY or COM_STMT_PREPARE packet. + * + * @return The relevant type bits if the statement is transaction + * related, otherwise 0. + */ +uint32_t qc_get_trx_type_mask(GWBUF* stmt); + /** * Returns whether the statement is a DROP TABLE statement. * diff --git a/server/core/maxscale/query_classifier.h b/server/core/maxscale/query_classifier.h new file mode 100644 index 000000000..5adff4d31 --- /dev/null +++ b/server/core/maxscale/query_classifier.h @@ -0,0 +1,40 @@ +#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: 2019-07-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 + +MXS_BEGIN_DECLS + +typedef enum qc_trx_parse_using +{ + QC_TRX_PARSE_USING_QC, /**< Use the query classifier. */ + QC_TRX_PARSE_USING_REGEX, /**< Use regexp mathing. */ + QC_TRX_PARSE_USING_PARSER, /**< Use custom parser. */ +} qc_trx_parse_using_t; + +/** + * Returns the type bitmask of transaction related statements. + * + * @param stmt A COM_QUERY or COM_STMT_PREPARE packet. + * @param use What method should be used. + * + * @return The relevant type bits if the statement is transaction + * related, otherwise 0. + * + * @see qc_get_trx_type_mask + */ +uint32_t qc_get_trx_type_mask_using(GWBUF* stmt, qc_trx_parse_using_t use); + +MXS_END_DECLS diff --git a/server/core/query_classifier.c b/server/core/query_classifier.c index 631ee2b5b..ad10c665c 100644 --- a/server/core/query_classifier.c +++ b/server/core/query_classifier.c @@ -11,10 +11,12 @@ * Public License. */ -#include +#include "maxscale/query_classifier.h" #include #include #include +#include +#include #include #include "../core/maxscale/modules.h" @@ -34,10 +36,166 @@ struct type_name_info size_t name_len; }; -static const char default_qc_name[] = "qc_sqlite"; +static const char DEFAULT_QC_NAME[] = "qc_sqlite"; +static const char QC_TRX_PARSE_USING[] = "QC_TRX_PARSE_USING"; static QUERY_CLASSIFIER* classifier; +typedef struct qc_trx_regex +{ + const char* match; + uint32_t type_mask; + pcre2_code* code; +} QC_TRX_REGEX; + +static QC_TRX_REGEX qc_trx_regexes[] = +{ + { + "^\\s*BEGIN(\\s+WORK)?\\s*;?\\s*$", + QUERY_TYPE_BEGIN_TRX + }, + { + "^\\s*COMMIT(\\s+WORK)?\\s*;?\\s*$", + QUERY_TYPE_COMMIT, + }, + { + "^\\s*ROLLBACK(\\s+WORK)?\\s*;?\\s*$", + QUERY_TYPE_ROLLBACK + }, + { + "^\\s*START\\s+TRANSACTION\\s+READ\\s+ONLY\\s*;?\\s*$", + QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_READ + }, + { + "^\\s*START\\s+TRANSACTION\\s+READ\\s+WRITE\\s*;?\\s*$", + QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_WRITE + }, + { + "^\\s*START\\s+TRANSACTION(\\s*;?\\s*|(\\s+.*))$", + QUERY_TYPE_BEGIN_TRX + }, + { + "^\\s*SET\\s+AUTOCOMMIT\\s*\\=\\s*(1|true)\\s*;?\\s*$", + QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT + }, + { + "^\\s*SET\\s+AUTOCOMMIT\\s*\\=\\s*(0|false)\\s*;?\\s*$", + QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT + } +}; + +#define N_TRX_REGEXES (sizeof(qc_trx_regexes) / sizeof(qc_trx_regexes[0])) + +static thread_local pcre2_match_data* qc_trx_thread_datas[N_TRX_REGEXES]; + +static qc_trx_parse_using_t qc_trx_parse_using = QC_TRX_PARSE_USING_QC; + +static bool compile_trx_regexes(); +static bool create_trx_thread_datas(); +static void free_trx_regexes(); +static void free_trx_thread_datas(); + +static bool compile_trx_regexes() +{ + QC_TRX_REGEX* regex = qc_trx_regexes; + QC_TRX_REGEX* end = regex + N_TRX_REGEXES; + + bool success = true; + + while (success && (regex < end)) + { + int errcode; + PCRE2_SIZE erroffset; + regex->code = pcre2_compile((PCRE2_SPTR)regex->match, PCRE2_ZERO_TERMINATED, PCRE2_CASELESS, + &errcode, &erroffset, NULL); + + if (!regex->code) + { + success = false; + PCRE2_UCHAR errbuf[512]; + pcre2_get_error_message(errcode, errbuf, sizeof(errbuf)); + + MXS_ERROR("Regex compilation failed at %lu for regex '%s': %s.", + erroffset, regex->match, errbuf); + } + + ++regex; + } + + if (!success) + { + free_trx_regexes(); + } + + return success; +} + +static void free_trx_regexes() +{ + QC_TRX_REGEX* begin = qc_trx_regexes; + QC_TRX_REGEX* regex = begin + N_TRX_REGEXES; + + while (regex > begin) + { + --regex; + + if (regex->code) + { + pcre2_code_free(regex->code); + regex->code = NULL; + } + } +} + +static bool create_trx_thread_datas() +{ + bool success = true; + + QC_TRX_REGEX* regex = qc_trx_regexes; + QC_TRX_REGEX* end = regex + N_TRX_REGEXES; + + pcre2_match_data** data = qc_trx_thread_datas; + + while (success && (regex < end)) + { + *data = pcre2_match_data_create_from_pattern(regex->code, NULL); + + if (!*data) + { + success = false; + MXS_ERROR("PCRE2 match data creation failed."); + } + + ++regex; + ++data; + } + + if (!success) + { + free_trx_thread_datas(); + } + + return success; +} + +static void free_trx_thread_datas() +{ + pcre2_match_data** begin = qc_trx_thread_datas; + pcre2_match_data** data = begin + N_TRX_REGEXES; + + while (data > begin) + { + --data; + + if (*data) + { + pcre2_match_data_free(*data); + *data = NULL; + } + } +} + + bool qc_setup(const char* plugin_name, const char* plugin_args) { QC_TRACE(); @@ -45,8 +203,8 @@ bool qc_setup(const char* plugin_name, const char* plugin_args) if (!plugin_name || (*plugin_name == 0)) { - MXS_NOTICE("No query classifier specified, using default '%s'.", default_qc_name); - plugin_name = default_qc_name; + MXS_NOTICE("No query classifier specified, using default '%s'.", DEFAULT_QC_NAME); + plugin_name = DEFAULT_QC_NAME; } int32_t rv = QC_RESULT_ERROR; @@ -59,7 +217,6 @@ bool qc_setup(const char* plugin_name, const char* plugin_args) if (rv != QC_RESULT_OK) { qc_unload(classifier); - classifier = NULL; } } @@ -71,11 +228,57 @@ bool qc_process_init(uint32_t kind) QC_TRACE(); ss_dassert(classifier); - bool rc = true; + const char* parse_using = getenv(QC_TRX_PARSE_USING); - if (kind & QC_INIT_PLUGIN) + if (parse_using) { - rc = classifier->qc_process_init() == 0; + if (strcmp(parse_using, "QC_TRX_PARSE_USING_QC") == 0) + { + qc_trx_parse_using = QC_TRX_PARSE_USING_QC; + MXS_NOTICE("Transaction detection using QC."); + } + else if (strcmp(parse_using, "QC_TRX_PARSE_USING_REGEX") == 0) + { + qc_trx_parse_using = QC_TRX_PARSE_USING_REGEX; + MXS_NOTICE("Transaction detection using REGEX."); + } + else if (strcmp(parse_using, "QC_TRX_PARSE_USING_PARSER") == 0) + { + ss_dassert(!true); + } + else + { + MXS_NOTICE("QC_TRX_PARSE_USING set, but the value %s is not known. " + "Parsing using QC.", parse_using); + } + } + + bool rc = compile_trx_regexes(); + + if (rc) + { + rc = qc_thread_init(QC_INIT_SELF); + + if (rc) + { + if (kind & QC_INIT_PLUGIN) + { + rc = classifier->qc_process_init() == 0; + + if (!rc) + { + qc_thread_end(QC_INIT_SELF); + } + } + } + else + { + free_trx_regexes(); + } + } + else + { + MXS_ERROR("Could not compile transaction regexes."); } return rc; @@ -89,8 +292,11 @@ void qc_process_end(uint32_t kind) if (kind & QC_INIT_PLUGIN) { classifier->qc_process_end(); - classifier = NULL; } + + qc_thread_end(QC_INIT_SELF); + + free_trx_regexes(); } QUERY_CLASSIFIER* qc_load(const char* plugin_name) @@ -113,6 +319,7 @@ void qc_unload(QUERY_CLASSIFIER* classifier) { // TODO: The module loading/unloading needs an overhaul before we // TODO: actually can unload something. + classifier = NULL; } bool qc_thread_init(uint32_t kind) @@ -120,11 +327,20 @@ bool qc_thread_init(uint32_t kind) QC_TRACE(); ss_dassert(classifier); - bool rc = true; + bool rc = create_trx_thread_datas(); - if (kind & QC_INIT_PLUGIN) + if (rc) { - rc = classifier->qc_thread_init() == 0; + if (kind & QC_INIT_PLUGIN) + { + rc = classifier->qc_thread_init() == 0; + + if (!rc) + { + free_trx_thread_datas(); + rc = false; + } + } } return rc; @@ -139,6 +355,8 @@ void qc_thread_end(uint32_t kind) { classifier->qc_thread_end(); } + + free_trx_thread_datas(); } qc_parse_result_t qc_parse(GWBUF* query) @@ -797,3 +1015,82 @@ char* qc_typemask_to_string(uint32_t types) return s; } + +static uint32_t qc_get_trx_type_mask_using_qc(GWBUF* stmt) +{ + uint32_t type_mask = qc_get_type_mask(stmt); + + if (!(type_mask & QUERY_TYPE_BEGIN_TRX)) + { + type_mask &= ~(QUERY_TYPE_WRITE | QUERY_TYPE_READ); + } + + type_mask &= (QUERY_TYPE_BEGIN_TRX | + QUERY_TYPE_WRITE | + QUERY_TYPE_READ | + QUERY_TYPE_COMMIT | + QUERY_TYPE_ROLLBACK | + QUERY_TYPE_ENABLE_AUTOCOMMIT | + QUERY_TYPE_DISABLE_AUTOCOMMIT); + + return type_mask; +} + +static uint32_t qc_get_trx_type_mask_using_regex(GWBUF* stmt) +{ + uint32_t type_mask = 0; + + char* sql; + int len; + + // This will exclude prepared statement but we are fine with that. + if (modutil_extract_SQL(stmt, &sql, &len)) + { + QC_TRX_REGEX* regex = qc_trx_regexes; + QC_TRX_REGEX* end = regex + N_TRX_REGEXES; + pcre2_match_data** data = qc_trx_thread_datas; + + while ((type_mask == 0) && (regex < end)) + { + if (pcre2_match(regex->code, (PCRE2_SPTR)sql, len, 0, 0, *data, NULL) >= 0) + { + type_mask = regex->type_mask; + } + + ++regex; + ++data; + } + } + + return type_mask; +} + +uint32_t qc_get_trx_type_mask_using(GWBUF* stmt, qc_trx_parse_using_t use) +{ + uint32_t type_mask = 0; + + switch (use) + { + case QC_TRX_PARSE_USING_QC: + type_mask = qc_get_trx_type_mask_using_qc(stmt); + break; + + case QC_TRX_PARSE_USING_REGEX: + type_mask = qc_get_trx_type_mask_using_regex(stmt); + break; + + case QC_TRX_PARSE_USING_PARSER: + ss_dassert(false); + break; + + default: + ss_dassert(!true); + } + + return type_mask; +} + +uint32_t qc_get_trx_type_mask(GWBUF* stmt) +{ + return qc_get_trx_type_mask_using(stmt, qc_trx_parse_using); +} diff --git a/server/core/test/CMakeLists.txt b/server/core/test/CMakeLists.txt index 2c873ba4a..41daf2f6f 100644 --- a/server/core/test/CMakeLists.txt +++ b/server/core/test/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(test_queuemanager testqueuemanager.c) add_executable(test_server testserver.c) add_executable(test_service testservice.c) add_executable(test_spinlock testspinlock.c) +add_executable(test_trxtracking testtrxtracking.cc) add_executable(test_users testusers.c) add_executable(testfeedback testfeedback.c) add_executable(testmaxscalepcre2 testmaxscalepcre2.c) @@ -33,6 +34,7 @@ target_link_libraries(test_queuemanager maxscale-common) target_link_libraries(test_server maxscale-common) target_link_libraries(test_service maxscale-common) target_link_libraries(test_spinlock maxscale-common) +target_link_libraries(test_trxtracking maxscale-common) target_link_libraries(test_users maxscale-common) target_link_libraries(testfeedback maxscale-common) target_link_libraries(testmaxscalepcre2 maxscale-common) @@ -58,6 +60,7 @@ add_test(TestSpinlock test_spinlock) add_test(TestUsers test_users) add_test(TestModulecmd testmodulecmd) add_test(TestConfig testconfig) +add_test(TestTrxTracking test_trxtracking) # This test requires external dependencies and thus cannot be run # as a part of the core test set diff --git a/server/core/test/testtrxtracking.cc b/server/core/test/testtrxtracking.cc new file mode 100644 index 000000000..3c63a4923 --- /dev/null +++ b/server/core/test/testtrxtracking.cc @@ -0,0 +1,295 @@ +/* + * 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: 2019-07-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 "../../server/core/maxscale/query_classifier.h" + +using namespace std; + +namespace +{ + +GWBUF* create_gwbuf(const char* zStmt) +{ + size_t len = strlen(zStmt); + size_t payload_len = len + 1; + size_t gwbuf_len = MYSQL_HEADER_LEN + payload_len; + + GWBUF* pBuf = gwbuf_alloc(gwbuf_len); + + *((unsigned char*)((char*)GWBUF_DATA(pBuf))) = payload_len; + *((unsigned char*)((char*)GWBUF_DATA(pBuf) + 1)) = (payload_len >> 8); + *((unsigned char*)((char*)GWBUF_DATA(pBuf) + 2)) = (payload_len >> 16); + *((unsigned char*)((char*)GWBUF_DATA(pBuf) + 3)) = 0x00; + *((unsigned char*)((char*)GWBUF_DATA(pBuf) + 4)) = 0x03; + memcpy((char*)GWBUF_DATA(pBuf) + 5, zStmt, len); + + return pBuf; +} + +uint32_t get_qc_trx_type_mask(GWBUF* pBuf) +{ + return qc_get_trx_type_mask_using(pBuf, QC_TRX_PARSE_USING_QC); +} + +uint32_t get_regex_trx_type_mask(GWBUF* pBuf) +{ + return qc_get_trx_type_mask_using(pBuf, QC_TRX_PARSE_USING_REGEX); +} + +} + +namespace +{ + +struct test_case +{ + const char* zStmt; + uint32_t type_mask; +} test_cases[] = +{ + { "BEGIN", QUERY_TYPE_BEGIN_TRX }, + { "BEGIN WORK", QUERY_TYPE_BEGIN_TRX }, + { "BEGIN WORK", QUERY_TYPE_BEGIN_TRX }, + + { "COMMIT", QUERY_TYPE_COMMIT }, + { "COMMIT WORK", QUERY_TYPE_COMMIT }, + { "COMMIT WORK", QUERY_TYPE_COMMIT }, + + { "ROLLBACK", QUERY_TYPE_ROLLBACK }, + { "ROLLBACK WORK", QUERY_TYPE_ROLLBACK }, + { "ROLLBACK WORK", QUERY_TYPE_ROLLBACK }, + + { "START TRANSACTION", QUERY_TYPE_BEGIN_TRX }, + { "START TRANSACTION", QUERY_TYPE_BEGIN_TRX }, + { "START TRANSACTION READ ONLY", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_READ }, + { "START TRANSACTION READ ONLY", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_READ }, + { "START TRANSACTION READ ONLY ", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_READ }, + { "START TRANSACTION READ ONLY ", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_READ }, + { "START TRANSACTION READ WRITE", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_WRITE }, + { "START TRANSACTION READ WRITE", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_WRITE }, + { "START TRANSACTION READ WRITE", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_WRITE }, + { "START TRANSACTION READ WRITE", QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_WRITE }, + { "START TRANSACTION WITH CONSISTENT SNAPSHOT", QUERY_TYPE_BEGIN_TRX }, + { "START TRANSACTION WITH CONSISTENT SNAPSHOT", QUERY_TYPE_BEGIN_TRX }, + { "START TRANSACTION WITH CONSISTENT SNAPSHOT", QUERY_TYPE_BEGIN_TRX }, + { "START TRANSACTION WITH CONSISTENT SNAPSHOT", QUERY_TYPE_BEGIN_TRX }, + { "START TRANSACTION WITH CONSISTENT SNAPSHOT", QUERY_TYPE_BEGIN_TRX }, + { "START TRANSACTION WITH CONSISTENT SNAPSHOT", QUERY_TYPE_BEGIN_TRX }, + + { "SET AUTOCOMMIT=true", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT=true", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT =true", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT =true", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT = true", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT = true", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT=1", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT=1", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT =1", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT =1", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT = 1", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT = 1", QUERY_TYPE_COMMIT|QUERY_TYPE_ENABLE_AUTOCOMMIT }, + + { "SET AUTOCOMMIT=false", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT=false", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT =false", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT =false", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT = false", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT = false", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT=0", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT=0", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT =0", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT =0", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT = 0", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT }, + { "SET AUTOCOMMIT = 0", QUERY_TYPE_BEGIN_TRX|QUERY_TYPE_DISABLE_AUTOCOMMIT }, +}; + +const size_t N_TEST_CASES = sizeof(test_cases)/sizeof(test_cases[0]); + +bool test(uint32_t (*getter)(GWBUF*), const char* zStmt, uint32_t expected_type_mask) +{ + int rc = true; + + GWBUF* pBuf = create_gwbuf(zStmt); + + uint32_t type_mask = getter(pBuf); + + if (type_mask != expected_type_mask) + { + cerr << zStmt << ": expected " << expected_type_mask << ", but got " << type_mask << "." << endl; + rc = false; + } + + gwbuf_free(pBuf); + + return rc; +} + +bool test(uint32_t (*getter)(GWBUF*)) +{ + bool rc = true; + + test_case* pTest = test_cases; + test_case* pEnd = pTest + N_TEST_CASES; + + while (pTest < pEnd) + { + string base(pTest->zStmt); + cout << base << endl; + + string s; + + // Just the string + s = base; + if (!test(getter, s.c_str(), pTest->type_mask)) + { + rc = false; + } + + // Prepended with one space. + s = " " + base; + if (!test(getter, s.c_str(), pTest->type_mask)) + { + rc = false; + } + + // Prepended with two spaces. + s = " " + base; + if (!test(getter, s.c_str(), pTest->type_mask)) + { + rc = false; + } + + // Appended with one space. + s = base + " "; + if (!test(getter, s.c_str(), pTest->type_mask)) + { + rc = false; + } + + // Appended with two spaces. + s = base + " "; + if (!test(getter, s.c_str(), pTest->type_mask)) + { + rc = false; + } + + // Appended with a ";". + s = base + ";"; + if (!test(getter, s.c_str(), pTest->type_mask)) + { + rc = false; + } + + // Appended with a " ;". + s = base + " ;"; + if (!test(getter, s.c_str(), pTest->type_mask)) + { + rc = false; + } + + // Appended with a " ;". + s = base + " ;"; + if (!test(getter, s.c_str(), pTest->type_mask)) + { + rc = false; + } + + // Appended with a "; ". + s = base + "; "; + if (!test(getter, s.c_str(), pTest->type_mask)) + { + rc = false; + } + + // Appended with a "; ". + s = base + "; "; + if (!test(getter, s.c_str(), pTest->type_mask)) + { + rc = false; + } + + // Appended with a " ; ". + s = base + " ; "; + if (!test(getter, s.c_str(), pTest->type_mask)) + { + rc = false; + } + + // Appended with a " ; ". + s = base + " ; "; + if (!test(getter, s.c_str(), pTest->type_mask)) + { + rc = false; + } + + ++pTest; + } + + 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)) + { + // We have to setup something in order for the regexes to be compiled. + if (qc_setup("qc_sqlite", NULL) && qc_process_init(QC_INIT_BOTH)) + { + rc = EXIT_SUCCESS; + + cout << "QC" << endl; + cout << "==" << endl; + if (!test(get_qc_trx_type_mask)) + { + rc = EXIT_FAILURE; + } + cout << endl; + + cout << "Regex" << endl; + cout << "=====" << endl; + if (!test(get_regex_trx_type_mask)) + { + rc = EXIT_FAILURE; + } + cout << endl; + + qc_process_end(QC_INIT_BOTH); + } + else + { + cerr << "error: Could not initialize qc_sqlite." << endl; + } + + mxs_log_finish(); + } + else + { + cerr << "error: Could not initialize log." << endl; + } + + return rc; +} diff --git a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c index 526c269fb..51cae36ad 100644 --- a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c +++ b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c @@ -1523,7 +1523,7 @@ static int route_by_statement(MXS_SESSION* session, uint64_t capabilities, GWBUF if (MYSQL_GET_COMMAND(data) == MYSQL_COM_QUERY) { - uint32_t type = qc_get_type_mask(packetbuf); + uint32_t type = qc_get_trx_type_mask(packetbuf); if (type & QUERY_TYPE_BEGIN_TRX) {