From 46ca0676c0daffc14f9f2e6f6d034eb52d9b04f8 Mon Sep 17 00:00:00 2001 From: Mark Riddoch Date: Wed, 4 Jun 2014 23:04:17 +0100 Subject: [PATCH] A filter parameter fix and a new filter, the regex filter. The regexfilter is an example of a query rewriting filter that uses regular expressions to match the SQL text and allows a replacement text to be used for each match of the regular expression. Regexfilter has no special SQL undertanding, it merely uses the features avialable inthe regex library to perform matches. Statements that span multiple packets are not supported by the regex filter. --- server/core/filter.c | 2 +- server/include/modutil.h | 1 + server/modules/filter/Makefile | 11 +- server/modules/filter/regexfilter.c | 354 ++++++++++++++++++++++++++++ 4 files changed, 363 insertions(+), 5 deletions(-) create mode 100644 server/modules/filter/regexfilter.c diff --git a/server/core/filter.c b/server/core/filter.c index 9336d6903..85327a475 100644 --- a/server/core/filter.c +++ b/server/core/filter.c @@ -271,7 +271,7 @@ int i; { for (i = 0; filter->parameters[i]; i++) ; - filter->parameters = (FILTER_PARAMETER **)realloc(filter->options, + filter->parameters = (FILTER_PARAMETER **)realloc(filter->parameters, (i + 2) * sizeof(FILTER_PARAMETER *)); } filter->parameters[i] = (FILTER_PARAMETER *)calloc(1, sizeof(FILTER_PARAMETER)); diff --git a/server/include/modutil.h b/server/include/modutil.h index 3dbe82132..5949cb059 100644 --- a/server/include/modutil.h +++ b/server/include/modutil.h @@ -33,4 +33,5 @@ extern int modutil_is_SQL(GWBUF *); extern int modutil_extract_SQL(GWBUF *, char **, int *); +extern GWBUF *modutil_repalce_SQL(GWBUF *, char *); #endif diff --git a/server/modules/filter/Makefile b/server/modules/filter/Makefile index 273ca44d3..14b226b7d 100644 --- a/server/modules/filter/Makefile +++ b/server/modules/filter/Makefile @@ -36,10 +36,12 @@ TESTSRCS=testfilter.c TESTOBJ=$(TESTSRCS:.c=.o) QLASRCS=qlafilter.c QLAOBJ=$(QLASRCS:.c=.o) -SRCS=$(TESTSRCS) +REGEXSRCS=regexfilter.c +REGEXOBJ=$(REGEXSRCS:.c=.o) +SRCS=$(TESTSRCS) $(QLASRCS) $(REGEXSRCS) OBJ=$(SRCS:.c=.o) LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -MODULES= libtestfilter.so libqlafilter.so +MODULES= libtestfilter.so libqlafilter.so libregexfilter.so all: $(MODULES) @@ -50,6 +52,9 @@ libtestfilter.so: $(TESTOBJ) libqlafilter.so: $(QLAOBJ) $(CC) $(LDFLAGS) $(QLAOBJ) $(LIBS) -o $@ +libregexfilter.so: $(REGEXOBJ) + $(CC) $(LDFLAGS) $(REGEXOBJ) $(LIBS) -o $@ + .c.o: $(CC) $(CFLAGS) $< -o $@ @@ -70,12 +75,10 @@ cleantests: $(MAKE) -C test cleantests buildtests: - $(MAKE) -C readwritesplit/test DEBUG=Y buildtests $(MAKE) -C test DEBUG=Y buildtests runtests: $(MAKE) -C test runtests - $(MAKE) -C readwritesplit runtests testall: $(MAKE) -C test testall diff --git a/server/modules/filter/regexfilter.c b/server/modules/filter/regexfilter.c new file mode 100644 index 000000000..882b1fd68 --- /dev/null +++ b/server/modules/filter/regexfilter.c @@ -0,0 +1,354 @@ +/* + * This file is distributed as part of MaxScale by SkySQL. 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 SkySQL Ab 2014 + */ +#include +#include +#include +#include +#include +#include + +/** + * regexfilter.c - a very simple regular expression rewrite filter. + * + * A simple regular expression query rewrite filter. + * Two parameters should be defined in the filter configuration + * match= + * replace= + */ + +MODULE_INFO info = { + MODULE_API_FILTER, + MODULE_ALPHA_RELEASE, + FILTER_VERSION, + "A query rewrite filter that uses regular expressions to rewite queries" +}; + +static char *version_str = "V1.0.0"; + +static FILTER *createInstance(char **options, FILTER_PARAMETER **params); +static void *newSession(FILTER *instance, SESSION *session); +static void closeSession(FILTER *instance, void *session); +static void freeSession(FILTER *instance, void *session); +static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream); +static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); +static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); + +static char *regex_replace(char *sql, int length, regex_t *re, char *replace); + +static FILTER_OBJECT MyObject = { + createInstance, + newSession, + closeSession, + freeSession, + setDownstream, + routeQuery, + diagnostic, +}; + +/** + * Instance structure + */ +typedef struct { + char *match; /* Regular expression to match */ + char *replace; /* Replacement text */ + regex_t re; /* Compiled regex text */ +} REGEX_INSTANCE; + +/** + * The session structure for this regex filter + */ +typedef struct { + DOWNSTREAM down; + int no_change; + int replacements; +} REGEX_SESSION; + +/** + * Implementation of the mandatory version entry point + * + * @return version string of the module + */ +char * +version() +{ + return version_str; +} + +/** + * The module initialisation routine, called when the module + * is first loaded. + */ +void +ModuleInit() +{ +} + +/** + * The module entry point routine. It is this routine that + * must populate the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ +FILTER_OBJECT * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Create an instance of the filter for a particular service + * within MaxScale. + * + * @param options The options for this filter + * + * @return The instance data for this new instance + */ +static FILTER * +createInstance(char **options, FILTER_PARAMETER **params) +{ +REGEX_INSTANCE *my_instance; +int i; + + if ((my_instance = calloc(1, sizeof(REGEX_INSTANCE))) != NULL) + { + my_instance->match = NULL; + my_instance->replace = NULL; + + for (i = 0; params[i]; i++) + { + if (!strcmp(params[i]->name, "match")) + my_instance->match = strdup(params[i]->value); + if (!strcmp(params[i]->name, "replace")) + my_instance->replace = strdup(params[i]->value); + } + if (my_instance->match == NULL || my_instance->replace == NULL) + { + return NULL; + } + + if (regcomp(&my_instance->re, my_instance->match, REG_ICASE)) + { + free(my_instance->match); + free(my_instance->replace); + free(my_instance); + return NULL; + } + } + return (FILTER *)my_instance; +} + +/** + * Associate a new session with this instance of the filter. + * + * @param instance The filter instance data + * @param session The session itself + * @return Session specific data for this session + */ +static void * +newSession(FILTER *instance, SESSION *session) +{ +REGEX_INSTANCE *my_instance = (REGEX_INSTANCE *)instance; +REGEX_SESSION *my_session; + + if ((my_session = calloc(1, sizeof(REGEX_SESSION))) != NULL) + { + my_session->no_change = 0; + my_session->replacements = 0; + } + + return my_session; +} + +/** + * Close a session with the filter, this is the mechanism + * by which a filter may cleanup data structure etc. + * + * @param instance The filter instance data + * @param session The session being closed + */ +static void +closeSession(FILTER *instance, void *session) +{ +} + +/** + * Free the memory associated with this filter session. + * + * @param instance The filter instance data + * @param session The session being closed + */ +static void +freeSession(FILTER *instance, void *session) +{ + free(session); + return; +} + +/** + * Set the downstream component for this filter. + * + * @param instance The filter instance data + * @param session The session being closed + * @param downstream The downstream filter or router + */ +static void +setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream) +{ +REGEX_SESSION *my_session = (REGEX_SESSION *)session; + + my_session->down = *downstream; +} + +/** + * The routeQuery entry point. This is passed the query buffer + * to which the filter should be applied. Once applied the + * query shoudl normally be passed to the downstream component + * (filter or router) in the filter chain. + * + * @param instance The filter instance data + * @param session The filter session + * @param queue The query data + */ +static int +routeQuery(FILTER *instance, void *session, GWBUF *queue) +{ +REGEX_INSTANCE *my_instance = (REGEX_INSTANCE *)instance; +REGEX_SESSION *my_session = (REGEX_SESSION *)session; +char *sql, *newsql; +int length; + + if (modutil_is_SQL(queue)) + { + modutil_extract_SQL(queue, &sql, &length); + newsql = regex_replace(sql, length, &my_instance->re, + my_instance->replace); + if (newsql) + { + queue = modutil_replace_SQL(queue, newsql); + free(newsql); + my_session->replacements++; + } + else + my_session->no_change++; + + } + return my_session->down.routeQuery(my_session->down.instance, + my_session->down.session, queue); +} + +/** + * Diagnostics routine + * + * If fsession is NULL then print diagnostics on the filter + * instance as a whole, otherwise print diagnostics for the + * particular session. + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + * @param dcb The DCB for diagnostic output + */ +static void +diagnostic(FILTER *instance, void *fsession, DCB *dcb) +{ +REGEX_INSTANCE *my_instance = (REGEX_INSTANCE *)instance; +REGEX_SESSION *my_session = (REGEX_SESSION *)fsession; + + dcb_printf(dcb, "\t\tSearch and replace: s/%s/%s/\n", + my_instance->match, my_instance->replace); + if (my_session) + { + dcb_printf(dcb, "\t\tNo. of queries unaltered by filter: %d\n", + my_session->no_change); + dcb_printf(dcb, "\t\tNo. of queries altered by filter: %d\n", + my_session->replacements); + } +} + +/** + * Perform a regular expression match and subsititution on the SQL + * + * @param sql The original SQL text + * @param length The length of the SQL text + * @param re The compiled regular expression + * @param replace The replacement text + * @return The replaced text or NULL if no replacement was done. + */ +static char * +regex_replace(char *sql, int length, regex_t *re, char *replace) +{ +char *orig, *result, *ptr; +int i, res_size, res_length, rep_length; +int last_match; +regmatch_t match[10]; + + orig = strndup(sql, length); + if (regexec(re, orig, 10, match, 0)) + { + free(orig); + return NULL; + } + res_size = 2 * length; + result = (char *)malloc(res_size); + res_length = 0; + rep_length = strlen(replace); + last_match = 0; + + for (i = 0; i < 10; i++) + { + if (match[i].rm_so != -1) + { + if (res_length + match[i].rm_so > res_size) + { + result = (char *)realloc(result, res_size + length); + res_size += length; + } + ptr = &result[res_length]; + if (last_match < match[i].rm_so) + { + int to_copy = match[i].rm_so - last_match; + memcpy(ptr, &sql[last_match], to_copy); + res_length += to_copy; + } + last_match = match[i].rm_eo; + if (res_length + match[i].rm_so > res_size) + { + result = (char *)realloc(result, res_size + rep_length); + res_size += length; + } + ptr = &result[res_length]; + memcpy(ptr, replace, rep_length); + res_length += rep_length; + } + } + if (res_length + length - last_match + 1 > res_size) + { + result = (char *)realloc(result, res_size + length); + res_size += length; + } + if (last_match < length) + { + ptr = &result[res_length]; + memcpy(ptr, &sql[last_match], length - last_match); + res_length += length - last_match; + } + result[res_length] = 0; + + return result; +}