325 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * This file is distributed as part of MaxScale.  It is free
 | |
|  * software: you can redistribute it and/or modify it under the terms of the
 | |
|  * GNU General Public License as published by the Free Software Foundation,
 | |
|  * version 2.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful, but WITHOUT
 | |
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | |
|  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 | |
|  * details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License along with
 | |
|  * this program; if not, write to the Free Software Foundation, Inc., 51
 | |
|  * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | |
|  *
 | |
|  * Copyright MariaDB Corporation Ab 2014
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @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 <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <service.h>
 | |
| #include <session.h>
 | |
| #include <router.h>
 | |
| #include <modules.h>
 | |
| #include <modinfo.h>
 | |
| #include <modutil.h>
 | |
| #include <atomic.h>
 | |
| #include <spinlock.h>
 | |
| #include <dcb.h>
 | |
| #include <poll.h>
 | |
| #include <maxinfo.h>
 | |
| #include <skygw_utils.h>
 | |
| #include <log_manager.h>
 | |
| 
 | |
| static MAXINFO_TREE *make_tree_node(MAXINFO_OPERATOR, char *, MAXINFO_TREE *, MAXINFO_TREE *);
 | |
| static void free_tree(MAXINFO_TREE *);
 | |
| static char *fetch_token(char *, int *, char **);
 | |
| static MAXINFO_TREE *parse_column_list(char **sql);
 | |
| static MAXINFO_TREE *parse_table_name(char **sql);
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * 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:
 | |
| 			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;
 | |
| 					free_tree(tree);
 | |
| 					return NULL;
 | |
| 				}
 | |
| 			}
 | |
| 			// Malformed show
 | |
| 			free(text);
 | |
| 			free_tree(tree);
 | |
| 			*parse_error = PARSE_MALFORMED_SHOW;
 | |
| 			return NULL;
 | |
| #if 0
 | |
| 		case	LT_SELECT:
 | |
| 			free(text);	// not needed
 | |
| 			col = parse_column_list(&ptr);
 | |
| 			table = parse_table_name(&ptr);
 | |
| 			return make_tree_node(MAXOP_SELECT, NULL, col, table);
 | |
| #endif
 | |
| 		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;
 | |
| 	}
 | |
| 	free(text);
 | |
| 	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);
 | |
| 	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 *)malloc(sizeof(MAXINFO_TREE))) == NULL)
 | |
| 		return NULL;
 | |
| 	node->op = op;
 | |
| 	node->value = value;
 | |
| 	node->left = left;
 | |
| 	node->right = right;
 | |
| 
 | |
| 	return node;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Recusrsively free the storage associated with a parse tree
 | |
|  *
 | |
|  * @param tree	The parse tree to free
 | |
|  */
 | |
| static void
 | |
| free_tree(MAXINFO_TREE *tree)
 | |
| {
 | |
| 	if (tree->left)
 | |
| 		free_tree(tree->left);
 | |
| 	if (tree->right)
 | |
| 		free_tree(tree->right);
 | |
| 	if (tree->value)
 | |
| 		free(tree->value);
 | |
| 	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 },
 | |
| 	{ 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)
 | |
| 		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;
 | |
| }
 | 
