diff --git a/server/modules/filter/hintfilter/hintparser.cc b/server/modules/filter/hintfilter/hintparser.cc index b8ee98a18..2f9048716 100644 --- a/server/modules/filter/hintfilter/hintparser.cc +++ b/server/modules/filter/hintfilter/hintparser.cc @@ -13,649 +13,17 @@ #define MXS_MODULE_NAME "hintfilter" -#include -#include -#include +#include + #include -#include -#include #include + #include "mysqlhint.hh" -#include /** - * hintparser.c - Find any comment in the SQL packet and look for MAXSCALE - * hints in that comment. + * Code for parsing SQL comments and processing them into MaxScale hints */ -/** - * The keywords in the hint syntax - */ -static struct -{ - const char* keyword; - TOKEN_VALUE token; -} keywords[] = -{ - {"maxscale", TOK_MAXSCALE }, - {"prepare", TOK_PREPARE }, - {"start", TOK_START }, - {"begin", TOK_START }, - {"stop", TOK_STOP }, - {"end", TOK_STOP }, - {"=", TOK_EQUAL }, - {"route", TOK_ROUTE }, - {"to", TOK_TO }, - {"master", TOK_MASTER }, - {"slave", TOK_SLAVE }, - {"server", TOK_SERVER }, - {"last", TOK_LAST }, - {NULL, static_cast(0)} -}; - -static HINT_TOKEN* hint_next_token(GWBUF** buf, char** ptr); -static void hint_pop(HINT_SESSION*); -static HINT* lookup_named_hint(HINT_SESSION*, const char*); -static void create_named_hint(HINT_SESSION*, const char*, HINT*); -static void hint_push(HINT_SESSION*, HINT*); -static const char* token_get_keyword(HINT_TOKEN* token); -static void token_free(HINT_TOKEN* token); - -typedef enum -{ - HM_EXECUTE, - HM_START, - HM_PREPARE -} HINT_MODE; - -void token_free(HINT_TOKEN* token) -{ - if (token->value != NULL) - { - MXS_FREE(token->value); - } - MXS_FREE(token); -} - -static const char* token_get_keyword(HINT_TOKEN* token) -{ - switch (token->token) - { - case TOK_END: - return "End of hint"; - break; - - case TOK_LINEBRK: - return "End of line"; - break; - - case TOK_STRING: - return token->value; - break; - - default: - { - int i = 0; - while (i < TOK_LINEBRK && keywords[i].token != token->token) - { - i++; - } - - mxb_assert(i != TOK_LINEBRK); - - if (i == TOK_LINEBRK) - { - return "Unknown token"; - } - else - { - return keywords[i].keyword; - } - } - break; - } -} - -/** - * Parse the hint comments in the MySQL statement passed in request. - * Add any hints to the buffer for later processing. - * - * @param session The filter session - * @param request The MySQL request buffer - * @return The hints parsed in this statement or active on the - * stack - */ -HINT* hint_parser(HINT_SESSION* session, GWBUF* request) -{ - char* ptr, lastch = ' '; - int len, residual, state; - int found, escape, quoted, squoted; - HINT* rval = NULL; - char* pname = nullptr; - char* lvalue = nullptr; - char* hintname = nullptr; - 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; - found = 0; - escape = 0; - quoted = 0; - squoted = 0; - do - { - while (len--) - { - if (*ptr == '\\') - { - escape = 1; - } - else if (*ptr == '\"' && quoted) - { - quoted = 0; - } - else if (*ptr == '\"' && quoted == 0) - { - quoted = 0; - } - else if (*ptr == '\'' && squoted) - { - squoted = 0; - } - else if (*ptr == '\"' && squoted == 0) - { - squoted = 0; - } - else if (quoted || squoted) - { - } - else if (escape) - { - escape = 0; - } - else if (*ptr == '#') - { - found = 1; - break; - } - else if (*ptr == '/') - { - lastch = '/'; - } - else if (*ptr == '*' && lastch == '/') - { - found = 1; - multiline_comment = true; - break; - } - else if (*ptr == '-' && lastch == '-') - { - found = 1; - break; - } - else if (*ptr == '-') - { - lastch = '-'; - } - else - { - lastch = *ptr; - } - ptr++; - } - if (found) - { - break; - } - - buf = buf->next; - if (buf) - { - len = GWBUF_LENGTH(buf); - ptr = (char*)GWBUF_DATA(buf); - } - } - while (buf); - - if (!found) /* No comment so we need do no more */ - { - goto retblock; - } - - /* - * If we have got here then we have a comment, ptr point to - * the comment character if it is a '#' comment or the second - * character of the comment if it is a -- or \/\* comment - * - * Move to the next character in the SQL. - */ - ptr++; - if (ptr > (char*)(buf->end)) - { - buf = buf->next; - if (buf) - { - ptr = (char*)GWBUF_DATA(buf); - } - else - { - goto retblock; - } - } - - tok = hint_next_token(&buf, &ptr); - - if (tok == NULL) - { - goto retblock; - } - - /** This is not MaxScale hint because it doesn't start with 'maxscale' */ - if (tok->token != TOK_MAXSCALE) - { - token_free(tok); - goto retblock; - } - token_free(tok); - - state = HS_INIT; - - 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: - switch (tok->token) - { - case TOK_ROUTE: - state = HS_ROUTE; - break; - - case TOK_STRING: - state = HS_NAME; - lvalue = MXS_STRDUP_A(tok->value); - break; - - case TOK_STOP: - /* Action: pop active hint */ - hint_pop(session); - state = HS_INIT; - break; - - case TOK_START: - hintname = NULL; - mode = HM_START; - state = HS_INIT; - break; - - default: - /* Error: expected hint, name or STOP */ - MXS_ERROR("Syntax error in hint. Expected " - "'route', 'stop' or hint name instead of " - "'%s'. Hint ignored.", - token_get_keyword(tok)); - token_free(tok); - goto retblock; - } - break; - - case HS_ROUTE: - if (tok->token != TOK_TO) - { - /* Error, expect TO */ - MXS_ERROR("Syntax error in hint. Expected " - "'to' instead of '%s'. Hint ignored.", - token_get_keyword(tok)); - token_free(tok); - goto retblock; - } - state = HS_ROUTE1; - break; - - case HS_ROUTE1: - switch (tok->token) - { - case TOK_MASTER: - rval = hint_create_route(rval, - HINT_ROUTE_TO_MASTER, - NULL); - break; - - case TOK_SLAVE: - rval = hint_create_route(rval, - HINT_ROUTE_TO_SLAVE, - NULL); - break; - - case TOK_LAST: - rval = hint_create_route(rval, - HINT_ROUTE_TO_LAST_USED, - NULL); - break; - - case TOK_SERVER: - state = HS_ROUTE_SERVER; - break; - - default: - /* Error expected MASTER, SLAVE or SERVER */ - MXS_ERROR("Syntax error in hint. Expected " - "'master', 'slave', or 'server' instead " - "of '%s'. Hint ignored.", - token_get_keyword(tok)); - - token_free(tok); - goto retblock; - } - break; - - case HS_ROUTE_SERVER: - if (tok->token == TOK_STRING) - { - rval = hint_create_route(rval, - HINT_ROUTE_TO_NAMED_SERVER, - tok->value); - } - else - { - /* Error: Expected server name */ - MXS_ERROR("Syntax error in hint. Expected " - "server name instead of '%s'. Hint " - "ignored.", - token_get_keyword(tok)); - token_free(tok); - goto retblock; - } - break; - - case HS_NAME: - switch (tok->token) - { - case TOK_EQUAL: - pname = lvalue; - lvalue = NULL; - state = HS_PVALUE; - break; - - case TOK_PREPARE: - pname = lvalue; - state = HS_PREPARE; - break; - - case TOK_START: - /* Action start(lvalue) */ - hintname = lvalue; - mode = HM_START; - state = HS_INIT; - break; - - default: - /* Error, token tok->value not expected */ - MXS_ERROR("Syntax error in hint. Expected " - "'=', 'prepare', or 'start' instead of " - "'%s'. Hint ignored.", - token_get_keyword(tok)); - token_free(tok); - goto retblock; - } - break; - - 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: - mode = HM_PREPARE; - hintname = lvalue; - switch (tok->token) - { - case TOK_ROUTE: - state = HS_ROUTE; - break; - - case TOK_STRING: - state = HS_NAME; - lvalue = tok->value; - break; - - default: - /* Error, token tok->value not expected */ - MXS_ERROR("Syntax error in hint. Expected " - "'route' or hint name instead of " - "'%s'. Hint ignored.", - token_get_keyword(tok)); - token_free(tok); - goto retblock; - } - break; - } - token_free(tok); - } /*< while */ - - if (tok && tok->token == TOK_END) - { - token_free(tok); - } - - switch (mode) - { - case HM_START: - /* - * We are starting either a predefined set of hints, - * creating a new set of hints and starting in a single - * operation or starting an anonymous block of hints. - */ - if (hintname == NULL && rval != NULL) - { - /* We are starting an anonymous block of hints */ - hint_push(session, rval); - rval = NULL; - } - else if (hintname && rval) - { - /* We are creating and starting a block of hints */ - if (lookup_named_hint(session, hintname) != NULL) - { - /* Error hint with this name already exists */ - } - else - { - create_named_hint(session, hintname, rval); - hint_push(session, hint_dup(rval)); - } - } - else if (hintname && rval == NULL) - { - /* We starting an already define set of named hints */ - rval = lookup_named_hint(session, hintname); - hint_push(session, hint_dup(rval)); - rval = NULL; - } - else if (hintname == NULL && rval == NULL) - { - /* Error case */ - } - break; - - case HM_PREPARE: - /* - * We are preparing a named set of hints. Note this does - * not trigger the usage of these hints currently. - */ - if (hintname == NULL || rval == NULL) - { - /* Error case, name and hints must be defined */ - } - else - { - create_named_hint(session, hintname, rval); - } - /* We are not starting the hints now, so return an empty - * hint set. - */ - rval = NULL; - break; - - case HM_EXECUTE: - /* - * We have a one-off hint for the statement we are - * currently forwarding. - */ - break; - } - -retblock: - MXS_FREE(hintname); - - if (rval == NULL) - { - /* No new hint parsed in this statement, apply the current - * top of stack if there is one. - */ - if (session->stack) - { - rval = hint_dup(session->stack->hint); - } - } - return rval; -} - -/** - * Return the next token in the inout stream - * - * @param buf A pointer to the buffer point, will be updated if a - * new buffer is used. - * @param ptr The pointer within the buffer we are processing - * @return A HINT token - */ -static HINT_TOKEN* hint_next_token(GWBUF** buf, char** ptr) -{ - char word[100], * dest; - int inword = 0; - int endtag = 0; - char inquote = '\0'; - int i, found; - HINT_TOKEN* tok; - - if ((tok = (HINT_TOKEN*)MXS_MALLOC(sizeof(HINT_TOKEN))) == NULL) - { - return NULL; - } - tok->value = NULL; - dest = word; - while (*ptr < (char*)((*buf)->end) || (*buf)->next) - { - /** word ends, don't move ptr but return with read word */ - if (inword && inquote == '\0' - && (isspace(**ptr) || **ptr == '=')) - { - inword = 0; - break; - } - /** found '=' or '\n', move ptr and return with the char */ - else if (!inword && inquote == '\0' && ((**ptr == '=') || (**ptr == '\n'))) - { - *dest = **ptr; - dest++; - (*ptr)++; - break; - } - else if (**ptr == '\'' && inquote == '\'') - { - inquote = '\0'; - } - else if (**ptr == '\'') - { - inquote = **ptr; - } - /** Any other character which belongs to the word, move ahead */ - - else if (**ptr == '/' && endtag) - { - /** Comment end tag found, rewind the pointer back and return the token */ - inword = 0; - (*ptr)--; - break; - } - else if (**ptr == '*' && !endtag) - { - endtag = 1; - } - else if (inword || (isspace(**ptr) == 0)) - { - *dest++ = **ptr; - inword = 1; - } - (*ptr)++; - - if (*ptr > (char*)((*buf)->end) && (*buf)->next) - { - *buf = (*buf)->next; - *ptr = static_cast((*buf)->start); - } - - if (dest - word > 98) - { - break; - } - } /*< while */ - *dest = 0; - - /* We now have a word in the local word, check to see if it is a - * token we recognise. - */ - if (word[0] == '\0' || (word[0] == '*' && word[1] == '/')) - { - tok->token = TOK_END; - return tok; - } - if (word[0] == '\n') - { - tok->token = TOK_LINEBRK; - return tok; - } - found = 0; - for (i = 0; keywords[i].keyword; i++) - { - if (strcasecmp(word, keywords[i].keyword) == 0) - { - tok->token = keywords[i].token; - found = 1; - break; - } - } - if (found == 0) - { - tok->token = TOK_STRING; - tok->value = MXS_STRDUP_A(word); - } - - return tok; -} - /** * hint_pop - pop the hint off the top of the stack if it is not empty *