diff --git a/build_gateway.inc b/build_gateway.inc index 83ffaf762..0e1d6b75f 100644 --- a/build_gateway.inc +++ b/build_gateway.inc @@ -12,7 +12,7 @@ # # Set debug flags # -DEBUG := +DEBUG := ${MAXSCALE_DEBUG} # # Set build env @@ -22,7 +22,7 @@ UNIX := Y # # Set MaxScale branch directory # -ROOT_PATH := $(HOME)/src/bazaar/tmp/maxscale +ROOT_PATH := $(HOME)/${MAXSCALE_SOURCE} 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 # -DYNLIB := +DYNLIB := ${MAXSCALE_DYNLIB} # # Set path to Embedded MySQL Server @@ -51,3 +51,4 @@ endif # Set path to MySQL errors file # ERRMSG := $(HOME)/usr/share/mysql + diff --git a/query_classifier/query_classifier.cc b/query_classifier/query_classifier.cc index 477420e1a..1d9e9f3e1 100644 --- a/query_classifier/query_classifier.cc +++ b/query_classifier/query_classifier.cc @@ -541,8 +541,15 @@ static skygw_query_type_t resolve_query_type( type |= QUERY_TYPE_SESSION_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; } @@ -884,6 +891,183 @@ char* skygw_query_classifier_get_stmtname( } /** + * 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;xalias); + 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. * diff --git a/query_classifier/query_classifier.h b/query_classifier/query_classifier.h index 8284650f4..ccf08a6ea 100644 --- a/query_classifier/query_classifier.h +++ b/query_classifier/query_classifier.h @@ -45,7 +45,9 @@ typedef enum { 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_EXEC_STMT = 0x1000, /*< Execute prepared statement */ - QUERY_TYPE_SESSION_READ = 0x2000 /*< Read session data (from master 31.8.14) */ + 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; @@ -72,6 +74,10 @@ skygw_query_type_t query_classifier_get_type(GWBUF* querybuf); /** Free THD context and close 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 *)); diff --git a/server/core/adminusers.c b/server/core/adminusers.c index 6ed70ed3c..61cd7c077 100644 --- a/server/core/adminusers.c +++ b/server/core/adminusers.c @@ -298,6 +298,7 @@ char* admin_remove_user( fname, err))); fclose(fp); + fclose(fp_tmp); unlink(fname_tmp); return ADMIN_ERR_PWDFILEACCESS; } @@ -325,6 +326,7 @@ char* admin_remove_user( fname, err))); fclose(fp); + fclose(fp_tmp); unlink(fname_tmp); return ADMIN_ERR_PWDFILEACCESS; } diff --git a/server/core/config.c b/server/core/config.c index f2c9185dc..1a8689bfb 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -65,6 +65,29 @@ static char *config_file = NULL; static GATEWAY_CONF gateway; 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 * @@ -508,7 +531,7 @@ int error_count = 0; CONFIG_CONTEXT *obj1 = context; while (obj1) { - if (strcmp(s, obj1->object) == 0 && + if (strcmp(trim(s), obj1->object) == 0 && obj->element && obj1->element) { serviceAddBackend( diff --git a/server/core/dbusers.c b/server/core/dbusers.c index 145f30caa..d318fd5f2 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -380,7 +380,7 @@ getUsers(SERVICE *service, struct users *users) memcpy(users->cksum, hash, SHA_DIGEST_LENGTH); free(users_data); - + free(key.user); mysql_free_result(result); mysql_close(con); diff --git a/server/core/filter.c b/server/core/filter.c index 405a01470..3b3ef4dd6 100644 --- a/server/core/filter.c +++ b/server/core/filter.c @@ -318,6 +318,9 @@ filterApply(FILTER_DEF *filter, SESSION *session, DOWNSTREAM *downstream) { DOWNSTREAM *me; + if (filter == NULL) + return NULL; + if (filter->obj == NULL) { /* Filter not yet loaded */ @@ -359,7 +362,7 @@ DOWNSTREAM *me; 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 diff --git a/server/core/hashtable.c b/server/core/hashtable.c index 50857bfec..81646a864 100644 --- a/server/core/hashtable.c +++ b/server/core/hashtable.c @@ -63,6 +63,10 @@ static void hashtable_read_lock(HASHTABLE *table); static void hashtable_read_unlock(HASHTABLE *table); static void hashtable_write_lock(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 @@ -89,14 +93,42 @@ nullfn(void *data) HASHTABLE * hashtable_alloc(int size, int (*hashfn)(), int (*cmpfn)()) { -HASHTABLE *rval; + return hashtable_alloc_real(NULL, size, hashfn, cmpfn); +} - if ((rval = malloc(sizeof(HASHTABLE))) == NULL) - return NULL; +HASHTABLE* hashtable_alloc_flat( + 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) - rval->ht_chk_top = CHK_NUM_HASHTABLE; - rval->ht_chk_tail = CHK_NUM_HASHTABLE; + rval->ht_chk_top = CHK_NUM_HASHTABLE; + rval->ht_chk_tail = CHK_NUM_HASHTABLE; #endif rval->hashsize = size; rval->hashfn = hashfn; @@ -114,7 +146,7 @@ HASHTABLE *rval; return NULL; } memset(rval->entries, 0, size * sizeof(HASHENTRIES *)); - + return rval; } @@ -143,7 +175,11 @@ HASHENTRIES *entry, *ptr; } } free(table->entries); - free(table); + + if (!table->ht_isflat) + { + free(table); + } } /** diff --git a/server/core/service.c b/server/core/service.c index 080a65d8e..2068c4757 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -1009,7 +1009,7 @@ bool service_set_param_value ( { char* p; int valint; - bool succp; + bool succp = true; /** * Find out whether the value is numeric and ends with '%' or '\0' diff --git a/server/core/session.c b/server/core/session.c index 62e1015e4..db59f9e6e 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -333,13 +333,15 @@ bool session_free( { 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].session); } 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].session); } @@ -653,6 +655,14 @@ int i; session->n_filters = service->n_filters; 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, &session->head)) == NULL) { diff --git a/server/core/test/makefile b/server/core/test/makefile index 44c5d08a6..7f6831082 100644 --- a/server/core/test/makefile +++ b/server/core/test/makefile @@ -8,7 +8,7 @@ include ../../../makefile.inc include ../../../test.inc CC=cc -TESTLOG := $(shell pwd)/testhash.log +TESTLOG := $(shell pwd)/testcore.log LOGPATH := $(ROOT_PATH)/log_manager UTILSPATH := $(ROOT_PATH)/utils @@ -21,7 +21,7 @@ LDFLAGS=-rdynamic -L$(LOGPATH) \ LIBS= -lz -lm -lcrypt -lcrypto -ldl -laio -lrt -pthread -llog_manager \ -L../../inih/extra -linih -lssl -lstdc++ -TESTS=testhash testspinlock testfilter +TESTS=testhash testspinlock testfilter testadminusers cleantests: - $(DEL) *.o @@ -40,17 +40,25 @@ testhash: testhash.c -I$(ROOT_PATH)/server/include \ -I$(ROOT_PATH)/utils \ testhash.c ../hashtable.o ../atomic.o ../spinlock.o -o testhash + 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 @@ -62,4 +70,4 @@ runtests: $(TESTS) @echo "-------------------------------" >> $(TESTLOG) $(foreach var,$(TESTS),./runtest.sh $(var) $(TESTLOG);) @echo "" >> $(TESTLOG) - @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) \ No newline at end of file + @cat $(TESTLOG) >> $(TEST_MAXSCALE_LOG) diff --git a/server/core/test/testadminusers.c b/server/core/test/testadminusers.c new file mode 100644 index 000000000..00ec3a452 --- /dev/null +++ b/server/core/test/testadminusers.c @@ -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 +#include +#include + +#include + + +/** + * 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); +} + diff --git a/server/core/test/testfilter.c b/server/core/test/testfilter.c index eefc8ecfa..55f7fadf3 100644 --- a/server/core/test/testfilter.c +++ b/server/core/test/testfilter.c @@ -140,6 +140,8 @@ int i, n_filters = 1000; return 0; } + +int main(int argc, char **argv) { int result = 0; diff --git a/server/include/adminusers.h b/server/include/adminusers.h index d46570e38..07afa5390 100644 --- a/server/include/adminusers.h +++ b/server/include/adminusers.h @@ -29,6 +29,8 @@ * * @endverbatim */ +#include + #define ADMIN_SALT "MS" extern int admin_verify(char *, char *); diff --git a/server/include/hashtable.h b/server/include/hashtable.h index 92bc71b8e..175bd5d32 100644 --- a/server/include/hashtable.h +++ b/server/include/hashtable.h @@ -84,12 +84,17 @@ typedef struct hashtable { SPINLOCK spin; /**< Internal spinlock for the hashtable */ int n_readers; /**< Number of clients reading the table */ int writelock; /**< The table is locked by a writer */ + bool ht_isflat; /**< Indicates whether hashtable is in stack or heap */ #if defined(SS_DEBUG) skygw_chk_t ht_chk_tail; #endif } HASHTABLE; extern HASHTABLE *hashtable_alloc(int, int (*hashfn)(), int (*cmpfn)()); +HASHTABLE *hashtable_alloc_flat(HASHTABLE* target, + int size, + int (*hashfn)(), + int (*cmpfn)()); /**< Allocate a hashtable */ extern void hashtable_memory_fns(HASHTABLE *, HASHMEMORYFN, HASHMEMORYFN, HASHMEMORYFN, HASHMEMORYFN); /**< Provide an interface to control key/value memory diff --git a/server/modules/include/readwritesplit.h b/server/modules/include/readwritesplit.h index 1a8936c74..07254f37f 100644 --- a/server/modules/include/readwritesplit.h +++ b/server/modules/include/readwritesplit.h @@ -92,7 +92,8 @@ typedef enum rses_property_type_t { RSES_PROP_TYPE_UNDEFINED=-1, RSES_PROP_TYPE_SESCMD=0, 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_property_type_t; @@ -157,7 +158,7 @@ struct rses_property_st { rses_property_type_t rses_prop_type; union rses_prop_data { mysql_sescmd_t sescmd; - void* placeholder; /*< to be removed due new type */ + HASHTABLE* temp_tables; } rses_prop_data; rses_property_t* rses_prop_next; /*< next property of same type */ #if defined(SS_DEBUG) diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index cfdc121eb..00bcf0b59 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -281,6 +281,47 @@ static bool have_enough_servers( static SPINLOCK instlock; 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 * @@ -1210,12 +1251,22 @@ static int routeQuery( char* startpos; mysql_server_cmd_t packet_type; uint8_t* packet; - int ret = 0; + int ret = 0; + int tsize = 0; + int klen = 0; + int i = 0; DCB* master_dcb = NULL; DCB* target_dcb = NULL; ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance; ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; + rses_property_t* rses_prop_tmp; bool rses_is_closed = false; + bool target_tmp_table = false; + char* dbname; + char* hkey; + char** tbl; + HASHTABLE* h; + MYSQL_session* data; size_t len; route_target_t route_target; bool succp = false; @@ -1234,6 +1285,7 @@ static int routeQuery( packet = GWBUF_DATA(querybuf); packet_type = packet[4]; + rses_prop_tmp = router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES]; if (rses_is_closed) { @@ -1264,6 +1316,9 @@ static int routeQuery( master_dcb = router_cli_ses->rses_master_ref->bref_dcb; CHK_DCB(master_dcb); + + data = (MYSQL_session*)master_dcb->session->data; + dbname = data->db; switch(packet_type) { case MYSQL_COM_QUIT: /*< 1 QUIT will close all sessions */ @@ -1309,6 +1364,48 @@ static int routeQuery( break; } /**< 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; irses_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; irses_autocommit_enabled = true; 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; irses_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 * possible to have a hint for routing to a named server which can @@ -2486,6 +2695,11 @@ static void rses_property_done( case RSES_PROP_TYPE_SESCMD: mysql_sescmd_done(&prop->rses_prop_data.sescmd); break; + + case RSES_PROP_TYPE_TMPTABLES: + hashtable_free(prop->rses_prop_data.temp_tables); + break; + default: LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, @@ -2885,8 +3099,23 @@ static bool execute_sescmd_in_backend( sescmd_cursor_clone_querybuf(scur)); 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: /** * Mark session command buffer, it triggers writing @@ -3171,7 +3400,7 @@ static bool route_session_write( { succp = false; goto return_succp; - } + } /** * Additional reference is created to querybuf to * prevent it from being released before properties diff --git a/server/modules/routing/readwritesplit/test/rwsplit.sh b/server/modules/routing/readwritesplit/test/rwsplit.sh index c2e403b16..f55960009 100755 --- a/server/modules/routing/readwritesplit/test/rwsplit.sh +++ b/server/modules/routing/readwritesplit/test/rwsplit.sh @@ -230,6 +230,16 @@ if [ "$a" != "$TRETVAL" ]; then else echo "$TINPUT PASSED">>$TLOG ; 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 @@ -238,6 +248,10 @@ RUNCMD=mysql\ --host=$THOST\ -P$TPORT\ -u$TUSER\ -p$TPWD\ --unbuffered=true\ --d 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 @@ -255,15 +269,19 @@ 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 - b=`$RUNCMD < $TINPUT 2>&1` - if [[ "`echo "$b"|grep -i 'null'`" != "" ]] + if [[ $(( j % 50 )) -eq 0 ]] then - err=`echo "$b" | grep -i null` + 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 diff --git a/server/modules/routing/readwritesplit/test/test_temporary_table.sql b/server/modules/routing/readwritesplit/test/test_temporary_table.sql new file mode 100644 index 000000000..374510389 --- /dev/null +++ b/server/modules/routing/readwritesplit/test/test_temporary_table.sql @@ -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; diff --git a/utils/skygw_utils.cc b/utils/skygw_utils.cc index d8b768199..707957214 100644 --- a/utils/skygw_utils.cc +++ b/utils/skygw_utils.cc @@ -1912,6 +1912,7 @@ char* replace_literal( fprintf(stderr, "Regex memory allocation failed : %s\n", strerror(errno)); free(search_re); + free(newstr); newstr = haystack; goto retblock; } @@ -1928,6 +1929,7 @@ char* replace_literal( search_re, error_message); free(search_re); + free(newstr); newstr = haystack; goto retblock; } @@ -1936,6 +1938,7 @@ char* replace_literal( if (rc != 0) { free(search_re); + free(newstr); regfree(&re); newstr = haystack; goto retblock;