This commit is contained in:
Markus Makela
2014-09-04 10:36:59 +03:00
80 changed files with 2981 additions and 1082 deletions

View File

@ -48,6 +48,7 @@ clean:
(cd client; touch depend.mk; make clean) (cd client; touch depend.mk; make clean)
depend: depend:
echo '#define MAXSCALE_VERSION "'`cat $(ROOT_PATH)/VERSION`'"' > $(ROOT_PATH)/server/include/version.h
(cd log_manager; make depend) (cd log_manager; make depend)
(cd query_classifier; make depend) (cd query_classifier; make depend)
(cd server; make depend) (cd server; make depend)

2
README
View File

@ -182,7 +182,7 @@ on localhost:
* a master on port 3000, with server_id=2 * a master on port 3000, with server_id=2
* a slave on port 3001, server_id doesn't matter * a slave on port 3001, server_id doesn't matter
* a slave on port 2002, server_id doesn't matter * a slave on port 3002, server_id doesn't matter
On the master full privileges on the databases "test" and "FOO" On the master full privileges on the databases "test" and "FOO"
are needed, on the saves SELECT permissions on test.* should are needed, on the saves SELECT permissions on test.* should

View File

@ -12,7 +12,7 @@
# #
# Set debug flags # Set debug flags
# #
DEBUG := DEBUG := ${MAXSCALE_DEBUG}
# #
# Set build env # Set build env
@ -22,7 +22,7 @@ UNIX := Y
# #
# Set MaxScale branch directory # Set MaxScale branch directory
# #
ROOT_PATH := $(HOME)/src/bazaar/tmp/maxscale ROOT_PATH := $(HOME)/${MAXSCALE_SOURCE}
INC_PATH := $(HOME)/usr/include INC_PATH := $(HOME)/usr/include
# #
@ -38,7 +38,7 @@ MYSQL_HEADERS := -I$(INC_PATH) -I$(MYSQL_ROOT)/ -I$(MYSQL_ROOT)/private/ -I$(MYS
# #
# Set DYNLIB=Y if you want to link MaxScale with dynamic embedded lib # Set DYNLIB=Y if you want to link MaxScale with dynamic embedded lib
# #
DYNLIB := DYNLIB := ${MAXSCALE_DYNLIB}
# #
# Set path to Embedded MySQL Server # Set path to Embedded MySQL Server
@ -51,3 +51,4 @@ endif
# Set path to MySQL errors file # Set path to MySQL errors file
# #
ERRMSG := $(HOME)/usr/share/mysql ERRMSG := $(HOME)/usr/share/mysql

View File

@ -20,6 +20,9 @@
# client program # client program
# 18/06/14 Mark Riddoch Addition of conditional for histedit # 18/06/14 Mark Riddoch Addition of conditional for histedit
include ../build_gateway.inc
include ../makefile.inc
ifeq ($(wildcard /usr/include/histedit.h), ) ifeq ($(wildcard /usr/include/histedit.h), )
HISTLIB= HISTLIB=
HISTFLAG= HISTFLAG=
@ -63,13 +66,14 @@ maxadmin: $(OBJ)
clean: clean:
rm -f $(OBJ) maxadmin $(DEL) $(OBJ) maxadmin
$(DEL) *.so
tags: tags:
ctags $(SRCS) $(HDRS) ctags $(SRCS) $(HDRS)
depend: depend:
@rm -f depend.mk @$(DEL) depend.mk
cc -M $(CFLAGS) $(SRCS) > depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk
install: maxadmin install: maxadmin

169
gcov.diff
View File

@ -1,169 +0,0 @@
diff --git a/makefile.inc b/makefile.inc
index f2d93bf..c7dbffa 100644
--- a/makefile.inc
+++ b/makefile.inc
@@ -24,8 +24,8 @@ endif
# -O2 -g -pipe -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fstack-protector --param=ssp-buffer-size=4 -fPIC
-CFLAGS := $(CFLAGS) -Wall
-LDLIBS := $(LDLIBS) -pthread
+CFLAGS := $(CFLAGS) -Wall -fprofile-arcs -ftest-coverage
+LDLIBS := $(LDLIBS) -pthread -lgcov
LDMYSQL := -lmysqld
CPP_LDLIBS := -lstdc++
diff --git a/server/core/Makefile b/server/core/Makefile
index cb0250d..fe0f579 100644
--- a/server/core/Makefile
+++ b/server/core/Makefile
@@ -46,7 +46,7 @@ CC=cc
CFLAGS=-c -I/usr/include -I../include -I../modules/include -I../inih \
$(MYSQL_HEADERS) \
-I$(LOGPATH) -I$(UTILSPATH) \
- -Wall -g
+ -Wall -g -fprofile-arcs -ftest-coverage
include ../../makefile.inc
@@ -76,7 +76,7 @@ POBJS=maxpasswd.o secrets.o utils.o
LIBS=-L$(EMBEDDED_LIB) \
-lmysqld \
-lz -lm -lcrypt -lcrypto -ldl -laio -lrt -pthread -llog_manager \
- -L../inih/extra -linih -lssl -lstdc++
+ -L../inih/extra -linih -lssl -lstdc++ -lgcov
all: maxscale maxkeys maxpasswd
diff --git a/server/modules/filter/Makefile b/server/modules/filter/Makefile
index f8cf910..ca022b8 100644
--- a/server/modules/filter/Makefile
+++ b/server/modules/filter/Makefile
@@ -25,7 +25,7 @@ UTILSPATH := $(ROOT_PATH)/utils
CC=cc
CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) \
- -I$(UTILSPATH) -Wall -g
+ -I$(UTILSPATH) -Wall -g -fprofile-arcs -ftest-coverage
include ../../../makefile.inc
@@ -44,7 +44,7 @@ TEESRCS=tee.c
TEEOBJ=$(TEESRCS:.c=.o)
SRCS=$(TESTSRCS) $(QLASRCS) $(REGEXSRCS) $(TOPNSRCS) $(TEESRCS)
OBJ=$(SRCS:.c=.o)
-LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager
+LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -lgcov
MODULES= libtestfilter.so libqlafilter.so libregexfilter.so libtopfilter.so libtee.so libhintfilter.so
diff --git a/server/modules/filter/hint/Makefile b/server/modules/filter/hint/Makefile
index 4f21947..3ad0e3b 100644
--- a/server/modules/filter/hint/Makefile
+++ b/server/modules/filter/hint/Makefile
@@ -25,7 +25,7 @@ UTILSPATH := $(ROOT_PATH)/utils
CC=cc
CFLAGS=-c -fPIC -I/usr/include -I../../include -I../../../include -I$(LOGPATH) \
- -I$(UTILSPATH) -Wall -g
+ -I$(UTILSPATH) -Wall -g -fprofile-arcs -ftest-coverage
include ../../../../makefile.inc
@@ -34,7 +34,7 @@ LDFLAGS=-shared -L$(LOGPATH) -Wl,-rpath,$(DEST)/lib \
SRCS= hintfilter.c hintparser.c
OBJ=$(SRCS:.c=.o)
-LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager
+LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -lgcov
libhintfilter.so: $(OBJ)
$(CC) $(LDFLAGS) $(OBJ) $(LIBS) -o $@
diff --git a/server/modules/monitor/Makefile b/server/modules/monitor/Makefile
index f815efe..308f496 100644
--- a/server/modules/monitor/Makefile
+++ b/server/modules/monitor/Makefile
@@ -25,7 +25,7 @@ UTILSPATH := $(ROOT_PATH)/utils
CC=cc
CFLAGS=-c -fPIC -I. -I/usr/include -I../include -I../../include -I$(LOGPATH) \
- -I$(UTILSPATH) $(MYSQL_HEADERS) -Wall -g
+ -I$(UTILSPATH) $(MYSQL_HEADERS) -Wall -g -fprofile-arcs -ftest-coverage
LDFLAGS=-shared -L$(LOGPATH) -Wl,-rpath,$(DEST)/lib \
-Wl,-rpath,$(LOGPATH) -Wl,-rpath,$(UTILSPATH) \
@@ -42,7 +42,7 @@ NDBCLUSTEROBJ=$(NDBCLUSTERSRCS:.c=.o)
SRCS=$(MYSQLSRCS) $(GALERASRCS) $(NDBCLUSTERSRCS)
OBJ=$(SRCS:.c=.o)
LIBS=$(UTILSPATH)/skygw_utils.o -llog_manager \
- -L$(EMBEDDED_LIB) -lmysqld
+ -L$(EMBEDDED_LIB) -lmysqld -lgcov
MODULES=libmysqlmon.so libgaleramon.so libndbclustermon.so
diff --git a/server/modules/protocol/Makefile b/server/modules/protocol/Makefile
index 54a8f8c..c8913ab 100644
--- a/server/modules/protocol/Makefile
+++ b/server/modules/protocol/Makefile
@@ -31,7 +31,7 @@ UTILSPATH := $(ROOT_PATH)/utils
CC=cc
CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) \
- -I$(UTILSPATH) -Wall -g
+ -I$(UTILSPATH) -Wall -g -fprofile-arcs -ftest-coverage
include ../../../makefile.inc
@@ -51,7 +51,7 @@ MAXSCALEDOBJ=$(MAXSCALEDSRCS:.c=.o)
SRCS=$(MYSQLCLIENTSRCS) $(MYSQLBACKENDSRCS) $(TELNETDSRCS) $(HTTPDSRCS) \
$(MAXSCALEDSRCS)
OBJ=$(SRCS:.c=.o)
-LIBS=$(UTILSPATH)/skygw_utils.o
+LIBS=$(UTILSPATH)/skygw_utils.o -lgcov
MODULES=libMySQLClient.so libMySQLBackend.so libtelnetd.so libHTTPD.so \
libmaxscaled.so
diff --git a/server/modules/routing/Makefile b/server/modules/routing/Makefile
index dbb0503..8304766 100644
--- a/server/modules/routing/Makefile
+++ b/server/modules/routing/Makefile
@@ -29,7 +29,7 @@ UTILSPATH := $(ROOT_PATH)/utils
CC=cc
CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) \
- -I$(UTILSPATH) -Wall -g
+ -I$(UTILSPATH) -Wall -g -fprofile-arcs -ftest-coverage
include ../../../makefile.inc
@@ -48,7 +48,7 @@ WEBSRCS=webserver.o
WEBOBJ=$(WEBSRCS:.c=.o)
SRCS=$(TESTSRCS) $(READCONSRCS) $(DEBUGCLISRCS) cli.c
OBJ=$(SRCS:.c=.o)
-LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager
+LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -lgcov
MODULES= libdebugcli.so libreadconnroute.so libtestroute.so libcli.so \
libwebserver.so
diff --git a/server/modules/routing/readwritesplit/Makefile b/server/modules/routing/readwritesplit/Makefile
index c60f2ff..a3a643e 100644
--- a/server/modules/routing/readwritesplit/Makefile
+++ b/server/modules/routing/readwritesplit/Makefile
@@ -27,7 +27,7 @@ QCLASSPATH := $(ROOT_PATH)/query_classifier
CC=cc
CFLAGS=-c -fPIC -I/usr/include -I../../include -I../../../include \
-I$(LOGPATH) -I$(UTILSPATH) -I$(QCLASSPATH) \
- $(MYSQL_HEADERS) -Wall -g
+ $(MYSQL_HEADERS) -Wall -g -fprofile-arcs -ftest-coverage
include ../../../../makefile.inc
@@ -38,7 +38,7 @@ LDFLAGS=-shared -L$(LOGPATH) -L$(QCLASSPATH) -L$(EMBEDDED_LIB) \
SRCS=readwritesplit.c
OBJ=$(SRCS:.c=.o)
-LIBS=-lssl -pthread -llog_manager -lquery_classifier -lmysqld
+LIBS=-lssl -pthread -llog_manager -lquery_classifier -lmysqld -lgcov
MODULES=libreadwritesplit.so
all: $(MODULES)

View File

@ -255,11 +255,7 @@ static int logmanager_write_log(
static blockbuf_t* blockbuf_init(logfile_id_t id); static blockbuf_t* blockbuf_init(logfile_id_t id);
static void blockbuf_node_done(void* bb_data); static void blockbuf_node_done(void* bb_data);
static char* blockbuf_get_writepos( static char* blockbuf_get_writepos(
#if 0
int** refcount,
#else
blockbuf_t** p_bb, blockbuf_t** p_bb,
#endif
logfile_id_t id, logfile_id_t id,
size_t str_len, size_t str_len,
bool flush); bool flush);
@ -653,7 +649,16 @@ static int logmanager_write_log(
int safe_str_len; int safe_str_len;
timestamp_len = get_timestamp_len(); timestamp_len = get_timestamp_len();
safe_str_len = MIN(timestamp_len-1+str_len, lf->lf_buf_size);
/** Findout how much can be safely written with current block size */
if (timestamp_len-1+str_len > lf->lf_buf_size)
{
safe_str_len = lf->lf_buf_size;
}
else
{
safe_str_len = timestamp_len-1+str_len;
}
/** /**
* Seek write position and register to block buffer. * Seek write position and register to block buffer.
* Then print formatted string to write position. * Then print formatted string to write position.
@ -673,9 +678,9 @@ static int logmanager_write_log(
* of the timestamp string. * of the timestamp string.
*/ */
if (use_valist) { if (use_valist) {
vsnprintf(wp+timestamp_len, safe_str_len, str, valist); vsnprintf(wp+timestamp_len, safe_str_len-timestamp_len, str, valist);
} else { } else {
snprintf(wp+timestamp_len, safe_str_len, "%s", str); snprintf(wp+timestamp_len, safe_str_len-timestamp_len, "%s", str);
} }
/** write to syslog */ /** write to syslog */
@ -694,12 +699,7 @@ static int logmanager_write_log(
break; break;
} }
} }
wp[safe_str_len-1] = '\n';
/** remove double line feed */
if (wp[timestamp_len+str_len-2] == '\n') {
wp[timestamp_len+str_len-2]=' ';
}
wp[timestamp_len+str_len-1]='\n';
blockbuf_unregister(bb); blockbuf_unregister(bb);
/** /**

View File

@ -44,7 +44,7 @@ install: liblink
install liblog_manager.so.1.0.1 liblog_manager.so $(DEST)/lib install liblog_manager.so.1.0.1 liblog_manager.so $(DEST)/lib
depend: depend:
@rm -f depend @$(DEL) depend
$(CPP) -M $(CFLAGS) \ $(CPP) -M $(CFLAGS) \
$(MYSQL_HEADERS) \ $(MYSQL_HEADERS) \
-I$(UTILS_PATH) -I./ \ -I$(UTILS_PATH) -I./ \

View File

@ -39,7 +39,7 @@ buildtests:
-o testlog \ -o testlog \
-I$(MARIADB_SRC_PATH)/include \ -I$(MARIADB_SRC_PATH)/include \
-I$(LOG_MANAGER_PATH) -I$(UTILS_PATH) testlog.c \ -I$(LOG_MANAGER_PATH) -I$(UTILS_PATH) testlog.c \
-llog_manager $(LDLIBS) \ -lstdc++ -llog_manager $(LDLIBS) \
$(UTILS_PATH)/skygw_utils.o $(UTILS_PATH)/skygw_utils.o

View File

@ -8,6 +8,8 @@ SRCS := query_classifier.cc
UTILS_PATH := $(ROOT_PATH)/utils UTILS_PATH := $(ROOT_PATH)/utils
QUERY_CLASSIFIER_PATH := $(ROOT_PATH)/query_classifier QUERY_CLASSIFIER_PATH := $(ROOT_PATH)/query_classifier
LOG_MANAGER_PATH := $(ROOT_PATH)/log_manager LOG_MANAGER_PATH := $(ROOT_PATH)/log_manager
SERVER_INC_PATH := $(ROOT_PATH)/server/include
MODULE_INC_PATH := $(ROOT_PATH)/server/modules/include
makeall: clean all makeall: clean all
@ -33,7 +35,6 @@ runtests:
testall: testall:
$(MAKE) -C test testall $(MAKE) -C test testall
utils: utils:
$(MAKE) -C $(UTILS_PATH) clean all $(MAKE) -C $(UTILS_PATH) clean all
@ -43,6 +44,9 @@ libcomp:
$(CPP) -c $(CFLAGS) \ $(CPP) -c $(CFLAGS) \
$(MYSQL_HEADERS) \ $(MYSQL_HEADERS) \
-I$(LOG_MANAGER_PATH) \ -I$(LOG_MANAGER_PATH) \
-I$(SERVER_INC_PATH) \
-I$(MODULE_INC_PATH) \
-I$(UTILS_PATH) \
-I./ \ -I./ \
-fPIC ./query_classifier.cc -o query_classifier.o -fPIC ./query_classifier.cc -o query_classifier.o
@ -62,10 +66,13 @@ install: liblink
install ./libquery_classifier.so.1.0.1 ./libquery_classifier.so $(DEST)/lib install ./libquery_classifier.so.1.0.1 ./libquery_classifier.so $(DEST)/lib
depend: depend:
@rm -f depend @$(DEL) depend
$(CPP) -M $(CFLAGS) \ $(CPP) -M $(CFLAGS) \
$(MYSQL_HEADERS) \ $(MYSQL_HEADERS) \
-I$(LOG_MANAGER_PATH) \ -I$(LOG_MANAGER_PATH) \
-I$(SERVER_INC_PATH) \
-I$(MODULE_INC_PATH) \
-I$(UTILS_PATH) \
-I./ \ -I./ \
$(SRCS) > depend $(SRCS) > depend

View File

@ -34,6 +34,7 @@
#include "../utils/skygw_types.h" #include "../utils/skygw_types.h"
#include "../utils/skygw_debug.h" #include "../utils/skygw_debug.h"
#include <log_manager.h> #include <log_manager.h>
#include <mysql_client_server_protocol.h>
#include <mysql.h> #include <mysql.h>
#include <my_sys.h> #include <my_sys.h>
@ -83,122 +84,158 @@ static bool skygw_stmt_causes_implicit_commit(
static int is_autocommit_stmt( static int is_autocommit_stmt(
LEX* lex); LEX* lex);
static void parsing_info_set_plain_str(void* ptr,
char* str);
/** /**
* @node (write brief function description here) * Calls parser for the query includede in the buffer. Creates and adds parsing
* information to buffer if it doesn't exist already. Resolves the query type.
* *
* Parameters: * @param querybuf buffer including the query and possibly the parsing information
* @param query_str - <usage>
* <description>
*
* @param client_flag - <usage>
* <description>
*
* @return
*
*
* @details (write detailed description here)
* *
* @return query type
*/ */
skygw_query_type_t skygw_query_classifier_get_type( skygw_query_type_t query_classifier_get_type(
const char* query, GWBUF* querybuf)
unsigned long client_flags,
MYSQL** p_mysql)
{ {
MYSQL* mysql; MYSQL* mysql;
char* query_str;
const char* user = "skygw";
const char* db = "skygw";
THD* thd;
skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN; skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN;
bool failp = FALSE; bool succp;
ss_info_dassert(query != NULL, ("query_str is NULL")); ss_info_dassert(querybuf != NULL, ("querybuf is NULL"));
query_str = const_cast<char*>(query); /** Create parsing info for the query and store it to buffer */
LOGIF(LT, (skygw_log_write( succp = query_is_parsed(querybuf);
LOGFILE_TRACE,
"Query : \"%s\"", query_str)));
/** Get server handle */ if (!succp)
mysql = mysql_init(NULL);
if (mysql == NULL) {
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : call to mysql_real_connect failed due %d, %s.",
mysql_errno(mysql),
mysql_error(mysql))));
mysql_library_end();
goto return_qtype;
}
if (p_mysql != NULL)
{ {
*p_mysql = mysql; succp = parse_query(querybuf);
} }
/** Set methods and authentication to mysql */ /** Read thd pointer and resolve the query type with it. */
mysql_options(mysql, MYSQL_READ_DEFAULT_GROUP, "libmysqld_skygw"); if (succp)
mysql_options(mysql, MYSQL_OPT_USE_EMBEDDED_CONNECTION, NULL);
mysql->methods = &embedded_methods;
mysql->user = my_strdup(user, MYF(0));
mysql->db = my_strdup(db, MYF(0));
mysql->passwd = NULL;
/** Get one or create new THD object to be use in parsing */
thd = get_or_create_thd_for_parsing(mysql, query_str);
if (thd == NULL)
{ {
skygw_query_classifier_free(mysql); parsing_info_t* pi;
*p_mysql = NULL;
goto return_qtype;
}
/**
* Create parse_tree inside thd.
* thd and even lex are readable even if parser failed so let it
* continue despite failure.
*/
failp = create_parse_tree(thd);
qtype = resolve_query_type(thd);
if (p_mysql == NULL) pi = (parsing_info_t*)gwbuf_get_buffer_object_data(querybuf,
{ GWBUF_PARSING_INFO);
skygw_query_classifier_free(mysql);
if (pi != NULL)
{
mysql = (MYSQL *)pi->pi_handle;
/** Find out the query type */
if (mysql != NULL)
{
qtype = resolve_query_type((THD *)mysql->thd);
}
}
} }
return_qtype:
return qtype; return qtype;
} }
/**
void skygw_query_classifier_free( * Create parsing info and try to parse the query included in the query buffer.
MYSQL* mysql) * Store pointer to created parse_tree_t object to buffer.
*
* @param querybuf buffer including the query and possibly the parsing information
*
* @return true if succeed, false otherwise
*/
bool parse_query (
GWBUF* querybuf)
{ {
if (mysql->thd != NULL) bool succp;
THD* thd;
uint8_t* data;
size_t len;
char* query_str;
parsing_info_t* pi;
CHK_GWBUF(querybuf);
/** Do not parse without releasing previous parse info first */
ss_dassert(!query_is_parsed(querybuf));
if (query_is_parsed(querybuf))
{ {
(*mysql->methods->free_embedded_thd)(mysql); return false;
mysql->thd = NULL;
} }
mysql_close(mysql); /** Create parsing info */
mysql_thread_end(); pi = parsing_info_init(parsing_info_done);
if (pi == NULL)
{
succp = false;
goto retblock;
}
/** Extract query and copy it to different buffer */
data = (uint8_t*)GWBUF_DATA(querybuf);
len = MYSQL_GET_PACKET_LEN(data)-1; /*< distract 1 for packet type byte */
query_str = (char *)malloc(len+1);
if (query_str == NULL)
{
/** Free parsing info data */
parsing_info_done(pi);
succp = false;
goto retblock;
}
memcpy(query_str, &data[5], len);
memset(&query_str[len], 0, 1);
parsing_info_set_plain_str(pi, query_str);
/** Get one or create new THD object to be use in parsing */
thd = get_or_create_thd_for_parsing((MYSQL *)pi->pi_handle, query_str);
if (thd == NULL)
{
/** Free parsing info data */
parsing_info_done(pi);
succp = false;
goto retblock;
}
/**
* Create parse_tree inside thd.
* thd and lex are readable even if creating parse tree fails.
*/
create_parse_tree(thd);
/** Add complete parsing info struct to the query buffer */
gwbuf_add_buffer_object(querybuf,
GWBUF_PARSING_INFO,
(void *)pi,
parsing_info_done);
succp = true;
retblock:
return succp;
} }
/**
* If buffer has non-NULL gwbuf_parsing_info it is parsed and it has parsing
* information included.
*
* @param buf buffer being examined
*
* @return true or false
*/
bool query_is_parsed(
GWBUF* buf)
{
CHK_GWBUF(buf);
return GWBUF_IS_PARSED(buf);
}
/** /**
* @node (write brief function description here) * Create a thread context, thd, init embedded server, connect to it, and allocate
* query to thd.
* *
* Parameters: * Parameters:
* @param mysql - <usage> * @param mysql Database handle
* <description>
* *
* @param query_str - <usage> * @param query_str Query in plain txt string
* <description>
* *
* @return * @return Thread context pointer
*
*
* @details (write detailed description here)
* *
*/ */
static THD* get_or_create_thd_for_parsing( static THD* get_or_create_thd_for_parsing(
@ -475,7 +512,15 @@ static skygw_query_type_t resolve_query_type(
} }
else if (lex->option_type == OPT_SESSION) else if (lex->option_type == OPT_SESSION)
{ {
type |= QUERY_TYPE_SESSION_WRITE; /** SHOW commands are all reads to one backend */
if (lex->sql_command == SQLCOM_SHOW_VARIABLES)
{
type |= QUERY_TYPE_SESSION_READ;
}
else
{
type |= QUERY_TYPE_SESSION_WRITE;
}
goto return_qtype; goto return_qtype;
} }
/** /**
@ -494,10 +539,18 @@ static skygw_query_type_t resolve_query_type(
force_data_modify_op_replication) force_data_modify_op_replication)
{ {
type |= QUERY_TYPE_SESSION_WRITE; type |= QUERY_TYPE_SESSION_WRITE;
} else {
type |= QUERY_TYPE_WRITE;
} }
else
{
type |= QUERY_TYPE_WRITE;
if (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE &&
lex->sql_command == SQLCOM_CREATE_TABLE)
{
type |= QUERY_TYPE_CREATE_TMP_TABLE;
}
}
goto return_qtype; goto return_qtype;
} }
@ -635,7 +688,6 @@ static skygw_query_type_t resolve_query_type(
"%lu [resolve_query_type] " "%lu [resolve_query_type] "
"functype FUNC_SP, stored proc " "functype FUNC_SP, stored proc "
"or unknown function.", "or unknown function.",
"%s:%s",
pthread_self()))); pthread_self())));
break; break;
case Item_func::UDF_FUNC: case Item_func::UDF_FUNC:
@ -648,7 +700,6 @@ static skygw_query_type_t resolve_query_type(
pthread_self()))); pthread_self())));
break; break;
case Item_func::NOW_FUNC: case Item_func::NOW_FUNC:
case Item_func::GSYSVAR_FUNC:
func_qtype |= QUERY_TYPE_LOCAL_READ; func_qtype |= QUERY_TYPE_LOCAL_READ;
LOGIF(LD, (skygw_log_write( LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG, LOGFILE_DEBUG,
@ -657,8 +708,30 @@ static skygw_query_type_t resolve_query_type(
"executed in MaxScale.", "executed in MaxScale.",
pthread_self()))); pthread_self())));
break; break;
/** System session variable */
case Item_func::GSYSVAR_FUNC:
/** User-defined variable read */
case Item_func::GUSERVAR_FUNC:
/** User-defined variable modification */
case Item_func::SUSERVAR_FUNC:
func_qtype |= QUERY_TYPE_SESSION_READ;
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [resolve_query_type] "
"functype SUSERVAR_FUNC, could be "
"executed in MaxScale.",
pthread_self())));
break;
case Item_func::UNKNOWN_FUNC: case Item_func::UNKNOWN_FUNC:
func_qtype |= QUERY_TYPE_READ; if (item->name != NULL &&
strcmp(item->name, "last_insert_id()") == 0)
{
func_qtype |= QUERY_TYPE_SESSION_READ;
}
else
{
func_qtype |= QUERY_TYPE_READ;
}
/** /**
* Many built-in functions are of this * Many built-in functions are of this
* type, for example, rand(), soundex(), * type, for example, rand(), soundex(),
@ -816,3 +889,379 @@ char* skygw_query_classifier_get_stmtname(
return ((THD *)(mysql->thd))->lex->prepared_stmt_name.str; return ((THD *)(mysql->thd))->lex->prepared_stmt_name.str;
} }
/**
* Finds the head of the list of tables affected by the current select statement.
* @param thd Pointer to a valid THD
* @return Pointer to the head of the TABLE_LIST chain or NULL in case of an error
*/
void* skygw_get_affected_tables(void* thdp)
{
THD* thd = (THD*)thdp;
if(thd == NULL ||
thd->lex == NULL ||
thd->lex->current_select == NULL)
{
ss_dassert(thd != NULL &&
thd->lex != NULL &&
thd->lex->current_select != NULL);
return NULL;
}
return (void*)thd->lex->current_select->table_list.first;
}
/**
* Reads the parsetree and lists all the affected tables and views in the query.
* In the case of an error, the size of the table is set to zero and no memory is allocated.
* The caller must free the allocated memory.
*
* @param querybuf GWBUF where the table names are extracted from
* @param tblsize Pointer where the number of tables is written
* @return Array of null-terminated strings with the table names
*/
char** skygw_get_table_names(GWBUF* querybuf,int* tblsize)
{
parsing_info_t* pi;
MYSQL* mysql;
THD* thd;
TABLE_LIST* tbl;
int i = 0, currtblsz = 0;
char**tables,**tmp;
if (!GWBUF_IS_PARSED(querybuf))
{
tables = NULL;
goto retblock;
}
pi = (parsing_info_t *)gwbuf_get_buffer_object_data(querybuf,
GWBUF_PARSING_INFO);
if (pi == NULL)
{
tables = NULL;
goto retblock;
}
if (pi->pi_query_plain_str == NULL ||
(mysql = (MYSQL *)pi->pi_handle) == NULL ||
(thd = (THD *)mysql->thd) == NULL)
{
ss_dassert(pi->pi_query_plain_str != NULL &&
mysql != NULL &&
thd != NULL);
tables = NULL;
goto retblock;
}
thd->lex->current_select = thd->lex->all_selects_list;
while(thd->lex->current_select){
tbl = (TABLE_LIST*)skygw_get_affected_tables(thd);
while (tbl)
{
if(i >= currtblsz){
tmp = (char**)malloc(sizeof(char*)*(currtblsz*2+1));
if(tmp){
if(currtblsz > 0){
int x;
for(x = 0;x<currtblsz;x++){
tmp[x] = tables[x];
}
free(tables);
}
tables = tmp;
currtblsz = currtblsz*2 + 1;
}
}
tables[i++] = strdup(tbl->alias);
tbl=tbl->next_local;
}
thd->lex->current_select = thd->lex->current_select->next_select_in_list();
}
retblock:
*tblsize = i;
return tables;
}
/**
* Extract the name of the created table.
* @param querybuf Buffer to use.
* @return A pointer to the name if a table was created, otherwise NULL
*/
char* skygw_get_created_table_name(GWBUF* querybuf)
{
parsing_info_t* pi;
MYSQL* mysql;
THD* thd;
if (!GWBUF_IS_PARSED(querybuf))
{
return NULL;
}
pi = (parsing_info_t *)gwbuf_get_buffer_object_data(querybuf,
GWBUF_PARSING_INFO);
if (pi == NULL)
{
return NULL;
}
if ((mysql = (MYSQL *)pi->pi_handle) == NULL ||
(thd = (THD *)mysql->thd) == NULL)
{
ss_dassert(mysql != NULL &&
thd != NULL);
return NULL;
}
if(thd->lex->create_last_non_select_table &&
thd->lex->create_last_non_select_table->table_name){
char* name = strdup(thd->lex->create_last_non_select_table->table_name);
return name;
}else{
return NULL;
}
}
/**
* Checks whether the buffer contains a DROP TABLE... query.
* @param querybuf Buffer to inspect
* @return true if it contains the query otherwise false
*/
bool is_drop_table_query(GWBUF* querybuf)
{
parsing_info_t* pi;
MYSQL* mysql;
THD* thd;
if (!GWBUF_IS_PARSED(querybuf))
{
return false;
}
pi = (parsing_info_t *)gwbuf_get_buffer_object_data(querybuf,
GWBUF_PARSING_INFO);
if (pi == NULL)
{
return false;
}
if ((mysql = (MYSQL *)pi->pi_handle) == NULL ||
(thd = (THD *)mysql->thd) == NULL)
{
ss_dassert(mysql != NULL &&
thd != NULL);
return false;
}
return thd->lex->sql_command == SQLCOM_DROP_TABLE;
}
/*
* Replace user-provided literals with question marks. Return a copy of the
* querystr with replacements.
*
* @param querybuf GWBUF buffer including necessary parsing info
*
* @return Copy of querystr where literals are replaces with question marks or
* NULL if querystr is NULL, thread context or lex are NULL or if replacement
* function fails.
*
* Replaced literal types are STRING_ITEM,INT_ITEM,DECIMAL_ITEM,REAL_ITEM,
* VARBIN_ITEM,NULL_ITEM
*/
char* skygw_get_canonical(
GWBUF* querybuf)
{
parsing_info_t* pi;
MYSQL* mysql;
THD* thd;
LEX* lex;
Item* item;
char* querystr;
if (!GWBUF_IS_PARSED(querybuf))
{
querystr = NULL;
goto retblock;
}
pi = (parsing_info_t *)gwbuf_get_buffer_object_data(querybuf,
GWBUF_PARSING_INFO);
CHK_PARSING_INFO(pi);
if (pi == NULL)
{
querystr = NULL;
goto retblock;
}
if (pi->pi_query_plain_str == NULL ||
(mysql = (MYSQL *)pi->pi_handle) == NULL ||
(thd = (THD *)mysql->thd) == NULL ||
(lex = thd->lex) == NULL)
{
ss_dassert(pi->pi_query_plain_str != NULL &&
mysql != NULL &&
thd != NULL &&
lex != NULL);
querystr = NULL;
goto retblock;
}
querystr = strdup(pi->pi_query_plain_str);
for (item=thd->free_list; item != NULL; item=item->next)
{
Item::Type itype;
if (item->name == NULL)
{
continue;
}
itype = item->type();
if (itype == Item::STRING_ITEM)
{
String tokenstr;
String* res = item->val_str_ascii(&tokenstr);
if (res->is_empty()) /*< empty string */
{
querystr = replace_literal(querystr, "\"\"", "\"?\"");
}
else
{
querystr = replace_literal(querystr, res->ptr(), "?");
}
}
else if (itype == Item::INT_ITEM ||
itype == Item::DECIMAL_ITEM ||
itype == Item::REAL_ITEM ||
itype == Item::VARBIN_ITEM ||
itype == Item::NULL_ITEM)
{
querystr = replace_literal(querystr, item->name, "?");
}
} /*< for */
retblock:
return querystr;
}
/**
* Create parsing information; initialize mysql handle, allocate parsing info
* struct and set handle and free function pointer to it.
*
* @param donefun pointer to free function
*
* @return pointer to parsing information
*/
parsing_info_t* parsing_info_init(
void (*donefun)(void *))
{
parsing_info_t* pi = NULL;
MYSQL* mysql;
const char* user = "skygw";
const char* db = "skygw";
ss_dassert(donefun != NULL);
/** Get server handle */
mysql = mysql_init(NULL);
ss_dassert(mysql != NULL);
if (mysql == NULL) {
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : call to mysql_real_connect failed due %d, %s.",
mysql_errno(mysql),
mysql_error(mysql))));
goto retblock;
}
/** Set methods and authentication to mysql */
mysql_options(mysql, MYSQL_READ_DEFAULT_GROUP, "libmysqld_skygw");
mysql_options(mysql, MYSQL_OPT_USE_EMBEDDED_CONNECTION, NULL);
mysql->methods = &embedded_methods;
mysql->user = my_strdup(user, MYF(0));
mysql->db = my_strdup(db, MYF(0));
mysql->passwd = NULL;
pi = (parsing_info_t*)calloc(1, sizeof(parsing_info_t));
if (pi == NULL)
{
mysql_close(mysql);
goto retblock;
}
#if defined(SS_DEBUG)
pi->pi_chk_top = CHK_NUM_PINFO;
pi->pi_chk_tail = CHK_NUM_PINFO;
#endif
/** Set handle and free function to parsing info struct */
pi->pi_handle = mysql;
pi->pi_done_fp = donefun;
retblock:
return pi;
}
/**
* Free function for parsing info. Called by gwbuf_free or in case initialization
* of parsing information fails.
*
* @param ptr Pointer to parsing information, cast required
*
* @return void
*
*/
void parsing_info_done(
void* ptr)
{
parsing_info_t* pi = (parsing_info_t *)ptr;
if (pi->pi_handle != NULL)
{
MYSQL* mysql = (MYSQL *)pi->pi_handle;
if (mysql->thd != NULL)
{
(*mysql->methods->free_embedded_thd)(mysql);
mysql->thd = NULL;
}
mysql_close(mysql);
}
/** Free plain text query string */
if (pi->pi_query_plain_str != NULL)
{
free(pi->pi_query_plain_str);
}
free(pi);
}
/**
* Add plain text query string to parsing info.
*
* @param ptr Pointer to parsing info struct, cast required
* @param str String to be added
*
* @return void
*/
static void parsing_info_set_plain_str(
void* ptr,
char* str)
{
parsing_info_t* pi = (parsing_info_t *)ptr;
CHK_PARSING_INFO(pi);
pi->pi_query_plain_str = str;
}

View File

@ -20,7 +20,8 @@ Copyright SkySQL Ab
/** getpid */ /** getpid */
#include <unistd.h> #include <unistd.h>
#include <mysql.h> #include <mysql.h>
#include "../utils/skygw_utils.h" #include <skygw_utils.h>
#include <buffer.h>
EXTERN_C_BLOCK_BEGIN EXTERN_C_BLOCK_BEGIN
@ -43,23 +44,46 @@ typedef enum {
QUERY_TYPE_COMMIT = 0x0200, /*< COMMIT */ QUERY_TYPE_COMMIT = 0x0200, /*< COMMIT */
QUERY_TYPE_PREPARE_NAMED_STMT = 0x0400, /*< Prepared stmt with name from user */ QUERY_TYPE_PREPARE_NAMED_STMT = 0x0400, /*< Prepared stmt with name from user */
QUERY_TYPE_PREPARE_STMT = 0x0800, /*< Prepared stmt with id provided by server */ QUERY_TYPE_PREPARE_STMT = 0x0800, /*< Prepared stmt with id provided by server */
QUERY_TYPE_EXEC_STMT = 0x1000 /*< Execute prepared statement */ QUERY_TYPE_EXEC_STMT = 0x1000, /*< Execute prepared statement */
QUERY_TYPE_SESSION_READ = 0x2000, /*< Read session data (from master 31.8.14) */
QUERY_TYPE_CREATE_TMP_TABLE = 0x4000, /*< Create temporary table */
QUERY_TYPE_READ_TMP_TABLE = 0x8000 /*< Read temporary table */
} skygw_query_type_t; } skygw_query_type_t;
typedef struct parsing_info_st {
#if defined(SS_DEBUG)
skygw_chk_t pi_chk_top;
#endif
void* pi_handle; /*< parsing info object pointer */
char* pi_query_plain_str; /*< query as plain string */
void (*pi_done_fp)(void *); /*< clean-up function for parsing info */
#if defined(SS_DEBUG)
skygw_chk_t pi_chk_tail;
#endif
} parsing_info_t;
#define QUERY_IS_TYPE(mask,type) ((mask & type) == type) #define QUERY_IS_TYPE(mask,type) ((mask & type) == type)
/** /**
* Create THD and use it for creating parse tree. Examine parse tree and * Create THD and use it for creating parse tree. Examine parse tree and
* classify the query. * classify the query.
*/ */
skygw_query_type_t skygw_query_classifier_get_type( skygw_query_type_t query_classifier_get_type(GWBUF* querybuf);
const char* query_str,
unsigned long client_flags,
MYSQL** mysql);
/** Free THD context and close MYSQL */ /** Free THD context and close MYSQL */
void skygw_query_classifier_free(MYSQL* mysql); char* skygw_query_classifier_get_stmtname(MYSQL* mysql);
char* skygw_query_classifier_get_stmtname(MYSQL* mysql); char* skygw_get_created_table_name(GWBUF* querybuf);
bool is_drop_table_query(GWBUF* querybuf);
void* skygw_get_affected_tables(void* thdp);
char** skygw_get_table_names(GWBUF* querybuf,int* tblsize);
char* skygw_get_canonical(GWBUF* querybuf);
bool parse_query (GWBUF* querybuf);
parsing_info_t* parsing_info_init(void (*donefun)(void *));
void parsing_info_done(void* ptr);
bool query_is_parsed(GWBUF* buf);
EXTERN_C_BLOCK_END EXTERN_C_BLOCK_END

View File

@ -0,0 +1,64 @@
# cleantests - clean local and subdirectories' tests
# buildtests - build all local and subdirectories' tests
# runtests - run all local tests
# testall - clean, build and run local and subdirectories' tests
include ../../../build_gateway.inc
include ../../../makefile.inc
include ../../../test.inc
CC = gcc
CPP = g++
TESTPATH := $(shell pwd)
TESTLOG := $(TESTPATH)/testqclass.log
QUERY_CLASSIFIER_PATH := $(ROOT_PATH)/query_classifier
LOG_MANAGER_PATH := $(ROOT_PATH)/log_manager
UTILS_PATH := $(ROOT_PATH)/utils
CORE_PATH := $(ROOT_PATH)/server/core
TESTAPP = $(TESTPATH)/canonizer
LDFLAGS=-L$(QUERY_CLASSIFIER_PATH) \
-L$(LOG_MANAGER_PATH) \
-L$(EMBEDDED_LIB) \
-Wl,-rpath,$(DEST)/lib \
-Wl,-rpath,$(EMBEDDED_LIB) \
-Wl,-rpath,$(LOG_MANAGER_PATH) \
-Wl,-rpath,$(QUERY_CLASSIFIER_PATH)
LIBS=-lstdc++ -lpthread -lquery_classifier -lz -ldl -lssl -laio -lcrypt -lcrypto -lrt \
-llog_manager $(UTILS_PATH)/skygw_utils.o $(CORE_PATH)/buffer.o $(CORE_PATH)/atomic.o $(CORE_PATH)/spinlock.o
CFLAGS=-g $(MYSQL_HEADERS) \
-I$(QUERY_CLASSIFIER_PATH) \
$(MYSQL_HEADERS) \
-I$(ROOT_PATH)/server/include \
-I$(UTILS_PATH)
EMBFLAGS=$(shell mysql_config --cflags --libmysqld-libs)
testall:
$(MAKE) cleantests
$(MAKE) buildtests
$(MAKE) runtests
cleantests:
- $(DEL) *.o
- $(DEL) *~
- $(DEL) canonizer
- $(DEL) aria_log*
- $(DEL) ib*
buildtests: $(OBJS)
cp $(ERRMSG)/errmsg.sys .
$(CC) $(CFLAGS) $(LDFLAGS) $(EMBFLAGS) $(LIBS) canonizer.c -o $(TESTAPP)
runtests:
@echo "" > $(TESTLOG)
@echo "-------------------------------" >> $(TESTLOG)
@echo $(shell date) >> $(TESTLOG)
@echo "Canonical Query Tests" >> $(TESTLOG)
@echo "-------------------------------" >> $(TESTLOG)
@echo "" >> $(TESTLOG)
./canontest.sh $(TESTLOG) input.sql output.sql expected.sql

View File

@ -0,0 +1,101 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <query_classifier.h>
#include <buffer.h>
#include <mysql.h>
static char* server_options[] = {
"SkySQL Gateway",
"--datadir=./",
"--language=./",
"--skip-innodb",
"--default-storage-engine=myisam",
NULL
};
const int num_elements = (sizeof(server_options) / sizeof(char *)) - 1;
static char* server_groups[] = {
"embedded",
"server",
"server",
NULL
};
int main(int argc, char** argv)
{
int fdin,fdout,i=0,fnamelen,fsz,lines = 0;
unsigned int psize;
GWBUF** qbuff;
char *qin, *outnm, *buffer, *tok;
if(argc != 3){
printf("Usage: canonizer <input file> <output file>\n");
return 1;
}
bool failed = mysql_library_init(num_elements, server_options, server_groups);
if(failed){
printf("Embedded server init failed.\n");
return 1;
}
fnamelen = strlen(argv[1]) + 16;
fdin = open(argv[1],O_RDONLY);
fsz = lseek(fdin,0,SEEK_END);
lseek(fdin,0,SEEK_SET);
if(!(buffer = malloc(sizeof(char)*fsz))){
printf("Error: Failed to allocate memory.");
return 1;
}
read(fdin,buffer,fsz);
tok = strpbrk(buffer,"\n");
lines = 1;
while((tok = strpbrk(tok + 1,"\n"))){
lines++;
}
qbuff = malloc(sizeof(GWBUF*)*lines);
i = 0;
tok = strtok(buffer,"\n");
while(tok){
qin = strdup(tok);
psize = strlen(qin);
qbuff[i] = gwbuf_alloc(psize + 6);
*(qbuff[i]->sbuf->data + 0) = (unsigned char)psize;
*(qbuff[i]->sbuf->data + 1) = (unsigned char)(psize>>8);
*(qbuff[i]->sbuf->data + 2) = (unsigned char)(psize>>16);
*(qbuff[i]->sbuf->data + 4) = 0x03;
memcpy(qbuff[i]->sbuf->data + 5,qin,psize);
*(qbuff[i]->sbuf->data + 5 + psize) = 0x00;
tok = strtok(NULL,"\n");
free(qin);
i++;
}
fdout = open(argv[2],O_TRUNC|O_CREAT|O_WRONLY,S_IRWXU|S_IXGRP|S_IXOTH);
for(i = 0;i<lines;i++){
parse_query(qbuff[i]);
tok = skygw_get_canonical(qbuff[i]);
write(fdout,tok,strlen(tok));
write(fdout,"\n",1);
gwbuf_free(qbuff[i]);
}
close(fdin);
close(fdout);
return 0;
}

View File

@ -0,0 +1,21 @@
#! /bin/sh
if [[ $# -ne 4 ]]
then
echo "Usage: canontest.sh <logfile name> <input file> <output file> <expected output>"
exit 0
fi
TESTLOG=$1
INPUT=$2
OUTPUT=$3
EXPECTED=$4
DIFFLOG=diff.out
$PWD/canonizer $INPUT $OUTPUT
diff $OUTPUT $EXPECTED > $DIFFLOG
if [ $? -eq 0 ]
then
echo "PASSED" >> $TESTLOG
else
echo "FAILED" >> $TESTLOG
echo "Diff output: " >> $TESTLOG
cat $DIFFLOG >> $TESTLOG
fi

View File

@ -0,0 +1,17 @@
select md5(?) =?, sleep(?), rand(?);
select * from my1 where md5(?) =?;
select md5(?) =?;
select * from my1 where md5(?) =?;
select sleep(?)
select * from tst where lname='?'
select ?,?,?,?,?,? from tst
select * from tst where fname like '?'
select * from tst where lname like '?' order by fname
insert into tst values ("?","?"),("?",?),("?","?")
drop table if exists tst
create table tst(fname varchar(30), lname varchar(30))
update tst set lname="?" where fname like '?' or lname like '?'
delete from tst where lname like '?' and fname like '?'
select ? from tst where fname='?' or lname like '?'
select ?,?,?,? from tst where name='?' or name='?' or name='?'
select count(?),count(?),count(?),count(?),count (?),count(?) from tst

View File

@ -0,0 +1,17 @@
select md5("200000foo") =10, sleep(2), rand(100);
select * from my1 where md5("110") =10;
select md5("100foo") =10;
select * from my1 where md5("100") =10;
select sleep(2);
select * from tst where lname='Doe';
select 1,2,3,4,5,6 from tst;
select * from tst where fname like '%a%';
select * from tst where lname like '%e%' order by fname;
insert into tst values ("John","Doe"),("Plato",null),("Nietzsche","");
drop table if exists tst;
create table tst(fname varchar(30), lname varchar(30));
update tst set lname="Human" where fname like '%a%' or lname like '%a%';
delete from tst where lname like '%man%' and fname like '%ard%';
select 100 from tst where fname='10' or lname like '%100%';
select 1,20,300,4000 from tst where name='1000' or name='200' or name='30' or name='4';
select count(1),count(10),count(100),count(2),count (20),count(200) from tst;

View File

@ -18,7 +18,7 @@ UTILS_PATH := $(ROOT_PATH)/utils
TESTAPP = $(TESTPATH)/testmain TESTAPP = $(TESTPATH)/testmain
testall:buildtests testall:buildtests
$(MAKE) -C canonical_tests testall
testalllaters: testalllaters:
$(MAKE) cleantests $(MAKE) cleantests
$(MAKE) DEBUG=Y DYNLIB=Y buildtests $(MAKE) DEBUG=Y DYNLIB=Y buildtests

View File

@ -24,6 +24,9 @@
# 08/07/13 Mark Riddoch Addition of monitor modules # 08/07/13 Mark Riddoch Addition of monitor modules
# 16/07/13 Mark Riddoch Renamed things to match the new naming # 16/07/13 Mark Riddoch Renamed things to match the new naming
include ../build_gateway.inc
include ../makefile.inc
DEST=$(HOME)/usr/local/skysql DEST=$(HOME)/usr/local/skysql
all: all:
@ -45,7 +48,7 @@ testall:
$(MAKE) -C test HAVE_SRV=$(HAVE_SRV) testall $(MAKE) -C test HAVE_SRV=$(HAVE_SRV) testall
clean: clean:
(cd Documentation; rm -rf html) (cd Documentation; $(DEL) html)
(cd core; touch depend.mk ; make clean) (cd core; touch depend.mk ; make clean)
(cd modules/routing; touch depend.mk ; make clean) (cd modules/routing; touch depend.mk ; make clean)
(cd modules/protocol; touch depend.mk ; make clean) (cd modules/protocol; touch depend.mk ; make clean)

View File

@ -109,14 +109,14 @@ maxpasswd: $(POBJS)
echo '#define MAXSCALE_VERSION "'`cat ../../VERSION`'"' > ../include/version.h echo '#define MAXSCALE_VERSION "'`cat ../../VERSION`'"' > ../include/version.h
clean: clean:
rm -f $(OBJ) maxscale $(DEL) $(OBJ) maxscale
- rm *.so $(DEL) *.so
tags: tags:
ctags $(SRCS) $(HDRS) ctags $(SRCS) $(HDRS)
depend: ../include/version.h depend: ../include/version.h
@rm -f depend.mk @$(DEL) depend.mk
cc -M $(CFLAGS) $(SRCS) > depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk
install: maxscale maxkeys maxpasswd install: maxscale maxkeys maxpasswd

View File

@ -298,6 +298,7 @@ char* admin_remove_user(
fname, fname,
err))); err)));
fclose(fp); fclose(fp);
fclose(fp_tmp);
unlink(fname_tmp); unlink(fname_tmp);
return ADMIN_ERR_PWDFILEACCESS; return ADMIN_ERR_PWDFILEACCESS;
} }
@ -325,6 +326,7 @@ char* admin_remove_user(
fname, fname,
err))); err)));
fclose(fp); fclose(fp);
fclose(fp_tmp);
unlink(fname_tmp); unlink(fname_tmp);
return ADMIN_ERR_PWDFILEACCESS; return ADMIN_ERR_PWDFILEACCESS;
} }

View File

@ -41,6 +41,11 @@
#include <atomic.h> #include <atomic.h>
#include <skygw_debug.h> #include <skygw_debug.h>
static buffer_object_t* gwbuf_remove_buffer_object(
GWBUF* buf,
buffer_object_t* bufobj);
/** /**
* Allocate a new gateway buffer structure of size bytes. * Allocate a new gateway buffer structure of size bytes.
* *
@ -78,7 +83,7 @@ SHARED_BUF *sbuf;
free(sbuf); free(sbuf);
return NULL; return NULL;
} }
spinlock_init(&rval->lock); spinlock_init(&rval->gwbuf_lock);
rval->start = sbuf->data; rval->start = sbuf->data;
rval->end = rval->start + size; rval->end = rval->start + size;
sbuf->refcount = 1; sbuf->refcount = 1;
@ -87,7 +92,8 @@ SHARED_BUF *sbuf;
rval->hint = NULL; rval->hint = NULL;
rval->properties = NULL; rval->properties = NULL;
rval->gwbuf_type = GWBUF_TYPE_UNDEFINED; rval->gwbuf_type = GWBUF_TYPE_UNDEFINED;
rval->command = 0; rval->gwbuf_info = GWBUF_INFO_NONE;
rval->gwbuf_bufobj = NULL;
CHK_GWBUF(rval); CHK_GWBUF(rval);
return rval; return rval;
} }
@ -102,11 +108,20 @@ gwbuf_free(GWBUF *buf)
{ {
BUF_PROPERTY *prop; BUF_PROPERTY *prop;
buffer_object_t* bo;
CHK_GWBUF(buf); CHK_GWBUF(buf);
if (atomic_add(&buf->sbuf->refcount, -1) == 1) if (atomic_add(&buf->sbuf->refcount, -1) == 1)
{ {
free(buf->sbuf->data); free(buf->sbuf->data);
free(buf->sbuf); free(buf->sbuf);
bo = buf->gwbuf_bufobj;
while (bo != NULL)
{
bo = gwbuf_remove_buffer_object(buf, bo);
}
} }
while (buf->properties) while (buf->properties)
{ {
@ -153,6 +168,8 @@ GWBUF *rval;
rval->gwbuf_type = buf->gwbuf_type; rval->gwbuf_type = buf->gwbuf_type;
rval->properties = NULL; rval->properties = NULL;
rval->hint = NULL; rval->hint = NULL;
rval->gwbuf_info = buf->gwbuf_info;
rval->gwbuf_bufobj = buf->gwbuf_bufobj;
rval->next = NULL; rval->next = NULL;
CHK_GWBUF(rval); CHK_GWBUF(rval);
return rval; return rval;
@ -181,6 +198,8 @@ GWBUF *gwbuf_clone_portion(
clonebuf->gwbuf_type = buf->gwbuf_type; /*< clone the type for now */ clonebuf->gwbuf_type = buf->gwbuf_type; /*< clone the type for now */
clonebuf->properties = NULL; clonebuf->properties = NULL;
clonebuf->hint = NULL; clonebuf->hint = NULL;
clonebuf->gwbuf_info = buf->gwbuf_info;
clonebuf->gwbuf_bufobj = buf->gwbuf_bufobj;
clonebuf->next = NULL; clonebuf->next = NULL;
CHK_GWBUF(clonebuf); CHK_GWBUF(clonebuf);
return clonebuf; return clonebuf;
@ -323,11 +342,13 @@ int rval = 0;
} }
/** /**
* Trim bytes form the end of a GWBUF structure * Trim bytes form the end of a GWBUF structure. If the
* buffer has n_bytes or less then it will be freed and
* NULL will be returned.
* *
* @param buf The buffer to trim * @param buf The buffer to trim
* @param nbytes The number of bytes to trim off * @param n_bytes The number of bytes to trim off
* @return The buffer chain * @return The buffer chain or NULL if buffer has <= n_bytes
*/ */
GWBUF * GWBUF *
gwbuf_trim(GWBUF *buf, unsigned int n_bytes) gwbuf_trim(GWBUF *buf, unsigned int n_bytes)
@ -361,6 +382,90 @@ void gwbuf_set_type(
} }
} }
/**
* Add a buffer object to GWBUF buffer.
*
* @param buf GWBUF where object is added
* @param id Type identifier for object
* @param data Object data
* @param donefun_dp Clean-up function to be executed before buffer is freed.
*/
void gwbuf_add_buffer_object(
GWBUF* buf,
bufobj_id_t id,
void* data,
void (*donefun_fp)(void *))
{
buffer_object_t** p_b;
buffer_object_t* newb;
CHK_GWBUF(buf);
newb = (buffer_object_t *)malloc(sizeof(buffer_object_t));
newb->bo_id = id;
newb->bo_data = data;
newb->bo_donefun_fp = donefun_fp;
newb->bo_next = NULL;
/** Lock */
spinlock_acquire(&buf->gwbuf_lock);
p_b = &buf->gwbuf_bufobj;
/** Search the end of the list and add there */
while (*p_b != NULL)
{
p_b = &(*p_b)->bo_next;
}
*p_b = newb;
/** Set flag */
buf->gwbuf_info |= GWBUF_INFO_PARSED;
/** Unlock */
spinlock_release(&buf->gwbuf_lock);
}
/**
* Search buffer object which matches with the id.
*
* @param buf GWBUF to be searched
* @param id Identifier for the object
*
* @return Searched buffer object or NULL if not found
*/
void* gwbuf_get_buffer_object_data(
GWBUF* buf,
bufobj_id_t id)
{
buffer_object_t* bo;
CHK_GWBUF(buf);
/** Lock */
spinlock_acquire(&buf->gwbuf_lock);
bo = buf->gwbuf_bufobj;
while (bo != NULL && bo->bo_id != id)
{
bo = bo->bo_next;
}
/** Unlock */
spinlock_release(&buf->gwbuf_lock);
return bo->bo_data;
}
/**
* @return pointer to next buffer object or NULL
*/
static buffer_object_t* gwbuf_remove_buffer_object(
GWBUF* buf,
buffer_object_t* bufobj)
{
buffer_object_t* next;
next = bufobj->bo_next;
/** Call corresponding clean-up function to clean buffer object's data */
bufobj->bo_donefun_fp(bufobj->bo_data);
free(bufobj);
return next;
}
/** /**
* Add a property to a buffer. * Add a property to a buffer.
@ -380,10 +485,10 @@ BUF_PROPERTY *prop;
prop->name = strdup(name); prop->name = strdup(name);
prop->value = strdup(value); prop->value = strdup(value);
spinlock_acquire(&buf->lock); spinlock_acquire(&buf->gwbuf_lock);
prop->next = buf->properties; prop->next = buf->properties;
buf->properties = prop; buf->properties = prop;
spinlock_release(&buf->lock); spinlock_release(&buf->gwbuf_lock);
return 1; return 1;
} }
@ -398,11 +503,11 @@ gwbuf_get_property(GWBUF *buf, char *name)
{ {
BUF_PROPERTY *prop; BUF_PROPERTY *prop;
spinlock_acquire(&buf->lock); spinlock_acquire(&buf->gwbuf_lock);
prop = buf->properties; prop = buf->properties;
while (prop && strcmp(prop->name, name) != 0) while (prop && strcmp(prop->name, name) != 0)
prop = prop->next; prop = prop->next;
spinlock_release(&buf->lock); spinlock_release(&buf->gwbuf_lock);
if (prop) if (prop)
return prop->value; return prop->value;
return NULL; return NULL;
@ -451,7 +556,7 @@ gwbuf_add_hint(GWBUF *buf, HINT *hint)
{ {
HINT *ptr; HINT *ptr;
spinlock_acquire(&buf->lock); spinlock_acquire(&buf->gwbuf_lock);
if (buf->hint) if (buf->hint)
{ {
ptr = buf->hint; ptr = buf->hint;
@ -463,7 +568,6 @@ HINT *ptr;
{ {
buf->hint = hint; buf->hint = hint;
} }
spinlock_release(&buf->lock); spinlock_release(&buf->gwbuf_lock);
return 1; return 1;
} }

View File

@ -65,6 +65,29 @@ static char *config_file = NULL;
static GATEWAY_CONF gateway; static GATEWAY_CONF gateway;
char *version_string = NULL; char *version_string = NULL;
/**
* Trim whitespace from the front and rear of a string
*
* @param str String to trim
* @return Trimmed string, changes are done in situ
*/
static char *
trim(char *str)
{
char *ptr;
while (isspace(*str))
str++;
/* Point to last character of the string */
ptr = str + strlen(str) - 1;
while (ptr > str && isspace(*ptr))
*ptr-- = 0;
return str;
}
/** /**
* Config item handler for the ini file reader * Config item handler for the ini file reader
* *
@ -508,7 +531,7 @@ int error_count = 0;
CONFIG_CONTEXT *obj1 = context; CONFIG_CONTEXT *obj1 = context;
while (obj1) while (obj1)
{ {
if (strcmp(s, obj1->object) == 0 && if (strcmp(trim(s), obj1->object) == 0 &&
obj->element && obj1->element) obj->element && obj1->element)
{ {
serviceAddBackend( serviceAddBackend(

View File

@ -187,14 +187,6 @@ getUsers(SERVICE *service, struct users *users)
if (service_user == NULL || service_passwd == NULL) if (service_user == NULL || service_passwd == NULL)
return -1; return -1;
/** multi-thread environment requires that thread init succeeds. */
if (mysql_thread_init()) {
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : mysql_thread_init failed.")));
return -1;
}
con = mysql_init(NULL); con = mysql_init(NULL);
if (con == NULL) { if (con == NULL) {
@ -388,10 +380,9 @@ getUsers(SERVICE *service, struct users *users)
memcpy(users->cksum, hash, SHA_DIGEST_LENGTH); memcpy(users->cksum, hash, SHA_DIGEST_LENGTH);
free(users_data); free(users_data);
free(key.user);
mysql_free_result(result); mysql_free_result(result);
mysql_close(con); mysql_close(con);
mysql_thread_end();
return total_users; return total_users;
} }

View File

@ -978,9 +978,10 @@ int below_water;
} }
if (dolog) if (dolog)
{ {
LOGIF(LE, (skygw_log_write_flush( LOGIF(LD, (skygw_log_write(
LOGFILE_ERROR, LOGFILE_DEBUG,
"Error : Writing to %s socket failed due %d, %s.", "%lu [dcb_write] Writing to %s socket failed due %d, %s.",
pthread_self(),
dcb_isclient(dcb) ? "client" : "backend server", dcb_isclient(dcb) ? "client" : "backend server",
saved_errno, saved_errno,
strerror(saved_errno)))); strerror(saved_errno))));
@ -1703,14 +1704,15 @@ int gw_write(
/** /**
* Add a callback * Add a callback
* *
* Duplicate registrations are not allowed, therefore an error will be returned if * Duplicate registrations are not allowed, therefore an error will be
* the specific function, reason and userdata triple are already registered. * returned if the specific function, reason and userdata triple
* are already registered.
* An error will also be returned if the is insufficient memeory available to * An error will also be returned if the is insufficient memeory available to
* create the registration. * create the registration.
* *
* @param dcb The DCB to add the callback to * @param dcb The DCB to add the callback to
* @param reason The callback reason * @param reason The callback reason
* @param cb The callback function to call * @param callback The callback function to call
* @param userdata User data to send in the call * @param userdata User data to send in the call
* @return Non-zero (true) if the callback was added * @return Non-zero (true) if the callback was added
*/ */
@ -1766,7 +1768,7 @@ int rval = 1;
* *
* @param dcb The DCB to add the callback to * @param dcb The DCB to add the callback to
* @param reason The callback reason * @param reason The callback reason
* @param cb The callback function to call * @param callback The callback function to call
* @param userdata User data to send in the call * @param userdata User data to send in the call
* @return Non-zero (true) if the callback was removed * @return Non-zero (true) if the callback was removed
*/ */
@ -1839,7 +1841,7 @@ DCB_CALLBACK *cb, *nextcb;
/** /**
* Check the passed DCB to ensure it is in the list of allDCBS * Check the passed DCB to ensure it is in the list of allDCBS
* *
* @param DCB The DCB to check * @param dcb The DCB to check
* @return 1 if the DCB is in the list, otherwise 0 * @return 1 if the DCB is in the list, otherwise 0
*/ */
int int
@ -1936,8 +1938,8 @@ void dcb_call_foreach (
* Null protocol write routine used for cloned dcb's. It merely consumes * Null protocol write routine used for cloned dcb's. It merely consumes
* buffers written on the cloned DCB. * buffers written on the cloned DCB.
* *
* @params dcb The descriptor control block * @param dcb The descriptor control block
* @params buf The buffer beign written * @param buf The buffer being written
* @return Always returns a good write operation result * @return Always returns a good write operation result
*/ */
static int static int

View File

@ -39,8 +39,8 @@
extern int lm_enabled_logfiles_bitmask; extern int lm_enabled_logfiles_bitmask;
static SPINLOCK filter_spin = SPINLOCK_INIT; static SPINLOCK filter_spin = SPINLOCK_INIT; /**< Protects the list of all filters */
static FILTER_DEF *allFilters = NULL; static FILTER_DEF *allFilters = NULL; /**< The list of all filters */
/** /**
* Allocate a new filter within MaxScale * Allocate a new filter within MaxScale
@ -79,7 +79,7 @@ FILTER_DEF *filter;
/** /**
* Deallocate the specified filter * Deallocate the specified filter
* *
* @param server The service to deallocate * @param filter The filter to deallocate
* @return Returns true if the server was freed * @return Returns true if the server was freed
*/ */
void void
@ -243,8 +243,8 @@ int i;
/** /**
* Add a router option to a service * Add a router option to a service
* *
* @param service The service to add the router option to * @param filter The filter to add the option to
* @param option The option string * @param option The option string
*/ */
void void
filterAddOption(FILTER_DEF *filter, char *option) filterAddOption(FILTER_DEF *filter, char *option)
@ -273,7 +273,7 @@ int i;
/** /**
* Add a router parameter to a service * Add a router parameter to a service
* *
* @param service The service to add the router option to * @param filter The filter to add the parameter to
* @param name The parameter name * @param name The parameter name
* @param value The parameter value * @param value The parameter value
*/ */
@ -318,6 +318,9 @@ filterApply(FILTER_DEF *filter, SESSION *session, DOWNSTREAM *downstream)
{ {
DOWNSTREAM *me; DOWNSTREAM *me;
if (filter == NULL)
return NULL;
if (filter->obj == NULL) if (filter->obj == NULL)
{ {
/* Filter not yet loaded */ /* Filter not yet loaded */
@ -359,7 +362,7 @@ DOWNSTREAM *me;
UPSTREAM * UPSTREAM *
filterUpstream(FILTER_DEF *filter, void *fsession, UPSTREAM *upstream) filterUpstream(FILTER_DEF *filter, void *fsession, UPSTREAM *upstream)
{ {
UPSTREAM *me; UPSTREAM *me = NULL;
/* /*
* The the filter has no setUpstream entry point then is does * The the filter has no setUpstream entry point then is does

View File

@ -1339,11 +1339,16 @@ int main(int argc, char **argv)
/* Init MaxScale poll system */ /* Init MaxScale poll system */
poll_init(); poll_init();
/*< /**
* Start the services that were created above * Init mysql thread context for main thread as well. Needed when users
*/ * are queried from backends.
*/
mysql_thread_init();
/** Start the services that were created above */
n_services = serviceStartAll(); n_services = serviceStartAll();
if (n_services == 0)
if (n_services == 0)
{ {
char* logerr = "Failed to start any MaxScale services. Exiting."; char* logerr = "Failed to start any MaxScale services. Exiting.";
print_log_n_stderr(true, !daemon_mode, logerr, logerr, 0); print_log_n_stderr(true, !daemon_mode, logerr, logerr, 0);
@ -1396,9 +1401,13 @@ int main(int argc, char **argv)
/*< Stop all the monitors */ /*< Stop all the monitors */
monitorStopAll(); monitorStopAll();
LOGIF(LM, (skygw_log_write( LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE, LOGFILE_MESSAGE,
"MaxScale is shutting down."))); "MaxScale is shutting down.")));
/** Release mysql thread context*/
mysql_thread_end();
datadir_cleanup(); datadir_cleanup();
LOGIF(LM, (skygw_log_write( LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE, LOGFILE_MESSAGE,

View File

@ -63,6 +63,10 @@ static void hashtable_read_lock(HASHTABLE *table);
static void hashtable_read_unlock(HASHTABLE *table); static void hashtable_read_unlock(HASHTABLE *table);
static void hashtable_write_lock(HASHTABLE *table); static void hashtable_write_lock(HASHTABLE *table);
static void hashtable_write_unlock(HASHTABLE *table); static void hashtable_write_unlock(HASHTABLE *table);
static HASHTABLE *hashtable_alloc_real(HASHTABLE* target,
int size,
int (*hashfn)(),
int (*cmpfn)());
/** /**
* Special null function used as default memory allfunctions in the hashtable * Special null function used as default memory allfunctions in the hashtable
@ -89,14 +93,42 @@ nullfn(void *data)
HASHTABLE * HASHTABLE *
hashtable_alloc(int size, int (*hashfn)(), int (*cmpfn)()) hashtable_alloc(int size, int (*hashfn)(), int (*cmpfn)())
{ {
HASHTABLE *rval; return hashtable_alloc_real(NULL, size, hashfn, cmpfn);
}
if ((rval = malloc(sizeof(HASHTABLE))) == NULL) HASHTABLE* hashtable_alloc_flat(
return NULL; HASHTABLE* target,
int size,
int (*hashfn)(),
int (*cmpfn)())
{
return hashtable_alloc_real(target, size, hashfn, cmpfn);
}
static HASHTABLE *
hashtable_alloc_real(
HASHTABLE* target,
int size,
int (*hashfn)(),
int (*cmpfn)())
{
HASHTABLE *rval;
if (target == NULL)
{
if ((rval = malloc(sizeof(HASHTABLE))) == NULL)
return NULL;
rval->ht_isflat = false;
}
else
{
rval = target;
rval->ht_isflat = true;
}
#if defined(SS_DEBUG) #if defined(SS_DEBUG)
rval->ht_chk_top = CHK_NUM_HASHTABLE; rval->ht_chk_top = CHK_NUM_HASHTABLE;
rval->ht_chk_tail = CHK_NUM_HASHTABLE; rval->ht_chk_tail = CHK_NUM_HASHTABLE;
#endif #endif
rval->hashsize = size; rval->hashsize = size;
rval->hashfn = hashfn; rval->hashfn = hashfn;
@ -143,7 +175,11 @@ HASHENTRIES *entry, *ptr;
} }
} }
free(table->entries); free(table->entries);
free(table);
if (!table->ht_isflat)
{
free(table);
}
} }
/** /**

View File

@ -281,6 +281,7 @@ MODULES *mod = registered;
* @param dlhandle The handle returned by dlopen * @param dlhandle The handle returned by dlopen
* @param version The version string returned by the module * @param version The version string returned by the module
* @param modobj The module object * @param modobj The module object
* @param mod_info The module information
*/ */
static void static void
register_module(const char *module, const char *type, void *dlhandle, char *version, void *modobj, MODULE_INFO *mod_info) register_module(const char *module, const char *type, void *dlhandle, char *version, void *modobj, MODULE_INFO *mod_info)

View File

@ -34,7 +34,7 @@
* Encrypt a password for storing in the MaxScale.cnf file * Encrypt a password for storing in the MaxScale.cnf file
* *
* @param argc Argument count * @param argc Argument count
* @param arv Argument vector * @param argv Argument vector
*/ */
int int
main(int argc, char **argv) main(int argc, char **argv)

View File

@ -29,6 +29,7 @@
*/ */
#include <buffer.h> #include <buffer.h>
#include <string.h> #include <string.h>
#include <mysql_client_server_protocol.h>
/** /**
* Check if a GWBUF structure is a MySQL COM_QUERY packet * Check if a GWBUF structure is a MySQL COM_QUERY packet
@ -171,3 +172,57 @@ GWBUF *addition;
return orig; return orig;
} }
/**
* Copy query string from GWBUF buffer to separate memory area.
*
* @param buf GWBUF buffer including the query
*
* @return Plaint text query if the packet type is COM_QUERY. Otherwise return
* a string including the packet type.
*/
char* modutil_get_query(
GWBUF* buf)
{
uint8_t* packet;
mysql_server_cmd_t packet_type;
size_t len;
char* query_str;
packet = GWBUF_DATA(buf);
packet_type = packet[4];
switch (packet_type) {
case MYSQL_COM_QUIT:
len = strlen("[Quit msg]")+1;
if ((query_str = (char *)malloc(len+1)) == NULL)
{
goto retblock;
}
memcpy(query_str, "[Quit msg]", len);
memset(&query_str[len], 0, 1);
break;
case MYSQL_COM_QUERY:
len = MYSQL_GET_PACKET_LEN(packet)-1; /*< distract 1 for packet type byte */
if ((query_str = (char *)malloc(len+1)) == NULL)
{
goto retblock;
}
memcpy(query_str, &packet[5], len);
memset(&query_str[len], 0, 1);
break;
default:
len = strlen(STRPACKETTYPE(packet_type))+1;
if ((query_str = (char *)malloc(len+1)) == NULL)
{
goto retblock;
}
memcpy(query_str, STRPACKETTYPE(packet_type), len);
memset(&query_str[len], 0, 1);
break;
} /*< switch */
retblock:
return query_str;
}

View File

@ -207,7 +207,8 @@ MONITOR *ptr;
/** /**
* Show a single monitor * Show a single monitor
* *
* @param dcb DCB for printing output * @param dcb DCB for printing output
* @param monitor The monitor to print information regarding
*/ */
void void
monitorShow(DCB *dcb, MONITOR *monitor) monitorShow(DCB *dcb, MONITOR *monitor)
@ -303,7 +304,7 @@ monitorSetInterval (MONITOR *mon, unsigned long interval)
* Enable Replication Heartbeat support in monitor. * Enable Replication Heartbeat support in monitor.
* *
* @param mon The monitor instance * @param mon The monitor instance
* @param interval The sampling interval in milliseconds * @param replication_heartbeat The replication heartbeat
*/ */
void void
monitorSetReplicationHeartbeat(MONITOR *mon, int replication_heartbeat) monitorSetReplicationHeartbeat(MONITOR *mon, int replication_heartbeat)
@ -312,28 +313,3 @@ monitorSetReplicationHeartbeat(MONITOR *mon, int replication_heartbeat)
mon->module->replicationHeartbeat(mon->handle, replication_heartbeat); mon->module->replicationHeartbeat(mon->handle, replication_heartbeat);
} }
} }
/**
* Iterate over the monitors, calling a function per call
*
* @param fcn The function to call
* @param data The data to pass to each call
*/
void
monitorIterate(void (*fcn)(MONITOR *, void *), void *data)
{
MONITOR *monitor, *next;
spinlock_acquire(&monLock);
monitor = allMonitors;
while (monitor)
{
next = monitor->next;
spinlock_release(&monLock);
(*fcn)(monitor, data);
spinlock_acquire(&monLock);
monitor = next;
}
spinlock_release(&monLock);
}

View File

@ -252,8 +252,10 @@ poll_waitevents(void *arg)
static bool process_zombies_only = false; /*< flag for all threads */ static bool process_zombies_only = false; /*< flag for all threads */
DCB *zombies = NULL; DCB *zombies = NULL;
/* Add this thread to the bitmask of running polling threads */ /** Add this thread to the bitmask of running polling threads */
bitmask_set(&poll_mask, thread_id); bitmask_set(&poll_mask, thread_id);
/** Init mysql thread context for use with a mysql handle and a parser */
mysql_thread_init();
while (1) while (1)
{ {
@ -495,6 +497,8 @@ poll_waitevents(void *arg)
return; return;
} }
} /*< while(1) */ } /*< while(1) */
/** Release mysql thread context */
mysql_thread_end();
} }
/** /**

View File

@ -148,8 +148,7 @@ server_set_unique_name(SERVER *server, char *name)
* Find an existing server using the unique section name in * Find an existing server using the unique section name in
* configuration file * configuration file
* *
* @param servname The Server name or address * @param name The Server name defined in the header file
* @param port The server port
* @return The server or NULL if not found * @return The server or NULL if not found
*/ */
SERVER * SERVER *
@ -563,28 +562,3 @@ SERVER_PARAM *param = server->parameters;
} }
return NULL; return NULL;
} }
/**
* Iterate over the servers, calling a function per call
*
* @param fcn The function to call
* @param data The data to pass to each call
*/
void
serverIterate(void (*fcn)(SERVER *, void *), void *data)
{
SERVER *server, *next;
spinlock_acquire(&server_spin);
server = allServers;
while (server)
{
next = server->next;
spinlock_release(&server_spin);
(*fcn)(server, data);
spinlock_acquire(&server_spin);
server = next;
}
spinlock_release(&server_spin);
}

View File

@ -675,6 +675,7 @@ int n = 0;
"Unable to find filter '%s' for service '%s'\n", "Unable to find filter '%s' for service '%s'\n",
trim(ptr), service->name trim(ptr), service->name
))); )));
n--;
} }
flist[n] = NULL; flist[n] = NULL;
ptr = strtok_r(NULL, "|", &brkt); ptr = strtok_r(NULL, "|", &brkt);
@ -1008,7 +1009,7 @@ bool service_set_param_value (
{ {
char* p; char* p;
int valint; int valint;
bool succp; bool succp = true;
/** /**
* Find out whether the value is numeric and ends with '%' or '\0' * Find out whether the value is numeric and ends with '%' or '\0'
@ -1171,28 +1172,3 @@ serviceGetWeightingParameter(SERVICE *service)
{ {
return service->weightby; return service->weightby;
} }
/**
* Iterate over the services, calling a function per call
*
* @param fcn The function to call
* @param data The data to pass to each call
*/
void
serviceIterate(void (*fcn)(SERVICE *, void *), void *data)
{
SERVICE *service, *next;
spinlock_acquire(&service_spin);
service = allServices;
while (service)
{
next = service->next;
spinlock_release(&service_spin);
(*fcn)(service, data);
spinlock_acquire(&service_spin);
service = next;
}
spinlock_release(&service_spin);
}

View File

@ -333,13 +333,15 @@ bool session_free(
{ {
for (i = 0; i < session->n_filters; i++) for (i = 0; i < session->n_filters; i++)
{ {
session->filters[i].filter->obj->closeSession( if (session->filters[i].filter)
session->filters[i].filter->obj->closeSession(
session->filters[i].instance, session->filters[i].instance,
session->filters[i].session); session->filters[i].session);
} }
for (i = 0; i < session->n_filters; i++) for (i = 0; i < session->n_filters; i++)
{ {
session->filters[i].filter->obj->freeSession( if (session->filters[i].filter)
session->filters[i].filter->obj->freeSession(
session->filters[i].instance, session->filters[i].instance,
session->filters[i].session); session->filters[i].session);
} }
@ -653,6 +655,14 @@ int i;
session->n_filters = service->n_filters; session->n_filters = service->n_filters;
for (i = service->n_filters - 1; i >= 0; i--) for (i = service->n_filters - 1; i >= 0; i--)
{ {
if (service->filters[i] == NULL)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Service '%s' contians an unresolved filter.\n",
service->name)));
return 0;
}
if ((head = filterApply(service->filters[i], session, if ((head = filterApply(service->filters[i], session,
&session->head)) == NULL) &session->head)) == NULL)
{ {
@ -757,28 +767,3 @@ session_getUser(SESSION *session)
{ {
return (session && session->client) ? session->client->user : NULL; return (session && session->client) ? session->client->user : NULL;
} }
/**
* Iterate over the sessions, calling a function per call
*
* @param fcn The function to call
* @param data The data to pass to each call
*/
void
sessionIterate(void (*fcn)(SESSION *, void *), void *data)
{
SESSION *session, *next;
spinlock_acquire(&session_spin);
session = allSessions;
while (session)
{
next = session->next;
spinlock_release(&session_spin);
(*fcn)(session, data);
spinlock_acquire(&session_spin);
session = next;
}
spinlock_release(&session_spin);
}

View File

@ -8,7 +8,20 @@ include ../../../makefile.inc
include ../../../test.inc include ../../../test.inc
CC=cc CC=cc
TESTLOG := $(shell pwd)/testhash.log TESTLOG := $(shell pwd)/testcore.log
LOGPATH := $(ROOT_PATH)/log_manager
UTILSPATH := $(ROOT_PATH)/utils
LDFLAGS=-rdynamic -L$(LOGPATH) \
-Wl,-rpath,$(DEST)/lib \
-Wl,-rpath,$(LOGPATH) -Wl,-rpath,$(UTILSPATH) \
-Wl,-rpath,$(EMBEDDED_LIB)
LIBS= -lz -lm -lcrypt -lcrypto -ldl -laio -lrt -pthread -llog_manager \
-L../../inih/extra -linih -lssl -lstdc++
TESTS=testhash testspinlock testfilter testadminusers
cleantests: cleantests:
- $(DEL) *.o - $(DEL) *.o
@ -20,19 +33,41 @@ testall:
$(MAKE) DEBUG=Y buildtests $(MAKE) DEBUG=Y buildtests
$(MAKE) runtests $(MAKE) runtests
buildtests : buildtests : $(TESTS)
testhash: testhash.c
$(CC) $(CFLAGS) \ $(CC) $(CFLAGS) \
-I$(ROOT_PATH)/server/include \ -I$(ROOT_PATH)/server/include \
-I$(ROOT_PATH)/utils \ -I$(ROOT_PATH)/utils \
testhash.c ../hashtable.o ../atomic.o ../spinlock.o -o testhash testhash.c ../hashtable.o ../atomic.o ../spinlock.o -o testhash
runtests: testspinlock: testspinlock.c
$(CC) $(CFLAGS) \
-I$(ROOT_PATH)/server/include \
-I$(ROOT_PATH)/utils \
testspinlock.c ../spinlock.o ../atomic.o ../thread.o -o testspinlock
testfilter: testfilter.c libcore.a
$(CC) $(CFLAGS) $(LDFLAGS) \
-I$(ROOT_PATH)/server/include \
-I$(ROOT_PATH)/utils \
testfilter.c libcore.a $(UTILSPATH)/skygw_utils.o $(LIBS) -o testfilter
testadminusers: testadminusers.c libcore.a
$(CC) $(CFLAGS) $(LDFLAGS) \
-I$(ROOT_PATH)/server/include \
-I$(ROOT_PATH)/utils \
testadminusers.c libcore.a $(UTILSPATH)/skygw_utils.o $(LIBS) -o testadminusers
libcore.a: ../*.o
ar rv libcore.a ../*.o
runtests: $(TESTS)
@echo "" > $(TESTLOG) @echo "" > $(TESTLOG)
@echo "-------------------------------" >> $(TESTLOG) @echo "-------------------------------" >> $(TESTLOG)
@echo $(shell date) >> $(TESTLOG) @echo $(shell date) >> $(TESTLOG)
@echo "Test MaxScale core" >> $(TESTLOG) @echo "Test MaxScale core" >> $(TESTLOG)
@echo "-------------------------------" >> $(TESTLOG) @echo "-------------------------------" >> $(TESTLOG)
@-./testhash 2>> $(TESTLOG) $(foreach var,$(TESTS),./runtest.sh $(var) $(TESTLOG);)
if [ "$$?" == "0" ];then echo "MaxScale core PASSED">> $(TESTLOG);else echo "MaxScale core FAILED">> $(TESTLOG);fi
@echo "" >> $(TESTLOG) @echo "" >> $(TESTLOG)
@cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG)

10
server/core/test/runtest.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
test=$1
log=$2
echo Running test $test >> $log
./$test 2>> $log
if [ $? -ne 0 ]; then
echo $test " " FAILED >> $log
else
echo $test " " PASSED >> $log
fi

View File

@ -0,0 +1,278 @@
/*
* 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 SkySQL Ab 2014
*/
/**
*
* @verbatim
* Revision History
*
* Date Who Description
* 20-08-2014 Mark Riddoch Initial implementation
*
* @endverbatim
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <adminusers.h>
/**
* test1 default user
*
* Test that the username password admin/skysql is accepted if no users
* have been created and that no other users are accepted
*
* WARNING: $MAXSCALE_HOME/etc/passwd must be removed before this test is run
*/
static int
test1()
{
if (admin_verify("admin", "skysql") == 0)
{
fprintf(stderr, "admin_verify: test 1.1 (default user) failed.\n");
return 1;
}
if (admin_verify("bad", "user"))
{
fprintf(stderr, "admin_verify: test 1.2 (wrong user) failed.\n");
return 1;
}
return 0;
}
/**
* test2 creating users
*
* Create a user
* Try to create a duplicate user - expects a failure
* Remove that user - expected to fail as one user must always remain
*/
static int
test2()
{
char *err;
if ((err = admin_add_user("user0", "passwd0")) != NULL)
{
fprintf(stderr, "admin_add_user: test 2.1 (add user) failed, %s.\n", err);
return 1;
}
if (admin_add_user("user0", "passwd0") == NULL)
{
fprintf(stderr, "admin_add_user: test 2.2 (add user) failed, du;plicate.\n");
return 1;
}
/* Deleting the last user is forbidden so we expect this to fail */
if ((err = admin_remove_user("user0", "passwd0")) == NULL)
{
fprintf(stderr, "admin_remove_user: test 2.3 (add user) failed, %s.\n", err);
return 1;
}
return 0;
}
/**
* test3 search/verify users
*
* Create a user
* Search for that user
* Search for a non-existant user
* Remove the user
* Search for the user that was removed
*/
static int
test3()
{
char *err;
if ((err = admin_add_user("user1", "passwd1")) != NULL)
{
fprintf(stderr, "admin_add_user: test 3.1 (add user) failed, %s.\n", err);
return 1;
}
if (admin_search_user("user1") == 0)
{
fprintf(stderr, "admin_search_user: test 3.2 (search user) failed.\n");
return 1;
}
if (admin_search_user("user2") != 0)
{
fprintf(stderr, "admin_search_user: test 3.3 (search user) failed, unexpeted user found.\n");
return 1;
}
if ((err = admin_remove_user("user1", "passwd1")) != NULL)
{
fprintf(stderr, "admin_remove_user: test 3.4 (add user) failed, %s.\n", err);
return 1;
}
if (admin_search_user("user1"))
{
fprintf(stderr, "admin_search_user: test 3.5 (search user) failed - user was deleted.\n");
return 1;
}
return 0;
}
/**
* test4 verify users
*
* Create a numebr of users
* search for each user in turn
* verify each user in turn (password verification)
* Verify each user in turn with incorrect password
* Randomly verify each user
* Remove each user
*/
static int
test4()
{
char *err, user[40], passwd[40];
int i, n_users = 50;
for (i = 1; i < n_users; i++)
{
sprintf(user, "user%d", i);
sprintf(passwd, "passwd%d", i);
if ((err = admin_add_user(user, passwd)) != NULL)
{
fprintf(stderr, "admin_add_user: test 4.1 (add user) failed, %s.\n", err);
return 1;
}
}
for (i = 1; i < n_users; i++)
{
sprintf(user, "user%d", i);
if (admin_search_user(user) == 0)
{
fprintf(stderr, "admin_search_user: test 4.2 (search user) failed.\n");
return 1;
}
}
for (i = 1; i < n_users; i++)
{
sprintf(user, "user%d", i);
sprintf(passwd, "passwd%d", i);
if (admin_verify(user, passwd) == 0)
{
fprintf(stderr, "admin_verify: test 4.3 (search user) failed.\n");
return 1;
}
}
for (i = 1; i < n_users; i++)
{
sprintf(user, "user%d", i);
sprintf(passwd, "badpasswd%d", i);
if (admin_verify(user, passwd) != 0)
{
fprintf(stderr, "admin_verify: test 4.4 (search user) failed.\n");
return 1;
}
}
srand(time(0));
for (i = 1; i < 1000; i++)
{
int j;
j = rand() % n_users;
if (j == 0)
j = 1;
sprintf(user, "user%d", j);
sprintf(passwd, "passwd%d", j);
if (admin_verify(user, passwd) == 0)
{
fprintf(stderr, "admin_verify: test 4.5 (random) failed.\n");
return 1;
}
}
for (i = 1; i < n_users; i++)
{
sprintf(user, "user%d", i);
sprintf(passwd, "passwd%d", i);
if ((err = admin_remove_user(user, passwd)) != NULL)
{
fprintf(stderr, "admin_remove_user: test 4.6 (add user) failed, %s.\n", err);
return 1;
}
}
return 0;
}
/**
* test5 remove first user
*
* Create a user so that user0 may be removed
* Remove the first user created (user0)
*/
static int
test5()
{
char *err;
if ((err = admin_add_user("user", "passwd")) != NULL)
{
fprintf(stderr, "admin_add_user: test 5.1 (add user) failed, %s.\n", err);
return 1;
}
if ((err = admin_remove_user("user0", "passwd0")) != NULL)
{
fprintf(stderr, "admin_remove_user: test 5.2 (add user) failed, %s.\n", err);
return 1;
}
return 0;
}
int
main(int argc, char **argv)
{
int result = 0;
result += test1();
result += test2();
result += test3();
result += test4();
result += test5();
exit(result);
}

View File

@ -0,0 +1,155 @@
/*
* 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 SkySQL Ab 2014
*/
/**
*
* @verbatim
* Revision History
*
* Date Who Description
* 19-08-2014 Mark Riddoch Initial implementation
*
* @endverbatim
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <filter.h>
/**
* test1 Filter creation, finding and deletion
*
*/
static int
test1()
{
FILTER_DEF *f1, *f2;
if ((f1 = filter_alloc("test1", "module")) == NULL)
{
fprintf(stderr, "filter_alloc: test 1 failed.\n");
return 1;
}
if ((f2 = filter_find("test1")) == NULL)
{
fprintf(stderr, "filter_find: test 2 failed.\n");
return 1;
}
filter_free(f1);
if ((f2 = filter_find("test1")) != NULL)
{
fprintf(stderr, "filter_find: test 3 failed delete.\n");
return 1;
}
return 0;
}
/**
* Passive tests for filter_add_option and filter_add_parameter
*
* These tests add options and parameters to a filter, the only failure
* is related hard crashes, such as SIGSEGV etc. as there are no good hooks
* to check the creation of parameters and options currently.
*/
static int
test2()
{
FILTER_DEF *f1;
if ((f1 = filter_alloc("test1", "module")) == NULL)
{
fprintf(stderr, "filter_alloc: test 1 failed.\n");
return 1;
}
filterAddOption(f1, "option1");
filterAddOption(f1, "option2");
filterAddOption(f1, "option3");
filterAddParameter(f1, "name1", "value1");
filterAddParameter(f1, "name2", "value2");
filterAddParameter(f1, "name3", "value3");
return 0;
}
/**
* test3 Filter creation, finding and deletion soak test
*
*/
static int
test3()
{
FILTER_DEF *f1;
char name[40];
int i, n_filters = 1000;
for (i = 0; i < n_filters; i++)
{
sprintf(name, "filter%d", i);
if ((f1 = filter_alloc(name, "module")) == NULL)
{
fprintf(stderr,
"filter_alloc: test 3 failed with %s.\n", name);
return 1;
}
}
for (i = 0; i < n_filters; i++)
{
sprintf(name, "filter%d", i);
if ((f1 = filter_find(name)) == NULL)
{
fprintf(stderr, "filter_find: test 3 failed.\n");
return 1;
}
}
for (i = 0; i < n_filters; i++)
{
sprintf(name, "filter%d", i);
if ((f1 = filter_find(name)) == NULL)
{
fprintf(stderr, "filter_find: test 3 failed.\n");
return 1;
}
filter_free(f1);
if ((f1 = filter_find(name)) != NULL)
{
fprintf(stderr,
"filter_find: test 3 failed - found deleted filter.\n");
return 1;
}
}
return 0;
}
int
main(int argc, char **argv)
{
int result = 0;
result += test1();
result += test2();
result += test3();
exit(result);
}

View File

@ -0,0 +1,132 @@
/*
* 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 SkySQL Ab 2014
*/
/**
*
* @verbatim
* Revision History
*
* Date Who Description
* 18/08-2014 Mark Riddoch Initial implementation
*
* @endverbatim
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <spinlock.h>
#include <thread.h>
/**
* test1 spinlock_acquire_nowait tests
*
* Test that spinlock_acquire_nowait returns false if the spinlock
* is already taken.
*
* Test that spinlock_acquire_nowait returns true if the spinlock
* is not taken.
*
* Test that spinlock_acquire_nowait does hold the spinlock.
*/
static int
test1()
{
SPINLOCK lck;
spinlock_init(&lck);
spinlock_acquire(&lck);
if (spinlock_acquire_nowait(&lck))
{
fprintf(stderr, "spinlock_acquire_nowait: test 1 failed.\n");
return 1;
}
spinlock_release(&lck);
if (!spinlock_acquire_nowait(&lck))
{
fprintf(stderr, "spinlock_acquire_nowait: test 2 failed.\n");
return 1;
}
if (spinlock_acquire_nowait(&lck))
{
fprintf(stderr, "spinlock_acquire_nowait: test 3 failed.\n");
return 1;
}
spinlock_release(&lck);
return 0;
}
static int acquire_time;
static void
test2_helper(void *data)
{
SPINLOCK *lck = (SPINLOCK *)data;
unsigned long t1 = time(0);
spinlock_acquire(lck);
acquire_time = time(0) - t1;
spinlock_release(lck);
return;
}
/**
* Check that spinlock correctly blocks another thread whilst the spinlock
* is held.
*
* Take out a lock.
* Start a second thread to take the same lock
* sleep for 10 seconds
* release lock
* verify that second thread took at least 8 seconds to obtain the lock
*/
static int
test2()
{
SPINLOCK lck;
void *handle;
acquire_time = 0;
spinlock_init(&lck);
spinlock_acquire(&lck);
handle = thread_start(test2_helper, (void *)&lck);
sleep(10);
spinlock_release(&lck);
thread_wait(handle);
if (acquire_time < 8)
{
fprintf(stderr, "spinlock: test 1 failed.\n");
return 1;
}
return 0;
}
main(int argc, char **argv)
{
int result = 0;
result += test1();
result += test2();
exit(result);
}

View File

@ -204,7 +204,7 @@ void gw_sha1_2_str(const uint8_t *in, int in_len, const uint8_t *in2, int in2_le
/** /**
* @node Gets errno corresponding to latest socket error * node Gets errno corresponding to latest socket error
* *
* Parameters: * Parameters:
* @param fd - in, use * @param fd - in, use

View File

@ -29,6 +29,8 @@
* *
* @endverbatim * @endverbatim
*/ */
#include <dcb.h>
#define ADMIN_SALT "MS" #define ADMIN_SALT "MS"
extern int admin_verify(char *, char *); extern int admin_verify(char *, char *);

View File

@ -43,11 +43,14 @@
* *
* @endverbatim * @endverbatim
*/ */
#include <spinlock.h> #include <string.h>
#include <skygw_debug.h> #include <skygw_debug.h>
#include <hint.h> #include <hint.h>
#include <spinlock.h>
EXTERN_C_BLOCK_BEGIN
/** /**
* Buffer properties - used to store properties related to the buffer * Buffer properties - used to store properties related to the buffer
* contents. This may be added at any point during the processing of the * contents. This may be added at any point during the processing of the
@ -89,6 +92,35 @@ typedef struct {
int refcount; /*< Reference count on the buffer */ int refcount; /*< Reference count on the buffer */
} SHARED_BUF; } SHARED_BUF;
typedef enum
{
GWBUF_INFO_NONE = 0x0,
GWBUF_INFO_PARSED = 0x1
} gwbuf_info_t;
#define GWBUF_IS_PARSED(b) (b->gwbuf_info & GWBUF_INFO_PARSED)
/**
* A structure for cleaning up memory allocations of structures which are
* referred to by GWBUF and deallocated in gwbuf_free but GWBUF doesn't
* know what they are.
* All functions on the list are executed before freeing memory of GWBUF struct.
*/
typedef enum
{
GWBUF_PARSING_INFO
} bufobj_id_t;
typedef struct buffer_object_st buffer_object_t;
struct buffer_object_st {
bufobj_id_t bo_id;
void* bo_data;
void (*bo_donefun_fp)(void *);
buffer_object_t* bo_next;
};
/** /**
* The buffer structure used by the descriptor control blocks. * The buffer structure used by the descriptor control blocks.
* *
@ -98,14 +130,15 @@ typedef struct {
* be copied within the gateway. * be copied within the gateway.
*/ */
typedef struct gwbuf { typedef struct gwbuf {
SPINLOCK gwbuf_lock;
struct gwbuf *next; /*< Next buffer in a linked chain of buffers */ struct gwbuf *next; /*< Next buffer in a linked chain of buffers */
void *start; /*< Start of the valid data */ void *start; /*< Start of the valid data */
void *end; /*< First byte after the valid data */ void *end; /*< First byte after the valid data */
SHARED_BUF *sbuf; /*< The shared buffer with the real data */ SHARED_BUF *sbuf; /*< The shared buffer with the real data */
int command;/*< The command type for the queue */ buffer_object_t *gwbuf_bufobj; /*< List of objects referred to by GWBUF */
gwbuf_info_t gwbuf_info; /*< Info bits */
gwbuf_type_t gwbuf_type; /*< buffer's data type information */ gwbuf_type_t gwbuf_type; /*< buffer's data type information */
HINT *hint; /*< Hint data for this buffer */ HINT *hint; /*< Hint data for this buffer */
SPINLOCK lock;
BUF_PROPERTY *properties; /*< Buffer properties */ BUF_PROPERTY *properties; /*< Buffer properties */
} GWBUF; } GWBUF;
@ -144,4 +177,14 @@ extern int gwbuf_add_property(GWBUF *buf, char *name, char *value);
extern char *gwbuf_get_property(GWBUF *buf, char *name); extern char *gwbuf_get_property(GWBUF *buf, char *name);
extern GWBUF *gwbuf_make_contiguous(GWBUF *); extern GWBUF *gwbuf_make_contiguous(GWBUF *);
extern int gwbuf_add_hint(GWBUF *, HINT *); extern int gwbuf_add_hint(GWBUF *, HINT *);
void gwbuf_add_buffer_object(GWBUF* buf,
bufobj_id_t id,
void* data,
void (*donefun_fp)(void *));
void* gwbuf_get_buffer_object_data(GWBUF* buf, bufobj_id_t id);
EXTERN_C_BLOCK_END
#endif #endif

View File

@ -145,9 +145,9 @@ typedef enum {
DCB_STATE_POLLING, /*< Waiting in the poll loop */ DCB_STATE_POLLING, /*< Waiting in the poll loop */
DCB_STATE_LISTENING, /*< The DCB is for a listening socket */ DCB_STATE_LISTENING, /*< The DCB is for a listening socket */
DCB_STATE_DISCONNECTED, /*< The socket is now closed */ DCB_STATE_DISCONNECTED, /*< The socket is now closed */
DCB_STATE_FREED, /*< Memory freed */
DCB_STATE_NOPOLLING, /*< Removed from poll mask */ DCB_STATE_NOPOLLING, /*< Removed from poll mask */
DCB_STATE_ZOMBIE /*< DCB is no longer active, waiting to free it */ DCB_STATE_ZOMBIE, /*< DCB is no longer active, waiting to free it */
DCB_STATE_FREED /*< Memory freed */
} dcb_state_t; } dcb_state_t;
typedef enum { typedef enum {

View File

@ -61,7 +61,7 @@ typedef struct {
* filter pipline * filter pipline
* routeQuery Called on each query that requires * routeQuery Called on each query that requires
* routing * routing
* clientReply * clientReply Called for each reply packet
* diagnostics Called to force the filter to print * diagnostics Called to force the filter to print
* diagnostic output * diagnostic output
* *
@ -88,21 +88,21 @@ typedef struct filter_object {
*/ */
#define FILTER_VERSION {1, 1, 0} #define FILTER_VERSION {1, 1, 0}
/** /**
* The definition of a filter form the configuration file. * The definition of a filter from the configuration file.
* This is basically the link between a plugin to load and the * This is basically the link between a plugin to load and the
* optons to pass to that plugin. * optons to pass to that plugin.
*/ */
typedef struct filter_def { typedef struct filter_def {
char *name; /*< The Filter name */ char *name; /**< The Filter name */
char *module; /*< The module to load */ char *module; /**< The module to load */
char **options; /*< The options set for this filter */ char **options; /**< The options set for this filter */
FILTER_PARAMETER FILTER_PARAMETER
**parameters; /*< The filter parameters */ **parameters; /**< The filter parameters */
FILTER filter; FILTER filter; /**< The runtime filter */
FILTER_OBJECT *obj; FILTER_OBJECT *obj; /**< The "MODULE_OBJECT" for the filter */
SPINLOCK spin; SPINLOCK spin; /**< Spinlock to protect the filter definition */
struct filter_def struct filter_def
*next; /*< Next filter in the chain of all filters */ *next; /**< Next filter in the chain of all filters */
} FILTER_DEF; } FILTER_DEF;
FILTER_DEF *filter_alloc(char *, char *); FILTER_DEF *filter_alloc(char *, char *);

View File

@ -84,12 +84,17 @@ typedef struct hashtable {
SPINLOCK spin; /**< Internal spinlock for the hashtable */ SPINLOCK spin; /**< Internal spinlock for the hashtable */
int n_readers; /**< Number of clients reading the table */ int n_readers; /**< Number of clients reading the table */
int writelock; /**< The table is locked by a writer */ int writelock; /**< The table is locked by a writer */
bool ht_isflat; /**< Indicates whether hashtable is in stack or heap */
#if defined(SS_DEBUG) #if defined(SS_DEBUG)
skygw_chk_t ht_chk_tail; skygw_chk_t ht_chk_tail;
#endif #endif
} HASHTABLE; } HASHTABLE;
extern HASHTABLE *hashtable_alloc(int, int (*hashfn)(), int (*cmpfn)()); extern HASHTABLE *hashtable_alloc(int, int (*hashfn)(), int (*cmpfn)());
HASHTABLE *hashtable_alloc_flat(HASHTABLE* target,
int size,
int (*hashfn)(),
int (*cmpfn)());
/**< Allocate a hashtable */ /**< Allocate a hashtable */
extern void hashtable_memory_fns(HASHTABLE *, HASHMEMORYFN, HASHMEMORYFN, HASHMEMORYFN, HASHMEMORYFN); extern void hashtable_memory_fns(HASHTABLE *, HASHMEMORYFN, HASHMEMORYFN, HASHMEMORYFN, HASHMEMORYFN);
/**< Provide an interface to control key/value memory /**< Provide an interface to control key/value memory

View File

@ -36,4 +36,6 @@ extern int modutil_is_SQL(GWBUF *);
extern int modutil_extract_SQL(GWBUF *, char **, int *); extern int modutil_extract_SQL(GWBUF *, char **, int *);
extern int modutil_MySQL_Query(GWBUF *, char **, int *, int *); extern int modutil_MySQL_Query(GWBUF *, char **, int *, int *);
extern GWBUF *modutil_replace_SQL(GWBUF *, char *); extern GWBUF *modutil_replace_SQL(GWBUF *, char *);
char* modutil_get_query(GWBUF* buf);
#endif #endif

View File

@ -110,5 +110,4 @@ extern void monitorList(DCB *);
extern void monitorSetId(MONITOR *, unsigned long); extern void monitorSetId(MONITOR *, unsigned long);
extern void monitorSetInterval (MONITOR *, unsigned long); extern void monitorSetInterval (MONITOR *, unsigned long);
extern void monitorSetReplicationHeartbeat(MONITOR *, int); extern void monitorSetReplicationHeartbeat(MONITOR *, int);
extern void monitorIterate(void (*fcn)(MONITOR *, void *), void *data);
#endif #endif

View File

@ -169,5 +169,4 @@ extern void serverAddParameter(SERVER *, char *, char *);
extern char *serverGetParameter(SERVER *, char *); extern char *serverGetParameter(SERVER *, char *);
extern void server_update(SERVER *, char *, char *, char *); extern void server_update(SERVER *, char *, char *, char *);
extern void server_set_unique_name(SERVER *, char *); extern void server_set_unique_name(SERVER *, char *);
extern void serverIterate(void (*fcn)(SERVER *, void *), void *data);
#endif #endif

View File

@ -161,7 +161,6 @@ extern void serviceSetFilters(SERVICE *, char *);
extern int serviceEnableRootUser(SERVICE *, int ); extern int serviceEnableRootUser(SERVICE *, int );
extern void serviceWeightBy(SERVICE *, char *); extern void serviceWeightBy(SERVICE *, char *);
extern char *serviceGetWeightingParameter(SERVICE *); extern char *serviceGetWeightingParameter(SERVICE *);
extern void serviceIterate(void (*fcn)(SERVICE *, void *), void *data);
extern void service_update(SERVICE *, char *, char *, char *); extern void service_update(SERVICE *, char *, char *, char *);
extern int service_refresh_users(SERVICE *); extern int service_refresh_users(SERVICE *);
extern void printService(SERVICE *); extern void printService(SERVICE *);

View File

@ -157,7 +157,6 @@ void dprintAllSessions(struct dcb *);
void dprintSession(struct dcb *, SESSION *); void dprintSession(struct dcb *, SESSION *);
void dListSessions(struct dcb *); void dListSessions(struct dcb *);
char *session_state(int); char *session_state(int);
void sessionIterate(void (*fcn)(SESSION *, void *), void *data);
bool session_link_dcb(SESSION *, struct dcb *); bool session_link_dcb(SESSION *, struct dcb *);
SESSION* get_session_by_router_ses(void* rses); SESSION* get_session_by_router_ses(void* rses);
#endif #endif

View File

@ -81,7 +81,7 @@ tags:
(cd hint; touch depend.mk; make tags) (cd hint; touch depend.mk; make tags)
depend: depend:
@rm -f depend.mk @$(DEL) depend.mk
cc -M $(CFLAGS) $(SRCS) > depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk
(cd hint; touch depend.mk; make depend) (cd hint; touch depend.mk; make depend)

View File

@ -17,6 +17,9 @@
*/ */
/** /**
* @file qlafilter.c - Quary Log All Filter
* @verbatim
*
* QLA Filter - Query Log All. A primitive query logging filter, simply * QLA Filter - Query Log All. A primitive query logging filter, simply
* used to verify the filter mechanism for downstream filters. All queries * used to verify the filter mechanism for downstream filters. All queries
* that are passed through the filter will be written to file. * that are passed through the filter will be written to file.
@ -33,6 +36,7 @@
* 11/06/2014 Mark Riddoch Addition of source and match parameters * 11/06/2014 Mark Riddoch Addition of source and match parameters
* 19/06/2014 Mark Riddoch Addition of user parameter * 19/06/2014 Mark Riddoch Addition of user parameter
* *
* @endverbatim
*/ */
#include <stdio.h> #include <stdio.h>
#include <fcntl.h> #include <fcntl.h>
@ -154,6 +158,7 @@ GetModuleObject()
* within MaxScale. * within MaxScale.
* *
* @param options The options for this filter * @param options The options for this filter
* @param params The array of name/value pair parameters for the filter
* *
* @return The instance data for this new instance * @return The instance data for this new instance
*/ */

View File

@ -27,7 +27,8 @@
extern int lm_enabled_logfiles_bitmask; extern int lm_enabled_logfiles_bitmask;
/** /**
* regexfilter.c - a very simple regular expression rewrite filter. * @file regexfilter.c - a very simple regular expression rewrite filter.
* @verbatim
* *
* A simple regular expression query rewrite filter. * A simple regular expression query rewrite filter.
* Two parameters should be defined in the filter configuration * Two parameters should be defined in the filter configuration
@ -39,6 +40,7 @@ extern int lm_enabled_logfiles_bitmask;
* *
* Date Who Description * Date Who Description
* 19/06/2014 Mark Riddoch Addition of source and user parameters * 19/06/2014 Mark Riddoch Addition of source and user parameters
* @endverbatim
*/ */
MODULE_INFO info = { MODULE_INFO info = {
@ -132,6 +134,7 @@ GetModuleObject()
* within MaxScale. * within MaxScale.
* *
* @param options The options for this filter * @param options The options for this filter
* @param params The array of name/value pair parameters for the filter
* *
* @return The instance data for this new instance * @return The instance data for this new instance
*/ */

View File

@ -18,6 +18,7 @@
/** /**
* @file tee.c A filter that splits the processing pipeline in two * @file tee.c A filter that splits the processing pipeline in two
* @verbatim
* *
* Conditionally duplicate requests and send the duplicates to another service * Conditionally duplicate requests and send the duplicates to another service
* within MaxScale. * within MaxScale.
@ -41,6 +42,7 @@
* 20/06/2014 Mark Riddoch Initial implementation * 20/06/2014 Mark Riddoch Initial implementation
* 24/06/2014 Mark Riddoch Addition of support for multi-packet queries * 24/06/2014 Mark Riddoch Addition of support for multi-packet queries
* *
* @endverbatim
*/ */
#include <stdio.h> #include <stdio.h>
#include <fcntl.h> #include <fcntl.h>
@ -162,6 +164,7 @@ GetModuleObject()
* within MaxScale. * within MaxScale.
* *
* @param options The options for this filter * @param options The options for this filter
* @param params The array of name/value pair parameters for the filter
* *
* @return The instance data for this new instance * @return The instance data for this new instance
*/ */

View File

@ -396,8 +396,9 @@ void print_help()
{ {
printf("\nFilter Test Harness\n\n" printf("\nFilter Test Harness\n\n"
"List of commands:\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n" "List of commands:\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n "
"%-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n" "%-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n %-32s%s\n "
"%-32s%s\n %-32s%s\n"
,"help","Prints this help message." ,"help","Prints this help message."
,"run","Feeds the contents of the buffer to the filter chain." ,"run","Feeds the contents of the buffer to the filter chain."
,"add <filter name>","Loads a filter and appeds it to the end of the chain." ,"add <filter name>","Loads a filter and appeds it to the end of the chain."
@ -407,6 +408,8 @@ void print_help()
,"config <file name>","Loads filter configurations from a file." ,"config <file name>","Loads filter configurations from a file."
,"in <file name>","Source file for the SQL statements." ,"in <file name>","Source file for the SQL statements."
,"out <file name>","Destination file for the SQL statements. Defaults to stdout if no parameters were passed." ,"out <file name>","Destination file for the SQL statements. Defaults to stdout if no parameters were passed."
,"threads <number>","Sets the amount of threads to use"
,"sessions <number>","How many sessions to create for each filter. This clears all loaded filters."
,"quiet","Print only error messages." ,"quiet","Print only error messages."
,"verbose","Print everything." ,"verbose","Print everything."
,"exit","Exit the program" ,"exit","Exit the program"
@ -490,9 +493,14 @@ FILTER_PARAMETER** read_params(int* paramc)
int routeQuery(void* ins, void* session, GWBUF* queue) int routeQuery(void* ins, void* session, GWBUF* queue)
{ {
int buffsz = (int)(queue->end - (queue->start + 5)); unsigned int buffsz = 0;
unsigned char* ptr = (void*)queue->start;
char *qstr; char *qstr;
buffsz += *ptr++;
buffsz += *ptr++ << 8;
buffsz += *ptr++ << 16;
if(queue->hint){ if(queue->hint){
buffsz += 40; buffsz += 40;
if(queue->hint->data){ if(queue->hint->data){
@ -506,7 +514,7 @@ int routeQuery(void* ins, void* session, GWBUF* queue)
qstr = calloc(buffsz,sizeof(char)); qstr = calloc(buffsz,sizeof(char));
if(qstr){ if(qstr){
memcpy(qstr,queue->start + 5,(int)(queue->end - 1 - (queue->start + 5))); memcpy(qstr,queue->start + 5,buffsz - 1);
if(queue->hint){ if(queue->hint){
char *ptr = qstr + (int)(queue->end - 1 - (queue->start + 5)); char *ptr = qstr + (int)(queue->end - 1 - (queue->start + 5));
@ -621,9 +629,9 @@ void manual_query()
gwbuf_set_type(instance.buffer[0],GWBUF_TYPE_MYSQL); gwbuf_set_type(instance.buffer[0],GWBUF_TYPE_MYSQL);
memcpy(instance.buffer[0]->sbuf->data + 5,query,qlen); memcpy(instance.buffer[0]->sbuf->data + 5,query,qlen);
instance.buffer[0]->sbuf->data[0] = (qlen>>0&1)|(qlen>>1&1) << 1; instance.buffer[0]->sbuf->data[0] = (qlen);
instance.buffer[0]->sbuf->data[1] = (qlen>>2&1)|(qlen>>3&1) << 1; instance.buffer[0]->sbuf->data[1] = (qlen << 8);
instance.buffer[0]->sbuf->data[2] = (qlen>>4&1)|(qlen>>5&1) << 1; instance.buffer[0]->sbuf->data[2] = (qlen << 16);
instance.buffer[0]->sbuf->data[3] = 0x00; instance.buffer[0]->sbuf->data[3] = 0x00;
instance.buffer[0]->sbuf->data[4] = 0x03; instance.buffer[0]->sbuf->data[4] = 0x03;
@ -706,9 +714,9 @@ int load_query()
memcpy(tmpbff[i]->sbuf->data + 5,query_list[i],strnlen(query_list[i],buff_sz)); memcpy(tmpbff[i]->sbuf->data + 5,query_list[i],strnlen(query_list[i],buff_sz));
qlen = strnlen(query_list[i],buff_sz); qlen = strnlen(query_list[i],buff_sz);
tmpbff[i]->sbuf->data[0] = (qlen>>0&1)|(qlen>>1&1) << 1; tmpbff[i]->sbuf->data[0] = qlen;
tmpbff[i]->sbuf->data[1] = (qlen>>2&1)|(qlen>>3&1) << 1; tmpbff[i]->sbuf->data[1] = (qlen << 8);
tmpbff[i]->sbuf->data[2] = (qlen>>4&1)|(qlen>>5&1) << 1; tmpbff[i]->sbuf->data[2] = (qlen << 16);
tmpbff[i]->sbuf->data[3] = 0x00; tmpbff[i]->sbuf->data[3] = 0x00;
tmpbff[i]->sbuf->data[4] = 0x03; tmpbff[i]->sbuf->data[4] = 0x03;

View File

@ -21,13 +21,15 @@
#include <modutil.h> #include <modutil.h>
/** /**
* testfilter.c - a very simple test filter. * @file testfilter.c - a very simple test filter.
* @verbatim
* *
* This filter is a very simple example used to test the filter API, * This filter is a very simple example used to test the filter API,
* it merely counts the number of statements that flow through the * it merely counts the number of statements that flow through the
* filter pipeline. * filter pipeline.
* *
* Reporting is done via the diagnostics print routine. * Reporting is done via the diagnostics print routine.
* @endverbatim
*/ */
MODULE_INFO info = { MODULE_INFO info = {
@ -114,6 +116,7 @@ GetModuleObject()
* within MaxScale. * within MaxScale.
* *
* @param options The options for this filter * @param options The options for this filter
* @param params The array of name/value pair parameters for the filter
* *
* @return The instance data for this new instance * @return The instance data for this new instance
*/ */

View File

@ -17,6 +17,9 @@
*/ */
/** /**
* @file topfilter.c - Top N Longest Running Queries
* @verbatim
*
* TOPN Filter - Query Log All. A primitive query logging filter, simply * TOPN Filter - Query Log All. A primitive query logging filter, simply
* used to verify the filter mechanism for downstream filters. All queries * used to verify the filter mechanism for downstream filters. All queries
* that are passed through the filter will be written to file. * that are passed through the filter will be written to file.
@ -30,6 +33,8 @@
* *
* Date Who Description * Date Who Description
* 18/06/2014 Mark Riddoch Addition of source and user filters * 18/06/2014 Mark Riddoch Addition of source and user filters
*
* @endverbatim
*/ */
#include <stdio.h> #include <stdio.h>
#include <fcntl.h> #include <fcntl.h>
@ -172,6 +177,7 @@ GetModuleObject()
* within MaxScale. * within MaxScale.
* *
* @param options The options for this filter * @param options The options for this filter
* @param params The array of name/value pair parameters for the filter
* *
* @return The instance data for this new instance * @return The instance data for this new instance
*/ */

View File

@ -48,20 +48,18 @@
#define HTTPD_FIELD_MAXLEN 8192 #define HTTPD_FIELD_MAXLEN 8192
#define HTTPD_REQUESTLINE_MAXLEN 8192 #define HTTPD_REQUESTLINE_MAXLEN 8192
typedef enum {
METHOD_UNKNOWN = 0,
METHOD_POST,
METHOD_PUT,
METHOD_GET,
METHOD_HEAD
} HTTP_METHOD;
/** /**
* HTTPD session specific data * HTTPD session specific data
* *
*/ */
typedef struct httpd_session { typedef struct httpd_session {
HTTP_METHOD method; char user[HTTPD_USER_MAXLEN]; /*< username for authentication*/
GWBUF *saved; char *cookies; /*< all input cookies */
int request_len; char hostname[HTTPD_HOSTNAME_MAXLEN]; /*< The hostname */
char *url; char useragent[HTTPD_USERAGENT_MAXLEN]; /*< The useragent */
char method[HTTPD_METHOD_MAXLEN]; /*< The HTTPD Method */
char *url; /*< the URL in the request */
char *path_info; /*< the Pathinfo, starts with /, is the extra path segments after the document name */
char *query_string; /*< the Query string, starts with ?, after path_info and document name */
int headers_received; /*< All the headers has been received, if 1 */
} HTTPD_session; } HTTPD_session;

View File

@ -102,6 +102,12 @@ typedef enum {
MYSQL_IDLE MYSQL_IDLE
} mysql_auth_state_t; } mysql_auth_state_t;
typedef enum {
MYSQL_PROTOCOL_ALLOC,
MYSQL_PROTOCOL_ACTIVE,
MYSQL_PROTOCOL_DONE
} mysql_protocol_state_t;
/* /*
* MySQL session specific data * MySQL session specific data
@ -270,6 +276,7 @@ typedef struct {
server_command_t protocol_command; /*< session command list */ server_command_t protocol_command; /*< session command list */
server_command_t* protocol_cmd_history; /*< session command history */ server_command_t* protocol_cmd_history; /*< session command history */
mysql_auth_state_t protocol_auth_state; /*< Authentication status */ mysql_auth_state_t protocol_auth_state; /*< Authentication status */
mysql_protocol_state_t protocol_state; /*< Protocol struct status */
uint8_t scramble[MYSQL_SCRAMBLE_LEN]; /*< server scramble, uint8_t scramble[MYSQL_SCRAMBLE_LEN]; /*< server scramble,
* created or received */ * created or received */
uint32_t server_capabilities; /*< server capabilities, uint32_t server_capabilities; /*< server capabilities,

View File

@ -92,7 +92,8 @@ typedef enum rses_property_type_t {
RSES_PROP_TYPE_UNDEFINED=-1, RSES_PROP_TYPE_UNDEFINED=-1,
RSES_PROP_TYPE_SESCMD=0, RSES_PROP_TYPE_SESCMD=0,
RSES_PROP_TYPE_FIRST = RSES_PROP_TYPE_SESCMD, RSES_PROP_TYPE_FIRST = RSES_PROP_TYPE_SESCMD,
RSES_PROP_TYPE_LAST=RSES_PROP_TYPE_SESCMD, RSES_PROP_TYPE_TMPTABLES,
RSES_PROP_TYPE_LAST=RSES_PROP_TYPE_TMPTABLES,
RSES_PROP_TYPE_COUNT=RSES_PROP_TYPE_LAST+1 RSES_PROP_TYPE_COUNT=RSES_PROP_TYPE_LAST+1
} rses_property_type_t; } rses_property_type_t;
@ -157,7 +158,7 @@ struct rses_property_st {
rses_property_type_t rses_prop_type; rses_property_type_t rses_prop_type;
union rses_prop_data { union rses_prop_data {
mysql_sescmd_t sescmd; mysql_sescmd_t sescmd;
void* placeholder; /*< to be removed due new type */ HASHTABLE* temp_tables;
} rses_prop_data; } rses_prop_data;
rses_property_t* rses_prop_next; /*< next property of same type */ rses_property_t* rses_prop_next; /*< next property of same type */
#if defined(SS_DEBUG) #if defined(SS_DEBUG)

View File

@ -20,6 +20,8 @@
# 28/07/14 Massimiliano Pinto new monitor ndbcluster added # 28/07/14 Massimiliano Pinto new monitor ndbcluster added
include ../../../build_gateway.inc include ../../../build_gateway.inc
include ../../../makefile.inc
LOGPATH := $(ROOT_PATH)/log_manager LOGPATH := $(ROOT_PATH)/log_manager
UTILSPATH := $(ROOT_PATH)/utils UTILSPATH := $(ROOT_PATH)/utils
@ -61,13 +63,13 @@ libndbclustermon.so: $(NDBCLUSTEROBJ)
$(CC) $(CFLAGS) $< -o $@ $(CC) $(CFLAGS) $< -o $@
clean: clean:
rm -f $(OBJ) $(MODULES) $(DEL) $(OBJ) $(MODULES)
tags: tags:
ctags $(SRCS) $(HDRS) ctags $(SRCS) $(HDRS)
depend: depend:
@rm -f depend.mk @$(DEL) depend.mk
cc -M $(CFLAGS) $(SRCS) > depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk
install: $(MODULES) install: $(MODULES)

View File

@ -76,7 +76,7 @@ libmaxscaled.so: $(MAXSCALEDOBJ)
$(CC) $(CFLAGS) $< -o $@ $(CC) $(CFLAGS) $< -o $@
clean: clean:
rm -f $(OBJ) $(MODULES) $(DEL) $(OBJ) $(MODULES)
tags: tags:
ctags $(SRCS) $(HDRS) ctags $(SRCS) $(HDRS)
@ -85,7 +85,7 @@ install: $(MODULES)
install -D $(MODULES) $(DEST)/modules install -D $(MODULES) $(DEST)/modules
depend: depend:
rm -f depend.mk @$(DEL) depend.mk
cc -M $(CFLAGS) $(SRCS) > depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk
include depend.mk include depend.mk

View File

@ -33,8 +33,6 @@
* Date Who Description * Date Who Description
* 08/07/2013 Massimiliano Pinto Initial version * 08/07/2013 Massimiliano Pinto Initial version
* 09/07/2013 Massimiliano Pinto Added /show?dcb|session for all dcbs|sessions * 09/07/2013 Massimiliano Pinto Added /show?dcb|session for all dcbs|sessions
* 11/07/2014 Mark Riddoch Recoded as more generic protocol module
* removing hardcoded example
* *
* @endverbatim * @endverbatim
*/ */
@ -42,10 +40,6 @@
#include <httpd.h> #include <httpd.h>
#include <gw.h> #include <gw.h>
#include <modinfo.h> #include <modinfo.h>
#include <skygw_utils.h>
#include <log_manager.h>
extern int lm_enabled_logfiles_bitmask;
MODULE_INFO info = { MODULE_INFO info = {
MODULE_API_PROTOCOL, MODULE_API_PROTOCOL,
@ -54,6 +48,7 @@ MODULE_INFO info = {
"An experimental HTTPD implementation for use in admnistration" "An experimental HTTPD implementation for use in admnistration"
}; };
#define ISspace(x) isspace((int)(x))
#define HTTP_SERVER_STRING "Gateway(c) v.1.0.0" #define HTTP_SERVER_STRING "Gateway(c) v.1.0.0"
static char *version_str = "V1.0.1"; static char *version_str = "V1.0.1";
@ -65,8 +60,8 @@ static int httpd_hangup(DCB *dcb);
static int httpd_accept(DCB *dcb); static int httpd_accept(DCB *dcb);
static int httpd_close(DCB *dcb); static int httpd_close(DCB *dcb);
static int httpd_listen(DCB *dcb, char *config); static int httpd_listen(DCB *dcb, char *config);
static char *httpd_nextline(GWBUF *buf, char *ptr); static int httpd_get_line(int sock, char *buf, int size);
static void httpd_process_header(GWBUF *buf, char *sol, HTTPD_session *client_data); static void httpd_send_headers(DCB *dcb, int final);
/** /**
* The "module object" for the httpd protocol module. * The "module object" for the httpd protocol module.
@ -126,99 +121,132 @@ GetModuleObject()
* @return * @return
*/ */
static int static int
httpd_read_event(DCB *dcb) httpd_read_event(DCB* dcb)
{ {
SESSION *session = dcb->session; //SESSION *session = dcb->session;
GWBUF *buf = NULL; //ROUTER_OBJECT *router = session->service->router;
char *ptr, *sol; //ROUTER *router_instance = session->service->router_instance;
HTTPD_session *client_data = NULL; //void *rsession = session->router_session;
int n;
// Read all the available data int numchars = 1;
if ((n = dcb_read(dcb, &buf)) != -1) char buf[HTTPD_REQUESTLINE_MAXLEN-1] = "";
{ char *query_string = NULL;
client_data = dcb->data; char method[HTTPD_METHOD_MAXLEN-1] = "";
char url[HTTPD_SMALL_BUFFER] = "";
int cgi = 0;
size_t i, j;
int headers_read = 0;
HTTPD_session *client_data = NULL;
if (client_data->saved) client_data = dcb->data;
{
buf = gwbuf_append(client_data->saved, buf);
client_data->saved = NULL;
}
buf = gwbuf_make_contiguous(buf);
ptr = GWBUF_DATA(buf);
if (strncasecmp(ptr, "POST", 4))
{
client_data->method = METHOD_POST;
gwbuf_add_property(buf, "Method", "POST");
ptr = ptr + 4;
}
else if (strncasecmp(ptr, "PUT", 3))
{
client_data->method = METHOD_PUT;
gwbuf_add_property(buf, "Method", "PUT");
ptr = ptr + 3;
}
else if (strncasecmp(ptr, "GET", 3))
{
client_data->method = METHOD_GET;
gwbuf_add_property(buf, "Method", "GET");
ptr = ptr + 3;
}
else if (strncasecmp(ptr, "HEAD", 4))
{
client_data->method = METHOD_HEAD;
gwbuf_add_property(buf, "Method", "HEAD");
ptr = ptr + 4;
}
while (ptr < (char *)(buf->end) && isspace(*ptr))
ptr++;
sol = ptr;
while (ptr < (char *)(buf->end) && isspace(*ptr) == 0)
ptr++;
client_data->url = strndup(sol, ptr - sol);
gwbuf_add_property(buf, "URL", client_data->url);
while ((sol = httpd_nextline(buf, ptr)) != NULL &&
*sol != '\n' && *sol != '\r')
{
httpd_process_header(buf, sol, client_data);
ptr = sol;
}
/*
* We have read all the headers, or run out of data to
* examine.
*/
if (sol == NULL)
{
client_data->saved = buf;
return 0;
}
else
{
if (((char *)(buf->end) - sol)
< client_data->request_len)
{
client_data->saved = buf;
}
else
{
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"HTTPD: request %s.\n", client_data->url)));
SESSION_ROUTE_QUERY(session, buf);
if (client_data->url)
{
free(client_data->url);
client_data->url = NULL;
}
}
}
/**
* get the request line
* METHOD URL HTTP_VER\r\n
*/
numchars = httpd_get_line(dcb->fd, buf, sizeof(buf));
i = 0; j = 0;
while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) {
method[i] = buf[j];
i++; j++;
} }
method[i] = '\0';
strcpy(client_data->method, method);
/* check allowed http methods */
if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) {
//httpd_unimplemented(dcb->fd);
return 0;
}
if (strcasecmp(method, "POST") == 0)
cgi = 1;
i = 0;
while (ISspace(buf[j]) && (j < sizeof(buf))) {
j++;
}
while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) {
url[i] = buf[j];
i++; j++;
}
url[i] = '\0';
/**
* Get the query string if availble
*/
if (strcasecmp(method, "GET") == 0) {
query_string = url;
while ((*query_string != '?') && (*query_string != '\0'))
query_string++;
if (*query_string == '?') {
cgi = 1;
*query_string = '\0';
query_string++;
}
}
/**
* Get the request headers
*/
while ((numchars > 0) && strcmp("\n", buf)) {
char *value = NULL;
char *end = NULL;
numchars = httpd_get_line(dcb->fd, buf, sizeof(buf));
if ( (value = strchr(buf, ':'))) {
*value = '\0';
value++;
end = &value[strlen(value) -1];
*end = '\0';
if (strncasecmp(buf, "Hostname", 6) == 0) {
strcpy(client_data->hostname, value);
}
if (strncasecmp(buf, "useragent", 9) == 0) {
strcpy(client_data->useragent, value);
}
}
}
if (numchars) {
headers_read = 1;
memcpy(&client_data->headers_received, &headers_read, sizeof(int));
}
/**
* Now begins the server reply
*/
/* send all the basic headers and close with \r\n */
httpd_send_headers(dcb, 1);
/**
* ToDO: launch proper content handling based on the requested URI, later REST interface
*
*/
dcb_printf(dcb, "Welcome to HTTPD Gateway (c) %s\n\n", version_str);
if (strcmp(url, "/show") == 0) {
if (strlen(query_string)) {
if (strcmp(query_string, "dcb") == 0)
dprintAllDCBs(dcb);
if (strcmp(query_string, "session") == 0)
dprintAllSessions(dcb);
}
}
/* force the client connecton close */
dcb_close(dcb);
return 0; return 0;
} }
@ -259,18 +287,6 @@ httpd_write(DCB *dcb, GWBUF *queue)
static int static int
httpd_error(DCB *dcb) httpd_error(DCB *dcb)
{ {
HTTPD_session *client_data = NULL;
if (dcb->data)
{
client_data = dcb->data;
if (client_data->url)
{
free(client_data->url);
client_data->url = NULL;
}
free(dcb->data);
dcb->data = NULL;
}
dcb_close(dcb); dcb_close(dcb);
return 0; return 0;
} }
@ -302,7 +318,7 @@ int n_connect = 0;
{ {
int so = -1; int so = -1;
struct sockaddr_in addr; struct sockaddr_in addr;
socklen_t addrlen = 0; socklen_t addrlen;
DCB *client = NULL; DCB *client = NULL;
HTTPD_session *client_data = NULL; HTTPD_session *client_data = NULL;
@ -317,11 +333,10 @@ int n_connect = 0;
memcpy(&client->func, &MyObject, sizeof(GWPROTOCOL)); memcpy(&client->func, &MyObject, sizeof(GWPROTOCOL));
/* we don't need the session */ /* we don't need the session */
client->session = session_alloc(dcb->session->service, client); client->session = NULL;
/* create the session data for HTTPD */ /* create the session data for HTTPD */
client_data = (HTTPD_session *)calloc(1, sizeof(HTTPD_session)); client_data = (HTTPD_session *)calloc(1, sizeof(HTTPD_session));
memset(client_data, 0, sizeof(HTTPD_session));
client->data = client_data; client->data = client_data;
if (poll_add_dcb(client) == -1) if (poll_add_dcb(client) == -1)
@ -410,84 +425,51 @@ int rc;
} }
/** /**
* Return the start of the next line int the buffer. * HTTPD get line from client
*
* @param buf The GWBUF chain
* @param ptr Start point within the buffer
*
* @return the start of the next line or NULL if there are no more lines
*/ */
static char * static int httpd_get_line(int sock, char *buf, int size) {
httpd_nextline(GWBUF *buf, char *ptr) int i = 0;
{ char c = '\0';
while (ptr < (char *)(buf->end) && *ptr != '\n' && *ptr != '\r') int n;
ptr++;
if (ptr >= (char *)(buf->end))
return NULL;
/* Skip prcisely one CR/LF */ while ((i < size - 1) && (c != '\n')) {
if (*ptr == '\r') n = recv(sock, &c, 1, 0);
ptr++; /* DEBUG printf("%02X\n", c); */
if (*ptr == '\n') if (n > 0) {
ptr++; if (c == '\r') {
return ptr; n = recv(sock, &c, 1, MSG_PEEK);
} /* DEBUG printf("%02X\n", c); */
if ((n > 0) && (c == '\n'))
/** recv(sock, &c, 1, 0);
* The headers to extract from the HTTP request and add as properties to the else
* GWBUF structure. c = '\n';
*/
static char *headers[] = {
"Content-Type",
"User-Agent",
"From",
"Date",
NULL
};
/**
* Process a single header line
*
* @param buf The GWBUF that contains the request
* @param sol The current start of line
* @param client_data The client data structure for this request
*/
static void
httpd_process_header(GWBUF *buf, char *sol, HTTPD_session *client_data)
{
char *ptr = sol;
char cbuf[300];
int len, i;
/* Find the end of the line */
while (ptr < (char *)(buf->end) && *ptr != '\n' && *ptr != '\r')
ptr++;
if (strncmp(sol, "Content-Length:", strlen("Content-Length:")) == 0)
{
char *p1 = sol + strlen("Content-Length:");
while (isspace(*p1))
p1++;
len = ptr - p1;
strncpy(cbuf, p1, len);
cbuf[len] = 0;
client_data->request_len = atoi(cbuf);
gwbuf_add_property(buf, "Content-Length", cbuf);
}
else
{
for (i = 0; headers[i]; i++)
{
if (strncmp(sol, headers[i], strlen(headers[i])) == 0)
{
char *p1 = sol + strlen(headers[i]) + 1;
while (isspace(*p1))
p1++;
len = ptr - p1;
strncpy(cbuf, p1, len);
cbuf[len] = 0;
gwbuf_add_property(buf, headers[i], cbuf);
} }
} buf[i] = c;
i++;
} else
c = '\n';
}
buf[i] = '\0';
return i;
}
/**
* HTTPD send basic headers with 200 OK
*/
static void httpd_send_headers(DCB *dcb, int final)
{
char date[64] = "";
const char *fmt = "%a, %d %b %Y %H:%M:%S GMT";
time_t httpd_current_time = time(NULL);
strftime(date, sizeof(date), fmt, localtime(&httpd_current_time));
dcb_printf(dcb, "HTTP/1.1 200 OK\r\nDate: %s\r\nServer: %s\r\nConnection: close\r\nContent-Type: text/plain\r\n", date, HTTP_SERVER_STRING);
/* close the headers */
if (final) {
dcb_printf(dcb, "\r\n");
} }
} }

View File

@ -761,12 +761,13 @@ return_rc:
*/ */
static int gw_error_backend_event(DCB *dcb) static int gw_error_backend_event(DCB *dcb)
{ {
SESSION* session; SESSION* session;
void* rsession; void* rsession;
ROUTER_OBJECT* router; ROUTER_OBJECT* router;
ROUTER* router_instance; ROUTER* router_instance;
GWBUF* errbuf; GWBUF* errbuf;
bool succp; bool succp;
session_state_t ses_state;
CHK_DCB(dcb); CHK_DCB(dcb);
session = dcb->session; session = dcb->session;
@ -775,11 +776,6 @@ static int gw_error_backend_event(DCB *dcb)
router = session->service->router; router = session->service->router;
router_instance = session->service->router_instance; router_instance = session->service->router_instance;
#if defined(SS_DEBUG)
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Backend error event handling.")));
#endif
/** /**
* Avoid running redundant error handling procedure. * Avoid running redundant error handling procedure.
* dcb_close is already called for the DCB. Thus, either connection is * dcb_close is already called for the DCB. Thus, either connection is
@ -795,6 +791,34 @@ static int gw_error_backend_event(DCB *dcb)
0, 0,
"Lost connection to backend server."); "Lost connection to backend server.");
spinlock_acquire(&session->ses_lock);
ses_state = session->state;
spinlock_release(&session->ses_lock);
/**
* Session might be initialized when DCB already is in the poll set.
* Thus hangup can occur in the middle of session initialization.
* Only complete and successfully initialized sessions allow for
* calling error handler.
*/
while (ses_state == SESSION_STATE_READY)
{
spinlock_acquire(&session->ses_lock);
ses_state = session->state;
spinlock_release(&session->ses_lock);
}
if (ses_state != SESSION_STATE_ROUTER_READY)
{
gwbuf_free(errbuf);
goto retblock;
}
#if defined(SS_DEBUG)
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Backend error event handling.")));
#endif
router->handleError(router_instance, router->handleError(router_instance,
rsession, rsession,
errbuf, errbuf,
@ -810,6 +834,7 @@ static int gw_error_backend_event(DCB *dcb)
} }
dcb_close(dcb); dcb_close(dcb);
retblock:
return 1; return 1;
} }
@ -923,12 +948,13 @@ return_fd:
static int static int
gw_backend_hangup(DCB *dcb) gw_backend_hangup(DCB *dcb)
{ {
SESSION* session; SESSION* session;
void* rsession; void* rsession;
ROUTER_OBJECT* router; ROUTER_OBJECT* router;
ROUTER* router_instance; ROUTER* router_instance;
bool succp; bool succp;
GWBUF* errbuf; GWBUF* errbuf;
session_state_t ses_state;
CHK_DCB(dcb); CHK_DCB(dcb);
session = dcb->session; session = dcb->session;
@ -938,18 +964,39 @@ gw_backend_hangup(DCB *dcb)
router = session->service->router; router = session->service->router;
router_instance = session->service->router_instance; router_instance = session->service->router_instance;
errbuf = mysql_create_custom_error(
1,
0,
"Lost connection to backend server.");
spinlock_acquire(&session->ses_lock);
ses_state = session->state;
spinlock_release(&session->ses_lock);
/**
* Session might be initialized when DCB already is in the poll set.
* Thus hangup can occur in the middle of session initialization.
* Only complete and successfully initialized sessions allow for
* calling error handler.
*/
while (ses_state == SESSION_STATE_READY)
{
spinlock_acquire(&session->ses_lock);
ses_state = session->state;
spinlock_release(&session->ses_lock);
}
if (ses_state != SESSION_STATE_ROUTER_READY)
{
gwbuf_free(errbuf);
goto retblock;
}
#if defined(SS_DEBUG) #if defined(SS_DEBUG)
LOGIF(LE, (skygw_log_write_flush( LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR, LOGFILE_ERROR,
"Backend hangup error handling."))); "Backend hangup error handling.")));
#endif #endif
errbuf = mysql_create_custom_error(
1,
0,
"Lost connection to backend server.");
router->handleError(router_instance, router->handleError(router_instance,
rsession, rsession,
errbuf, errbuf,
@ -958,7 +1005,8 @@ gw_backend_hangup(DCB *dcb)
&succp); &succp);
/** There are not required backends available, close session. */ /** There are not required backends available, close session. */
if (!succp) { if (!succp)
{
#if defined(SS_DEBUG) #if defined(SS_DEBUG)
LOGIF(LE, (skygw_log_write_flush( LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR, LOGFILE_ERROR,
@ -971,7 +1019,8 @@ gw_backend_hangup(DCB *dcb)
} }
dcb_close(dcb); dcb_close(dcb);
return 1; retblock:
return 1;
} }
/** /**

View File

@ -65,7 +65,7 @@ static int gw_client_hangup_event(DCB *dcb);
int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message); int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message);
int MySQLSendHandshake(DCB* dcb); int MySQLSendHandshake(DCB* dcb);
static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue); static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue);
static int route_by_statement(SESSION *, GWBUF *); static int route_by_statement(SESSION *, GWBUF **);
/* /*
* The "module object" for the mysqld client protocol module. * The "module object" for the mysqld client protocol module.
@ -783,7 +783,7 @@ int gw_read_client_event(
* Feed each statement completely and separately * Feed each statement completely and separately
* to router. * to router.
*/ */
rc = route_by_statement(session, read_buffer); rc = route_by_statement(session, &read_buffer);
if (read_buffer != NULL) if (read_buffer != NULL)
{ {
@ -1300,12 +1300,19 @@ static int gw_error_client_event(
STRDCBSTATE(dcb->state), STRDCBSTATE(dcb->state),
(session != NULL ? session : NULL)))); (session != NULL ? session : NULL))));
if (session != NULL && session->state == SESSION_STATE_STOPPING)
{
goto retblock;
}
#if defined(SS_DEBUG) #if defined(SS_DEBUG)
LOGIF(LE, (skygw_log_write_flush( LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR, LOGFILE_ERROR,
"Client error event handling."))); "Client error event handling.")));
#endif #endif
dcb_close(dcb); dcb_close(dcb);
retblock:
return 1; return 1;
} }
@ -1380,12 +1387,19 @@ gw_client_hangup_event(DCB *dcb)
{ {
CHK_SESSION(session); CHK_SESSION(session);
} }
if (session != NULL && session->state == SESSION_STATE_STOPPING)
{
goto retblock;
}
#if defined(SS_DEBUG) #if defined(SS_DEBUG)
LOGIF(LE, (skygw_log_write_flush( LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR, LOGFILE_ERROR,
"Client hangup error handling."))); "Client hangup error handling.")));
#endif #endif
dcb_close(dcb); dcb_close(dcb);
retblock:
return 1; return 1;
} }
@ -1399,14 +1413,16 @@ gw_client_hangup_event(DCB *dcb)
* Return 1 in success. If the last packet is incomplete return success but * Return 1 in success. If the last packet is incomplete return success but
* leave incomplete packet to readbuf. * leave incomplete packet to readbuf.
*/ */
static int route_by_statement(SESSION *session, GWBUF *readbuf) static int route_by_statement(
SESSION* session,
GWBUF** p_readbuf)
{ {
int rc = -1; int rc = -1;
GWBUF* packetbuf; GWBUF* packetbuf;
#if defined(SS_DEBUG) #if defined(SS_DEBUG)
GWBUF* tmpbuf; GWBUF* tmpbuf;
tmpbuf = readbuf; tmpbuf = *p_readbuf;
while (tmpbuf != NULL) while (tmpbuf != NULL)
{ {
ss_dassert(GWBUF_IS_TYPE_MYSQL(tmpbuf)); ss_dassert(GWBUF_IS_TYPE_MYSQL(tmpbuf));
@ -1415,15 +1431,14 @@ static int route_by_statement(SESSION *session, GWBUF *readbuf)
#endif #endif
do do
{ {
ss_dassert(GWBUF_IS_TYPE_MYSQL(readbuf)); ss_dassert(GWBUF_IS_TYPE_MYSQL((*p_readbuf)));
packetbuf = gw_MySQL_get_next_packet(&readbuf); packetbuf = gw_MySQL_get_next_packet(p_readbuf);
ss_dassert(GWBUF_IS_TYPE_MYSQL(packetbuf));
if (packetbuf != NULL) if (packetbuf != NULL)
{ {
CHK_GWBUF(packetbuf); CHK_GWBUF(packetbuf);
ss_dassert(GWBUF_IS_TYPE_MYSQL(packetbuf));
/** /**
* This means that buffer includes exactly one MySQL * This means that buffer includes exactly one MySQL
* statement. * statement.
@ -1446,7 +1461,7 @@ static int route_by_statement(SESSION *session, GWBUF *readbuf)
goto return_rc; goto return_rc;
} }
} }
while (readbuf != NULL); while (*p_readbuf != NULL);
return_rc: return_rc:
return rc; return rc;

View File

@ -79,6 +79,7 @@ MySQLProtocol* mysql_protocol_init(
strerror(eno)))); strerror(eno))));
goto return_p; goto return_p;
} }
p->protocol_state = MYSQL_PROTOCOL_ALLOC;
p->protocol_auth_state = MYSQL_ALLOC; p->protocol_auth_state = MYSQL_ALLOC;
p->protocol_command.scom_cmd = MYSQL_COM_UNDEFINED; p->protocol_command.scom_cmd = MYSQL_COM_UNDEFINED;
p->protocol_command.scom_nresponse_packets = 0; p->protocol_command.scom_nresponse_packets = 0;
@ -90,6 +91,7 @@ MySQLProtocol* mysql_protocol_init(
/*< Assign fd with protocol */ /*< Assign fd with protocol */
p->fd = fd; p->fd = fd;
p->owner_dcb = dcb; p->owner_dcb = dcb;
p->protocol_state = MYSQL_PROTOCOL_ACTIVE;
CHK_PROTOCOL(p); CHK_PROTOCOL(p);
return_p: return_p:
return p; return p;
@ -107,15 +109,30 @@ return_p:
void mysql_protocol_done ( void mysql_protocol_done (
DCB* dcb) DCB* dcb)
{ {
server_command_t* scmd = ((MySQLProtocol *)dcb->protocol)->protocol_cmd_history; MySQLProtocol* p;
server_command_t* scmd;
server_command_t* scmd2; server_command_t* scmd2;
p = (MySQLProtocol *)dcb->protocol;
spinlock_acquire(&p->protocol_lock);
if (p->protocol_state != MYSQL_PROTOCOL_ACTIVE)
{
goto retblock;
}
scmd = p->protocol_cmd_history;
while (scmd != NULL) while (scmd != NULL)
{ {
scmd2 = scmd->scom_next; scmd2 = scmd->scom_next;
free(scmd); free(scmd);
scmd = scmd2; scmd = scmd2;
} }
p->protocol_state = MYSQL_PROTOCOL_DONE;
retblock:
spinlock_release(&p->protocol_lock);
} }
@ -1497,6 +1514,9 @@ GWBUF* gw_MySQL_get_next_packet(
size_t packetlen; size_t packetlen;
size_t totalbuflen; size_t totalbuflen;
uint8_t* data; uint8_t* data;
size_t nbytes_copied = 0;
uint8_t* target;
readbuf = *p_readbuf; readbuf = *p_readbuf;
if (readbuf == NULL) if (readbuf == NULL)
@ -1523,42 +1543,27 @@ GWBUF* gw_MySQL_get_next_packet(
packetbuf = NULL; packetbuf = NULL;
goto return_packetbuf; goto return_packetbuf;
} }
/** there is one complete packet in the buffer */
if (packetlen == buflen) packetbuf = gwbuf_alloc(packetlen);
{ target = GWBUF_DATA(packetbuf);
packetbuf = gwbuf_clone_portion(readbuf, 0, packetlen); packetbuf->gwbuf_type = readbuf->gwbuf_type; /*< Copy the type too */
*p_readbuf = gwbuf_consume(readbuf, packetlen);
goto return_packetbuf;
}
/** /**
* Packet spans multiple buffers. * Copy first MySQL packet to packetbuf and leave posible other
* Allocate buffer for complete packet * packets to read buffer.
* copy packet parts into it and consume copied bytes
*/ */
else if (packetlen > buflen) while (nbytes_copied < packetlen && totalbuflen > 0)
{ {
size_t nbytes_copied = 0; uint8_t* src = GWBUF_DATA((*p_readbuf));
uint8_t* target; size_t bytestocopy;
packetbuf = gwbuf_alloc(packetlen); bytestocopy = MIN(buflen,packetlen-nbytes_copied);
target = GWBUF_DATA(packetbuf);
while (nbytes_copied < packetlen) memcpy(target+nbytes_copied, src, bytestocopy);
{ *p_readbuf = gwbuf_consume((*p_readbuf), bytestocopy);
uint8_t* src = GWBUF_DATA(readbuf); totalbuflen = gwbuf_length((*p_readbuf));
size_t buflen = GWBUF_LENGTH(readbuf); nbytes_copied += bytestocopy;
memcpy(target+nbytes_copied, src, buflen);
*p_readbuf = gwbuf_consume(readbuf, buflen);
nbytes_copied += buflen;
}
ss_dassert(nbytes_copied == packetlen);
}
else
{
packetbuf = gwbuf_clone_portion(readbuf, 0, packetlen);
*p_readbuf = gwbuf_consume(readbuf, packetlen);
} }
ss_dassert(buflen == 0 || nbytes_copied == packetlen);
return_packetbuf: return_packetbuf:
return packetbuf; return packetbuf;
@ -1625,11 +1630,16 @@ void protocol_archive_srv_command(
MySQLProtocol* p) MySQLProtocol* p)
{ {
server_command_t* s1; server_command_t* s1;
server_command_t** s2; server_command_t* h1;
int len = 0; int len = 0;
spinlock_acquire(&p->protocol_lock); spinlock_acquire(&p->protocol_lock);
if (p->protocol_state != MYSQL_PROTOCOL_ACTIVE)
{
goto retblock;
}
s1 = &p->protocol_command; s1 = &p->protocol_command;
LOGIF(LT, (skygw_log_write( LOGIF(LT, (skygw_log_write(
@ -1639,17 +1649,18 @@ void protocol_archive_srv_command(
p->owner_dcb->fd))); p->owner_dcb->fd)));
/** Copy to history list */ /** Copy to history list */
s2 = &p->protocol_cmd_history; if ((h1 = p->protocol_cmd_history) == NULL)
if (*s2 != NULL)
{ {
while ((*s2)->scom_next != NULL) p->protocol_cmd_history = server_command_copy(s1);
{ }
*s2 = (*s2)->scom_next; else
len += 1; {
} while (h1->scom_next != NULL)
{
h1 = h1->scom_next;
}
h1->scom_next = server_command_copy(s1);
} }
*s2 = server_command_copy(s1);
/** Keep history limits, remove oldest */ /** Keep history limits, remove oldest */
if (len > MAX_CMD_HISTORY) if (len > MAX_CMD_HISTORY)
@ -1669,6 +1680,8 @@ void protocol_archive_srv_command(
p->protocol_command = *(s1->scom_next); p->protocol_command = *(s1->scom_next);
free(s1->scom_next); free(s1->scom_next);
} }
retblock:
spinlock_release(&p->protocol_lock); spinlock_release(&p->protocol_lock);
} }
@ -1685,6 +1698,10 @@ void protocol_add_srv_command(
spinlock_acquire(&p->protocol_lock); spinlock_acquire(&p->protocol_lock);
if (p->protocol_state != MYSQL_PROTOCOL_ACTIVE)
{
goto retblock;
}
/** this is the only server command in protocol */ /** this is the only server command in protocol */
if (p->protocol_command.scom_cmd == MYSQL_COM_UNDEFINED) if (p->protocol_command.scom_cmd == MYSQL_COM_UNDEFINED)
{ {
@ -1717,6 +1734,7 @@ void protocol_add_srv_command(
c = c->scom_next; c = c->scom_next;
} }
#endif #endif
retblock:
spinlock_release(&p->protocol_lock); spinlock_release(&p->protocol_lock);
} }

View File

@ -44,13 +44,10 @@ DEBUGCLISRCS=debugcli.c debugcmd.c
DEBUGCLIOBJ=$(DEBUGCLISRCS:.c=.o) DEBUGCLIOBJ=$(DEBUGCLISRCS:.c=.o)
CLISRCS=cli.c debugcmd.c CLISRCS=cli.c debugcmd.c
CLIOBJ=$(CLISRCS:.c=.o) CLIOBJ=$(CLISRCS:.c=.o)
WEBSRCS=webserver.o
WEBOBJ=$(WEBSRCS:.c=.o)
SRCS=$(TESTSRCS) $(READCONSRCS) $(DEBUGCLISRCS) cli.c SRCS=$(TESTSRCS) $(READCONSRCS) $(DEBUGCLISRCS) cli.c
OBJ=$(SRCS:.c=.o) OBJ=$(SRCS:.c=.o)
LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager
MODULES= libdebugcli.so libreadconnroute.so libtestroute.so libcli.so \ MODULES= libdebugcli.so libreadconnroute.so libtestroute.so libcli.so
libwebserver.so
all: $(MODULES) all: $(MODULES)
@ -67,9 +64,6 @@ libdebugcli.so: $(DEBUGCLIOBJ)
libcli.so: $(CLIOBJ) libcli.so: $(CLIOBJ)
$(CC) $(LDFLAGS) $(CLIOBJ) $(LIBS) -o $@ $(CC) $(LDFLAGS) $(CLIOBJ) $(LIBS) -o $@
libwebserver.so: $(WEBOBJ)
$(CC) $(LDFLAGS) $(WEBOBJ) $(LIBS) -o $@
libreadwritesplit.so: libreadwritesplit.so:
# (cd readwritesplit; touch depend.mk ; make; cp $@ ..) # (cd readwritesplit; touch depend.mk ; make; cp $@ ..)
@ -77,7 +71,7 @@ libreadwritesplit.so:
$(CC) $(CFLAGS) $< -o $@ $(CC) $(CFLAGS) $< -o $@
clean: clean:
rm -f $(OBJ) $(MODULES) $(DEL) $(OBJ) $(MODULES)
(cd readwritesplit; touch depend.mk; make clean) (cd readwritesplit; touch depend.mk; make clean)
tags: tags:
@ -85,7 +79,7 @@ tags:
(cd readwritesplit; make tags) (cd readwritesplit; make tags)
depend: depend:
@rm -f depend.mk @$(DEL) depend.mk
cc -M $(CFLAGS) $(SRCS) > depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk
(cd readwritesplit; touch depend.mk ; make depend) (cd readwritesplit; touch depend.mk ; make depend)

View File

@ -50,13 +50,13 @@ libreadwritesplit.so: $(OBJ)
$(CC) $(CFLAGS) $< -o $@ $(CC) $(CFLAGS) $< -o $@
clean: clean:
rm -f $(OBJ) $(MODULES) $(DEL) $(OBJ) $(MODULES)
tags: tags:
ctags $(SRCS) $(HDRS) ctags $(SRCS) $(HDRS)
depend: depend:
@rm -f depend.mk @$(DEL) depend.mk
cc -M $(CFLAGS) $(SRCS) > depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk
install: $(MODULES) install: $(MODULES)

View File

@ -31,6 +31,7 @@
#include <dcb.h> #include <dcb.h>
#include <spinlock.h> #include <spinlock.h>
#include <modinfo.h> #include <modinfo.h>
#include <modutil.h>
#include <mysql_client_server_protocol.h> #include <mysql_client_server_protocol.h>
MODULE_INFO info = { MODULE_INFO info = {
@ -280,6 +281,47 @@ static bool have_enough_servers(
static SPINLOCK instlock; static SPINLOCK instlock;
static ROUTER_INSTANCE* instances; static ROUTER_INSTANCE* instances;
static int hashkeyfun(void* key);
static int hashcmpfun (void *, void *);
static int hashkeyfun(
void* key)
{
if(key == NULL){
return 0;
}
unsigned int hash = 0,c = 0;
char* ptr = (char*)key;
while((c = *ptr++)){
hash = c + (hash << 6) + (hash << 16) - hash;
}
return *(int *)key;
}
static int hashcmpfun(
void* v1,
void* v2)
{
char* i1 = (char*) v1;
char* i2 = (char*) v2;
return strcmp(i1,i2);
}
static void* hstrdup(void* fval)
{
char* str = (char*)fval;
return strdup(str);
}
static void* hfree(void* fval)
{
free (fval);
return NULL;
}
/** /**
* Implementation of the mandatory version entry point * Implementation of the mandatory version entry point
* *
@ -956,76 +998,74 @@ static bool get_dcb(
/** get root master from available servers */ /** get root master from available servers */
master_host = get_root_master(backend_ref, rses->rses_nbackends); master_host = get_root_master(backend_ref, rses->rses_nbackends);
if (name != NULL) /*< Choose backend by name from a hint */
{
ss_dassert(btype != BE_MASTER); /*< Master dominates and no name should be passed with it */
for (i=0; i<rses->rses_nbackends; i++)
{
BACKEND* b = backend_ref[i].bref_backend;
/**
* To become chosen:
* backend must be in use, name must match,
* root master node must be found,
* backend's role must be either slave, relay
* server, or master.
*/
if (BREF_IS_IN_USE((&backend_ref[i])) &&
(strncasecmp(
name,
b->backend_server->unique_name,
PATH_MAX) == 0) &&
master_host != NULL &&
(SERVER_IS_SLAVE(b->backend_server) ||
SERVER_IS_RELAY_SERVER(b->backend_server) ||
SERVER_IS_MASTER(b->backend_server)))
{
*p_dcb = backend_ref[i].bref_dcb;
succp = true;
ss_dassert(backend_ref[i].bref_dcb->state != DCB_STATE_ZOMBIE);
break;
}
}
if (succp)
{
goto return_succp;
}
}
if (btype == BE_SLAVE) if (btype == BE_SLAVE)
{ {
if (name != NULL) /*< Choose backend by name (hint) */ for (i=0; i<rses->rses_nbackends; i++)
{ {
for (i=0; i<rses->rses_nbackends; i++) BACKEND* b = backend_ref[i].bref_backend;
{ /**
BACKEND* b = backend_ref[i].bref_backend; * To become chosen:
* backend must be in use,
/** * root master node must be found,
* To become chosen: * backend is not allowed to be the master,
* backend must be in use, name must match, * backend's role can be either slave or relay
* root master node must be found, * server and it must have least connections
* backend's role must be either slave, relay * at the moment.
* server, or master. */
*/ if (BREF_IS_IN_USE((&backend_ref[i])) &&
if (BREF_IS_IN_USE((&backend_ref[i])) && master_host != NULL &&
(strncasecmp( b->backend_server != master_host->backend_server &&
name, (max_rlag == MAX_RLAG_UNDEFINED ||
b->backend_server->unique_name, (b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE &&
MIN(strlen(b->backend_server->unique_name), PATH_MAX)) == 0) && b->backend_server->rlag <= max_rlag)) &&
master_host != NULL && (SERVER_IS_SLAVE(b->backend_server) ||
#if 0 SERVER_IS_RELAY_SERVER(b->backend_server)) &&
(max_rlag == MAX_RLAG_UNDEFINED || (smallest_nconn == -1 ||
(b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE && b->backend_conn_count < smallest_nconn))
b->backend_server->rlag <= max_rlag)) && {
#endif *p_dcb = backend_ref[i].bref_dcb;
(SERVER_IS_SLAVE(b->backend_server) || smallest_nconn = b->backend_conn_count;
SERVER_IS_RELAY_SERVER(b->backend_server) || succp = true;
SERVER_IS_MASTER(b->backend_server))) ss_dassert(backend_ref[i].bref_dcb->state != DCB_STATE_ZOMBIE);
{ }
*p_dcb = backend_ref[i].bref_dcb; }
succp = true;
ss_dassert(backend_ref[i].bref_dcb->state != DCB_STATE_ZOMBIE);
break;
}
}
}
if (!succp) /*< No hints or finding named backend failed */
{
for (i=0; i<rses->rses_nbackends; i++)
{
BACKEND* b = backend_ref[i].bref_backend;
/**
* To become chosen:
* backend must be in use,
* root master node must be found,
* backend is not allowed to be the master,
* backend's role can be either slave or relay
* server and it must have least connections
* at the moment.
*/
if (BREF_IS_IN_USE((&backend_ref[i])) &&
master_host != NULL &&
b->backend_server != master_host->backend_server &&
(max_rlag == MAX_RLAG_UNDEFINED ||
(b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE &&
b->backend_server->rlag <= max_rlag)) &&
(SERVER_IS_SLAVE(b->backend_server) ||
SERVER_IS_RELAY_SERVER(b->backend_server)) &&
(smallest_nconn == -1 ||
b->backend_conn_count < smallest_nconn))
{
*p_dcb = backend_ref[i].bref_dcb;
smallest_nconn = b->backend_conn_count;
succp = true;
ss_dassert(backend_ref[i].bref_dcb->state != DCB_STATE_ZOMBIE);
}
}
}
if (!succp) /*< No valid slave was found, search master next */ if (!succp) /*< No valid slave was found, search master next */
{ {
@ -1065,7 +1105,7 @@ return_succp:
* Examine the query type, transaction state and routing hints. Find out the * Examine the query type, transaction state and routing hints. Find out the
* target for query routing. * target for query routing.
* *
* @param qtype Type of query * @param qtype Type of query
* @param trx_active Is transacation active or not * @param trx_active Is transacation active or not
* @param hint Pointer to list of hints attached to the query buffer * @param hint Pointer to list of hints attached to the query buffer
* *
@ -1086,10 +1126,22 @@ static route_target_t get_route_target (
/** hints don't affect on routing */ /** hints don't affect on routing */
target = TARGET_ALL; target = TARGET_ALL;
} }
else if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) && !trx_active) /**
* Read-only statements to slave or to master can be re-routed after
* the hints
*/
else if ((QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) ||
QUERY_IS_TYPE(qtype, QUERY_TYPE_SESSION_READ)) &&
!trx_active)
{ {
target = TARGET_SLAVE; if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ))
{
target = TARGET_SLAVE;
}
else
{
target = TARGET_MASTER;
}
/** process routing hints */ /** process routing hints */
while (hint != NULL) while (hint != NULL)
{ {
@ -1103,8 +1155,15 @@ static route_target_t get_route_target (
} }
else if (hint->type == HINT_ROUTE_TO_NAMED_SERVER) else if (hint->type == HINT_ROUTE_TO_NAMED_SERVER)
{ {
target |= TARGET_NAMED_SERVER; /*< add */ /**
} * Searching for a named server. If it can't be
* found, the oroginal target is chosen.
*/
target |= TARGET_NAMED_SERVER;
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Hint: route to named server : ")));
}
else if (hint->type == HINT_ROUTE_TO_UPTODATE_SERVER) else if (hint->type == HINT_ROUTE_TO_UPTODATE_SERVER)
{ {
/** not implemented */ /** not implemented */
@ -1140,6 +1199,7 @@ static route_target_t get_route_target (
} }
else if (hint->type == HINT_ROUTE_TO_SLAVE) else if (hint->type == HINT_ROUTE_TO_SLAVE)
{ {
target = TARGET_SLAVE;
LOGIF(LT, (skygw_log_write( LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE, LOGFILE_TRACE,
"Hint: route to slave."))); "Hint: route to slave.")));
@ -1191,15 +1251,27 @@ static int routeQuery(
char* startpos; char* startpos;
mysql_server_cmd_t packet_type; mysql_server_cmd_t packet_type;
uint8_t* packet; uint8_t* packet;
int ret = 0; int ret = 0;
int tsize = 0;
int klen = 0;
int i = 0;
DCB* master_dcb = NULL; DCB* master_dcb = NULL;
DCB* target_dcb = NULL; DCB* target_dcb = NULL;
ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance; ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance;
ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session;
rses_property_t* rses_prop_tmp;
bool rses_is_closed = false; bool rses_is_closed = false;
bool target_tmp_table = false;
char* dbname;
char* hkey;
char** tbl;
HASHTABLE* h;
MYSQL_session* data;
size_t len; size_t len;
MYSQL* mysql = NULL;
route_target_t route_target; route_target_t route_target;
bool succp = false;
int rlag_max = MAX_RLAG_UNDEFINED;
backend_type_t btype; /*< target backend type */
CHK_CLIENT_RSES(router_cli_ses); CHK_CLIENT_RSES(router_cli_ses);
@ -1213,6 +1285,7 @@ static int routeQuery(
packet = GWBUF_DATA(querybuf); packet = GWBUF_DATA(querybuf);
packet_type = packet[4]; packet_type = packet[4];
rses_prop_tmp = router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES];
if (rses_is_closed) if (rses_is_closed)
{ {
@ -1222,6 +1295,8 @@ static int routeQuery(
*/ */
if (packet_type != MYSQL_COM_QUIT) if (packet_type != MYSQL_COM_QUIT)
{ {
char* query_str = modutil_get_query(querybuf);
LOGIF(LE, LOGIF(LE,
(skygw_log_write_flush( (skygw_log_write_flush(
LOGFILE_ERROR, LOGFILE_ERROR,
@ -1229,19 +1304,22 @@ static int routeQuery(
"backend server. %s.", "backend server. %s.",
STRPACKETTYPE(packet_type), STRPACKETTYPE(packet_type),
STRQTYPE(qtype), STRQTYPE(qtype),
(querystr == NULL ? "(empty)" : querystr), (query_str == NULL ? "(empty)" : query_str),
(rses_is_closed ? "Router was closed" : (rses_is_closed ? "Router was closed" :
"Router has no backend servers where to " "Router has no backend servers where to "
"route to")))); "route to"))));
free(querybuf);
} }
goto return_ret; goto retblock;
} }
inst->stats.n_queries++; inst->stats.n_queries++;
startpos = (char *)&packet[5];
master_dcb = router_cli_ses->rses_master_ref->bref_dcb; master_dcb = router_cli_ses->rses_master_ref->bref_dcb;
CHK_DCB(master_dcb); CHK_DCB(master_dcb);
data = (MYSQL_session*)master_dcb->session->data;
dbname = data->db;
switch(packet_type) { switch(packet_type) {
case MYSQL_COM_QUIT: /*< 1 QUIT will close all sessions */ case MYSQL_COM_QUIT: /*< 1 QUIT will close all sessions */
case MYSQL_COM_INIT_DB: /*< 2 DDL must go to the master */ case MYSQL_COM_INIT_DB: /*< 2 DDL must go to the master */
@ -1261,44 +1339,16 @@ static int routeQuery(
break; break;
case MYSQL_COM_QUERY: case MYSQL_COM_QUERY:
plainsqlbuf = gwbuf_clone_transform(querybuf, qtype = query_classifier_get_type(querybuf);
GWBUF_TYPE_PLAINSQL);
len = GWBUF_LENGTH(plainsqlbuf);
/** unnecessary if buffer includes additional terminating null */
querystr = (char *)malloc(len+1);
memcpy(querystr, startpos, len);
memset(&querystr[len], 0, 1);
/**
* Use mysql handle to query information from parse tree.
* call skygw_query_classifier_free before exit!
*/
qtype = skygw_query_classifier_get_type(querystr, 0, &mysql);
break; break;
case MYSQL_COM_STMT_PREPARE: case MYSQL_COM_STMT_PREPARE:
plainsqlbuf = gwbuf_clone_transform(querybuf, qtype = query_classifier_get_type(querybuf);
GWBUF_TYPE_PLAINSQL);
len = GWBUF_LENGTH(plainsqlbuf);
/** unnecessary if buffer includes additional terminating null */
querystr = (char *)malloc(len+1);
memcpy(querystr, startpos, len);
memset(&querystr[len], 0, 1);
qtype = skygw_query_classifier_get_type(querystr, 0, &mysql);
qtype |= QUERY_TYPE_PREPARE_STMT; qtype |= QUERY_TYPE_PREPARE_STMT;
break; break;
case MYSQL_COM_STMT_EXECUTE: case MYSQL_COM_STMT_EXECUTE:
/** Parsing is not needed for this type of packet */ /** Parsing is not needed for this type of packet */
#if defined(NOT_USED)
plainsqlbuf = gwbuf_clone_transform(querybuf,
GWBUF_TYPE_PLAINSQL);
len = GWBUF_LENGTH(plainsqlbuf);
/** unnecessary if buffer includes additional terminating null */
querystr = (char *)malloc(len+1);
memcpy(querystr, startpos, len);
memset(&querystr[len], 0, 1);
qtype = skygw_query_classifier_get_type(querystr, 0, &mysql);
#endif
qtype = QUERY_TYPE_EXEC_STMT; qtype = QUERY_TYPE_EXEC_STMT;
break; break;
@ -1314,6 +1364,48 @@ static int routeQuery(
break; break;
} /**< switch by packet type */ } /**< switch by packet type */
/**
* Check if the query targets a temporary table
*/
if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ))
{
tbl = skygw_get_table_names(querybuf,&tsize);
if (tsize > 0)
{
/** Query targets at least one table */
for(i = 0; i<tsize && !target_tmp_table && tbl[i]; i++)
{
klen = strlen(dbname) + strlen(tbl[i]) + 2;
hkey = calloc(klen,sizeof(char));
strcpy(hkey,dbname);
strcat(hkey,".");
strcat(hkey,tbl[0]);
if (rses_prop_tmp &&
rses_prop_tmp->rses_prop_data.temp_tables)
{
if( (target_tmp_table =
(bool)hashtable_fetch(rses_prop_tmp->rses_prop_data.temp_tables,(void *)hkey)))
{
/**Query target is a temporary table*/
qtype = QUERY_TYPE_READ_TMP_TABLE;
LOGIF(LT,
(skygw_log_write(LOGFILE_TRACE,
"Query targets a temporary table: %s",hkey)));
}
}
free(hkey);
}
for (i = 0; i<tsize; i++)
{
free(tbl[i]);
}
free(tbl);
}
}
/** /**
* If autocommit is disabled or transaction is explicitly started * If autocommit is disabled or transaction is explicitly started
* transaction becomes active and master gets all statements until * transaction becomes active and master gets all statements until
@ -1350,6 +1442,118 @@ static int routeQuery(
router_cli_ses->rses_autocommit_enabled = true; router_cli_ses->rses_autocommit_enabled = true;
router_cli_ses->rses_transaction_active = false; router_cli_ses->rses_transaction_active = false;
} }
/**
* If query is of type QUERY_TYPE_CREATE_TMP_TABLE then find out
* the database and table name, create a hashvalue and
* add it to the router client session's property. If property
* doesn't exist then create it first. If the query is DROP TABLE...
* then see if it targets a temporary table and remove it from the hashtable
* if it does.
*/
if (QUERY_IS_TYPE(qtype, QUERY_TYPE_CREATE_TMP_TABLE))
{
bool is_temp = true;
char* tblname = NULL;
tblname = skygw_get_created_table_name(querybuf);
if (tblname && strlen(tblname) > 0)
{
klen = strlen(dbname) + strlen(tblname) + 2;
hkey = calloc(klen,sizeof(char));
strcpy(hkey,dbname);
strcat(hkey,".");
strcat(hkey,tblname);
}
else
{
hkey = NULL;
}
if(rses_prop_tmp == NULL)
{
if((rses_prop_tmp =
(rses_property_t*)calloc(1,sizeof(rses_property_t))))
{
#if defined(SS_DEBUG)
rses_prop_tmp->rses_prop_chk_top = CHK_NUM_ROUTER_PROPERTY;
rses_prop_tmp->rses_prop_chk_tail = CHK_NUM_ROUTER_PROPERTY;
#endif
rses_prop_tmp->rses_prop_rsession = router_cli_ses;
rses_prop_tmp->rses_prop_refcount = 1;
rses_prop_tmp->rses_prop_next = NULL;
rses_prop_tmp->rses_prop_type = RSES_PROP_TYPE_TMPTABLES;
router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES] = rses_prop_tmp;
}
}
if (rses_prop_tmp->rses_prop_data.temp_tables == NULL)
{
h = hashtable_alloc(7, hashkeyfun, hashcmpfun);
hashtable_memory_fns(h,hstrdup,NULL,hfree,NULL);
if (h != NULL)
{
rses_prop_tmp->rses_prop_data.temp_tables = h;
}
}
if (hkey &&
hashtable_add(rses_prop_tmp->rses_prop_data.temp_tables,
(void *)hkey,
(void *)is_temp) == 0) /*< Conflict in hash table */
{
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Temporary table conflict in hashtable: %s",
hkey)));
}
#if defined(SS_DEBUG)
{
bool retkey =
hashtable_fetch(
rses_prop_tmp->rses_prop_data.temp_tables,
hkey);
if (retkey)
{
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Temporary table added: %s",
hkey)));
}
}
#endif
free(hkey);
}
/** Check if DROP TABLE... targets a temporary table */
if (is_drop_table_query(querybuf))
{
tbl = skygw_get_table_names(querybuf,&tsize);
for(i = 0; i<tsize; i++)
{
klen = strlen(dbname) + strlen(tbl[i]) + 2;
hkey = calloc(klen,sizeof(char));
strcpy(hkey,dbname);
strcat(hkey,".");
strcat(hkey,tbl[i]);
if (rses_prop_tmp &&
rses_prop_tmp->rses_prop_data.temp_tables)
{
if (hashtable_delete(rses_prop_tmp->rses_prop_data.temp_tables,
(void *)hkey))
{
LOGIF(LT, (skygw_log_write(LOGFILE_TRACE,
"Temporary table dropped: %s",hkey)));
}
}
free(tbl[i]);
free(hkey);
}
free(tbl);
}
/** /**
* Find out where to route the query. Result may not be clear; it is * Find out where to route the query. Result may not be clear; it is
* possible to have a hint for routing to a named server which can * possible to have a hint for routing to a named server which can
@ -1357,161 +1561,193 @@ static int routeQuery(
* If query would otherwise be routed to slave then the hint determines * If query would otherwise be routed to slave then the hint determines
* actual target server if it exists. * actual target server if it exists.
* *
* route_target is a bitfield and may include multiple values. * route_target is a bitfield and may include :
* TARGET_ALL
* - route to all connected backend servers
* TARGET_SLAVE[|TARGET_NAMED_SERVER|TARGET_RLAG_MAX]
* - route primarily according to hints, then to slave and if those
* failed, eventually to master
* TARGET_MASTER[|TARGET_NAMED_SERVER|TARGET_RLAG_MAX]
* - route primarily according to the hints and if they failed,
* eventually to master
*/ */
route_target = get_route_target(qtype, route_target = get_route_target(qtype,
router_cli_ses->rses_transaction_active, router_cli_ses->rses_transaction_active,
querybuf->hint); querybuf->hint);
if (TARGET_IS_ALL(route_target)) if (TARGET_IS_ALL(route_target))
{
/**
* It is not sure if the session command in question requires
* response. Statement is examined in route_session_write.
* Router locking is done inside the function.
*/
succp = route_session_write(router_cli_ses,
gwbuf_clone(querybuf),
inst,
packet_type,
qtype);
if (succp)
{
ret = 1;
}
goto retblock;
}
/** Lock router session */
if (!rses_begin_locked_router_action(router_cli_ses))
{
goto retblock;
}
/**
* There is a hint which either names the target backend or
* hint which sets maximum allowed replication lag for the
* backend.
*/
if (TARGET_IS_NAMED_SERVER(route_target) ||
TARGET_IS_RLAG_MAX(route_target))
{
HINT* hint;
char* named_server = NULL;
hint = querybuf->hint;
while (hint != NULL)
{
if (hint->type == HINT_ROUTE_TO_NAMED_SERVER)
{
/**
* Set the name of searched
* backend server.
*/
named_server = hint->data;
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Hint: route to server "
"'%s'",
named_server)));
}
else if (hint->type == HINT_PARAMETER &&
(strncasecmp((char *)hint->data,
"max_slave_replication_lag",
strlen("max_slave_replication_lag")) == 0))
{
int val = (int) strtol((char *)hint->value,
(char **)NULL, 10);
if (val != 0 || errno == 0)
{
/**
* Set max. acceptable
* replication lag
* value for backend srv
*/
rlag_max = val;
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Hint: "
"max_slave_replication_lag=%d",
rlag_max)));
}
}
hint = hint->next;
} /*< while */
if (rlag_max == MAX_RLAG_UNDEFINED) /*< no rlag max hint, use config */
{
rlag_max = rses_get_max_replication_lag(router_cli_ses);
}
btype = BE_UNDEFINED; /*< target may be master or slave */
/**
* Search backend server by name or replication lag.
* If it fails, then try to find valid slave or master.
*/
succp = get_dcb(&target_dcb,
router_cli_ses,
btype,
named_server,
rlag_max);
}
if (!succp && TARGET_IS_SLAVE(route_target))
{
btype = BE_SLAVE;
if (rlag_max == MAX_RLAG_UNDEFINED) /*< no rlag max hint, use config */
{
rlag_max = rses_get_max_replication_lag(router_cli_ses);
}
/**
* Search suitable backend server, get DCB in target_dcb
*/
succp = get_dcb(&target_dcb,
router_cli_ses,
BE_SLAVE,
NULL,
rlag_max);
}
if (!succp && TARGET_IS_MASTER(route_target))
{
if (master_dcb == NULL)
{
succp = get_dcb(&master_dcb,
router_cli_ses,
BE_MASTER,
NULL,
MAX_RLAG_UNDEFINED);
}
else
{
succp = true;
}
target_dcb = master_dcb;
}
ss_dassert(succp);
if (succp) /*< Have DCB of the target backend */
{
if ((ret = target_dcb->func.write(target_dcb, gwbuf_clone(querybuf))) == 1)
{
backend_ref_t* bref;
atomic_add(&inst->stats.n_slave, 1);
/**
* Add one query response waiter to backend reference
*/
bref = get_bref_from_dcb(router_cli_ses, target_dcb);
bref_set_state(bref, BREF_QUERY_ACTIVE);
bref_set_state(bref, BREF_WAITING_RESULT);
}
else
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Routing query \"%s\" failed.",
querystr)));
}
}
rses_end_locked_router_action(router_cli_ses);
retblock:
#if defined(SS_DEBUG)
{ {
/** char* canonical_query_str;
* It is not sure if the session command in question requires
* response. Statement is examined in route_session_write.
*/
bool succp = route_session_write(
router_cli_ses,
querybuf,
inst,
packet_type,
qtype);
if (succp) canonical_query_str = skygw_get_canonical(querybuf);
if (canonical_query_str != NULL)
{ {
ret = 1;
}
goto return_ret;
}
/**
* Handle routing to master and to slave
*/
else
{
bool succp = true;
HINT* hint;
char* named_server = NULL;
int rlag_max = MAX_RLAG_UNDEFINED;
if (router_cli_ses->rses_transaction_active) /*< all to master */
{
route_target = TARGET_MASTER; /*< override old value */
LOGIF(LT, (skygw_log_write( LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE, LOGFILE_TRACE,
"Transaction is active, routing to Master."))); "Canonical version: %s",
canonical_query_str)));
free(canonical_query_str);
} }
LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, "%s", STRQTYPE(qtype))));
/** Lock router session */
if (!rses_begin_locked_router_action(router_cli_ses))
{
goto return_ret;
}
if (TARGET_IS_SLAVE(route_target))
{
if (TARGET_IS_NAMED_SERVER(route_target) ||
TARGET_IS_RLAG_MAX(route_target))
{
hint = querybuf->hint;
while (hint != NULL)
{
if (hint->type == HINT_ROUTE_TO_NAMED_SERVER)
{
named_server = hint->data;
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Hint: route to server "
"'%s'",
named_server)));
}
else if (hint->type == HINT_PARAMETER &&
(strncasecmp(
(char *)hint->data,
"max_slave_replication_lag",
strlen("max_slave_replication_lag")) == 0))
{
int val = (int) strtol((char *)hint->value,
(char **)NULL, 10);
if (val != 0 || errno == 0)
{
rlag_max = val;
LOGIF(LT, (skygw_log_write(
LOGFILE_TRACE,
"Hint: "
"max_slave_replication_lag=%d",
rlag_max)));
}
}
hint = hint->next;
}
}
if (rlag_max == MAX_RLAG_UNDEFINED) /*< no rlag max hint, use config */
{
rlag_max = rses_get_max_replication_lag(router_cli_ses);
}
succp = get_dcb(&target_dcb,
router_cli_ses,
BE_SLAVE,
named_server,
rlag_max);
}
else if (TARGET_IS_MASTER(route_target))
{
if (master_dcb == NULL)
{
succp = get_dcb(&master_dcb,
router_cli_ses,
BE_MASTER,
NULL,
MAX_RLAG_UNDEFINED);
}
target_dcb = master_dcb;
}
if (succp) /*< Have DCB of the target backend */
{
if ((ret = target_dcb->func.write(target_dcb, querybuf)) == 1)
{
backend_ref_t* bref;
atomic_add(&inst->stats.n_slave, 1);
/**
* Add one query response waiter to backend reference
*/
bref = get_bref_from_dcb(router_cli_ses, target_dcb);
bref_set_state(bref, BREF_QUERY_ACTIVE);
bref_set_state(bref, BREF_WAITING_RESULT);
}
else
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Routing query \"%s\" failed.",
querystr)));
}
}
rses_end_locked_router_action(router_cli_ses);
}
return_ret:
if (plainsqlbuf != NULL)
{
gwbuf_free(plainsqlbuf);
}
if (querystr != NULL)
{
free(querystr);
}
if (mysql != NULL)
{
skygw_query_classifier_free(mysql);
} }
#endif
gwbuf_free(querybuf);
return ret; return ret;
} }
@ -1725,8 +1961,10 @@ static void clientReply (
(uint8_t *)GWBUF_DATA((scur->scmd_cur_cmd->my_sescmd_buf)); (uint8_t *)GWBUF_DATA((scur->scmd_cur_cmd->my_sescmd_buf));
size_t len = MYSQL_GET_PACKET_LEN(buf); size_t len = MYSQL_GET_PACKET_LEN(buf);
char* cmdstr = (char *)malloc(len+1); char* cmdstr = (char *)malloc(len+1);
/** data+termination character == len */
snprintf(cmdstr, len, "%s", &buf[5]);
snprintf(cmdstr, len+1, "%s", &buf[5]); ss_dassert(len+4 == GWBUF_LENGTH(scur->scmd_cur_cmd->my_sescmd_buf));
LOGIF(LE, (skygw_log_write_flush( LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR, LOGFILE_ERROR,
@ -2457,6 +2695,11 @@ static void rses_property_done(
case RSES_PROP_TYPE_SESCMD: case RSES_PROP_TYPE_SESCMD:
mysql_sescmd_done(&prop->rses_prop_data.sescmd); mysql_sescmd_done(&prop->rses_prop_data.sescmd);
break; break;
case RSES_PROP_TYPE_TMPTABLES:
hashtable_free(prop->rses_prop_data.temp_tables);
break;
default: default:
LOGIF(LD, (skygw_log_write( LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG, LOGFILE_DEBUG,
@ -2856,8 +3099,23 @@ static bool execute_sescmd_in_backend(
sescmd_cursor_clone_querybuf(scur)); sescmd_cursor_clone_querybuf(scur));
break; break;
case MYSQL_COM_QUERY: case MYSQL_COM_INIT_DB:
case MYSQL_COM_INIT_DB: {
/**
* Record database name and store to session.
*/
GWBUF* tmpbuf;
MYSQL_session* data;
unsigned int qlen;
data = dcb->session->data;
tmpbuf = scur->scmd_cur_cmd->my_sescmd_buf;
qlen = MYSQL_GET_PACKET_LEN((unsigned char*)tmpbuf->start);
memset(data->db,0,MYSQL_DATABASE_MAXLEN+1);
strncpy(data->db,tmpbuf->start+5,qlen - 1);
}
/** Fallthrough */
case MYSQL_COM_QUERY:
default: default:
/** /**
* Mark session command buffer, it triggers writing * Mark session command buffer, it triggers writing
@ -3831,4 +4089,3 @@ static BACKEND *get_root_master(backend_ref_t *servers, int router_nservers) {
} }
return master_host; return master_host;
} }

View File

@ -230,3 +230,65 @@ if [ "$a" != "$TRETVAL" ]; then
else else
echo "$TINPUT PASSED">>$TLOG ; echo "$TINPUT PASSED">>$TLOG ;
fi fi
TINPUT=test_temporary_table.sql
a=`$RUNCMD < ./$TINPUT`
TRETVAL=1
if [ "$a" != "$TRETVAL" ]; then
echo "$TINPUT FAILED, return value $a when $TRETVAL was expected">>$TLOG;
else
echo "$TINPUT PASSED">>$TLOG ;
fi
echo "-----------------------------------" >> $TLOG
echo "Session variables: Stress Test 1" >> $TLOG
echo "-----------------------------------" >> $TLOG
RUNCMD=mysql\ --host=$THOST\ -P$TPORT\ -u$TUSER\ -p$TPWD\ --unbuffered=true\ --disable-reconnect\ -q\ -r
TINPUT=test_sescmd2.sql
for ((i = 0;i<1000;i++))
do
if [[ $(( i % 50 )) -eq 0 ]]
then
printf "."
fi
a=`$RUNCMD < $TINPUT 2>&1`
if [[ "`echo "$a"|grep -i 'error'`" != "" ]]
then
err=`echo "$a" | grep -i error`
break
fi
done
if [[ "$err" == "" ]]
then
echo "TEST PASSED" >> $TLOG
else
echo "$err" >> $TLOG
echo "Test FAILED at iteration $((i+1))" >> $TLOG
fi
echo "-----------------------------------" >> $TLOG
echo "Session variables: Stress Test 2" >> $TLOG
echo "-----------------------------------" >> $TLOG
echo ""
err=""
TINPUT=test_sescmd3.sql
for ((j = 0;j<1000;j++))
do
if [[ $(( j % 50 )) -eq 0 ]]
then
printf "."
fi
b=`$RUNCMD < $TINPUT 2>&1`
if [[ "`echo "$b"|grep -i 'null|error'`" != "" ]]
then
err=`echo "$b" | grep -i null|error`
break
fi
done
if [[ "$err" == "" ]]
then
echo "TEST PASSED" >> $TLOG
else
echo "Test FAILED at iteration $((j+1))" >> $TLOG
fi
echo "" >> $TLOG

View File

@ -1,15 +0,0 @@
-------------------------------
Tue Aug 5 13:31:41 EEST 2014
Test Read/Write split router - hint routing
-------------------------------
-- maxscale route to master FAILED, return value 3003 when 3000 was expected
-- maxscale route to server server1 FAILED, return value 3003 when 3000 was expected
-- maxscale route to server server2 FAILED, return value 3003 when 3001 was expected
-- maxscale route to server server3 FAILED, return value 3003 when 3002 was expected
-- maxscale route to server server4 PASSED
-- maxscale max_slave_replication_lag = 100 FAILED, return value 3003 when was expected
-- maxscale max_slave_replication_lag= 100 FAILED, return value 3003 when was expected
-- maxscale max_slave_replication_lag =100 FAILED, return value 3003 when was expected
-- maxscale max_slave_replication_lag=100 FAILED, return value 3003 when was expected

View File

@ -0,0 +1,20 @@
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

View File

@ -0,0 +1,16 @@
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
select @OLD_SQL_NOTES;
select @OLD_CHARACTER_SET_CLIENT;
select @OLD_CHARACTER_SET_RESULTS;
select @OLD_COLLATION_CONNECTION;
select @OLD_TIME_ZONE;
select @OLD_UNIQUE_CHECKS;
select @OLD_FOREIGN_KEY_CHECKS;
select @OLD_SQL_MODE;

View File

@ -0,0 +1,5 @@
use test;
drop table if exists t1;
create temporary table t1 (id integer);
insert into t1 values(1);
select id from t1;

View File

@ -3,6 +3,7 @@ include ../makefile.inc
CC = gcc CC = gcc
CPP = g++ CPP = g++
UTILS_PATH := $(ROOT_PATH)/utils
makeall: clean all makeall: clean all
@ -13,7 +14,7 @@ clean:
- $(DEL) *~ - $(DEL) *~
all: all:
$(CPP) -c $(CFLAGS) \ $(CPP) -c $(CFLAGS) -I$(UTILS_PATH) \
-fPIC skygw_utils.cc -o skygw_utils.o -fPIC skygw_utils.cc -o skygw_utils.o
cleantests: cleantests:

View File

@ -123,7 +123,8 @@ typedef enum skygw_chk_t {
CHK_NUM_SESCMD_CUR, CHK_NUM_SESCMD_CUR,
CHK_NUM_BACKEND, CHK_NUM_BACKEND,
CHK_NUM_BACKEND_REF, CHK_NUM_BACKEND_REF,
CHK_NUM_PREP_STMT CHK_NUM_PREP_STMT,
CHK_NUM_PINFO
} skygw_chk_t; } skygw_chk_t;
# define STRBOOL(b) ((b) ? "true" : "false") # define STRBOOL(b) ((b) ? "true" : "false")
@ -489,6 +490,13 @@ typedef enum skygw_chk_t {
"Prepared statement struct has invalid check fields"); \ "Prepared statement struct has invalid check fields"); \
} }
#define CHK_PARSING_INFO(p) { \
ss_info_dassert((p)->pi_chk_top == CHK_NUM_PINFO && \
(p)->pi_chk_tail == CHK_NUM_PINFO, \
"Parsing info struct has invalid check fields"); \
}
#if defined(SS_DEBUG) #if defined(SS_DEBUG)
bool conn_open[10240]; bool conn_open[10240];

View File

@ -44,4 +44,6 @@
# endif # endif
#endif #endif
#define MAX_ERROR_MSG PATH_MAX
#endif /* SKYGW_TYPES_H */ #endif /* SKYGW_TYPES_H */

View File

@ -23,9 +23,10 @@
#include <errno.h> #include <errno.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <stddef.h>
#include <regex.h>
#include "skygw_debug.h" #include "skygw_debug.h"
#include "skygw_types.h" #include <skygw_types.h>
#include "skygw_utils.h" #include "skygw_utils.h"
const char* timestamp_formatstr = "%04d %02d/%02d %02d:%02d:%02d "; const char* timestamp_formatstr = "%04d %02d/%02d %02d:%02d:%02d ";
@ -1863,3 +1864,104 @@ void skygw_file_done(
free(file); free(file);
} }
} }
/**
* Find the given needle - user-provided literal - and replace it with
* replacement string. Separate user-provided literals from matching table names
* etc. by searching only substrings preceded by non-letter and non-number.
*
* @param haystack Plain text query string, not to be freed
* @param needle Substring to be searched, not to be freed
* @param replacement Replacement text, not to be freed
*
* @return newly allocated string where needle is replaced
*/
char* replace_literal(
char* haystack,
const char* needle,
const char* replacement)
{
const char* prefix = "[ ='\",\\(]"; /*< ' ','=','(',''',''"',',' are allowed before needle */
const char* suffix = "([^[:alnum:]]|$)"; /*< alpha-num chars aren't allowed after the needle */
char* search_re;
char* newstr;
regex_t re;
regmatch_t match;
int rc;
size_t rlen = strlen(replacement);
size_t nlen = strlen(needle);
size_t hlen = strlen(haystack);
search_re = (char *)malloc(strlen(prefix)+nlen+strlen(suffix)+1);
if (search_re == NULL)
{
fprintf(stderr, "Regex memory allocation failed : %s\n",
strerror(errno));
newstr = haystack;
goto retblock;
}
sprintf(search_re, "%s%s%s", prefix, needle, suffix);
/** Allocate memory for new string +1 for terminating byte */
newstr = (char *)malloc(hlen-nlen+rlen+1);
if (newstr == NULL)
{
fprintf(stderr, "Regex memory allocation failed : %s\n",
strerror(errno));
free(search_re);
free(newstr);
newstr = haystack;
goto retblock;
}
rc = regcomp(&re, search_re, REG_EXTENDED|REG_ICASE);
ss_dassert(rc == 0);
if (rc != 0)
{
char error_message[MAX_ERROR_MSG];
regerror (rc, &re, error_message, MAX_ERROR_MSG);
fprintf(stderr,
"Regex error compiling '%s': %s\n",
search_re,
error_message);
free(search_re);
free(newstr);
newstr = haystack;
goto retblock;
}
rc = regexec(&re, haystack, 1, &match, 0);
if (rc != 0)
{
free(search_re);
free(newstr);
regfree(&re);
newstr = haystack;
goto retblock;
}
memcpy(newstr, haystack, match.rm_so+1);
memcpy(newstr+match.rm_so+1, replacement, rlen);
/** +1 is terminating byte */
memcpy(newstr+match.rm_so+1+rlen, haystack+match.rm_so+1+nlen, hlen-(match.rm_so+1)-nlen+1);
regfree(&re);
free(haystack);
free(search_re);
retblock:
return newstr;
}

View File

@ -192,4 +192,12 @@ int skygw_rwlock_init(skygw_rwlock_t** rwlock);
int atomic_add(int *variable, int value); int atomic_add(int *variable, int value);
EXTERN_C_BLOCK_BEGIN
char* replace_literal(char* haystack,
const char* needle,
const char* replacement);
EXTERN_C_BLOCK_END
#endif /* SKYGW_UTILS_H */ #endif /* SKYGW_UTILS_H */