diff --git a/query_classifier/makefile b/query_classifier/makefile new file mode 100644 index 000000000..67554007a --- /dev/null +++ b/query_classifier/makefile @@ -0,0 +1,43 @@ +include ../build_gateway.inc +include ../makefile.inc + +CC = gcc +CPP = g++ + +QUERY_CLASSIFIER_PATH := $(shell pwd) + +makeall: clean all + +clean: + - $(DEL) query_classifier.o + - $(DEL) libquery_classifier.so + - $(DEL) libquery_classifier.so.1.0.1 + - $(DEL) skygw_utils.o + - $(DEL) *~ + +all: utils lib + +utils: + make -C $(ROOT_PATH)/utils clean all + $(COPY) $(ROOT_PATH)/utils/skygw_utils.o ./ + +lib: libcomp liblink + +libcomp: + $(CPP) -c $(CFLAGS) \ + -I$(MARIADB_SRC_PATH)/libmysqld/ \ + -I$(MARIADB_SRC_PATH)/include/ \ + -I$(MARIADB_SRC_PATH)/sql \ + -I$(MARIADB_SRC_PATH)/regex/ \ + -I./ \ + -fPIC ./query_classifier.cc -o query_classifier.o + +liblink: + $(CPP) -shared \ + -L$(MARIADB_SRC_PATH)/libmysqld \ + -Wl,-soname,libquery_classifier.so \ + -Wl,-rpath,$(MARIADB_SRC_PATH)/libmysqld \ + -o libquery_classifier.so.1.0.1 ./query_classifier.o \ + $(LDLIBS) $(CPP_LDLIBS) + $(DEL) ./libquery_classifier.so + $(LINK) ./libquery_classifier.so.1.0.1 ./libquery_classifier.so diff --git a/query_classifier/query_classifier.cc b/query_classifier/query_classifier.cc new file mode 100644 index 000000000..fe55adc91 --- /dev/null +++ b/query_classifier/query_classifier.cc @@ -0,0 +1,409 @@ +/** + * @section LICENCE + * + * This file is distributed as part of the SkySQL Gateway. 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 + * + * @file + * + */ + +#define EMBEDDED_LIBRARY +#define MYSQL_YACC +#define MYSQL_LEX012 +#define MYSQL_SERVER +#if defined(MYSQL_CLIENT) +# undef MYSQL_CLIENT +#endif + +#include +#include "../utils/skygw_types.h" +#include "../utils/skygw_debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +static THD* get_or_create_thd_for_parsing( + MYSQL* mysql, + char* query_str); + +static unsigned long set_client_flags( + MYSQL* mysql); + +static bool create_parse_tree( + THD* thd); + +static skygw_query_type_t resolve_query_type( + THD* thd); + +/** + * @node (write brief function description here) + * + * Parameters: + * @param query_str - + * + * + * @param client_flag - + * + * + * @return + * + * + * @details (write detailed description here) + * + */ +skygw_query_type_t skygw_query_classifier_get_type( + const char* query, + unsigned long client_flags) +{ + MYSQL* mysql; + char* query_str; + const char* user = "skygw"; + const char* db = "skygw"; + THD* thd; + skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN; + bool failp = FALSE; + + ss_dfprintf(stderr, ">> skygw_query_classifier_get_type\n"); + ss_info_dassert(query != NULL, ("query_str is NULL")); + + query_str = const_cast(query); + + fprintf(stderr, " Query \"%s\"\n", query_str); + + /** Get server handle */ + mysql = mysql_init(NULL); + + if (mysql == NULL) { + fprintf(stderr, + "mysql_real_connect failed, %d : %s\n", + mysql_errno(mysql), + mysql_error(mysql)); + mysql_library_end(); + goto return_without_server; + } + + /** 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; + + /** 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) { + goto return_with_server_handle; + } + /** Create parse_tree inside thd */ + failp = create_parse_tree(thd); + + if (failp) { + goto return_with_thd; + } + qtype = resolve_query_type(thd); + +return_with_thd: + (*mysql->methods->free_embedded_thd)(mysql); + mysql->thd = 0; +return_with_server_handle: + mysql_close(mysql); + mysql_thread_end(); +return_without_server: + ss_dfprintf(stderr, + "<< skygw_query_classifier_get_type : %s\n", + STRQTYPE(qtype)); + ss_dfflush(stderr); + return qtype; +} + + + +/** + * @node (write brief function description here) + * + * Parameters: + * @param mysql - + * + * + * @param query_str - + * + * + * @return + * + * + * @details (write detailed description here) + * + */ +static THD* get_or_create_thd_for_parsing( + MYSQL* mysql, + char* query_str) +{ + THD* thd = NULL; + unsigned long client_flags; + char* db = mysql->options.db; + bool failp = FALSE; + size_t query_len; + + ss_dfprintf(stderr, "> get_or_create_thd_for_parsing\n"); + ss_info_dassert(mysql != NULL, ("mysql is NULL")); + ss_info_dassert(query_str != NULL, ("query_str is NULL")); + + query_len = strlen(query_str); + client_flags = set_client_flags(mysql); + + /** Get THD. + * NOTE: Instead of creating new every time, THD instance could + * be get from a pool of them. + */ + thd = (THD *)create_embedded_thd(client_flags); + + if (thd == NULL) { + ss_dfprintf(stderr, "Couldn't create embedded thd\n"); + goto return_thd; + } + mysql->thd = thd; + init_embedded_mysql(mysql, client_flags); + failp = check_embedded_connection(mysql, db); + + if (failp) { + ss_dfprintf(stderr, "Checking embedded connection failed.\n"); + goto return_err_with_thd; + } + thd->clear_data_list(); + + /** Check that we are calling the client functions in right order */ + if (mysql->status != MYSQL_STATUS_READY) { + set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); + goto return_err_with_thd; + } + /* Clear result variables */ + thd->current_stmt= NULL; + thd->store_globals(); + /* + We have to call free_old_query before we start to fill mysql->fields + for new query. In the case of embedded server we collect field data + during query execution (not during data retrieval as it is in remote + client). So we have to call free_old_query here + */ + free_old_query(mysql); + thd->extra_length = query_len; + thd->extra_data = query_str; + alloc_query(thd, query_str, query_len); + goto return_thd; + +return_err_with_thd: + (*mysql->methods->free_embedded_thd)(mysql); + thd = 0; + mysql->thd = 0; +return_thd: + ss_dfprintf(stderr, "< get_or_create_thd_for_parsing : %p\n", thd); + ss_dfflush(stderr); + return thd; +} + + + +/** + * @node Set client flags. This is copied from libmysqld.c:mysql_real_connect + * + * Parameters: + * @param mysql - + * + * + * @return + * + * + * @details (write detailed description here) + * + */ +static unsigned long set_client_flags( + MYSQL* mysql) +{ + unsigned long f = 0; + ss_dfprintf(stderr, "> set_client_flags\n"); + f |= mysql->options.client_flag; + + /* Send client information for access check */ + f |= CLIENT_CAPABILITIES; + + if (f & CLIENT_MULTI_STATEMENTS) { + f |= CLIENT_MULTI_RESULTS; + } + /** + * No compression in embedded as we don't send any data, + * and no pluggable auth, as we cannot do a client-server dialog + */ + f &= ~(CLIENT_COMPRESS | CLIENT_PLUGIN_AUTH); + + if (mysql->options.db != NULL) { + f |= CLIENT_CONNECT_WITH_DB; + } + ss_dfprintf(stderr, "< set_client_flags : %lu\n", f); + ss_dfflush(stderr); + return f; +} + + +static bool create_parse_tree( + THD* thd) +{ + Parser_state parser_state; + bool failp = FALSE; + const char* virtual_db = "skygw_virtual"; + ss_dfprintf(stderr, "> create_parse_tree\n"); + + if (parser_state.init(thd, thd->query(), thd->query_length())) { + failp = TRUE; + goto return_here; + } + mysql_reset_thd_for_next_command(thd, opt_userstat_running); + + /** Set some database to thd so that parsing won't fail because of + * missing database. Then parse. */ + failp = thd->set_db(virtual_db, sizeof(virtual_db)); + + if (failp) { + fprintf(stderr, "Setting database for thd failed\n"); + } + failp = parse_sql(thd, &parser_state, NULL); + + if (failp) { + fprintf(stderr, "parse_sql failed\n"); + } +return_here: + ss_dfprintf(stderr, "< create_parse_tree : %s\n", STRBOOL(failp)); + fflush(stderr); + return failp; +} + + +/** + * @node Detect query type, read-only, write, or session update + * + * Parameters: + * @param thd - + * + * + * @return + * + * + * @details Query type is deduced by checking for certain properties + * of them. The order is essential. Some SQL commands have multiple + * flags set and changing the order in which flags are tested, + * the resulting type may be different. + * + */ +static skygw_query_type_t resolve_query_type( + THD* thd) +{ + skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN; + LEX* lex; + /** + * By default, if sql_log_bin, that is, recording data modifications + * to binary log, is disabled, gateway treats operations normally. + * Effectively nothing is replicated. + * When force_data_modify_op_replication is TRUE, gateway distributes + * all write operations to all nodes. + */ + bool force_data_modify_op_replication; + + ss_dfprintf(stderr, "> resolve_query_type\n"); + ss_info_dassert(thd != NULL, ("thd is NULL\n")); + + force_data_modify_op_replication = FALSE; + lex = thd->lex; + + /** SELECT ..INTO variable|OUTFILE|DUMPFILE */ + if (lex->result != NULL) { + qtype = QUERY_TYPE_SESSION_WRITE; + goto return_here; + } + /** + * 1:ALTER TABLE, TRUNCATE, REPAIR, OPTIMIZE, ANALYZE, CHECK. + * 2:CREATE|ALTER|DROP|TRUNCATE|RENAME TABLE, LOAD, CREATE|DROP|ALTER DB, + * CREATE|DROP INDEX, CREATE|DROP VIEW, CREATE|DROP TRIGGER, + * CREATE|ALTER|DROP EVENT, UPDATE, INSERT, INSERT(SELECT), + * DELETE, REPLACE, REPLACE(SELECT), CREATE|RENAME|DROP USER, + * GRANT, REVOKE, OPTIMIZE, CREATE|ALTER|DROP FUNCTION|PROCEDURE, + * CREATE SPFUNCTION, INSTALL|UNINSTALL PLUGIN + */ + if (is_log_table_write_query(lex->sql_command) || + is_update_query(lex->sql_command)) + { + if (thd->variables.sql_log_bin == 0 && + force_data_modify_op_replication) + { + qtype = QUERY_TYPE_SESSION_WRITE; + } else { + qtype = QUERY_TYPE_WRITE; + } + + goto return_here; + } + + /** + * REVOKE ALL, ASSIGN_TO_KEYCACHE, + * PRELOAD_KEYS, FLUSH, RESET, CREATE|ALTER|DROP SERVER + */ + if (sql_command_flags[lex->sql_command] & CF_AUTO_COMMIT_TRANS) { + qtype = QUERY_TYPE_SESSION_WRITE; + goto return_here; + } + + /** Try to catch session modifications here */ + switch (lex->sql_command) { + case SQLCOM_CHANGE_DB: + case SQLCOM_SET_OPTION: + qtype = QUERY_TYPE_SESSION_WRITE; + break; + + case SQLCOM_SELECT: + qtype = QUERY_TYPE_READ; + break; + + case SQLCOM_CALL: + qtype = QUERY_TYPE_WRITE; + break; + + default: + break; + } +return_here: + ss_dfprintf(stderr, "< resolve_query_type : %s\n", STRQTYPE(qtype)); + return qtype; +} diff --git a/query_classifier/query_classifier.h b/query_classifier/query_classifier.h new file mode 100644 index 000000000..6e3398bc5 --- /dev/null +++ b/query_classifier/query_classifier.h @@ -0,0 +1,45 @@ +/* +This file is distributed as part of the SkySQL Gateway. 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 + +*/ + +/** getpid */ +#include +#include "../utils/skygw_utils.h" + +EXTERN_C_BLOCK_BEGIN + +/** + * Query type for skygateway. + * The meaninful difference is whether master data was modified + */ +typedef enum { + QUERY_TYPE_UNKNOWN = 7, /*!< Couln't find out or parse error */ + QUERY_TYPE_WRITE, /*!< Master data will be modified */ + QUERY_TYPE_READ, /*!< No updates */ + QUERY_TYPE_SESSION_WRITE /*!< Session data will be modified */ +} skygw_query_type_t; + + + +skygw_query_type_t skygw_query_classifier_get_type( + const char* query_str, + unsigned long client_flags); + + +EXTERN_C_BLOCK_END + diff --git a/query_classifier/test/makefile b/query_classifier/test/makefile new file mode 100644 index 000000000..5df6075be --- /dev/null +++ b/query_classifier/test/makefile @@ -0,0 +1,34 @@ +include ../../build_gateway.inc +include ../../makefile.inc + +CC = gcc +CPP = g++ + +TESTPATH := $(shell pwd) +QUERY_CLASSIFIER_PATH := $(ROOT_PATH)/query_classifier/ +TESTAPP = $(TESTPATH)/testmain + +runtest: makeall testall + +makeall: clean all + +clean: + - $(DEL) testmain.o + - $(DEL) testmain + - $(DEL) *~ + +all: testcomp testall + +testcomp: + $(CC) $(CFLAGS) \ + -L$(QUERY_CLASSIFIER_PATH) \ + -L$(MARIADB_SRC_PATH)/libmysqld \ + -Wl,-rpath,$(MARIADB_SRC_PATH)/libmysqld \ + -Wl,-rpath,$(QUERY_CLASSIFIER_PATH)/ \ + -o testmain -DSS_DEBUG \ + -I$(MARIADB_SRC_PATH)/include testmain.c \ + -lquery_classifier $(LDLIBS) \ + $(QUERY_CLASSIFIER_PATH)/skygw_utils.o + +testall: + - $(LAUNCH_DEBUGGER) $(TESTAPP) $(BACKGR) diff --git a/query_classifier/test/testmain.c b/query_classifier/test/testmain.c new file mode 100644 index 000000000..403bac139 --- /dev/null +++ b/query_classifier/test/testmain.c @@ -0,0 +1,471 @@ +#include +#include +#include + +#include "../../utils/skygw_utils.h" +//#include "skygw_debug.h" + //#include "skygw_types.h" +#include "../query_classifier.h" + +static char* server_options[] = { + "raatikka", + "--datadir=/home/raatikka/data/skygw_parse/", + "--skip-innodb", + "--default-storage-engine=myisam", + NULL +}; + +const int num_elements = (sizeof(server_options) / sizeof(char *)) - 1; + +static char* server_groups[] = { + "embedded", + "server", + "server", + "server", + NULL +}; + + + +static void slcursor_add_case( + slist_cursor_t* c, + void* data) +{ + slcursor_add_data(c, data); +} + + +typedef struct query_test_st query_test_t; + +struct query_test_st { + skygw_chk_t qt_chk_top; + const char* qt_query_str; + skygw_query_type_t qt_query_type; + skygw_query_type_t qt_result_type; + bool qt_should_fail; + bool qt_exec_also_in_server; + skygw_chk_t qt_chk_tail; +}; + + + + +static query_test_t* query_test_init( + const char* query_str, + skygw_query_type_t query_type, + bool case_should_fail, + bool exec_also_in_server) +{ + query_test_t* qtest; + + qtest = (query_test_t *)calloc(1, sizeof(query_test_t)); + ss_dassert(qtest != NULL); + qtest->qt_chk_top = CHK_NUM_QUERY_TEST; + qtest->qt_chk_tail = CHK_NUM_QUERY_TEST; + qtest->qt_query_str = query_str; + qtest->qt_query_type = query_type; + qtest->qt_should_fail = case_should_fail; + qtest->qt_exec_also_in_server = exec_also_in_server; + return qtest; +} + +const char* query_test_get_querystr( + query_test_t* qt) +{ + CHK_QUERY_TEST(qt); + return qt->qt_query_str; +} + +static skygw_query_type_t query_test_get_query_type( + query_test_t* qt) +{ + CHK_QUERY_TEST(qt); + return qt->qt_query_type; +} + +static skygw_query_type_t query_test_get_result_type( + query_test_t* qt) +{ + CHK_QUERY_TEST(qt); + return qt->qt_result_type; +} + + +static bool query_test_types_match( + query_test_t* qt) +{ + CHK_QUERY_TEST(qt); + return (qt->qt_query_type == qt->qt_result_type); +} + +static bool query_test_exec_also_in_server( + query_test_t* qt) +{ + CHK_QUERY_TEST(qt); + return (qt->qt_exec_also_in_server); +} + +static query_test_t* slcursor_get_case( + slist_cursor_t* c) +{ + return (query_test_t*)slcursor_get_data(c); +} + +int main(int argc, char** argv) +{ + slist_cursor_t* c; + const char* q; + query_test_t* qtest; + skygw_query_type_t qtype; + bool succp; + bool failp = TRUE; + unsigned int f = 0; + int nsucc = 0; + int nfail = 0; + MYSQL* mysql; + + ss_dfprintf(stderr, ">> testmain\n"); + c = slist_init(); + + /** Read-only SELECTs */ + q = "SELECT user from mysql.user"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_READ, FALSE, TRUE)); + + q = "select tt1.id, tt2.id from t1 tt1, t2 tt2 where tt1.name is " + "not null and tt2.name is not null"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_READ, FALSE, FALSE)); + + /** SELECT ..INTO clauses > session updates */ + q = "SELECT user from mysql.user INTO DUMPFILE '/tmp/dump1'"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_SESSION_WRITE, FALSE, FALSE)); + + q = "SELECT user INTO DUMPFILE '/tmp/dump2 ' from mysql.user"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_SESSION_WRITE, FALSE, FALSE)); + + q = "SELECT user from mysql.user INTO OUTFILE '/tmp/out1'"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_SESSION_WRITE, FALSE, FALSE)); + + /** Database and table name must be separated by a dot */ + q = "SELECT user INTO OUTFILE '/tmp/out2 ' from mysql-user"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_SESSION_WRITE, TRUE, FALSE)); + + /** Database and table name must be separated by a dot */ + q = "SELECT user INTO OUTFILE '/tmp/out2 ' from mysql_foo_user"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_SESSION_WRITE, FALSE, FALSE)); + + q = "SELECT user FROM mysql.user limit 1 INTO @local_variable"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_SESSION_WRITE, FALSE, FALSE)); + + q = "SELECT user INTO @local_variable FROM mysql.user limit 1"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_SESSION_WRITE, FALSE, FALSE)); + + q = "SELECT non_existent_attr INTO @d FROM non_existent_table"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_SESSION_WRITE, FALSE, FALSE)); + + q = "select * from table1 " + "where table1.field IN " + "(select * from table1a union select * from table1b) union " + "select * from table2 where table2.field = " + "(select (select f1 from table2a where table2a.f2 = table2b.f3) " + "from table2b where table2b.f1 = table2.f2) union " + "select * from table3"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_SESSION_WRITE, FALSE, TRUE)); + + /** Functions */ + q = "SELECT NOW()"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_READ, FALSE, FALSE)); + + q = "SELECT SOUNDEX('Hello')"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_READ, FALSE, FALSE)); + + q = "SELECT MY_UDF('Hello')"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_READ, FALSE, TRUE)); + + /** RENAME TABLEs */ + q = "RENAME TABLE T1 to T2"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_WRITE, FALSE, FALSE)); + + + /** INSERTs */ + q = "INSERT INTO T1 (SELECT * FROM T2)"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_WRITE, FALSE, TRUE)); + + q = "INSERT INTO T1 VALUES(2, 'foo', 'toomanyattributes')"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_WRITE, FALSE, TRUE)); + + q = "INSERT INTO T2 VALUES(1, 'sthrgey')"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_WRITE, FALSE, FALSE)); + + q = "INSERT INTO T2 VALUES(8, 'ergstrhe')"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_WRITE, FALSE, FALSE)); + + q = "INSERT INTO T2 VALUES(9, NULL)"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_WRITE, FALSE, FALSE)); + + + /** Ok, delimeter is client-side parameter which shouldn't be handled + * on server side. + */ + q = "delimiter //"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_SESSION_WRITE, TRUE, TRUE)); + + /** SETs, USEs > Session updates */ + q = "SET @a=1"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_SESSION_WRITE, FALSE, TRUE)); + + q = "USE TEST"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_SESSION_WRITE, FALSE, FALSE)); + + + /** Object creation statements */ + q = "create procedure si (out param1 int) \nbegin select count(*) " + "into param1 from t1; \nend"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_WRITE, FALSE, TRUE)); + + q = "CREATE TABLE T1 (id integer primary key, name varchar(10))"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_WRITE, FALSE, TRUE)); + + q = "DROP TABLE T1"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_WRITE, FALSE, FALSE)); + + q = "ALTER TABLE T1 ADD COLUMN WHYME INTEGER NOT NULL"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_WRITE, FALSE, FALSE)); + + q = "TRUNCATE TABLE T1"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_WRITE, FALSE, FALSE)); + + q = "DROP SERVER IF EXISTS VICTIMSRV"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_SESSION_WRITE, FALSE, TRUE)); + + q = "CREATE USER FOO IDENTIFIED BY 'BAR'"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_WRITE, FALSE, TRUE)); + + q = "OPTIMIZE NO_WRITE_TO_BINLOG TABLE T1"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_WRITE, FALSE, TRUE)); + + q = "SELECT NOW();CREATE TABLE T1 (ID INTEGER);" + "SET sql_log_bin=0;CREATE TABLE T2 (ID INTEGER)"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_WRITE, FALSE, TRUE)); + + + /** Setting database makes this SESSION_WRITE */ + q = "USE TEST;CREATE TABLE T1 (ID INTEGER);" + "SET sql_log_bin=0;CREATE TABLE T2 (ID INTEGER)"; + slcursor_add_case( + c, + query_test_init(q, QUERY_TYPE_SESSION_WRITE, FALSE, TRUE)); + + /** + * Init libmysqld. + */ + failp = mysql_library_init(num_elements, server_options, server_groups); + + if (failp) { + MYSQL* mysql = mysql_init(NULL); + ss_dassert(mysql != NULL); + fprintf(stderr, + "mysql_init failed, %d : %s\n", + mysql_errno(mysql), + mysql_error(mysql)); + goto return_without_server; + } + + fprintf(stderr, + "\nExecuting selected cases in " + "skygw_query_classifier_get_type :\n\n"); + /** + * Set cursor to the beginning, scan through the list and execute + * test cases. + */ + succp = slcursor_move_to_begin(c); + + while(succp) { + qtest = slcursor_get_case(c); + qtest->qt_result_type = + skygw_query_classifier_get_type(qtest->qt_query_str, f); + succp = slcursor_step_ahead(c); + } + /** + * Scan through test results and compare them against expected + * results. + */ + succp = slcursor_move_to_begin(c); + fprintf(stderr, "\nScanning through the results :\n\n"); + + while(succp) { + qtest = slcursor_get_case(c); + + if (!query_test_types_match(qtest)) { + nfail += 1; + ss_dfprintf(stderr, + "* Failed: \"%s\" -> %s (Expected %s)\n", + query_test_get_querystr(qtest), + STRQTYPE(query_test_get_result_type(qtest)), + STRQTYPE(query_test_get_query_type(qtest))); + } else { + nsucc += 1; + ss_dfprintf(stderr, + "Succeed\t: \"%s\" -> %s\n", + query_test_get_querystr(qtest), + STRQTYPE(query_test_get_query_type(qtest))); + } + succp = slcursor_step_ahead(c); + } + fprintf(stderr, + "------------------------------------------\n" + "Tests in total %d, SUCCEED %d, FAILED %d\n", + nsucc+nfail, + nsucc, + nfail); + + /** + * Scan test results and re-execute those which are marked to be + * executed also in the server. This serves mostly debugging purposes. + */ + succp = slcursor_move_to_begin(c); + mysql = mysql_init(NULL); + + if (mysql == NULL) { + fprintf(stderr, "mysql_init failed\n"); + goto return_without_server; + } + + mysql_options(mysql, + MYSQL_READ_DEFAULT_GROUP, + "libmysqld_client"); + mysql_options(mysql, MYSQL_OPT_USE_EMBEDDED_CONNECTION, NULL); + mysql_options(mysql, MYSQL_OPT_USE_EMBEDDED_CONNECTION, NULL); + + mysql = mysql_real_connect(mysql, + NULL, + "skygw", + "skygw", + NULL, + 0, + NULL, + CLIENT_MULTI_STATEMENTS); + + if (mysql == NULL) { + fprintf(stderr, "mysql_real_connect failed\n"); + goto return_with_handle; + } + + fprintf(stderr, + "\nRe-execution of selected cases in Embedded server :\n\n"); + + while(succp) { + qtest = slcursor_get_case(c); + + if (query_test_exec_also_in_server(qtest)) { + MYSQL_RES* results; + MYSQL_ROW record; + const char* query_str; + + query_str = query_test_get_querystr(qtest); + failp = mysql_query(mysql, query_str); + + if (failp) { + ss_dfprintf(stderr, + "* Failed: \"%s\" -> %d : %s\n", + query_str, + mysql_errno(mysql), + mysql_error(mysql)); + } else { + ss_dfprintf(stderr, + "Succeed\t: \"%s\"\n", + query_str); + results = mysql_store_result(mysql); + + if (results != NULL) { + + while((record = mysql_fetch_row(results))) { + while(record != NULL && *record != NULL) { + ss_dfprintf(stderr, "%s ", *record); + record++; + } + ss_dfprintf(stderr, "\n"); + } + mysql_free_result(results); + } + } + } + succp = slcursor_step_ahead(c); + + } + slist_done(c); + fprintf(stderr, "------------------------------------------\n"); + +return_with_handle: + mysql_close(mysql); + mysql_thread_end(); + mysql_library_end(); + +return_without_server: + ss_dfprintf(stderr, "\n<< testmain\n"); + fflush(stderr); + return 0; +}