#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 namespace maxscale { #define TBP_EXPECT_TOKEN(string_literal) string_literal, (sizeof(string_literal) - 1) // For debugging purposes. // #define TBP_LOG_UNEXPECTED_AND_EXHAUSTED #undef TBP_LOG_UNEXPECTED_AND_EXHAUSTED /** * @class TrxBoundaryParser * * @ TrxBoundaryParser is a class capable of parsing and returning the * correct type mask of statements affecting the transaction state and * autocommit mode. * * The class is intended to be used in context where the performance is * of utmost importance; consequently it is defined in its entirety * in the header to allow for aggressive inlining. */ class TrxBoundaryParser : public maxscale::CustomParser { TrxBoundaryParser(const TrxBoundaryParser&); TrxBoundaryParser& operator = (const TrxBoundaryParser&); public: enum token_t { TK_AUTOCOMMIT, TK_BEGIN, TK_COMMA, TK_COMMIT, TK_CONSISTENT, TK_DOT, TK_EQ, TK_FALSE, TK_GLOBAL, TK_GLOBAL_VAR, TK_ONE, TK_ONLY, TK_READ, TK_ROLLBACK, TK_SESSION, TK_SESSION_VAR, TK_SET, TK_SNAPSHOT, TK_START, TK_TRANSACTION, TK_TRUE, TK_WITH, TK_WORK, TK_WRITE, TK_ZERO, PARSER_UNKNOWN_TOKEN, PARSER_EXHAUSTED, }; /** * TrxBoundaryParser is not thread-safe. As a very lightweight class, * the intention is that an instance is created on the stack whenever * parsing needs to be performed. * * @code * void f(GWBUF *pBuf) * { * TrxBoundaryParser tbp; * * uint32_t type_mask = tbp.parse(pBuf); * ... * } * @endcode */ TrxBoundaryParser() { } /** * Return the type mask of a statement, provided the statement affects * transaction state or autocommit mode. * * @param pSql SQL statament. * @param len Length of pSql. * * @return The corresponding type mask or 0, if the statement does not * affect transaction state or autocommit mode. */ uint32_t type_mask_of(const char* pSql, size_t len) { uint32_t type_mask = 0; m_pSql = pSql; m_len = len; m_pI = m_pSql; m_pEnd = m_pI + m_len; return parse(); } /** * Return the type mask of a statement, provided the statement affects * transaction state or autocommit mode. * * @param pBuf A COM_QUERY * * @return The corresponding type mask or 0, if the statement does not * affect transaction state or autocommit mode. */ uint32_t type_mask_of(GWBUF* pBuf) { uint32_t type_mask = 0; char* pSql; if (modutil_extract_SQL(pBuf, &pSql, &m_len)) { m_pSql = pSql; m_pI = m_pSql; m_pEnd = m_pI + m_len; type_mask = parse(); } return type_mask; } private: enum token_required_t { TOKEN_REQUIRED, TOKEN_NOT_REQUIRED, }; void log_unexpected() { #ifdef TBP_LOG_UNEXPECTED_AND_EXHAUSTED MXS_NOTICE("Transaction tracking: In statement '%.*s', unexpected token at '%.*s'.", (int)m_len, m_pSql, (int)(m_pEnd - m_pI), m_pI); #endif } void log_exhausted() { #ifdef TBP_LOG_UNEXPECTED_AND_EXHAUSTED MXS_NOTICE("Transaction tracking: More tokens expected in statement '%.*s'.", (int)m_len, m_pSql); #endif } uint32_t parse() { uint32_t type_mask = 0; token_t token = next_token(); switch (token) { case TK_BEGIN: type_mask = parse_begin(type_mask); break; case TK_COMMIT: type_mask = parse_commit(type_mask); break; case TK_ROLLBACK: type_mask = parse_rollback(type_mask); break; case TK_START: type_mask = parse_start(type_mask); break; case TK_SET: type_mask = parse_set(0); break; default: ; } return type_mask; } uint32_t parse_begin(uint32_t type_mask) { type_mask |= QUERY_TYPE_BEGIN_TRX; token_t token = next_token(); switch (token) { case TK_WORK: type_mask = parse_work(type_mask); break; case PARSER_EXHAUSTED: break; default: type_mask = 0; log_unexpected(); } return type_mask; } uint32_t parse_commit(uint32_t type_mask) { type_mask |= QUERY_TYPE_COMMIT; token_t token = next_token(); switch (token) { case TK_WORK: type_mask = parse_work(type_mask); break; case PARSER_EXHAUSTED: break; default: type_mask = 0; log_unexpected(); } return type_mask; } uint32_t parse_only(uint32_t type_mask) { type_mask |= QUERY_TYPE_READ; token_t token = next_token(); switch (token) { case TK_COMMA: type_mask = parse_transaction(type_mask); break; case PARSER_EXHAUSTED: break; default: type_mask = 0; log_unexpected(); } return type_mask; } uint32_t parse_read(uint32_t type_mask) { token_t token = next_token(TOKEN_REQUIRED); switch (token) { case TK_ONLY: type_mask = parse_only(type_mask); break; case TK_WRITE: type_mask = parse_write(type_mask); break; case PARSER_EXHAUSTED: type_mask = 0; break; default: type_mask = 0; log_unexpected(); } return type_mask; } uint32_t parse_rollback(uint32_t type_mask) { type_mask |= QUERY_TYPE_ROLLBACK; token_t token = next_token(); switch (token) { case TK_WORK: type_mask = parse_work(type_mask); break; case PARSER_EXHAUSTED: break; default: type_mask = 0; log_unexpected(); } return type_mask; } uint32_t parse_set_autocommit(uint32_t type_mask) { token_t token = next_token(TOKEN_REQUIRED); switch (token) { case TK_EQ: token = next_token(TOKEN_REQUIRED); if (token == TK_ONE || token == TK_TRUE) { type_mask |= (QUERY_TYPE_COMMIT | QUERY_TYPE_ENABLE_AUTOCOMMIT); } else if (token == TK_ZERO || token == TK_FALSE) { type_mask = (QUERY_TYPE_BEGIN_TRX | QUERY_TYPE_DISABLE_AUTOCOMMIT); } else { type_mask = 0; if (token != PARSER_EXHAUSTED) { log_unexpected(); } } break; case PARSER_EXHAUSTED: type_mask = 0; break; default: type_mask = 0; log_unexpected(); } return type_mask; } uint32_t parse_set(uint32_t type_mask) { token_t token = next_token(TOKEN_REQUIRED); switch (token) { case TK_AUTOCOMMIT: type_mask = parse_set_autocommit(type_mask); break; case TK_GLOBAL: case TK_SESSION: token = next_token(TOKEN_REQUIRED); if (token == TK_AUTOCOMMIT) { type_mask = parse_set_autocommit(type_mask); } else { type_mask = 0; if (token != PARSER_EXHAUSTED) { log_unexpected(); } } break; case TK_GLOBAL_VAR: case TK_SESSION_VAR: token = next_token(TOKEN_REQUIRED); if (token == TK_DOT) { token = next_token(TOKEN_REQUIRED); if (token == TK_AUTOCOMMIT) { type_mask = parse_set_autocommit(type_mask); } else { type_mask = 0; if (token != PARSER_EXHAUSTED) { log_unexpected(); } } } else { type_mask = 0; if (token != PARSER_EXHAUSTED) { log_unexpected(); } } break; case PARSER_EXHAUSTED: type_mask = 0; break; default: type_mask = 0; log_unexpected(); } return type_mask; } uint32_t parse_start(uint32_t type_mask) { token_t token = next_token(TOKEN_REQUIRED); switch (token) { case TK_TRANSACTION: type_mask = parse_transaction(type_mask); break; case PARSER_EXHAUSTED: type_mask = 0; break; default: type_mask = 0; log_unexpected(); } return type_mask; } uint32_t parse_transaction(uint32_t type_mask) { type_mask |= QUERY_TYPE_BEGIN_TRX; token_t token = next_token(); switch (token) { case TK_READ: type_mask = parse_read(type_mask); break; case TK_WITH: type_mask = parse_with_consistent_snapshot(type_mask); break; case PARSER_EXHAUSTED: break; default: type_mask = 0; log_unexpected(); } return type_mask; } uint32_t parse_with_consistent_snapshot(uint32_t type_mask) { token_t token = next_token(TOKEN_REQUIRED); if (token == TK_CONSISTENT) { token = next_token(TOKEN_REQUIRED); if (token == TK_SNAPSHOT) { token = next_token(); switch (token) { case TK_COMMA: type_mask = parse_transaction(type_mask); break; case PARSER_EXHAUSTED: break; default: type_mask = 0; log_unexpected(); } } } return type_mask; } uint32_t parse_work(uint32_t type_mask) { token_t token = next_token(); switch (token) { case PARSER_EXHAUSTED: break; default: type_mask = 0; log_unexpected(); } return type_mask; } uint32_t parse_write(uint32_t type_mask) { type_mask |= QUERY_TYPE_WRITE; token_t token = next_token(); switch (token) { case TK_COMMA: type_mask = parse_transaction(type_mask); break; case PARSER_EXHAUSTED: break; default: type_mask = 0; log_unexpected(); } return type_mask; } // Significantly faster than library version. static char toupper(char c) { return (c >= 'a' && c <= 'z') ? c - ('a' - 'A') : c; } token_t expect_token(const char* zWord, int len, token_t token) { const char* pI = m_pI; const char* pEnd = zWord + len; while ((pI < m_pEnd) && (zWord < pEnd) && (toupper(*pI) == *zWord)) { ++pI; ++zWord; } if (zWord == pEnd) { if ((pI == m_pEnd) || (!isalpha(*pI))) // Handwritten isalpha not faster than library version. { m_pI = pI; } else { token = PARSER_UNKNOWN_TOKEN; } } else { token = PARSER_UNKNOWN_TOKEN; } return token; } void bypass_whitespace() { m_pI = modutil_MySQL_bypass_whitespace(const_cast(m_pI), m_pEnd - m_pI); } 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('A', 2)) { token = expect_token(TBP_EXPECT_TOKEN("@@AUTOCOMMIT"), TK_AUTOCOMMIT); } else if (is_next_alpha('S', 2)) { token = expect_token(TBP_EXPECT_TOKEN("@@SESSION"), TK_SESSION_VAR); } else if (is_next_alpha('G', 2)) { token = expect_token(TBP_EXPECT_TOKEN("@@GLOBAL"), TK_GLOBAL_VAR); } break; case 'a': case 'A': token = expect_token(TBP_EXPECT_TOKEN("AUTOCOMMIT"), TK_AUTOCOMMIT); break; case 'b': case 'B': token = expect_token(TBP_EXPECT_TOKEN("BEGIN"), TK_BEGIN); break; case ',': ++m_pI; token = TK_COMMA; break; case 'c': case 'C': if (is_next_alpha('O')) { if (is_next_alpha('M', 2)) { token = expect_token(TBP_EXPECT_TOKEN("COMMIT"), TK_COMMIT); } else if (is_next_alpha('N', 2)) { token = expect_token(TBP_EXPECT_TOKEN("CONSISTENT"), TK_CONSISTENT); } } break; case '.': ++m_pI; token = TK_DOT; break; case '=': ++m_pI; token = TK_EQ; break; case 'f': case 'F': token = expect_token(TBP_EXPECT_TOKEN("FALSE"), TK_FALSE); break; case 'g': case 'G': token = expect_token(TBP_EXPECT_TOKEN("GLOBAL"), TK_GLOBAL); break; case '1': { char c; if (!peek_next_char(&c) || !isdigit(c)) { ++m_pI; token = TK_ONE; } } break; case 'o': case 'O': if (is_next_alpha('F')) { token = expect_token(TBP_EXPECT_TOKEN("OFF"), TK_ZERO); } else if (is_next_alpha('N')) { if (is_next_alpha('L', 2)) { token = expect_token(TBP_EXPECT_TOKEN("ONLY"), TK_ONLY); } else { token = expect_token(TBP_EXPECT_TOKEN("ON"), TK_ONE); } } break; case 'r': case 'R': if (is_next_alpha('E')) { token = expect_token(TBP_EXPECT_TOKEN("READ"), TK_READ); } else if (is_next_alpha('O')) { token = expect_token(TBP_EXPECT_TOKEN("ROLLBACK"), TK_ROLLBACK); } break; case 's': case 'S': if (is_next_alpha('E')) { if (is_next_alpha('S', 2)) { token = expect_token(TBP_EXPECT_TOKEN("SESSION"), TK_SESSION); } else { token = expect_token(TBP_EXPECT_TOKEN("SET"), TK_SET); } } else if (is_next_alpha('N')) { token = expect_token(TBP_EXPECT_TOKEN("SNAPSHOT"), TK_SNAPSHOT); } else if (is_next_alpha('T')) { token = expect_token(TBP_EXPECT_TOKEN("START"), TK_START); } break; case 't': case 'T': if (is_next_alpha('R')) { if (is_next_alpha('A', 2)) { token = expect_token(TBP_EXPECT_TOKEN("TRANSACTION"), TK_TRANSACTION); } else if (is_next_alpha('U', 2)) { token = expect_token(TBP_EXPECT_TOKEN("TRUE"), TK_TRUE); } } break; case 'w': case 'W': if (is_next_alpha('I')) { token = expect_token(TBP_EXPECT_TOKEN("WITH"), TK_WITH); } else if (is_next_alpha('O')) { token = expect_token(TBP_EXPECT_TOKEN("WORK"), TK_WORK); } else if (is_next_alpha('R')) { token = expect_token(TBP_EXPECT_TOKEN("WRITE"), TK_WRITE); } break; case '0': { char c; if (!peek_next_char(&c) || !isdigit(c)) { ++m_pI; token = TK_ZERO; } } break; default: ; } } if ((token == PARSER_EXHAUSTED) && (required == TOKEN_REQUIRED)) { log_exhausted(); } return token; } }; }