329 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			329 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;
 | 
						|
			if (tree)
 | 
						|
				free_tree(tree);
 | 
						|
			return NULL;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	*parse_error = PARSE_SYNTAX_ERROR;
 | 
						|
	if (tree)
 | 
						|
		free_tree(tree);
 | 
						|
	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;
 | 
						|
}
 |