446 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			446 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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 maxinfo_parse.c - Parse the limited set of SQL that the MaxScale
 | |
|  * information schema can use
 | |
|  *
 | |
|  * @verbatim
 | |
|  * Revision History
 | |
|  *
 | |
|  * Date     Who           Description
 | |
|  * 16/02/15 Mark Riddoch  Initial implementation
 | |
|  *
 | |
|  * @endverbatim
 | |
|  */
 | |
| 
 | |
| #include "maxinfo.h"
 | |
| 
 | |
| #include <ctype.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <maxscale/alloc.h>
 | |
| #include <maxscale/service.h>
 | |
| #include <maxscale/session.h>
 | |
| #include <maxscale/router.h>
 | |
| #include <maxscale/modinfo.h>
 | |
| #include <maxscale/modutil.h>
 | |
| #include <maxscale/atomic.h>
 | |
| #include <maxscale/spinlock.h>
 | |
| #include <maxscale/dcb.h>
 | |
| #include <maxscale/poll.h>
 | |
| #include <maxscale/log_manager.h>
 | |
| 
 | |
| static MAXINFO_TREE *make_tree_node(MAXINFO_OPERATOR, char *, MAXINFO_TREE *, MAXINFO_TREE *);
 | |
| void maxinfo_free_tree(MAXINFO_TREE *); // This function is needed by maxinfo.c
 | |
| static char *fetch_token(char *, int *, char **);
 | |
| static MAXINFO_TREE *parse_column_list(char **sql);
 | |
| static MAXINFO_TREE *parse_table_name(char **sql);
 | |
| MAXINFO_TREE* maxinfo_parse_literals(MAXINFO_TREE *tree, int min_args, char *ptr,
 | |
|                                      PARSE_ERROR *parse_error);
 | |
| 
 | |
| /**
 | |
|  * Parse a SQL subset for the maxinfo plugin and return a parse tree
 | |
|  *
 | |
|  * @param sql       The SQL query
 | |
|  * @return  Parse tree or NULL on error
 | |
|  */
 | |
| MAXINFO_TREE *
 | |
| maxinfo_parse(char *sql, PARSE_ERROR *parse_error)
 | |
| {
 | |
|     int token;
 | |
|     char *ptr, *text;
 | |
|     MAXINFO_TREE *tree = NULL;
 | |
|     MAXINFO_TREE *col, *table;
 | |
| 
 | |
|     *parse_error = PARSE_NOERROR;
 | |
|     while ((ptr = fetch_token(sql, &token, &text)) != NULL)
 | |
|     {
 | |
|         switch (token)
 | |
|         {
 | |
|         case LT_SHOW:
 | |
|             MXS_FREE(text); // not needed
 | |
|             ptr = fetch_token(ptr, &token, &text);
 | |
|             if (ptr == NULL || token != LT_STRING)
 | |
|             {
 | |
|                 // Expected show "name"
 | |
|                 *parse_error = PARSE_MALFORMED_SHOW;
 | |
|                 return NULL;
 | |
|             }
 | |
|             tree = make_tree_node(MAXOP_SHOW, text, NULL, NULL);
 | |
|             if ((ptr = fetch_token(ptr, &token, &text)) == NULL)
 | |
|             {
 | |
|                 return tree;
 | |
|             }
 | |
|             else if (token == LT_LIKE)
 | |
|             {
 | |
|                 if ((ptr = fetch_token(ptr, &token, &text)) != NULL)
 | |
|                 {
 | |
|                     tree->right = make_tree_node(MAXOP_LIKE,
 | |
|                                                  text, NULL, NULL);
 | |
|                     return tree;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     // Expected expression
 | |
|                     *parse_error = PARSE_EXPECTED_LIKE;
 | |
|                     maxinfo_free_tree(tree);
 | |
|                     return NULL;
 | |
|                 }
 | |
|             }
 | |
|             // Malformed show
 | |
|             MXS_FREE(text);
 | |
|             maxinfo_free_tree(tree);
 | |
|             *parse_error = PARSE_MALFORMED_SHOW;
 | |
|             return NULL;
 | |
| #if 0
 | |
|         case LT_SELECT:
 | |
|             MXS_FREE(text); // not needed
 | |
|             col = parse_column_list(&ptr);
 | |
|             table = parse_table_name(&ptr);
 | |
|             return make_tree_node(MAXOP_SELECT, NULL, col, table);
 | |
| #endif
 | |
|         case LT_FLUSH:
 | |
|             MXS_FREE(text); // not needed
 | |
|             ptr = fetch_token(ptr, &token, &text);
 | |
|             return make_tree_node(MAXOP_FLUSH, text, NULL, NULL);
 | |
| 
 | |
|         case LT_SHUTDOWN:
 | |
|             MXS_FREE(text);
 | |
|             ptr = fetch_token(ptr, &token, &text);
 | |
|             tree = make_tree_node(MAXOP_SHUTDOWN, text, NULL, NULL);
 | |
| 
 | |
|             if ((ptr = fetch_token(ptr, &token, &text)) == NULL)
 | |
|             {
 | |
|                 /** Possibly SHUTDOWN MAXSCALE */
 | |
|                 return tree;
 | |
|             }
 | |
|             tree->right = make_tree_node(MAXOP_LITERAL, text, NULL, NULL);
 | |
| 
 | |
|             if ((ptr = fetch_token(ptr, &token, &text)) != NULL)
 | |
|             {
 | |
|                 /** Unknown token after SHUTDOWN MONITOR|SERVICE */
 | |
|                 *parse_error = PARSE_SYNTAX_ERROR;
 | |
|                 maxinfo_free_tree(tree);
 | |
|                 return NULL;
 | |
|             }
 | |
|             return tree;
 | |
| 
 | |
|         case LT_RESTART:
 | |
|             MXS_FREE(text);
 | |
|             ptr = fetch_token(ptr, &token, &text);
 | |
|             tree = make_tree_node(MAXOP_RESTART, text, NULL, NULL);
 | |
| 
 | |
|             if ((ptr = fetch_token(ptr, &token, &text)) == NULL)
 | |
|             {
 | |
|                 /** Missing token for RESTART MONITOR|SERVICE */
 | |
|                 *parse_error = PARSE_SYNTAX_ERROR;
 | |
|                 maxinfo_free_tree(tree);
 | |
|                 return NULL;
 | |
|             }
 | |
|             tree->right = make_tree_node(MAXOP_LITERAL, text, NULL, NULL);
 | |
| 
 | |
|             if ((ptr = fetch_token(ptr, &token, &text)) != NULL)
 | |
|             {
 | |
|                 /** Unknown token after RESTART MONITOR|SERVICE */
 | |
|                 *parse_error = PARSE_SYNTAX_ERROR;
 | |
|                 MXS_FREE(text);
 | |
|                 maxinfo_free_tree(tree);
 | |
|                 return NULL;
 | |
|             }
 | |
|             return tree;
 | |
| 
 | |
|         case LT_SET:
 | |
|             MXS_FREE(text); // not needed
 | |
|             ptr = fetch_token(ptr, &token, &text);
 | |
|             tree = make_tree_node(MAXOP_SET, text, NULL, NULL);
 | |
|             return maxinfo_parse_literals(tree, 2, ptr, parse_error);
 | |
| 
 | |
|         case LT_CLEAR:
 | |
|             MXS_FREE(text); // not needed
 | |
|             ptr = fetch_token(ptr, &token, &text);
 | |
|             tree = make_tree_node(MAXOP_CLEAR, text, NULL, NULL);
 | |
|             return maxinfo_parse_literals(tree, 2, ptr, parse_error);
 | |
|             break;
 | |
|         default:
 | |
|             *parse_error = PARSE_SYNTAX_ERROR;
 | |
|             return NULL;
 | |
|         }
 | |
|     }
 | |
|     *parse_error = PARSE_SYNTAX_ERROR;
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Parse a column list, may be a * or a valid list of string name
 | |
|  * separated by a comma
 | |
|  *
 | |
|  * @param sql   Pointer to pointer to column list updated to point to the table name
 | |
|  * @return  A tree of column names
 | |
|  */
 | |
| static MAXINFO_TREE *
 | |
| parse_column_list(char **ptr)
 | |
| {
 | |
|     int token, lookahead;
 | |
|     char *text, *text2;
 | |
|     MAXINFO_TREE *tree = NULL;
 | |
|     MAXINFO_TREE *rval = NULL;
 | |
|     *ptr = fetch_token(*ptr, &token, &text);
 | |
|     *ptr = fetch_token(*ptr, &lookahead, &text2);
 | |
|     switch (token)
 | |
|     {
 | |
|     case LT_STRING:
 | |
|         switch (lookahead)
 | |
|         {
 | |
|         case LT_COMMA:
 | |
|             rval = make_tree_node(MAXOP_COLUMNS, text, NULL,
 | |
|                                   parse_column_list(ptr));
 | |
|             break;
 | |
|         case LT_FROM:
 | |
|             rval = make_tree_node(MAXOP_COLUMNS, text, NULL,
 | |
|                                   NULL);
 | |
|             break;
 | |
|         default:
 | |
|             break;
 | |
|         }
 | |
|         break;
 | |
|     case LT_STAR:
 | |
|         if (lookahead != LT_FROM)
 | |
|             rval = make_tree_node(MAXOP_ALL_COLUMNS, NULL, NULL,
 | |
|                                   NULL);
 | |
|         break;
 | |
|     default:
 | |
|         break;
 | |
|     }
 | |
|     MXS_FREE(text);
 | |
|     MXS_FREE(text2);
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Parse a table name
 | |
|  *
 | |
|  * @param sql   Pointer to pointer to column list updated to point to the table name
 | |
|  * @return  A tree of table names
 | |
|  */
 | |
| static MAXINFO_TREE *
 | |
| parse_table_name(char **ptr)
 | |
| {
 | |
|     int token;
 | |
|     char *text;
 | |
|     MAXINFO_TREE *tree = NULL;
 | |
| 
 | |
|     *ptr = fetch_token(*ptr, &token, &text);
 | |
|     if  (token == LT_STRING)
 | |
|     {
 | |
|         return make_tree_node(MAXOP_TABLE, text, NULL, NULL);
 | |
|     }
 | |
|     MXS_FREE(text);
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Allocate and populate a parse tree node
 | |
|  *
 | |
|  * @param op    The node operator
 | |
|  * @param value The node value
 | |
|  * @param left  The left branch of the parse tree
 | |
|  * @param right The right branch of the parse tree
 | |
|  * @return The new parse tree node
 | |
|  */
 | |
| static MAXINFO_TREE *
 | |
| make_tree_node(MAXINFO_OPERATOR op, char *value, MAXINFO_TREE *left, MAXINFO_TREE *right)
 | |
| {
 | |
|     MAXINFO_TREE *node;
 | |
| 
 | |
|     if ((node = (MAXINFO_TREE *)MXS_MALLOC(sizeof(MAXINFO_TREE))) == NULL)
 | |
|     {
 | |
|         return NULL;
 | |
|     }
 | |
|     node->op = op;
 | |
|     node->value = value;
 | |
|     node->left = left;
 | |
|     node->right = right;
 | |
| 
 | |
|     return node;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Recursively free the storage associated with a parse tree
 | |
|  *
 | |
|  * @param tree  The parse tree to free
 | |
|  */
 | |
| void
 | |
| maxinfo_free_tree(MAXINFO_TREE *tree)
 | |
| {
 | |
|     if (tree->left)
 | |
|     {
 | |
|         maxinfo_free_tree(tree->left);
 | |
|     }
 | |
|     if (tree->right)
 | |
|     {
 | |
|         maxinfo_free_tree(tree->right);
 | |
|     }
 | |
|     if (tree->value)
 | |
|     {
 | |
|         MXS_FREE(tree->value);
 | |
|     }
 | |
|     MXS_FREE(tree);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * The set of keywords known to the tokeniser
 | |
|  */
 | |
| static struct
 | |
| {
 | |
|     char *text;
 | |
|     int token;
 | |
| } keywords[] =
 | |
| {
 | |
|     { "show",       LT_SHOW},
 | |
|     { "select",     LT_SELECT},
 | |
|     { "from",       LT_FROM},
 | |
|     { "like",       LT_LIKE},
 | |
|     { "=",          LT_EQUAL},
 | |
|     { ",",          LT_COMMA},
 | |
|     { "*",          LT_STAR},
 | |
|     { "flush",      LT_FLUSH},
 | |
|     { "set",        LT_SET},
 | |
|     { "clear",      LT_CLEAR},
 | |
|     { "shutdown",   LT_SHUTDOWN},
 | |
|     { "restart",    LT_RESTART},
 | |
|     { NULL, 0}
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Limited SQL tokeniser. Understands a limited set of key words and
 | |
|  * quoted strings.
 | |
|  *
 | |
|  * @param sql   The SQL to tokenise
 | |
|  * @param token The returned token
 | |
|  * @param text  The matching text
 | |
|  * @return  The next position to tokenise from
 | |
|  */
 | |
| static char *
 | |
| fetch_token(char *sql, int *token, char **text)
 | |
| {
 | |
|     char *s1, *s2, quote = '\0';
 | |
|     int i;
 | |
| 
 | |
|     s1 = sql;
 | |
|     while (*s1 && isspace(*s1))
 | |
|     {
 | |
|         s1++;
 | |
|     }
 | |
|     if (quote == '\0' && (*s1 == '\'' || *s1 == '\"'))
 | |
|     {
 | |
|         quote = *s1++;
 | |
|     }
 | |
|     if (*s1 == '/' && *(s1 + 1) == '*')
 | |
|     {
 | |
|         s1 += 2;
 | |
|         // Skip the comment
 | |
|         do
 | |
|         {
 | |
|             while (*s1 && *s1 != '*')
 | |
|             {
 | |
|                 s1++;
 | |
|             }
 | |
|         }
 | |
|         while (*(s1 + 1) && *(s1 + 1) != '/');
 | |
|         s1 += 2;
 | |
|         while (*s1 && isspace(*s1))
 | |
|         {
 | |
|             s1++;
 | |
|         }
 | |
|         if (quote == '\0' && (*s1 == '\'' || *s1 == '\"'))
 | |
|         {
 | |
|             quote = *s1++;
 | |
|         }
 | |
|     }
 | |
|     s2 = s1;
 | |
|     while (*s2)
 | |
|     {
 | |
|         if (quote == '\0' && (isspace(*s2)
 | |
|                               || *s2 == ',' || *s2 == '='))
 | |
|         {
 | |
|             break;
 | |
|         }
 | |
|         else if (quote == *s2)
 | |
|         {
 | |
|             break;
 | |
|         }
 | |
|         s2++;
 | |
|     }
 | |
| 
 | |
|     if (*s1 == '@' && *(s1 + 1) == '@')
 | |
|     {
 | |
|         *text = strndup(s1 + 2, (s2 - s1) - 2);
 | |
|         *token = LT_VARIABLE;
 | |
|         return s2;
 | |
|     }
 | |
| 
 | |
|     if (s1 == s2)
 | |
|     {
 | |
|         *text = NULL;
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     *text = strndup(s1, s2 - s1);
 | |
|     for (i = 0; keywords[i].text; i++)
 | |
|     {
 | |
|         if (strcasecmp(keywords[i].text, *text) == 0)
 | |
|         {
 | |
|             *token = keywords[i].token;
 | |
|             return s2;
 | |
|         }
 | |
|     }
 | |
|     *token = LT_STRING;
 | |
|     return s2;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Parse the remaining arguments as literals.
 | |
|  * @param tree Previous head of the parse tree
 | |
|  * @param min_args Minimum required number of arguments
 | |
|  * @param ptr Pointer to client command
 | |
|  * @param parse_error Pointer to parsing error to fill
 | |
|  * @return Parsed tree or NULL if parsing failed
 | |
|  */
 | |
| MAXINFO_TREE* maxinfo_parse_literals(MAXINFO_TREE *tree, int min_args, char *ptr,
 | |
|                                      PARSE_ERROR *parse_error)
 | |
| {
 | |
|     int token;
 | |
|     MAXINFO_TREE* node = tree;
 | |
|     char *text;
 | |
|     for (int i = 0; i < min_args; i++)
 | |
|     {
 | |
|         if ((ptr = fetch_token(ptr, &token, &text)) == NULL ||
 | |
|             (node->right = make_tree_node(MAXOP_LITERAL, text, NULL, NULL)) == NULL)
 | |
|         {
 | |
|             *parse_error = PARSE_SYNTAX_ERROR;
 | |
|             maxinfo_free_tree(tree);
 | |
|             if (ptr)
 | |
|             {
 | |
|                 MXS_FREE(text);
 | |
|             }
 | |
|             return NULL;
 | |
|         }
 | |
|         node = node->right;
 | |
|     }
 | |
| 
 | |
|     return tree;
 | |
| }
 | 
