From f11d60420da62ba1bd25b1cf7e0cb719f50f02e9 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Sun, 25 Feb 2018 20:38:25 +0200 Subject: [PATCH] MXS-1475 Add SetParser custom parser SetParser is capable of returning the exact variable and value of a "SET X=Y" statement, in the cases where X is of a specific set of variables; currently "SQL_MODE" and "@MAXSCALE...". The actual value of the SET statement also needs to be parsed in the case of SQL_MODE, but it becomes unnecessary convoluted if that information somehow should conditionally be expressable in a return value. So, the value will be parsed separately. --- .../protocol/MySQL/mariadbclient/setparser.hh | 610 ++++++++++++++++++ .../MySQL/mariadbclient/test/CMakeLists.txt | 6 +- .../mariadbclient/test/test_setparser.cc | 423 ++++++++++++ 3 files changed, 1038 insertions(+), 1 deletion(-) create mode 100644 server/modules/protocol/MySQL/mariadbclient/setparser.hh create mode 100644 server/modules/protocol/MySQL/mariadbclient/test/test_setparser.cc diff --git a/server/modules/protocol/MySQL/mariadbclient/setparser.hh b/server/modules/protocol/MySQL/mariadbclient/setparser.hh new file mode 100644 index 000000000..a4a597e13 --- /dev/null +++ b/server/modules/protocol/MySQL/mariadbclient/setparser.hh @@ -0,0 +1,610 @@ +#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 +#include + + +class SetParser : public maxscale::CustomParser +{ +public: + enum status_t + { + ERROR, // Some fatal error occurred; mem alloc failed, parsing failed, etc. + IS_SET_SQL_MODE, // The COM_QUERY is "set [GLOBAL|SESSION] sql_mode=..." + IS_SET_MAXSCALE, // The COM_QUERY is "set @MAXSCALE..." + NOT_RELEVANT // Neither of the above. + }; + + enum + { + UNUSED_FIRST = 0xFF, + TK_GLOBAL, + TK_GLOBAL_VAR, + TK_SESSION, + TK_SESSION_VAR, + TK_SET, + TK_SQL_MODE, + TK_MAXSCALE_VAR + }; + + SetParser() + { + } + + class Result + { + public: + Result() + : m_pVariable_begin(NULL) + , m_pVariable_end(NULL) + , m_pValue_begin(NULL) + , m_pValue_end(NULL) + {} + + const char* variable_begin() const { return m_pVariable_begin; } + const char* variable_end() const { return m_pVariable_end; } + const char* value_begin() const { return m_pValue_begin; } + const char* value_end() const { return m_pValue_end; } + + void set_variable_begin(const char* pVariable_begin) { m_pVariable_begin = pVariable_begin; } + void set_variable_end(const char* pVariable_end) { m_pVariable_end = pVariable_end; } + void set_value_begin(const char* pValue_begin) { m_pValue_begin = pValue_begin; } + void set_value_end(const char* pValue_end) { m_pValue_end = pValue_end; } + + private: + const char* m_pVariable_begin; + const char* m_pVariable_end; + const char* m_pValue_begin; + const char* m_pValue_end; + }; + + /** + * Return whether the statement is a "SET SQL_MODE=" statement and if so, + * whether the state is ORACLE, DEFAULT or something else. + * + * @param ppBuffer Address of pointer to buffer containing statement. + * The GWBUF must contain a complete statement, but the + * buffer need not be contiguous. + * @param pSql_mode Pointer to variable receiving the sql_mode state, if + * the statement is a "SET SQL_MODE=" statement. + * + * @return ERROR if a fatal error occurred during parsing + * IS_SET_SQL_MODE if the statement is a "SET SQL_MODE=" statement + * NOT_SET_SQL_MODE if the statement is not a "SET SQL_MODE=" + * statement + * + * @attention If the result cannot be deduced without parsing the statement, + * then the buffer will be made contiguous and the value of + * @c *ppBuffer will be updated accordingly. + */ + status_t check(GWBUF** ppBuffer, Result* pResult) + { + status_t rv = NOT_RELEVANT; + + GWBUF* pBuffer = *ppBuffer; + + ss_dassert(gwbuf_length(pBuffer) >= MYSQL_HEADER_LEN); + + size_t buf_len = GWBUF_LENGTH(pBuffer); + size_t payload_len; + if (buf_len >= MYSQL_HEADER_LEN) + { + // The first buffer in the chain contains the header so we + // can read the length directly. + payload_len = MYSQL_GET_PAYLOAD_LEN(GWBUF_DATA(pBuffer)); + } + else + { + // The first buffer in the chain does not contain the full + // header so we need to copy it first. + uint8_t header[MYSQL_HEADER_LEN]; + gwbuf_copy_data(pBuffer, 0, sizeof(header), header); + payload_len = MYSQL_GET_PAYLOAD_LEN(header); + } + + // sizeof(command_byte) + MIN(strlen("SET maxscale"), strlen("SET sql_mode=ORACLE")) + if (payload_len >= 13) + { + // We need 4 bytes from the payload to deduce whether more investigations are needed. + uint8_t payload[4]; + uint8_t* pPayload; + + if (buf_len >= MYSQL_HEADER_LEN + sizeof(payload)) + { + // Enough data in the first buffer of the chain, we can access directly. + pPayload = GWBUF_DATA(pBuffer) + MYSQL_HEADER_LEN; + } + else + { + // Not enough, we copy what we need. + gwbuf_copy_data(pBuffer, MYSQL_HEADER_LEN, sizeof(payload), payload); + pPayload = payload; + } + + uint8_t command = pPayload[0]; + + if (command == MXS_COM_QUERY) + { + const uint8_t* pStmt = &pPayload[1]; + + if (is_alpha(*pStmt)) + { + // First character is alphabetic, we can check whether it is "SET". + if (is_set(pStmt)) + { + // It is, so we must parse further and must therefore ensure that + // the buffer is contiguous. We get the same buffer back if it + // already is. + pBuffer = gwbuf_make_contiguous(*ppBuffer); + + if (pBuffer) + { + *ppBuffer = pBuffer; + initialize(pBuffer); + + rv = parse(pResult); + } + else + { + rv = ERROR; + } + } + } + else + { + // If the first character is not an alphabetic character we assume there + // is a comment and make the buffer contiguous to make it possible to + // efficiently bypass the whitespace. + pBuffer = gwbuf_make_contiguous(*ppBuffer); + + if (pBuffer) + { + *ppBuffer = pBuffer; + initialize(pBuffer); + + bypass_whitespace(); + + if (is_set(m_pI)) + { + rv = parse(pResult); + } + } + else + { + rv = ERROR; + } + } + } + } + + return rv; + } + + /** + * Returns a @c status_t as a string. + * + * @param status_t A result. + * + * @return The corresponding string. + */ + static const char* to_string(status_t result) + { + switch (result) + { + case ERROR: + return "ERROR"; + + case IS_SET_SQL_MODE: + return "IS_SET_SQL_MODE"; + + case IS_SET_MAXSCALE: + return "IS_SET_MAXSCALE"; + + case NOT_RELEVANT: + return "NOT_RELEVANT"; + + default: + ss_dassert(!true); + return "UNKNOWN"; + } + } + +private: + static bool is_set(const char* pStmt) + { + return + (pStmt[0] == 's' || pStmt[0] == 'S') && + (pStmt[1] == 'e' || pStmt[1] == 'E') && + (pStmt[2] == 't' || pStmt[2] == 'T'); + } + + static bool is_set(const uint8_t* pStmt) + { + return is_set(reinterpret_cast(pStmt)); + } + + static bool is_error(status_t rv) + { + return (rv == ERROR); + } + + status_t initialize(GWBUF* pBuffer) + { + ss_dassert(GWBUF_IS_CONTIGUOUS(pBuffer)); + + status_t rv = ERROR; + + char* pSql; + if (modutil_extract_SQL(pBuffer, &pSql, &m_len)) + { + m_pSql = pSql; + m_pI = m_pSql; + m_pEnd = m_pI + m_len; + } + + return ERROR; + } + + bool consume_id() + { + // Consumes "([a-zA-Z]([\.a-zA-Z0-9_])+)*" + + bool rv = false; + + if (is_alpha(*m_pI)) + { + rv = true; + + ++m_pI; + + while ((m_pI < m_pEnd) && (is_alpha(*m_pI) || is_number(*m_pI) || (*m_pI == '.') || (*m_pI == '_'))) + { + ++m_pI; + } + } + + return rv; + } + + void consume_value(const char** ppEnd = NULL) + { + // Consumes everything until a ',' outside of a commented string, or eol is + // encountered. + bool rv = false; + bool consumed = false; + const char* pEnd = NULL; + + while ((m_pI < m_pEnd) && (*m_pI != ',') && (*m_pI != ';')) + { + switch (*m_pI) + { + case '\'': + case '"': + case '`': + { + char quote = *m_pI; + ++m_pI; + while ((m_pI < m_pEnd) && (*m_pI != quote)) + { + ++m_pI; + } + } + break; + + default: + ++m_pI; + } + + pEnd = m_pI; + + bypass_whitespace(); + } + + if (ppEnd) + { + *ppEnd = pEnd; + } + } + + status_t parse(Result* pResult) + { + status_t rv = NOT_RELEVANT; + token_t token = next_token(); + + switch (token) + { + case TK_SET: + rv = parse_set(pResult); + break; + + case PARSER_EXHAUSTED: + log_exhausted(); + break; + + case PARSER_UNKNOWN_TOKEN: + default: + log_unexpected(); + break; + } + + return rv; + } + + status_t parse_set(Result* pResult) + { + status_t rv = NOT_RELEVANT; + + char c; + + do + { + bypass_whitespace(); + + const char* pVariable_begin = m_pI; + + token_t token = next_token(); + + switch (token) + { + case TK_GLOBAL: + rv = parse_set(pResult); + break; + + case TK_SESSION: + rv = parse_set(pResult); + break; + + case TK_GLOBAL_VAR: + case TK_SESSION_VAR: + if (next_token() == '.') + { + rv = parse_set(pResult); + } + else + { + rv = ERROR; + } + break; + + case TK_SQL_MODE: + { + const char* pVariable_end = m_pI; + + if (next_token() == '=') + { + pResult->set_variable_begin(pVariable_begin); + pResult->set_variable_end(pVariable_end); + + bypass_whitespace(); + + const char* pValue_begin = m_pI; + const char* pValue_end; + + consume_value(&pValue_end); + + pResult->set_value_begin(pValue_begin); + pResult->set_value_end(pValue_end); + + rv = IS_SET_SQL_MODE; + } + else + { + rv = ERROR; + } + } + break; + + case TK_MAXSCALE_VAR: + { + if (*m_pI == '.') + { + ++m_pI; + consume_id(); + const char* pVariable_end = m_pI; + + if (next_token() == '=') + { + pResult->set_variable_begin(pVariable_begin); + pResult->set_variable_end(pVariable_end); + + bypass_whitespace(); + + const char* pValue_begin = m_pI; + const char* pValue_end; + + consume_value(&pValue_end); + + pResult->set_value_begin(pValue_begin); + pResult->set_value_end(pValue_end); + + rv = IS_SET_MAXSCALE; + } + else + { + rv = ERROR; + } + } + else + { + rv = ERROR; + } + } + break; + + case PARSER_EXHAUSTED: + log_exhausted(); + rv = ERROR; + break; + + case PARSER_UNKNOWN_TOKEN: + // Might be something like "SET A=B, C=D, SQL_MODE=ORACLE", so we first consume + // the identifier and if it is followed by a "=" we consume the value. + { + char c; + if (consume_id()) + { + bypass_whitespace(); + + if (peek_current_char(&c) && (c == '=')) + { + ++m_pI; + consume_value(); + } + } + else + { + log_unexpected(); + rv = ERROR; + } + } + break; + + default: + log_unexpected(); + rv = ERROR; + break; + } + + c = 0; + + if (rv != ERROR) + { + bypass_whitespace(); + + if (peek_current_char(&c)) + { + if (c == ',') + { + ++m_pI; + } + else + { + c = 0; + } + } + else + { + c = 0; + } + } + } + while ((rv != ERROR) && (c == ',')); + + return rv; + } + + token_t next_token(token_required_t required = TOKEN_NOT_REQUIRED) + { + token_t token = PARSER_UNKNOWN_TOKEN; + + bypass_whitespace(); + + if (m_pI == m_pEnd) + { + token = PARSER_EXHAUSTED; + } + else if (*m_pI == ';') + { + ++m_pI; + + while ((m_pI != m_pEnd) && isspace(*m_pI)) + { + ++m_pI; + } + + if (m_pI != m_pEnd) + { + MXS_WARNING("Non-space data found after semi-colon: '%.*s'.", + (int)(m_pEnd - m_pI), m_pI); + } + + token = PARSER_EXHAUSTED; + } + else + { + switch (*m_pI) + { + case '@': + if (is_next_alpha('S', 2)) + { + token = expect_token(MXS_CP_EXPECT_TOKEN("@@SESSION"), TK_SESSION_VAR); + } + else if (is_next_alpha('G', 2)) + { + token = expect_token(MXS_CP_EXPECT_TOKEN("@@GLOBAL"), TK_GLOBAL_VAR); + } + else if (is_next_alpha('L', 2)) + { + token = expect_token(MXS_CP_EXPECT_TOKEN("@@LOCAL"), TK_SESSION_VAR); + } + else if (is_next_alpha('M', 1)) + { + token = expect_token(MXS_CP_EXPECT_TOKEN("@MAXSCALE"), TK_MAXSCALE_VAR); + } + break; + + case '.': + case '\'': + case '"': + case '`': + case ',': + case '=': + token = *m_pI; + ++m_pI; + break; + + case 'g': + case 'G': + token = expect_token(MXS_CP_EXPECT_TOKEN("GLOBAL"), TK_GLOBAL); + break; + + case 'l': + case 'L': + token = expect_token(MXS_CP_EXPECT_TOKEN("LOCAL"), TK_SESSION); + break; + + case 's': + case 'S': + if (is_next_alpha('E')) + { + if (is_next_alpha('S', 2)) + { + token = expect_token(MXS_CP_EXPECT_TOKEN("SESSION"), TK_SESSION); + } + else + { + token = expect_token(MXS_CP_EXPECT_TOKEN("SET"), TK_SET); + } + } + else if (is_next_alpha('Q')) + { + token = expect_token(MXS_CP_EXPECT_TOKEN("SQL_MODE"), TK_SQL_MODE); + } + break; + + default: + ; + } + } + + if ((token == PARSER_EXHAUSTED) && (required == TOKEN_REQUIRED)) + { + log_exhausted(); + } + + return token; + } +}; diff --git a/server/modules/protocol/MySQL/mariadbclient/test/CMakeLists.txt b/server/modules/protocol/MySQL/mariadbclient/test/CMakeLists.txt index 8151ab484..07994287b 100644 --- a/server/modules/protocol/MySQL/mariadbclient/test/CMakeLists.txt +++ b/server/modules/protocol/MySQL/mariadbclient/test/CMakeLists.txt @@ -1,4 +1,8 @@ +add_executable(test_setparser test_setparser.cc) add_executable(test_setsqlmodeparser test_setsqlmodeparser.cc) -target_link_libraries(test_setsqlmodeparser maxscale-common) +target_link_libraries(test_setsqlmodeparser maxscale-common) +target_link_libraries(test_setparser maxscale-common) + +add_test(test_setparser test_setarser) add_test(test_setsqlmodeparser test_setsqlmodeparser) diff --git a/server/modules/protocol/MySQL/mariadbclient/test/test_setparser.cc b/server/modules/protocol/MySQL/mariadbclient/test/test_setparser.cc new file mode 100644 index 000000000..86f0c213c --- /dev/null +++ b/server/modules/protocol/MySQL/mariadbclient/test/test_setparser.cc @@ -0,0 +1,423 @@ +/* + * 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 "../setparser.hh" +#include +#include +#include +#include +#include + +using namespace std; + +namespace +{ + +GWBUF* gwbuf_create_com_query(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; +} + +} + +namespace +{ + +typedef SetParser P; + +struct TEST_CASE +{ + const char* zStmt; + SetParser::status_t status; + const char* zVariable; + const char* zValue; +} test_cases[] = +{ + { + "SET SQL_MODE=DEFAULT", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "DEFAULT" + }, + { + "SET SQL_MODE=DEFAULT;", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "DEFAULT" + }, + { + "SET SQL_MODE=DEFAULT; ", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "DEFAULT" + }, + { + "-- This is a comment\nSET SQL_MODE=DEFAULT", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "DEFAULT" + }, + { + "#This is a comment\nSET SQL_MODE=DEFAULT", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "DEFAULT" + }, + { + "/*blah*/ SET /*blah*/ SQL_MODE /*blah*/ = /*blah*/ DEFAULT /*blah*/ ", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "DEFAULT" + }, + { + "SET SQL_MODE=ORACLE", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "ORACLE" + }, + { + "SET SQL_MODE=BLAH", // So short that it cannot be DEFAULT|ORACLE + P::IS_SET_SQL_MODE, + "SQL_MODE", + "BLAH" + }, + { + "SET SQL_MODE='BLAH'", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "'BLAH'" + }, + { + "SET SQL_MODE=BLAHBLAH", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "BLAHBLAH" + }, + { + "SET SQL_MODE='ORACLE'", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "'ORACLE'" + }, + { + "SET SQL_MODE='BLAH, A, B, ORACLE'", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "'BLAH, A, B, ORACLE'" + }, + { + "SET SQL_MODE='BLAH, A, B, XYZ_123'", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "'BLAH, A, B, XYZ_123'" + }, + { + "SET VAR1=1234, VAR2=3456, SQL_MODE='A,B, ORACLE'", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "'A,B, ORACLE'" + }, + { + "SET SQL_MODE=ORACLE, VAR1=3456, VAR2='A=b, c=d', SQL_MODE='A,B, ORACLE'", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "ORACLE" + }, + { + "SET GLOBAL SQL_MODE=ORACLE", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "ORACLE" + }, + { + "SET SESSION SQL_MODE=ORACLE", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "ORACLE" + }, + { + "SET LOCAL SQL_MODE=ORACLE", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "ORACLE" + }, + { + "SET @@GLOBAL.SQL_MODE=ORACLE", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "ORACLE" + }, + { + "SET @@SESSION.SQL_MODE=ORACLE", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "ORACLE" + }, + { + "SET @@LOCAL.SQL_MODE=ORACLE", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "ORACLE" + }, + { + "SET @@LOCAL . SQL_MODE = ORACLE", + P::IS_SET_SQL_MODE, + "SQL_MODE", + "ORACLE" + }, + { + "SET @@SESSION.blah = 1234, @@GLOBAL.blahblah = something, sql_mode=ORACLE", + P::IS_SET_SQL_MODE, + "sql_mode", + "ORACLE" + }, + { + "SET MAXSCALE=", + P::NOT_RELEVANT, + NULL, + NULL + }, + { + "SET MAXSCALE.CACHE.ENABLED=TRUE", + P::NOT_RELEVANT, + NULL, + NULL + }, + { + "SET @MAXSCALE.CACHE.ENABLED=TRUE", + P::IS_SET_MAXSCALE, + "@MAXSCALE.CACHE.ENABLED", + "TRUE" + }, + { + "SET @MAXSCALE.CACHE.ENABLED = TRUE /*blah*/", + P::IS_SET_MAXSCALE, + "@MAXSCALE.CACHE.ENABLED", + "TRUE" + }, +}; + +const int N_TEST_CASES = sizeof(test_cases) / sizeof(test_cases[0]); + +int test(GWBUF** ppStmt, SetParser::status_t expected_status, const char* zVariable, const char* zValue) +{ + int rv = EXIT_SUCCESS; + + SetParser parser; + + SetParser::Result result; + SetParser::status_t status = parser.check(ppStmt, &result); + + if (status == expected_status) + { + if ((status != SetParser::ERROR) && (status != SetParser::NOT_RELEVANT)) + { + size_t l1 = result.variable_end() - result.variable_begin(); + + if ((l1 == strlen(zVariable)) && (memcmp(result.variable_begin(), zVariable, l1) == 0)) + { + size_t l2 = result.value_end() - result.value_begin(); + + if ((l2 == strlen(zValue)) && (memcmp(result.value_begin(), zValue, l2) == 0)) + { + cout << "OK"; + } + else + { + cout << "ERROR: Expected value " + << "'" << zValue << "'" + << ", got '"; + cout.write(result.value_begin(), l2); + cout << "'."; + rv = EXIT_FAILURE; + } + } + else + { + cout << "ERROR: Expected variable " + << "'" << zVariable << "'" + << ", got '"; + cout.write(result.variable_begin(), l1); + cout << "'."; + rv = EXIT_FAILURE; + } + } + else + { + cout << "OK"; + } + } + else + { + cout << "ERROR: Expected " + << "'" << SetParser::to_string(expected_status) << "'" + << ", got " + << "'" << SetParser::to_string(status) << "'" + << "."; + rv = EXIT_FAILURE; + } + + cout << endl; + + return rv; +} + +int test(const TEST_CASE& test_case) +{ + int rv = EXIT_SUCCESS; + + cout << test_case.zStmt << ": "; + + GWBUF* pStmt = gwbuf_create_com_query(test_case.zStmt); + ss_dassert(pStmt); + + rv = test(&pStmt, test_case.status, test_case.zVariable, test_case.zValue); + + gwbuf_free(pStmt); + + return rv; +} + +int test_contiguous() +{ + int rv = EXIT_SUCCESS; + + cout << "Test contiguous statements\n" + << "--------------------------" << endl; + + for (int i = 0; i < N_TEST_CASES; ++i) + { + if (test(test_cases[i]) == EXIT_FAILURE) + { + rv = EXIT_FAILURE; + } + } + + cout << endl; + + return rv; +} + +int test_non_contiguous() +{ + int rv = EXIT_SUCCESS; + + cout << "Test non-contiguous statements\n" + << "------------------------------" << endl; + + for (int i = 0; i < N_TEST_CASES; ++i) + { + TEST_CASE& test_case = test_cases[i]; + + cout << test_case.zStmt << "(" << strlen(test_case.zStmt) << ": "; + + GWBUF* pTail = gwbuf_create_com_query(test_case.zStmt); + ss_dassert(pTail); + GWBUF* pStmt = NULL; + + while (pTail) + { + size_t n = MYSQL_HEADER_LEN + rand() % 10; // Between 4 and 13 bytes long chunks. + + GWBUF* pHead = gwbuf_split(&pTail, n); + + cout << GWBUF_LENGTH(pHead); + + pStmt = gwbuf_append(pStmt, pHead); + + if (pTail) + { + cout << ", "; + } + } + + cout << "): " << flush; + + if (test(&pStmt, test_case.status, test_case.zVariable, test_case.zValue) == EXIT_FAILURE) + { + rv = EXIT_FAILURE; + } + + gwbuf_free(pStmt); + } + + cout << endl; + + return rv; +} + +int test() +{ + int rv = EXIT_SUCCESS; + + if (test_contiguous() != EXIT_SUCCESS) + { + rv = EXIT_FAILURE; + } + + if (test_non_contiguous() != EXIT_SUCCESS) + { + rv = EXIT_FAILURE; + } + + if (rv == EXIT_SUCCESS) + { + cout << "OK" << endl; + } + else + { + cout << "ERROR" << endl; + } + + return rv; +} + +} + + +int main(int argc, char* argv[]) +{ + int rv = EXIT_SUCCESS; + + srand(time(NULL)); + + set_datadir(strdup("/tmp")); + set_langdir(strdup(".")); + set_process_datadir(strdup("/tmp")); + + if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT)) + { + rv = test(); + + mxs_log_finish(); + } + else + { + cerr << "error: Could not initialize log." << endl; + } + + return rv; +}