518 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			518 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| Copyright (C) 2013, MariaDB Corporation Ab
 | |
| 
 | |
| 
 | |
| This file is distributed as part of the MariaDB Corporation 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.
 | |
| 
 | |
| Author: Jan Lindström jan.lindstrom@mariadb.com
 | |
| 
 | |
| Created: 20-06-2013
 | |
| Updated:
 | |
| 
 | |
| */
 | |
| 
 | |
| #include <stdio.h>
 | |
| #include <string.h>
 | |
| #include <ctype.h>
 | |
| #include <stdlib.h>
 | |
| 
 | |
| #include "table_replication_parser.h"
 | |
| #include "table_replication_consistency.h"
 | |
| #include "log_manager.h"
 | |
| 
 | |
| namespace mysql {
 | |
| 
 | |
| namespace table_replication_parser {
 | |
| 
 | |
| typedef struct {
 | |
|         char* m_start;
 | |
|         char* m_pos;
 | |
| } tb_parser_t;
 | |
| 
 | |
| /***********************************************************************//**
 | |
| This internal function initializes internal parser data structure based on
 | |
| string to be parsed.*/
 | |
| static void
 | |
| tbr_parser_init(
 | |
| /*============*/
 | |
| 	tb_parser_t* m,  /*!< inout: Parser structure to initialize */
 | |
| 	const char* s)         /*!< in: String to parse */
 | |
| {
 | |
|         m->m_start = (char *)s;
 | |
|         m->m_pos = (char *)s;
 | |
| }
 | |
| 
 | |
| /***********************************************************************//**
 | |
| This internal function skips all space characters on front
 | |
| @return position on string with next non space character*/
 | |
| static char* 
 | |
| tbr_parser_skipwspc(
 | |
| 	char* str)  /*!< in string */
 | |
| {
 | |
|         while (isspace(*str)) {
 | |
|             str++;
 | |
|         }
 | |
|         return(str);
 | |
| }
 | |
| 
 | |
| 
 | |
| /***********************************************************************//**
 | |
| This internal function parses input string and tries to match it to the given keyword.
 | |
| @return true if next keyword matches, false if not
 | |
| */
 | |
| static bool
 | |
| tbr_match_keyword(
 | |
| /*==============*/
 | |
| 	tb_parser_t* m,        /*!< inout: Parser structure */
 | |
| 	const char* const_str) /*!< in: Keyword to match    */
 | |
| {
 | |
|         size_t len;
 | |
| 
 | |
| 	m->m_pos = tbr_parser_skipwspc(m->m_pos);
 | |
| 
 | |
|         if (const_str[0] == '\0') {
 | |
|             return(m->m_pos[0] == '\0');
 | |
|         }
 | |
| 
 | |
|         len = strlen(const_str);
 | |
| 
 | |
| 	// Parsing is based on comparing two srings ignoring case
 | |
|         if (strncasecmp(m->m_pos, const_str, len) == 0) {
 | |
| 		unsigned char c = (unsigned char)m->m_pos[len];
 | |
| 		if (isascii(c)) {
 | |
| 			if (isalnum(c)) {
 | |
| 				return (true);
 | |
| 			}
 | |
| 			if (c == '_') {
 | |
| 				return (false);
 | |
| 			}
 | |
| 		}
 | |
| 		m->m_pos += len;
 | |
| 		return(true);
 | |
|         }
 | |
|         return(false);
 | |
| }
 | |
| 
 | |
| /***********************************************************************//**
 | |
| Internal function to parse next quoted string
 | |
| @return true if quoted string found, false if not
 | |
| */
 | |
| static bool
 | |
| tbr_get_quoted(
 | |
| /*===========*/
 | |
|         tb_parser_t* m,   /*!< inout: Parser structure */
 | |
|         char* buf,        /*!< out: parsed string */
 | |
|         unsigned int size,/*!< in: buffer size */
 | |
|         bool keep_quotes) /*!< in: is quotes left on string */
 | |
| {
 | |
|         char quote;
 | |
|         tb_parser_t saved_m;
 | |
| 
 | |
| 	m->m_pos = tbr_parser_skipwspc(m->m_pos);
 | |
| 
 | |
|         saved_m = *m;
 | |
| 
 | |
|         quote = *m->m_pos++;
 | |
| 
 | |
|         if (keep_quotes) {
 | |
| 		*buf++ = quote;
 | |
| 		size--;
 | |
|         }
 | |
| 
 | |
|         while (*m->m_pos != '\0') {
 | |
| 		if (*m->m_pos == quote) {
 | |
| 			if ((m->m_pos)[1] == quote) {
 | |
| 				m->m_pos++;
 | |
| 
 | |
| 				if (keep_quotes) {
 | |
| 					*buf++ = quote;
 | |
| 
 | |
| 					if (size-- <= 1) {
 | |
| 						*m = saved_m;
 | |
| 						return(false);
 | |
| 					}
 | |
| 				}
 | |
| 			} else {
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		*buf++ = *m->m_pos++;
 | |
| 
 | |
| 		if (size-- <= 1) {
 | |
| 			*m = saved_m;
 | |
| 			return(true);
 | |
| 		}
 | |
|         }
 | |
| 
 | |
|         if (*m->m_pos != quote) {
 | |
| 		*m = saved_m;
 | |
| 		return(false);
 | |
|         }
 | |
| 
 | |
|         m->m_pos++;
 | |
| 
 | |
|         if (keep_quotes) {
 | |
| 		*buf++ = quote;
 | |
| 
 | |
| 		if (size-- <= 1) {
 | |
| 			*m = saved_m;
 | |
| 			return(true);
 | |
| 		}
 | |
|         }
 | |
| 
 | |
|         *buf = '\0';
 | |
| 
 | |
|         return(true);
 | |
| }
 | |
| 
 | |
| /***********************************************************************//**
 | |
| This internal function parses identifiers e.g. table name
 | |
| @return true if identifier is found, false if not
 | |
| */
 | |
| static bool
 | |
| tbr_get_id(
 | |
| /*=======*/
 | |
|         tb_parser_t* m, /*!< intout: Parser structure */
 | |
|         char* id_buf,   /*!< out: parsed identifier */
 | |
|         unsigned int id_size) /*!< in: identifier size */
 | |
| {
 | |
|         char* org_id_buf = id_buf;
 | |
|         tb_parser_t saved_m;
 | |
| 
 | |
| 	m->m_pos = tbr_parser_skipwspc(m->m_pos);
 | |
|         saved_m = *m;
 | |
| 
 | |
|         if (*m->m_pos == '"' || *m->m_pos == '`') {
 | |
| 		if (!tbr_get_quoted(m, id_buf, id_size, false)) {
 | |
| 			*m = saved_m;
 | |
| 			return(false);
 | |
| 		}
 | |
|         } else {
 | |
| 		while (isalnum(*m->m_pos) || *m->m_pos == '_') {
 | |
| 			*id_buf++ = *m->m_pos++;
 | |
| 
 | |
| 			if (id_size-- <= 1) {
 | |
| 				*m = saved_m;
 | |
| 				return(true);
 | |
| 			}
 | |
| 		}
 | |
| 		*id_buf = '\0';
 | |
|         }
 | |
| 
 | |
|         if (strlen(org_id_buf) > 0) {
 | |
| 		return(true);
 | |
|         } else {
 | |
| 		*m = saved_m;
 | |
| 		return(false);
 | |
|         }
 | |
| }
 | |
| 
 | |
| /***********************************************************************//**
 | |
| This internal function parses constants e.g. "."
 | |
| @return true if constant is found, false if not
 | |
| */
 | |
| static bool
 | |
| tbr_match_const(
 | |
| /*============*/
 | |
| 	tb_parser_t* m,  /*!< inout: Parser structure */
 | |
| 	const char* const_str) /*!< in: constant to be parsed */
 | |
| {
 | |
|         size_t len;
 | |
| 
 | |
| 	m->m_pos = tbr_parser_skipwspc(m->m_pos);
 | |
| 
 | |
|         if (const_str[0] == '\0') {
 | |
| 		return(m->m_pos[0] == '\0');
 | |
|         }
 | |
| 
 | |
|         len = strlen(const_str);
 | |
| 
 | |
|         if (strncasecmp(m->m_pos, const_str, len) == 0) {
 | |
| 		m->m_pos += len;
 | |
| 		return(true);
 | |
|         } else {
 | |
| 		return(false);
 | |
|         }
 | |
| }
 | |
| 
 | |
| /***********************************************************************//**
 | |
| This internal function skips to position where given keyword is found
 | |
| @return true if keyword is found, false if not
 | |
| */
 | |
| static bool
 | |
| tbr_skipto_keyword(
 | |
| /*===============*/
 | |
|         tb_parser_t* m,       /*!< inout: Parser structure */
 | |
|         const char* const_str,/*!< in: keyword to find*/
 | |
|         const char* end_str)  /*!< in: stop at this keyword */
 | |
| {
 | |
|         size_t len;
 | |
|         bool more = true;
 | |
| 
 | |
| 	m->m_pos = tbr_parser_skipwspc(m->m_pos);
 | |
| 
 | |
|         if (const_str[0] == '\0') {
 | |
| 		return(m->m_pos[0] == '\0');
 | |
|         }
 | |
| 
 | |
|         len = strlen(const_str);
 | |
| 
 | |
|         while (more) {
 | |
| 		if (strncasecmp(m->m_pos, const_str, len) == 0) {
 | |
| 			m->m_pos += len;
 | |
| 			return(true);
 | |
| 		} else {
 | |
| 			if(!(tbr_match_const(m, (char *)end_str))) {
 | |
| 				m->m_pos++;
 | |
| 
 | |
| 				if (*(m->m_pos) == '\0'){
 | |
| 					return (false);
 | |
| 				}
 | |
| 			} else {
 | |
| 				m->m_pos-=strlen(end_str);
 | |
| 				return (false);
 | |
| 			}
 | |
| 		}
 | |
|         }
 | |
| 
 | |
|         return(true);
 | |
| }
 | |
| 
 | |
| /***********************************************************************//**
 | |
| This internal function parses table name consisting database + "." + table
 | |
| @return true if table name is found, false if not
 | |
| */
 | |
| static bool
 | |
| tbr_get_tablename(
 | |
| /*==============*/
 | |
|         tb_parser_t* m,            /*!< inout: Parser structure */
 | |
|         char* dbname_buf,          /*!< out: Database name or empty string */
 | |
|         size_t dbname_size,        /*!< in: size of db buffer */
 | |
|         char* tabname_buf,         /*!< out: Tablename or empty string */
 | |
|         size_t tabname_size)       /*!< in: size of tablename buffer */
 | |
| {
 | |
|         tb_parser_t saved_m;
 | |
| 
 | |
|         saved_m = *m;
 | |
| 
 | |
| 	/* Try to parse database name */
 | |
|         if (!tbr_get_id(m, dbname_buf, dbname_size)) {
 | |
| 		return(false);
 | |
|         }
 | |
| 
 | |
| 	/* If string does not contain constant "." there is no database name */
 | |
|         if (!tbr_match_const(m, (char *)".")) {
 | |
| 		*m = saved_m;
 | |
| 		dbname_buf[0] = '\0';
 | |
| 
 | |
| 		if (!tbr_get_id(m, tabname_buf, tabname_size)) {
 | |
| 			return(false);
 | |
| 		}
 | |
| 		return(true);
 | |
|         }
 | |
| 
 | |
| 	/* Try to parser table name */
 | |
|         if (!tbr_get_id(m, tabname_buf, tabname_size)) {
 | |
| 		return(false);
 | |
|         }
 | |
| 
 | |
|         return(true);
 | |
| }
 | |
| 
 | |
| /***********************************************************************//**
 | |
| This function parses SQL-clauses and extracts table names
 | |
| from the clause.
 | |
| @return true if table names found, false if not
 | |
| */
 | |
| bool
 | |
| tbr_parser_table_names(
 | |
| /*===================*/
 | |
|         char **db_name,         /*!< inout: Array of db names */
 | |
| 	char **table_name,      /*!< inout: Array of table names */
 | |
| 	int *n_tables,          /*!< out: Number of db.table names found */
 | |
| 	const char* sql_string) /*!< in: SQL-clause */
 | |
| {
 | |
|         tb_parser_t m;
 | |
| 	size_t name_count=0;
 | |
| 	char *dbname=NULL;
 | |
| 	char *tbname=NULL;
 | |
| 	size_t len = strlen(sql_string);
 | |
| 
 | |
| 	tbr_parser_init(&m, sql_string);
 | |
| 	*n_tables = 0;
 | |
| 
 | |
| 	// MySQL does not support multi-table insert or replace
 | |
| 	if ((tbr_match_keyword(&m, "INSERT") || tbr_match_keyword(&m, "REPLACE")) &&
 | |
| 		tbr_skipto_keyword(&m, "INTO", "")) {
 | |
| 		dbname = (char *)malloc(len+1);
 | |
| 		tbname = (char *)malloc(len+1);
 | |
| 
 | |
| 		if (tbr_get_tablename(&m, dbname, len, tbname, len)) {
 | |
| 			db_name[name_count] = dbname;
 | |
| 			table_name[name_count] = tbname;
 | |
| 			name_count++;
 | |
| 
 | |
| 			if (tbr_debug) {
 | |
| 				skygw_log_write_flush( LOGFILE_TRACE,
 | |
| 					(char *)"TRC Debug: INSERT OR REPLACE to %s.%s",
 | |
| 					dbname, tbname);
 | |
| 			}
 | |
| 		} else {
 | |
| 			free(dbname);
 | |
| 			free(tbname);
 | |
| 			return (false); // Parse error
 | |
| 		}
 | |
| 	}
 | |
| 	// MySQL does support multi table delete/update
 | |
| 	if ((tbr_match_keyword(&m, "DELETE") &&
 | |
| 		tbr_skipto_keyword(&m, "FROM","")) ||
 | |
| 		(tbr_match_keyword(&m, "UPDATE"))) {
 | |
| 		dbname = (char *)malloc(len+1);
 | |
| 		tbname = (char *)malloc(len+1);
 | |
| 
 | |
| 		// These will eat the optional keywords from update
 | |
| 		tbr_match_keyword(&m, "LOW PRIORITY");
 | |
| 		tbr_match_keyword(&m, "IGNORE");
 | |
| 
 | |
| 		// Parse the first db.table name
 | |
| 		if (tbr_get_tablename(&m, dbname, len,tbname,len)) {
 | |
| 			db_name[name_count] = dbname;
 | |
| 			table_name[name_count] = tbname;
 | |
| 			name_count++;
 | |
| 
 | |
| 			// Table names are delimited by ","
 | |
| 			while(tbr_match_const(&m, ",")) {
 | |
| 				dbname = (char *)malloc(len+1);
 | |
| 				tbname = (char *)malloc(len+1);
 | |
| 				// Parse the next db.table name
 | |
| 				if (tbr_get_tablename(&m, dbname, len,tbname,len)) {
 | |
| 					db_name[name_count] = dbname;
 | |
| 					table_name[name_count] = tbname;
 | |
| 					name_count++;
 | |
| 
 | |
| 					if (tbr_debug) {
 | |
| 						skygw_log_write_flush( LOGFILE_TRACE,
 | |
| 							(char *)"TRC Debug: DELETE OR UPDATE to %s.%s",
 | |
| 							dbname, tbname);
 | |
| 					}
 | |
| 				} else {
 | |
| 					free(dbname);
 | |
| 					free(tbname);
 | |
| 					return (false);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// LOAD command
 | |
| 	if (tbr_match_keyword(&m, "LOAD") &&
 | |
| 		tbr_skipto_keyword(&m, "INTO", "")) {
 | |
| 
 | |
| 		// Eat TABLE keyword
 | |
| 		tbr_match_keyword(&m, "TABLE");
 | |
| 
 | |
| 		dbname = (char *)malloc(len+1);
 | |
| 		tbname = (char *)malloc(len+1);
 | |
| 
 | |
| 		if (tbr_get_tablename(&m, dbname, len, tbname, len)) {
 | |
| 			db_name[name_count] = dbname;
 | |
| 			table_name[name_count] = tbname;
 | |
| 			name_count++;
 | |
| 
 | |
| 			if (tbr_debug) {
 | |
| 				skygw_log_write_flush( LOGFILE_TRACE,
 | |
| 					(char *)"TRC Debug: LOAD to %s.%s",
 | |
| 					dbname, tbname);
 | |
| 			}
 | |
| 		} else {
 | |
| 			free(dbname);
 | |
| 			free(tbname);
 | |
| 			return (false); // Parse error
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Create/Drop table
 | |
| 	if (tbr_match_keyword(&m, "CREATE") &&
 | |
| 		tbr_skipto_keyword(&m, "DROP", "")) {
 | |
| 
 | |
| 		// Eat TEMPORARY keyword
 | |
| 		tbr_match_keyword(&m, "TEMPORARY");
 | |
| 
 | |
| 		// Eat IF NOT EXISTS
 | |
| 		tbr_match_keyword(&m, "IF NOT EXISTS");
 | |
| 
 | |
| 		// Eat IF EXISTS
 | |
| 		tbr_match_keyword(&m, "IF EXISTS");
 | |
| 
 | |
| 		// Eat TABLE keyword
 | |
| 		tbr_match_keyword(&m, "TABLE");
 | |
| 
 | |
| 		dbname = (char *)malloc(len+1);
 | |
| 		tbname = (char *)malloc(len+1);
 | |
| 
 | |
| 		if (tbr_get_tablename(&m, dbname, len, tbname, len)) {
 | |
| 			db_name[name_count] = dbname;
 | |
| 			table_name[name_count] = tbname;
 | |
| 			name_count++;
 | |
| 
 | |
| 			if (tbr_debug) {
 | |
| 			// Table names are delimited by ","
 | |
| 			while(tbr_match_const(&m, ",")) {
 | |
| 				dbname = (char *)malloc(len+1);
 | |
| 				tbname = (char *)malloc(len+1);
 | |
| 				// Parse the next db.table name
 | |
| 				if (tbr_get_tablename(&m, dbname, len,tbname,len)) {
 | |
| 					db_name[name_count] = dbname;
 | |
| 					table_name[name_count] = tbname;
 | |
| 					name_count++;
 | |
| 
 | |
| 					if (tbr_debug) {
 | |
| 						skygw_log_write_flush( LOGFILE_TRACE,
 | |
| 							(char *)"TRC Debug: DROP TABLE to %s.%s",
 | |
| 							dbname, tbname);
 | |
| 					}
 | |
| 				} else {
 | |
| 					free(dbname);
 | |
| 					free(tbname);
 | |
| 					return (false);
 | |
| 				}
 | |
| 			}
 | |
| 				skygw_log_write_flush( LOGFILE_TRACE,
 | |
| 					(char *)"TRC Debug: CREATE/DROP TABLE to %s.%s",
 | |
| 					dbname, tbname);
 | |
| 			}
 | |
| 		} else {
 | |
| 			free(dbname);
 | |
| 			free(tbname);
 | |
| 			return (false); // Parse error
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 	*n_tables = name_count;
 | |
| 
 | |
| 	if (name_count == 0) {
 | |
| 		return (false); // Parse error
 | |
| 	}
 | |
| 
 | |
| 	return (true);
 | |
| }
 | |
| 
 | |
| } // table_replication_parser
 | |
| 
 | |
| } // mysql
 | |
| 
 | 
