From bec245a9130cbedd461fe1f88682f276e7c18639 Mon Sep 17 00:00:00 2001 From: Georg Richter Date: Thu, 10 Dec 2015 12:52:51 +0100 Subject: [PATCH] Initial aurora implementation --- CMakeLists.txt | 12 + cmake/plugins.cmake | 11 +- include/ma_common.h | 4 +- include/mysql.h | 6 +- include/mysql/client_plugin.h | 1 + include/mysql_com.h | 1 + libmariadb/CMakeLists.txt | 3 + libmariadb/libmariadb.c | 145 +++-- libmariadb/my_stmt.c | 1 - libmariadb/net.c | 3 + plugins/connection/CMakeLists.txt | 16 +- plugins/connection/aurora.c | 820 +++++++++++++++++++++++++++++ plugins/connection/replication.c | 48 +- plugins/pvio/pvio_npipe.c | 2 +- unittest/libmariadb/CMakeLists.txt | 4 +- unittest/libmariadb/aurora.c | 67 +++ unittest/libmariadb/basic-t.c | 55 +- unittest/libmariadb/charset.c | 2 +- unittest/libmariadb/connection.c | 14 +- unittest/libmariadb/my_test.h | 30 +- unittest/libmariadb/ps.c | 5 +- unittest/libmariadb/ps_bugs.c | 3 +- 22 files changed, 1143 insertions(+), 110 deletions(-) create mode 100644 plugins/connection/aurora.c create mode 100644 unittest/libmariadb/aurora.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bbdaab7..1dbdc8a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,14 @@ IF(COMMAND CMAKE_POLICY) CMAKE_POLICY(SET CMP0003 NEW) ENDIF() +#Allow access to non existing targets +IF(CMAKE_VERSION VERSION_GREATER "2.9.9") + CMAKE_POLICY(SET CMP0026 OLD) + CMAKE_POLICY(SET CMP0042 OLD) + CMAKE_POLICY(SET CMP0045 OLD) +ENDIF() + + SET(MARIADB_CONNECTOR_C_COPYRIGHT "2013-2015 MariaDB Corporation Ab") ### Options ### @@ -149,6 +157,10 @@ IF(CMAKE_HAVE_PTHREAD_H) SET(CMAKE_REQUIRED_INCLUDES pthread.h) ENDIF() +IF(DBUG_OFF) + ADD_DEFINITIONS(-DDBUG_OFF=1) +ENDIF() + IF(WIN32) SET(HAVE_THREADS 1) ADD_DEFINITIONS(-DHAVE_DLOPEN) diff --git a/cmake/plugins.cmake b/cmake/plugins.cmake index 47e15597..2d09bb3a 100644 --- a/cmake/plugins.cmake +++ b/cmake/plugins.cmake @@ -1,12 +1,16 @@ # plugin configuration MACRO(REGISTER_PLUGIN name source struct type target allow) + SET(PLUGIN_TYPE ${${name}}) + IF(NOT PLUGIN_TYPE STREQUAL "OFF") + SET(PLUGIN_TYPE ${type}) + ENDIF() IF(PLUGINS) LIST(REMOVE_ITEM PLUGINS ${name}) ENDIF() SET(${name}_PLUGIN_SOURCE ${source}) MARK_AS_ADVANCED(${name}_PLUGIN_SOURCE}) - SET(${name}_PLUGIN_TYPE ${type}) + SET(${name}_PLUGIN_TYPE ${PLUGIN_TYPE}) IF(NOT ${target} STREQUAL "") SET(${name}_PLUGIN_TARGET ${target}) ENDIF() @@ -41,6 +45,7 @@ REGISTER_PLUGIN("TRACE_EXAMPLE" "${CMAKE_SOURCE_DIR}/plugins/trace/trace_example #Connection REGISTER_PLUGIN("REPLICATION" "${CMAKE_SOURCE_DIR}/plugins/connection/replication.c" "connection_replication_plugin" "STATIC" "" 1) +REGISTER_PLUGIN("AURORA" "${CMAKE_SOURCE_DIR}/plugins/connection/aurora.c" "connection_aurora_plugin" "STATIC" "" 1) # Allow registration of additional plugins IF(PLUGIN_CONF_FILE) @@ -50,7 +55,7 @@ ENDIF() SET(LIBMARIADB_SOURCES "") -MESSAGE(STATUS "Plugin configuration") +MESSAGE(STATUS "Plugin configuration:") FOREACH(PLUGIN ${PLUGINS}) IF(WITH_${PLUGIN}_PLUGIN AND ${${PLUGIN}_PLUGIN_CHG} GREATER 0) SET(${PLUGIN}_PLUGIN_TYPE ${WITH_${PLUGIN}_PLUGIN}) @@ -72,6 +77,6 @@ ENDIF() LIST(REMOVE_DUPLICATES LIBMARIADB_SOURCES) CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/libmariadb/client_plugin.c.in - ${CMAKE_BINARY_DIR}/libmariadb/client_plugin.c) + ${CMAKE_BINARY_DIR}/libmariadb/client_plugin.c) MARK_AS_ADVANCED(LIBMARIADB_SOURCES) diff --git a/include/ma_common.h b/include/ma_common.h index 6a6a80a0..048fc9ae 100644 --- a/include/ma_common.h +++ b/include/ma_common.h @@ -48,10 +48,12 @@ struct st_mysql_options_extension { double progress, const char *proc_info, unsigned int proc_info_length); - MARIADB_DB_DRIVER *db_driver; + MARIADB_DB_DRIVER *db_driver; char *ssl_fp; /* finger print of server certificate */ char *ssl_fp_list; /* white list of finger prints */ char *ssl_pw; /* password for encrypted certificates */ + char *url; /* for connection handler we need to save URL for reconnect */ + my_bool read_only; }; #define OPT_HAS_EXT_VAL(a,key) \ diff --git a/include/mysql.h b/include/mysql.h index ca47367e..3c000741 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -482,8 +482,8 @@ const char * STDCALL mysql_get_client_info(void); unsigned long STDCALL mysql_get_client_version(void); my_bool STDCALL mariadb_connection(MYSQL *mysql); const char * STDCALL mysql_get_server_name(MYSQL *mysql); -CHARSET_INFO * STDCALL mysql_get_charset_by_name(const char *csname); -CHARSET_INFO * STDCALL mysql_get_charset_by_nr(unsigned int csnr); +CHARSET_INFO * STDCALL mariadb_get_charset_by_name(const char *csname); +CHARSET_INFO * STDCALL mariadb_get_charset_by_nr(unsigned int csnr); size_t STDCALL mariadb_convert_string(const char *from, size_t *from_len, CHARSET_INFO *from_cs, char *to, size_t *to_len, CHARSET_INFO *to_cs, int *errorcode); int STDCALL mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...); @@ -493,6 +493,7 @@ my_socket STDCALL mysql_get_socket(const MYSQL *mysql); unsigned int STDCALL mysql_get_timeout_value(const MYSQL *mysql); unsigned int STDCALL mysql_get_timeout_value_ms(const MYSQL *mysql); int STDCALL mariadb_get_connection_type(MYSQL *mysql); +my_bool STDCALL mysql_reconnect(MYSQL *mysql); /* Async API */ int STDCALL mysql_close_start(MYSQL *sock); @@ -628,6 +629,7 @@ struct st_mysql_methods { int (*db_stmt_fetch_to_bind)(MYSQL_STMT *stmt, unsigned char *row); void (*db_stmt_flush_unbuffered)(MYSQL_STMT *stmt); void (*set_error)(MYSQL *mysql, unsigned int error_nr, const char *sqlstate, const char *format, ...); + my_bool (*reconnect)(MYSQL *mysql); }; /* synonyms/aliases functions */ diff --git a/include/mysql/client_plugin.h b/include/mysql/client_plugin.h index f9c4199b..616de69e 100644 --- a/include/mysql/client_plugin.h +++ b/include/mysql/client_plugin.h @@ -95,6 +95,7 @@ typedef struct st_ma_connection_plugin int (*options)(MYSQL *mysql, enum mysql_option, void *arg); int (*set_connection)(MYSQL *mysql,enum enum_server_command command, const char *arg, size_t length, my_bool skipp_check, void *opt_arg); + my_bool (*reconnect)(MYSQL *mysql); } MARIADB_CONNECTION_PLUGIN; #define MARIADB_DB_DRIVER(a) ((a)->ext_db) diff --git a/include/mysql_com.h b/include/mysql_com.h index 415e002d..4c684d74 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -230,6 +230,7 @@ typedef struct st_connection_handler { struct st_ma_connection_plugin *plugin; void *data; + my_bool active; my_bool free_data; } MA_CONNECTION_HANDLER; diff --git a/libmariadb/CMakeLists.txt b/libmariadb/CMakeLists.txt index e42f3888..7f0ba6e8 100644 --- a/libmariadb/CMakeLists.txt +++ b/libmariadb/CMakeLists.txt @@ -34,6 +34,8 @@ SET(EXPORT_SYMBOLS mariadb_dyncol_val_double mariadb_dyncol_val_long mariadb_dyncol_val_str + mariadb_get_charset_by_name + mariadb_get_charset_by_nr mariadb_get_connection_type mysql_affected_rows mysql_autocommit @@ -124,6 +126,7 @@ SET(EXPORT_SYMBOLS mysql_query mysql_query_cont mysql_query_start + mysql_reconnect mysql_read_query_result mysql_read_query_result_cont mysql_read_query_result_start diff --git a/libmariadb/libmariadb.c b/libmariadb/libmariadb.c index 3a1e7177..bab2a079 100644 --- a/libmariadb/libmariadb.c +++ b/libmariadb/libmariadb.c @@ -124,11 +124,14 @@ struct st_mysql_methods MARIADB_DEFAULT_METHODS; #define native_password_plugin_name "mysql_native_password" +#define IS_CONNHDLR_ACTIVE(mysql)\ + ((mysql)->net.conn_hdlr && (mysql)->net.conn_hdlr->active) + static void end_server(MYSQL *mysql); static void mysql_close_memory(MYSQL *mysql); void read_user_name(char *name); static void append_wild(char *to,char *end,const char *wild); -static my_bool mysql_reconnect(MYSQL *mysql); +my_bool mysql_reconnect(MYSQL *mysql); static int cli_report_progress(MYSQL *mysql, uchar *packet, uint length); extern int mysql_client_plugin_init(); @@ -354,13 +357,6 @@ mthd_my_send_cmd(MYSQL *mysql,enum enum_server_command command, const char *arg, DBUG_PRINT("info", ("server_command: %d packet_size: %u", command, length)); - if (mysql->net.conn_hdlr && mysql->net.conn_hdlr->data) - { - result= mysql->net.conn_hdlr->plugin->set_connection(mysql, command, arg, length, skipp_check, opt_arg); - if (result== -1) - DBUG_RETURN(result); - } - if (mysql->net.pvio == 0) { /* Do reconnect if possible */ if (mysql_reconnect(mysql)) @@ -376,6 +372,14 @@ mthd_my_send_cmd(MYSQL *mysql,enum enum_server_command command, const char *arg, goto end; } + if (IS_CONNHDLR_ACTIVE(mysql)) + { + result= mysql->net.conn_hdlr->plugin->set_connection(mysql, command, arg, length, skipp_check, opt_arg); + if (result== -1) + DBUG_RETURN(result); + } + + CLEAR_CLIENT_ERROR(mysql); mysql->info=0; @@ -384,13 +388,6 @@ mthd_my_send_cmd(MYSQL *mysql,enum enum_server_command command, const char *arg, if (!arg) arg=""; - /* check if connection kills itself */ - if (command == COM_PROCESS_KILL) - { - unsigned long thread_id= uint4korr(arg); - if (thread_id == mysql->thread_id) - skipp_check= 1; - } if (net_write_command(net,(uchar) command,arg, length ? length : (ulong) strlen(arg))) { @@ -660,7 +657,7 @@ enum option_val #define OPT_SET_EXTENDED_VALUE_INT(OPTS, KEY, VAL) \ CHECK_OPT_EXTENSION_SET(OPTS) \ -+ (OPTS)->extension->KEY= (VAL) + (OPTS)->extension->KEY= (VAL) static TYPELIB option_types={array_elements(default_options)-1, @@ -1145,6 +1142,7 @@ mysql_init(MYSQL *mysql) bzero((char*) (mysql),sizeof(*(mysql))); mysql->options.connect_timeout=CONNECT_TIMEOUT; mysql->charset= default_charset_info; + mysql->methods= &MARIADB_DEFAULT_METHODS; strmov(mysql->net.sqlstate, "00000"); mysql->net.last_error[0]= mysql->net.last_errno= 0; @@ -1291,9 +1289,38 @@ mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, uint port, const char *unix_socket,unsigned long client_flag) { + char *end; + if (!mysql->methods) mysql->methods= &MARIADB_DEFAULT_METHODS; + if (host && (end= strstr(host, "://"))) + { + MARIADB_CONNECTION_PLUGIN *plugin; + char plugin_name[64]; + + bzero(plugin_name, 64); + strncpy(plugin_name, host, MIN(end - host, 63)); + end+= 3; + + if (!(plugin= (MARIADB_CONNECTION_PLUGIN *)mysql_client_find_plugin(mysql, plugin_name, MARIADB_CLIENT_CONNECTION_PLUGIN))) + return NULL; + + if (!(mysql->net.conn_hdlr= (MA_CONNECTION_HANDLER *)my_malloc(sizeof(MA_CONNECTION_HANDLER), MYF(MY_ZEROFILL)))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return NULL; + } + + /* save URL for reconnect */ + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, url, host); + + mysql->net.conn_hdlr->plugin= plugin; + + if (plugin && plugin->connect) + return plugin->connect(mysql, end, user, passwd, db, port, unix_socket, client_flag); + } + return mysql->methods->db_connect(mysql, host, user, passwd, db, port, unix_socket, client_flag); } @@ -1320,32 +1347,6 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, if (!mysql->methods) mysql->methods= &MARIADB_DEFAULT_METHODS; - /* special case: - * If hostname contains "://", e.g. "repl://localhost", we need to process connection - * by connection plugin - */ - if (host && (end= strstr(host, "://"))) - { - MARIADB_CONNECTION_PLUGIN *plugin; - char plugin_name[64]; - - bzero(plugin_name, 64); - strncpy(plugin_name, host, MIN(end - host, 63)); - end+= 3; - if (!(plugin= (MARIADB_CONNECTION_PLUGIN *)mysql_client_find_plugin(mysql, plugin_name, MARIADB_CLIENT_CONNECTION_PLUGIN))) - DBUG_RETURN(NULL); - - if (!(mysql->net.conn_hdlr= (MA_CONNECTION_HANDLER *)my_malloc(sizeof(MA_CONNECTION_HANDLER), MYF(MY_ZEROFILL)))) - { - SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); - DBUG_RETURN(NULL); - } - - mysql->net.conn_hdlr->plugin= plugin; - - if (plugin->connect) - return plugin->connect(mysql, end, user, passwd, db, port, unix_socket, client_flag); - } ma_set_connect_attrs(mysql); @@ -1659,7 +1660,7 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, mysql->reconnect= 0; for (;begin < end; begin++) - { + { if (mysql_real_query(mysql, *begin, (unsigned long)strlen(*begin))) goto error; @@ -1715,14 +1716,22 @@ my_suspend_hook(my_bool suspend, void *data) } -static my_bool mysql_reconnect(MYSQL *mysql) +my_bool STDCALL mysql_reconnect(MYSQL *mysql) { MYSQL tmp_mysql; struct my_hook_data hook_data; struct mysql_async_context *ctxt= NULL; + LIST *li_stmt= mysql->stmts; DBUG_ENTER("mysql_reconnect"); + /* check if connection handler is active */ + if (IS_CONNHDLR_ACTIVE(mysql)) + { + if (mysql->net.conn_hdlr->plugin && mysql->net.conn_hdlr->plugin->connect) + DBUG_RETURN(mysql->net.conn_hdlr->plugin->reconnect(mysql)); + } + if (!mysql->reconnect || (mysql->server_status & SERVER_STATUS_IN_TRANS) || !mysql->host_info) { @@ -1734,6 +1743,13 @@ static my_bool mysql_reconnect(MYSQL *mysql) mysql_init(&tmp_mysql); tmp_mysql.options=mysql->options; + if (mysql->net.conn_hdlr) + { + tmp_mysql.net.conn_hdlr= mysql->net.conn_hdlr; + mysql->net.conn_hdlr= 0; + } + + /* don't reread options from configuration files */ tmp_mysql.options.my_cnf_group= tmp_mysql.options.my_cnf_file= NULL; @@ -1762,6 +1778,17 @@ static my_bool mysql_reconnect(MYSQL *mysql) DBUG_RETURN(1); } + for (;li_stmt;li_stmt= li_stmt->next) + { + MYSQL_STMT *stmt= (MYSQL_STMT *)li_stmt->data; + + if (stmt->state != MYSQL_STMT_INITTED) + { + stmt->state= MYSQL_STMT_INITTED; + SET_CLIENT_STMT_ERROR(stmt, CR_SERVER_LOST, SQLSTATE_UNKNOWN, 0); + } + } + tmp_mysql.reconnect= mysql->reconnect; tmp_mysql.free_me= mysql->free_me; tmp_mysql.stmts= mysql->stmts; @@ -1923,6 +1950,7 @@ static void mysql_close_options(MYSQL *mysql) my_free(mysql->options.extension->ssl_fp); my_free(mysql->options.extension->ssl_fp_list); my_free(mysql->options.extension->ssl_pw); + my_free(mysql->options.extension->url); if(hash_inited(&mysql->options.extension->connect_attrs)) hash_free(&mysql->options.extension->connect_attrs); if ((ctxt = mysql->options.extension->async_context) != 0) @@ -1985,7 +2013,8 @@ mysql_close(MYSQL *mysql) DBUG_ENTER("mysql_close"); if (mysql) /* Some simple safety */ { - if (mysql->net.conn_hdlr && mysql->net.conn_hdlr->data) + + if (IS_CONNHDLR_ACTIVE(mysql)) { void *p= (void *)mysql->net.conn_hdlr; mysql->net.conn_hdlr->plugin->close(mysql); @@ -1998,6 +2027,7 @@ mysql_close(MYSQL *mysql) /* reset the connection in all active statements */ ma_invalidate_stmts(mysql, "mysql_close()"); + mysql_close_memory(mysql); mysql_close_options(mysql); mysql->host_info=mysql->user=mysql->passwd=mysql->db=0; @@ -2048,7 +2078,9 @@ int mthd_my_read_query_result(MYSQL *mysql) DBUG_ENTER("mthd_my_read_query_result"); if (!mysql || (length = net_safe_read(mysql)) == packet_error) + { DBUG_RETURN(1); + } free_old_query(mysql); /* Free old result */ get_info: pos=(uchar*) mysql->net.read_pos; @@ -2496,7 +2528,6 @@ mysql_stat(MYSQL *mysql) DBUG_RETURN((char*) mysql->net.read_pos); } - int STDCALL mysql_ping(MYSQL *mysql) { @@ -2510,7 +2541,6 @@ mysql_ping(MYSQL *mysql) return rc; } - char * STDCALL mysql_get_server_info(MYSQL *mysql) { @@ -2878,10 +2908,8 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) OPT_SET_EXTENDED_VALUE_STR(&mysql->options, ssl_pw, (char *)arg1); break; case MARIADB_OPT_CONNECTION_READ_ONLY: - if (mysql->net.conn_hdlr) - DBUG_RETURN(mysql->net.conn_hdlr->plugin->options(mysql, MARIADB_OPT_CONNECTION_READ_ONLY, arg1)); - else - return -1; + OPT_SET_EXTENDED_VALUE_INT(&mysql->options, read_only, *(my_bool *)arg1); + break; default: va_end(ap); DBUG_RETURN(-1); @@ -3278,6 +3306,15 @@ int STDCALL mariadb_get_connection_type(MYSQL *mysql) return (int)mysql->net.pvio->type; } +CHARSET_INFO * STDCALL mariadb_get_charset_by_name(const char *csname) +{ + return (CHARSET_INFO *)mysql_find_charset_name(csname); +} + +CHARSET_INFO * STDCALL mariadb_get_charset_by_nr(unsigned int csnr) +{ + return (CHARSET_INFO *)mysql_find_charset_nr(csnr); +} /* * Default methods for a connection. These methods are * stored in mysql->methods and can be overwritten by @@ -3315,5 +3352,9 @@ struct st_mysql_methods MARIADB_DEFAULT_METHODS = { /* store values in bind buffer */ mthd_stmt_fetch_to_bind, /* skip unbuffered stmt result */ - mthd_stmt_flush_unbuffered + mthd_stmt_flush_unbuffered, + /* set error */ + my_set_error, + /* reconnect */ + mysql_reconnect }; diff --git a/libmariadb/my_stmt.c b/libmariadb/my_stmt.c index 7f0355b5..58a5c951 100644 --- a/libmariadb/my_stmt.c +++ b/libmariadb/my_stmt.c @@ -416,7 +416,6 @@ int store_param(MYSQL_STMT *stmt, int column, unsigned char **p) { DBUG_ENTER("store_param"); DBUG_PRINT("info", ("column: %d type: %d", column, stmt->params[column].buffer_type)); - printf("type: %d\n", column, stmt->params[column].buffer_type); switch (stmt->params[column].buffer_type) { case MYSQL_TYPE_TINY: int1store(*p, *(uchar *)stmt->params[column].buffer); diff --git a/libmariadb/net.c b/libmariadb/net.c index b8dbf45c..94c32104 100644 --- a/libmariadb/net.c +++ b/libmariadb/net.c @@ -182,7 +182,9 @@ static my_bool net_realloc(NET *net, size_t length) /* Remove unwanted characters from connection */ void net_clear(NET *net) { + size_t len; DBUG_ENTER("net_clear"); + ma_pvio_has_data(net->pvio, &len); net->compress_pkt_nr= net->pkt_nr=0; /* Ready for new command */ net->write_pos=net->buff; DBUG_VOID_RETURN; @@ -193,6 +195,7 @@ int net_flush(NET *net) { int error=0; DBUG_ENTER("net_flush"); + if (net->buff != net->write_pos) { error=net_real_write(net,(char*) net->buff, diff --git a/plugins/connection/CMakeLists.txt b/plugins/connection/CMakeLists.txt index f5d7cc19..f68269b6 100644 --- a/plugins/connection/CMakeLists.txt +++ b/plugins/connection/CMakeLists.txt @@ -13,10 +13,24 @@ IF(REPLICATION_PLUGIN_TYPE MATCHES "DYNAMIC") "FILE_DESCRIPTION:Connection plugin for master/slave environment") ENDIF() ADD_DEFINITIONS(-DHAVE_REPLICATION_DYNAMIC=1) - ADD_LIBRARY(replication SHARED replication.c ${EXPORT_FILE}) + ADD_LIBRARY(replication SHARED ${replication_RC} replication.c ${EXPORT_FILE}) SET(INSTALL_LIBS replication) ENDIF() +IF(REPLICATION_PLUGIN_TYPE MATCHES "DYNAMIC") + IF(WIN32) + SET_VERSION_INFO("TARGET:aurora" + "FILE_TYPE:VFT_DLL" + "SOURCE_FILE:plugins/connection/aurora.c" + "ORIGINAL_FILE_NAME:aurora.dll" + "FILE_DESCRIPTION:Connection plugin for Amazon AWS Aurora") + ENDIF() + ADD_DEFINITIONS(-DHAVE_AURORA_DYNAMIC=1) + ADD_LIBRARY(aurora SHARED ${aurora_RC} aurora.c ${EXPORT_FILE}) + SET(INSTALL_LIBS ${INSTALL_LIBS} aurora) +ENDIF() + + IF(INSTALL_LIBS) INSTALL(TARGETS ${INSTALL_LIBS} diff --git a/plugins/connection/aurora.c b/plugins/connection/aurora.c new file mode 100644 index 00000000..4c605058 --- /dev/null +++ b/plugins/connection/aurora.c @@ -0,0 +1,820 @@ +/************************************************************************************ + Copyright (C) 2015 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Part of this code includes code from the PHP project which + is freely available from http://www.php.net + *************************************************************************************/ + +/* MariaDB Connection plugin for Aurora failover */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef WIN32 +#include +#endif + +/* function prototypes */ +int aurora_init(char *errormsg, size_t errormsg_size, + int unused __attribute__((unused)), + va_list unused1 __attribute__((unused))); + +MYSQL *aurora_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, + const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag); +void aurora_close(MYSQL *mysql); +int aurora_command(MYSQL *mysql,enum enum_server_command command, const char *arg, + size_t length, my_bool skipp_check, void *opt_arg); +int aurora_set_options(MYSQL *msql, enum mysql_option option, void *arg); +my_bool aurora_reconnect(MYSQL *mysql); + +#define AURORA_MAX_INSTANCES 16 + +#define AURORA_UNKNOWN -1 +#define AURORA_PRIMARY 0 +#define AURORA_REPLICA 1 +#define AURORA_UNAVAILABLE 2 + +#define ENABLE_AURORA(mysql)\ + (mysql)->net.conn_hdlr->active= 1; +#define DISABLE_AURORA(mysql)\ + (mysql)->net.conn_hdlr->active= 0; + +#ifndef HAVE_REPLICATION_DYNAMIC +MARIADB_CONNECTION_PLUGIN connection_aurora_plugin = +#else +MARIADB_CONNECTION_PLUGIN _mysql_client_plugin_declaration_ = +#endif +{ + MARIADB_CLIENT_CONNECTION_PLUGIN, + MARIADB_CLIENT_CONNECTION_PLUGIN_INTERFACE_VERSION, + "aurora", + "Georg Richter", + "MariaDB connection plugin for Aurora failover", + {1, 0, 0}, + "LGPL", + aurora_init, + NULL, + aurora_connect, + aurora_close, + aurora_set_options, + aurora_command, + aurora_reconnect +}; + +typedef struct st_aurora_instance { + char *host; + int port; + time_t blacklisted; + int type; +} AURORA_INSTANCE; + +typedef struct st_conn_aurora { + MARIADB_PVIO *pvio[2]; + MYSQL *mysql[2]; + my_bool active[2]; + char *url; + unsigned int num_instances; + AURORA_INSTANCE instance[AURORA_MAX_INSTANCES]; + char *username, *password, *database; + unsigned int port; + unsigned long client_flag; + unsigned int last_instance_type; /* Primary or Replica */ + char primary_id[100]; +} AURORA; + +#define AURORA_BLACKLIST_TIMEOUT 150 + +#define AURORA_IS_BLACKLISTED(a, i) \ + ((time(NULL) - (a)->instance[(i)].blacklisted) < AURORA_BLACKLIST_TIMEOUT) + +/* {{{ my_bool aurora_swutch_connection */ +my_bool aurora_switch_connection(MYSQL *mysql, AURORA *aurora, int type) +{ + switch (type) + { + case AURORA_REPLICA: + if (aurora->mysql[AURORA_REPLICA]) + { + mysql->net.pvio= aurora->pvio[AURORA_REPLICA]; + aurora->pvio[AURORA_REPLICA]->mysql= mysql; + mysql->thread_id= aurora->mysql[AURORA_REPLICA]->thread_id; + aurora->last_instance_type= AURORA_REPLICA; + } + break; + case AURORA_PRIMARY: + if (aurora->mysql[AURORA_PRIMARY]) + { + if (aurora->mysql[AURORA_REPLICA]) + aurora->mysql[AURORA_REPLICA]->net.pvio->mysql= aurora->mysql[AURORA_REPLICA]; + mysql->net.pvio= aurora->pvio[AURORA_PRIMARY]; + mysql->thread_id= aurora->mysql[AURORA_PRIMARY]->thread_id; + aurora->last_instance_type= AURORA_PRIMARY; + } + break; + } +} +/* }}} */ + +/* {{{ int aurora_init + * + * plugin initialization function + */ +int aurora_init(char *errormsg, size_t errormsg_size, + int unused __attribute__((unused)), + va_list unused1 __attribute__((unused))) +{ + /* random generator initialization */ +#ifndef WIN32 + struct timeval tp; + gettimeofday(&tp,NULL); + srand(tp.tv_usec / 1000 + tp.tv_sec * 1000); +#else + srand(GetTickCount()); +#endif + return 0; +} +/* }}} */ + +/* {{{ void aurora_close_memory */ +void aurora_close_memory(AURORA *aurora) +{ + free(aurora->url); + free(aurora->username); + free(aurora->password); + free(aurora->database); + free(aurora); +} +/* }}} */ + +/* {{{ my_bool aurora_parse_url + * + * parse url + * Url has the following format: + * instance1:port, instance2:port, .., instanceN:port + * + */ +my_bool aurora_parse_url(const char *url, AURORA *aurora) +{ + char *p, *c; + int i; + + if (!url || url[0] == 0) + return 1; + + bzero(aurora->instance, (AURORA_MAX_INSTANCES + 1) * sizeof(char *)); + bzero(&aurora->port, (AURORA_MAX_INSTANCES + 1) * sizeof(int)); + + if (aurora->url) + free(aurora->url); + + aurora->url= strdup(url); + c= aurora->url; + + /* get instances */ + while((c)) + { + if (p= strchr(c, ',')) + { + *p= '\0'; + p++; + } + if (*c) + { + aurora->instance[aurora->num_instances].host= c; + aurora->num_instances++; + } + c= p; + } + + if (!aurora->num_instances) + return 0; + + /* check ports */ + for (i=0; i < aurora->num_instances && aurora->instance[i].host; i++) + { + aurora->instance[i].type= AURORA_UNKNOWN; + + /* We need to be aware of IPv6 addresses: According to RFC3986 sect. 3.2.2 + hostnames have to be enclosed in square brackets if a port is given */ + if (aurora->instance[i].host[0]== '[' && + strchr(aurora->instance[i].host, ':') && + (p= strchr(aurora->instance[i].host,']'))) + { + /* ignore first square bracket */ + memmove(aurora->instance[i].host, + aurora->instance[i].host+1, + strlen(aurora->instance[i].host) - 1); + p= strchr(aurora->instance[i].host,']'); + *p= 0; + p++; + } + else + p= aurora->instance[i].host; + if (p && (p= strchr(p, ':'))) + { + *p= '\0'; + p++; + aurora->instance[i].port= atoi(p); + } + } + return 0; +} +/* }}} */ + +/* {{{ int aurora_get_instance_type + * + * RETURNS: + * + * AURORA_PRIMARY + * AURORA_REPLICA + * -1 on error + */ +int aurora_get_instance_type(MYSQL *mysql) +{ + int rc; + char *query= "select variable_value from information_schema.global_variables where variable_name='INNODB_READ_ONLY' AND variable_value='OFF'"; + + if (!mysql_query(mysql, query)) + { + MYSQL_RES *res= mysql_store_result(mysql); + rc= mysql_num_rows(res) ? AURORA_PRIMARY : AURORA_REPLICA; + mysql_free_result(res); + return rc; + } + return -1; +} +/* }}} */ + +/* {{{ my_bool aurora_get_primary_id + * + * try to find primary instance from slave by retrieving + * primary_id information_schema.replica_host_status information + * + * If the function succeeds, primary_id will be copied into + * aurora->primary_id + * + * Returns: + * 1 on success + * 0 if an error occured or primary_id couldn't be + * found + */ +my_bool aurora_get_primary_id(MYSQL *mysql, AURORA *aurora) +{ + my_bool rc= 0; + + if (!mysql_query(mysql, "select server_id from information_schema.replica_host_status " + "where session_id = 'MASTER_SESSION_ID'")) + { + MYSQL_RES *res; + MYSQL_ROW row; + + if ((res= mysql_store_result(mysql))) + { + if ((row= mysql_fetch_row(res))) + { + if (row[0]) + { + strcpy(aurora->primary_id, row[0]); + rc= 1; + } + } + mysql_free_result(res); + } + } + return rc; +} +/* }}} */ + +/* {{{ unsigned int aurora_get_valid_instances + * + * returns the number of instances which are + * not blacklisted or don't have a type assigned. + */ +static unsigned int aurora_get_valid_instances(AURORA *aurora, AURORA_INSTANCE **instances) +{ + int i, valid_instances= 0; + + memset(instances, 0, sizeof(AURORA_INSTANCE *) * AURORA_MAX_INSTANCES); + + for (i=0; i < aurora->num_instances; i++) + { + if (aurora->instance[i].type != AURORA_UNAVAILABLE) + { + if (aurora->instance[i].type == AURORA_PRIMARY && aurora->active[AURORA_PRIMARY]) + continue; + instances[valid_instances]= &aurora->instance[i]; + valid_instances++; + } + } + return valid_instances; +} +/* }}} */ + +/* {{{ void aurora_refresh_blacklist() */ +void aurora_refresh_blacklist(AURORA *aurora) +{ + int i; + for (i=0; i < aurora->num_instances; i++) + { + if (aurora->instance[i].blacklisted && + !(AURORA_IS_BLACKLISTED(aurora, i))) + { + aurora->instance[i].blacklisted= 0; + aurora->instance[i].type= AURORA_UNKNOWN; + } + } +} +/* }}} */ + +/* {{{ MYSQL *aurora_connect_instance() */ +MYSQL *aurora_connect_instance(AURORA *aurora, AURORA_INSTANCE *instance, MYSQL *mysql) +{ + if (!mysql->methods->db_connect(mysql, + instance->host, + aurora->username, + aurora->password, + aurora->database, + instance->port ? instance->port : aurora->port, + NULL, + aurora->client_flag)) + { + /* connection not available */ + instance->blacklisted= time(NULL); + instance->type= AURORA_UNAVAILABLE; + return NULL; + } + + /* check if we are slave or master */ + switch (aurora_get_instance_type(mysql)) + { + case AURORA_PRIMARY: + instance->type= AURORA_PRIMARY; + return mysql; + break; + case AURORA_REPLICA: + instance->type= AURORA_REPLICA; + break; + default: + instance->type= AURORA_UNAVAILABLE; + instance->blacklisted= time(NULL); + return NULL; + } + if (!aurora->primary_id[0]) + aurora_get_primary_id(mysql, aurora); + return mysql; +} +/* }}} */ + +/* {{{ void aurora_copy_mysql() */ +void aurora_copy_mysql(MYSQL *from, MYSQL *to) +{ + LIST *li_stmt= to->stmts; + + for (;li_stmt;li_stmt= li_stmt->next) + { + MYSQL_STMT *stmt= (MYSQL_STMT *)li_stmt->data; + + if (stmt->state != MYSQL_STMT_INITTED) + { + stmt->state= MYSQL_STMT_INITTED; + SET_CLIENT_STMT_ERROR(stmt, CR_SERVER_LOST, SQLSTATE_UNKNOWN, 0); + } + } + + from->free_me= to->free_me; + from->reconnect= to->reconnect; + from->net.conn_hdlr= to->net.conn_hdlr; + from->stmts= to->stmts; + to->stmts= NULL; + + memset(&to->options, 0, sizeof(to->options)); + to->free_me= 0; + to->net.conn_hdlr= 0; + mysql_close(to); + *to= *from; + to->net.pvio= from->net.pvio; + to->net.pvio->mysql= to; + from->net.pvio= NULL; +} +/* }}} */ + +/* {{{ my_bool aurora_find_replica() */ +my_bool aurora_find_replica(AURORA *aurora) +{ + int valid_instances; + my_bool replica_found= 0; + AURORA_INSTANCE *instance[AURORA_MAX_INSTANCES]; + MYSQL mysql; +// struct st_dynamic_array *init_command= aurora->mysql[AURORA_PRIMARY]->options.init_command; + + if (aurora->num_instances < 2) + return 0; + + mysql_init(&mysql); + mysql.options= aurora->mysql[AURORA_PRIMARY]->options; + + /* don't execute init_command on slave */ + mysql.net.conn_hdlr= aurora->mysql[AURORA_PRIMARY]->net.conn_hdlr; + + valid_instances= aurora_get_valid_instances(aurora, instance); + + while (valid_instances && !replica_found) + { + int random_pick= rand() % valid_instances; + if ((aurora_connect_instance(aurora, instance[random_pick], &mysql))) + { + switch (instance[random_pick]->type) { + case AURORA_REPLICA: + if (!aurora->mysql[AURORA_REPLICA]) + { + aurora->mysql[AURORA_REPLICA]= mysql_init(NULL); + } + aurora_copy_mysql(&mysql, aurora->mysql[AURORA_REPLICA]); + aurora->active[AURORA_REPLICA]= 1; + return 1; + break; + case AURORA_PRIMARY: + aurora_copy_mysql(&mysql, aurora->mysql[AURORA_PRIMARY]); + aurora->pvio[AURORA_PRIMARY]= aurora->mysql[AURORA_PRIMARY]->net.pvio; + aurora->active[AURORA_PRIMARY]= 1; + continue; + break; + default: + mysql_close(&mysql); + return 0; + break; + } + } + valid_instances= aurora_get_valid_instances(aurora, instance); + } + return 0; +} +/* }}} */ + +/* {{{ AURORA_INSTANCE aurora_get_primary_id_instance() */ +AURORA_INSTANCE *aurora_get_primary_id_instance(AURORA *aurora) +{ + int i; + + if (!aurora->primary_id[0]) + return 0; + + for (i=0; i < aurora->num_instances; i++) + { + if (!strncmp(aurora->instance[i].host, aurora->primary_id, strlen(aurora->primary_id))) + return &aurora->instance[i]; + } + return NULL; +} +/* }}} */ + +/* {{{ my_bool aurora_find_primary() */ +my_bool aurora_find_primary(AURORA *aurora) +{ + int i; + AURORA_INSTANCE *instance= NULL; + MYSQL mysql; + my_bool check_primary= 1; + + if (!aurora->num_instances) + return 0; + + mysql_init(&mysql); + mysql.options= aurora->mysql[AURORA_PRIMARY]->options; + mysql.net.conn_hdlr= aurora->mysql[AURORA_PRIMARY]->net.conn_hdlr; + + for (i=0; i < aurora->num_instances; i++) + { + if (check_primary && aurora->primary_id[0]) + { + if ((instance= aurora_get_primary_id_instance(aurora)) && + aurora_connect_instance(aurora, instance, &mysql) && + instance->type == AURORA_PRIMARY) + { + aurora_copy_mysql(&mysql, aurora->mysql[AURORA_PRIMARY]); + aurora->active[AURORA_PRIMARY]= 1; + return 1; + } + /* primary id connect failed, don't try again */ + aurora->primary_id[0]= 0; + check_primary= 0; + } + if (aurora->instance[i].type != AURORA_UNAVAILABLE) + { + if (aurora_connect_instance(aurora, &aurora->instance[i], &mysql) + && aurora->instance[i].type == AURORA_PRIMARY) + { + aurora_copy_mysql(&mysql, aurora->mysql[AURORA_PRIMARY]); + aurora->active[AURORA_PRIMARY]= 1; + return 1; + } + } + } + return 0; +} +/* }}} */ + +/* {{{ void aurora_close_replica() */ +void aurora_close_replica(MYSQL *mysql, AURORA *aurora) +{ + if (aurora->mysql[AURORA_REPLICA]) + { + aurora->mysql[AURORA_REPLICA]->net.pvio->mysql= aurora->mysql[AURORA_REPLICA]; + aurora->mysql[AURORA_REPLICA]->net.conn_hdlr= 0; + mysql_close(aurora->mysql[AURORA_REPLICA]); + aurora->pvio[AURORA_REPLICA]= 0; + aurora->mysql[AURORA_REPLICA]= NULL; + } +} +/* }}} */ + +/* {{{ MYSQL *aurora_connect */ +MYSQL *aurora_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, + const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag) +{ + AURORA *aurora= NULL; + MA_CONNECTION_HANDLER *hdlr= mysql->net.conn_hdlr; + my_bool is_reconnect= 0; + + if ((aurora= (AURORA *)hdlr->data)) + { + aurora_refresh_blacklist(aurora); + if (aurora->mysql[aurora->last_instance_type]->net.pvio) + { + SET_CLIENT_ERROR(mysql, CR_ALREADY_CONNECTED, SQLSTATE_UNKNOWN, 0); + return NULL; + } + is_reconnect= 1; + } + else + { + if (!(aurora= (AURORA *)my_malloc(sizeof(AURORA), MYF(MY_ZEROFILL)))) + { + mysql->methods->set_error(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return NULL; + } + + mysql->net.conn_hdlr->data= (void *)aurora; + + aurora->mysql[AURORA_PRIMARY]= mysql; + + if (aurora_parse_url(host, aurora)) + { + goto error; + } + + if (user) + aurora->username= strdup(user); + if (passwd) + aurora->password= strdup(passwd); + if (db) + aurora->database= strdup(db); + aurora->port= port; + aurora->client_flag= client_flag; + aurora->pvio[AURORA_PRIMARY]= aurora->pvio[AURORA_REPLICA]= NULL; + hdlr->data= aurora; + } + + + /* In case of reconnect, close broken connection first */ + if (is_reconnect) + { + DISABLE_AURORA(mysql); + switch (aurora->last_instance_type) { + case AURORA_REPLICA: + aurora_close_replica(mysql, aurora); + aurora->pvio[AURORA_REPLICA]= NULL; + break; + case AURORA_PRIMARY: + /* pvio will be closed in mysql_reconnect() */ + aurora->pvio[AURORA_PRIMARY]= NULL; + aurora->primary_id[0]= 0; + break; + } + aurora->active[aurora->last_instance_type]= 0; + } + + if (!aurora->active[AURORA_REPLICA]) + { + if (aurora_find_replica(aurora)) + { + aurora->pvio[AURORA_REPLICA]= aurora->mysql[AURORA_REPLICA]->net.pvio; + aurora->mysql[AURORA_REPLICA]->net.conn_hdlr= mysql->net.conn_hdlr; + } + else + aurora->pvio[AURORA_REPLICA]= NULL; + } + + if (!aurora->active[AURORA_PRIMARY]) + { + if (aurora_find_primary(aurora)) + { + aurora->active[AURORA_PRIMARY]= 1; + aurora->pvio[AURORA_PRIMARY]= aurora->mysql[AURORA_PRIMARY]->net.pvio; + } + } + + aurora_switch_connection(mysql, aurora, AURORA_PRIMARY); + ENABLE_AURORA(mysql); + return mysql; +error: + aurora_close_memory(aurora); + return NULL; +} +/* }}} */ + +/* {{{ my_bool aurora_reconnect */ +my_bool aurora_reconnect(MYSQL *mysql) +{ + AURORA *aurora; + MA_CONNECTION_HANDLER *hdlr= mysql->net.conn_hdlr; + my_bool rc= 1; + + aurora= (AURORA *)hdlr->data; + + DISABLE_AURORA(mysql); + switch (aurora->last_instance_type) + { + case AURORA_REPLICA: + if (!(rc= mysql->methods->reconnect(aurora->mysql[aurora->last_instance_type]))) + aurora_switch_connection(mysql, aurora, AURORA_REPLICA); + break; + case AURORA_PRIMARY: + if (!(rc= mysql->methods->reconnect(aurora->mysql[aurora->last_instance_type]))) + aurora_switch_connection(mysql, aurora, AURORA_PRIMARY); + break; + default: + /* todo: error message */ + break; + } + ENABLE_AURORA(mysql); + return rc; +} +/* }}} */ + +/* {{{ void aurora_close */ +void aurora_close(MYSQL *mysql) +{ + MA_CONNECTION_HANDLER *hdlr= mysql->net.conn_hdlr; + AURORA *aurora= (AURORA *)hdlr->data; + + aurora_switch_connection(mysql, aurora, AURORA_PRIMARY); + + /* if the connection is not active yet, just return */ + if (!aurora->active[1]) + return; + + if (aurora->mysql[AURORA_REPLICA]) + { + /* we got options from primary, so don't free it twice */ + memset(&aurora->mysql[AURORA_REPLICA]->options, 0, sizeof(mysql->options)); + /* connection handler wull be freed in mysql_close() */ + aurora->mysql[AURORA_REPLICA]->net.conn_hdlr= 0; + + mysql_close(aurora->mysql[AURORA_REPLICA]); + } + + if (aurora->mysql[AURORA_PRIMARY]) + { + /* connection handler wull be freed in mysql_close() */ + aurora->mysql[AURORA_PRIMARY]->net.conn_hdlr= 0; + + aurora->mysql[AURORA_PRIMARY]->net.pvio= aurora->pvio[AURORA_PRIMARY]; + + mysql_close(aurora->mysql[AURORA_PRIMARY]); + } + + + +/* + if (aurora->mysql[AURORA_PRIMARY]) + { + aurora->mysql[AURORA_PRIMARY]->net.pvio= aurora->pvio[AURORA_PRIMARY]; + aurora->mysql[AURORA_PRIMARY]->net.conn_hdlr= 0; + mysql_close(aurora->mysql[AURORA_PRIMARY]); + } +*/ + /* free masrwe information */ + aurora_close_memory(aurora); +} +/* }}} */ + +/* {{{ my_bool is_replica_command */ +my_bool is_replica_command(const char *buffer, size_t buffer_len) +{ + const char *buffer_end= buffer + buffer_len; + + for (; buffer < buffer_end; ++buffer) + { + char c; + if (isalpha(c=*buffer)) + { + if (tolower(c) == 's') + return 1; + return 0; + } + } + return 0; +} +/* }}} */ + +/* {{{ my_bool is_replica_stmt */ +my_bool is_replica_stmt(MYSQL *mysql, const char *buffer) +{ + unsigned long stmt_id= uint4korr(buffer); + LIST *stmt_list= mysql->stmts; + + for (; stmt_list; stmt_list= stmt_list->next) + { + MYSQL_STMT *stmt= (MYSQL_STMT *)stmt_list->data; + if (stmt->stmt_id == stmt_id) + return 1; + } + return 0; +} +/* }}} */ + +/* {{{ int aurora_command */ +int aurora_command(MYSQL *mysql,enum enum_server_command command, const char *arg, + size_t length, my_bool skipp_check, void *opt_arg) +{ + AURORA *aurora= (AURORA *)mysql->net.conn_hdlr->data; + + /* if we don't have slave or slave became unavailable root traffic to master */ + if (!aurora->mysql[AURORA_REPLICA] || !OPT_HAS_EXT_VAL(mysql, read_only)) + { + if (command != COM_INIT_DB) + { + aurora_switch_connection(mysql, aurora, AURORA_PRIMARY); + return 0; + } + } + + switch(command) { + case COM_INIT_DB: + /* we need to change default database on primary and replica */ + if (aurora->mysql[AURORA_REPLICA] && aurora->last_instance_type != AURORA_REPLICA) + { + aurora_switch_connection(mysql, aurora, AURORA_REPLICA); + DISABLE_AURORA(mysql); + mysql_select_db(aurora->mysql[AURORA_REPLICA], arg); + ENABLE_AURORA(mysql); + aurora_switch_connection(mysql, aurora, AURORA_PRIMARY); + } + break; + case COM_QUERY: + case COM_STMT_PREPARE: + if (aurora->mysql[AURORA_REPLICA] && aurora->last_instance_type != AURORA_REPLICA) + aurora_switch_connection(mysql, aurora, AURORA_REPLICA); + break; + case COM_STMT_EXECUTE: + case COM_STMT_FETCH: + if (aurora->pvio[AURORA_REPLICA]->mysql->stmts && is_replica_stmt(aurora->pvio[AURORA_REPLICA]->mysql, arg)) + { + if (aurora->last_instance_type != AURORA_REPLICA) + aurora_switch_connection(mysql, aurora, AURORA_REPLICA); + } + else + { + if (aurora->last_instance_type != AURORA_PRIMARY) + aurora_switch_connection(mysql, aurora, AURORA_PRIMARY); + } + + default: + aurora_switch_connection(mysql, aurora, AURORA_PRIMARY); + break; + } + return 0; +} +/* }}} */ + +/* {{{ int aurora_set_options() */ +int aurora_set_options(MYSQL *mysql, enum mysql_option option, void *arg) +{ + switch(option) { + default: + return -1; + } +} +/* }}} */ diff --git a/plugins/connection/replication.c b/plugins/connection/replication.c index cf5320ec..c9415c01 100644 --- a/plugins/connection/replication.c +++ b/plugins/connection/replication.c @@ -70,7 +70,8 @@ MARIADB_CONNECTION_PLUGIN _mysql_client_plugin_declaration_ = repl_connect, repl_close, repl_set_options, - repl_command + repl_command, + NULL }; typedef struct st_conn_repl { @@ -81,10 +82,20 @@ typedef struct st_conn_repl { char *url; char *host[2]; int port[2]; + unsigned int current_type; } REPL_DATA; -#define SET_SLAVE(mysql, data) mysql->net.pvio= data->pvio[MARIADB_SLAVE] -#define SET_MASTER(mysql, data) mysql->net.pvio= data->pvio[MARIADB_MASTER] +#define SET_SLAVE(mysql, data)\ +{\ + mysql->net.pvio= data->pvio[MARIADB_SLAVE]; \ + data->current_type= MARIADB_SLAVE;\ +} + +#define SET_MASTER(mysql, data)\ +{\ + mysql->net.pvio= data->pvio[MARIADB_MASTER];\ + data->current_type= MARIADB_MASTER;\ +} /* parse url @@ -108,10 +119,8 @@ my_bool repl_parse_url(const char *url, REPL_DATA *data) memset(data->host, 0, 2 * sizeof(char *)); memset(data->port, 0, 2 * sizeof(int)); - if (data->url) - my_free(data->url); - - data->url= my_strdup(url, MYF(0)); + if (!data->url) + data->url= my_strdup(url, MYF(0)); data->host[MARIADB_MASTER]= p= data->url; /* get slaves */ @@ -150,10 +159,11 @@ my_bool repl_parse_url(const char *url, REPL_DATA *data) { /* We need to be aware of IPv6 addresses: According to RFC3986 sect. 3.2.2 hostnames have to be enclosed in square brackets if a port is given */ - if (data->host[i][0]= '[' && strchr(data->host[i], ':') && (p= strchr(data->host[i],']'))) + if (data->host[i][0]== '[' && strchr(data->host[i], ':') && (p= strchr(data->host[i],']'))) { /* ignore first square bracket */ - data->host[i]++; + memmove(data->host[i], data->host[i]+1, strlen(data->host[i]) - 1); + p= strchr(data->host[i],']'); *p= 0; p++; } @@ -176,12 +186,18 @@ MYSQL *repl_connect(MYSQL *mysql, const char *host, const char *user, const char REPL_DATA *data= NULL; MA_CONNECTION_HANDLER *hdlr= mysql->net.conn_hdlr; + if ((data= (REPL_DATA *)hdlr->data)) + { + ma_pvio_close(data->pvio[MARIADB_MASTER]); + data->pvio[MARIADB_MASTER]= 0; + repl_close(mysql); + } + if (!(data= calloc(1, sizeof(REPL_DATA)))) { mysql->methods->set_error(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); return NULL; } - memset(data->pvio, 0, 2 * sizeof(MARIADB_PVIO *)); if (repl_parse_url(host, data)) @@ -194,6 +210,7 @@ MYSQL *repl_connect(MYSQL *mysql, const char *host, const char *user, const char data->pvio[MARIADB_MASTER]= mysql->net.pvio; hdlr->data= data; + SET_MASTER(mysql, data); /* to allow immediate access without connection delay, we will start * connecting to slave(s) in background */ @@ -245,7 +262,6 @@ void repl_close(MYSQL *mysql) my_free(data->url); my_free(data); mysql->net.conn_hdlr->data= NULL; - mysql_close(mysql); } static my_bool is_slave_command(const char *buffer, size_t buffer_len) @@ -295,20 +311,20 @@ int repl_command(MYSQL *mysql,enum enum_server_command command, const char *arg, case COM_QUERY: case COM_STMT_PREPARE: if (is_slave_command(arg, length)) - SET_SLAVE(mysql, data); + SET_SLAVE(mysql, data) else - SET_MASTER(mysql,data); + SET_MASTER(mysql,data) break; case COM_STMT_EXECUTE: case COM_STMT_FETCH: if (data->pvio[MARIADB_SLAVE]->mysql->stmts && is_slave_stmt(data->pvio[MARIADB_SLAVE]->mysql, arg)) - SET_SLAVE(mysql, data); + SET_SLAVE(mysql, data) else - SET_MASTER(mysql,data); + SET_MASTER(mysql,data) break; default: - SET_MASTER(mysql,data); + SET_MASTER(mysql,data) break; } return 0; diff --git a/plugins/pvio/pvio_npipe.c b/plugins/pvio/pvio_npipe.c index 00d0c5e7..623f6ba9 100644 --- a/plugins/pvio/pvio_npipe.c +++ b/plugins/pvio/pvio_npipe.c @@ -251,7 +251,7 @@ my_bool pvio_npipe_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo) 0, /* no sharing */ NULL, /* default security attributes */ OPEN_EXISTING, - 0, /* default attributes */ + FILE_FLAG_OVERLAPPED, NULL)) != INVALID_HANDLE_VALUE) break; diff --git a/unittest/libmariadb/CMakeLists.txt b/unittest/libmariadb/CMakeLists.txt index 4e8d2125..f60d8638 100644 --- a/unittest/libmariadb/CMakeLists.txt +++ b/unittest/libmariadb/CMakeLists.txt @@ -21,7 +21,7 @@ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/unittest/mytap) ADD_DEFINITIONS(-DLIBMARIADB) -SET(API_TESTS "async" "basic-t" "fetch" "charset" "logs" "cursor" "errors" "view" "ps" "ps_bugs" +SET(API_TESTS "aurora" "async" "basic-t" "fetch" "charset" "logs" "cursor" "errors" "view" "ps" "ps_bugs" "sp" "result" "connection" "misc" "ps_new" "sqlite3" "thread" "dyncol") # Get finger print from server certificate @@ -61,7 +61,7 @@ ENDIF() FOREACH(API_TEST ${API_TESTS}) ADD_EXECUTABLE(${API_TEST} ${API_TEST}.c) - TARGET_LINK_LIBRARIES(${API_TEST} mytap mariadbclient ) + TARGET_LINK_LIBRARIES(${API_TEST} mytap libmariadb) ADD_TEST(${API_TEST} ${EXECUTABLE_OUTPUT_PATH}/${API_TEST}) SET_TESTS_PROPERTIES(${API_TEST} PROPERTIES TIMEOUT 120) ENDFOREACH(API_TEST) diff --git a/unittest/libmariadb/aurora.c b/unittest/libmariadb/aurora.c new file mode 100644 index 00000000..d9c89c45 --- /dev/null +++ b/unittest/libmariadb/aurora.c @@ -0,0 +1,67 @@ +/* +*/ + +#include "my_test.h" + +static int aurora1(MYSQL *mysql) +{ + int rc; + my_bool read_only= 1; + char *primary, *replica; + MYSQL_RES *res; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE t1 (a int, b varchar(20))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO t1 VALUES (1, 'foo'), (2, 'bar')"); + check_mysql_rc(rc, mysql); + + primary= mysql->host; + diag("primary: %s", primary); + + mysql_options(mysql, MARIADB_OPT_CONNECTION_READ_ONLY, &read_only); + + /* ensure, that this is a replica, so INSERT should fail */ + rc= mysql_query(mysql, "INSERT INTO t1 VALUES (3, 'error')"); + if (rc) + diag("Expected error: %s", mysql_error(mysql)); + + rc= mysql_query(mysql, "SELECT a, b FROM t1"); + check_mysql_rc(rc, mysql); + + res= mysql_store_result(mysql); + + diag("Num_rows: %d", mysql_num_rows(res)); + mysql_free_result(res); + + replica= mysql->host; + diag("replica: %s", replica); + diag("db: %s", mysql->db); + + return OK; +} + +struct my_tests_st my_tests[] = { + {"aurora1", aurora1, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {NULL, NULL, 0, 0, NULL, NULL} +}; + + +int main(int argc, char **argv) +{ + + mysql_library_init(0,0,NULL); + + if (argc > 1) + get_options(argc, argv); + + get_envvars(); + + run_tests(my_tests); + + mysql_server_end(); + return(exit_status()); +} diff --git a/unittest/libmariadb/basic-t.c b/unittest/libmariadb/basic-t.c index 4e535852..610ab460 100644 --- a/unittest/libmariadb/basic-t.c +++ b/unittest/libmariadb/basic-t.c @@ -55,13 +55,17 @@ static int test_conc75(MYSQL *my) { ulong thread_id= mysql_thread_id(mysql); /* force reconnect */ + mysql->reconnect= 1; + diag("killing connection"); mysql_kill(my, thread_id); - sleep(1); + sleep(2); + mysql_ping(mysql); rc= mysql_query(mysql, "load data local infile './nonexistingfile.csv' into table a (`a`)"); FAIL_IF(!test(mysql->options.client_flag | CLIENT_LOCAL_FILES), "client_flags not correct"); + diag("thread1: %d %d", thread_id, mysql_thread_id(mysql)); FAIL_IF(thread_id == mysql_thread_id(mysql), "new thread id expected"); - diag("cs: %s", mysql->charset->csname); - FAIL_IF(strcmp(mysql->charset->csname, "utf8"), "wrong character set"); + //diag("cs: %s", mysql->charset->csname); + //FAIL_IF(strcmp(mysql->charset->csname, "utf8"), "wrong character set"); } mysql_close(mysql); return OK; @@ -76,7 +80,12 @@ static int test_conc74(MYSQL *my) mysql= mysql_init(NULL); - mysql_real_connect(mysql, hostname, username, password, schema, port, socketname, 0| CLIENT_MULTI_RESULTS | CLIENT_REMEMBER_OPTIONS); + if (!mysql_real_connect(mysql, hostname, username, password, schema, port, socketname, 0| CLIENT_MULTI_RESULTS | CLIENT_REMEMBER_OPTIONS)) + { + diag("Error: %s", mysql_error(mysql)); + mysql_close(mysql); + return FAIL; + } rc= mysql_query(mysql, "DROP TABLE IF EXISTS a"); check_mysql_rc(rc, mysql); @@ -128,7 +137,11 @@ static int test_conc70(MYSQL *my) int rc; MYSQL_RES *res; MYSQL_ROW row; - MYSQL *mysql= mysql_init(NULL); + MYSQL *mysql; + + SKIP_CONNECTION_HANDLER; + + mysql= mysql_init(NULL); rc= mysql_query(my, "SET @a:=@@max_allowed_packet"); check_mysql_rc(rc, my); @@ -148,6 +161,14 @@ static int test_conc70(MYSQL *my) rc= mysql_query(mysql, "INSERT INTO t1 VALUES (REPEAT('A', 1024 * 1024 * 20))"); check_mysql_rc(rc, mysql); + if (mysql_warning_count(mysql)) + { + diag("server doesn't accept package size"); + return SKIP; + } + + sleep(20); + rc= mysql_query(mysql, "SELECT a FROM t1"); check_mysql_rc(rc, mysql); @@ -175,7 +196,11 @@ static int test_conc68(MYSQL *my) int rc; MYSQL_RES *res; MYSQL_ROW row; - MYSQL *mysql= mysql_init(NULL); + MYSQL *mysql; + + SKIP_CONNECTION_HANDLER; + + mysql= mysql_init(NULL); rc= mysql_query(my, "SET @a:=@@max_allowed_packet"); check_mysql_rc(rc, my); @@ -193,6 +218,11 @@ static int test_conc68(MYSQL *my) rc= mysql_query(mysql, "INSERT INTO t1 VALUES (REPEAT('A', 1024 * 1024 * 20))"); check_mysql_rc(rc, mysql); + if (mysql_warning_count(mysql)) + { + diag("server doesn't accept package size"); + return SKIP; + } rc= mysql_query(mysql, "SELECT a FROM t1"); check_mysql_rc(rc, mysql); @@ -504,6 +534,7 @@ static int test_mysql_insert_id(MYSQL *mysql) according to the manual, this might be 20 or 300, but it looks like auto_increment column takes priority over last_insert_id(). */ + diag("res: %d", res); FAIL_UNLESS(res == 20, ""); /* If first autogenerated number fails and 2nd works: */ rc= mysql_query(mysql, "drop table t2"); @@ -618,12 +649,9 @@ static int bug_conc1(MYSQL *mysql) { mysql_real_connect(mysql, hostname, username, password, schema, port, socketname, 0); + diag("errno: %d", mysql_errno(mysql)); FAIL_IF(mysql_errno(mysql) != CR_ALREADY_CONNECTED, "Expected errno=CR_ALREADY_CONNECTED"); - FAIL_IF(strcmp(mysql_error(mysql), ER(CR_ALREADY_CONNECTED)) != 0, - "Wrong error message"); - FAIL_IF(strcmp(ER(CR_ALREADY_CONNECTED), "Can't connect twice. Already connected") != 0, - "wrong error message"); return OK; } @@ -668,11 +696,14 @@ static int test_reconnect_maxpackage(MYSQL *my) { int rc; ulong max_packet= 0; - MYSQL *mysql= mysql_init(NULL); + MYSQL *mysql; MYSQL_RES *res; MYSQL_ROW row; char *query; + SKIP_CONNECTION_HANDLER; + mysql= mysql_init(NULL); + FAIL_IF(!mysql_real_connect(mysql, hostname, username, password, schema, port, socketname, CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS), mysql_error(mysql)); @@ -770,6 +801,8 @@ int main(int argc, char **argv) get_envvars(); + diag("user: %s", username); + run_tests(my_tests); return(exit_status()); diff --git a/unittest/libmariadb/charset.c b/unittest/libmariadb/charset.c index b5cdbf89..0226bc18 100644 --- a/unittest/libmariadb/charset.c +++ b/unittest/libmariadb/charset.c @@ -679,7 +679,7 @@ static int test_utf16_utf32_noboms(MYSQL *mysql) for (i= 0; i < sizeof(csname)/sizeof(char*); ++i) { - csinfo[i]= mysql_find_charset_name(csname[i]); + csinfo[i]= mariadb_get_charset_by_name(csname[i]); if (csinfo[i] == NULL) { diff --git a/unittest/libmariadb/connection.c b/unittest/libmariadb/connection.c index 11452977..e40b05e0 100644 --- a/unittest/libmariadb/connection.c +++ b/unittest/libmariadb/connection.c @@ -32,6 +32,7 @@ static int test_conc66(MYSQL *my) MYSQL *mysql= mysql_init(NULL); int rc; FILE *fp; + char query[1024]; if (!(fp= fopen("./my.cnf", "w"))) return FAIL; @@ -47,7 +48,8 @@ static int test_conc66(MYSQL *my) rc= mysql_options(mysql, MYSQL_READ_DEFAULT_FILE, "./my.cnf"); check_mysql_rc(rc, mysql); - rc= mysql_query(my, "GRANT ALL ON test.* TO 'conc66'@'localhost' IDENTIFIED BY 'test\";#test'"); + sprintf(query, "GRANT ALL ON %s.* TO 'conc66'@'%s' IDENTIFIED BY 'test\";#test'", schema, hostname); + rc= mysql_query(my, query); check_mysql_rc(rc, my); rc= mysql_query(my, "FLUSH PRIVILEGES"); check_mysql_rc(rc, my); @@ -57,7 +59,8 @@ static int test_conc66(MYSQL *my) diag("Error: %s", mysql_error(mysql)); return FAIL; } - rc= mysql_query(my, "DROP USER conc66@localhost"); + sprintf(query, "DROP user conc66@%s", hostname); + rc= mysql_query(my, query); check_mysql_rc(rc, my); mysql_close(mysql); @@ -561,6 +564,8 @@ static int test_reconnect(MYSQL *mysql) mysql_kill(mysql, mysql_thread_id(mysql1)); sleep(4); + mysql_ping(mysql1); + rc= mysql_query(mysql1, "SELECT 1 FROM DUAL LIMIT 0"); check_mysql_rc(rc, mysql1); diag("Thread_id after kill: %lu", mysql_thread_id(mysql1)); @@ -649,6 +654,8 @@ static int test_conc118(MYSQL *mysql) rc= mysql_kill(mysql, mysql_thread_id(mysql)); sleep(2); + mysql_ping(mysql); + rc= mysql_query(mysql, "SET @a:=1"); check_mysql_rc(rc, mysql); @@ -657,12 +664,9 @@ static int test_conc118(MYSQL *mysql) rc= mysql_kill(mysql, mysql_thread_id(mysql)); sleep(2); - mysql->host= "foo"; - rc= mysql_query(mysql, "SET @a:=1"); FAIL_IF(!rc, "error expected"); - mysql->host= hostname; rc= mysql_query(mysql, "SET @a:=1"); check_mysql_rc(rc, mysql); diff --git a/unittest/libmariadb/my_test.h b/unittest/libmariadb/my_test.h index e862ced1..e0db2beb 100644 --- a/unittest/libmariadb/my_test.h +++ b/unittest/libmariadb/my_test.h @@ -82,6 +82,13 @@ if (!(expr))\ return FAIL;\ } +#define SKIP_CONNECTION_HANDLER \ + if (hostname && strstr(hostname, "://"))\ + {\ + diag("Test skipped (connection handler)");\ + return SKIP;\ + } + /* connection options */ #define TEST_CONNECTION_DEFAULT 1 /* default connection */ #define TEST_CONNECTION_NONE 2 /* tests creates own connection */ @@ -104,7 +111,7 @@ struct my_tests_st char *skipmsg; }; -static char *schema = "test_c"; +static char *schema = 0; static char *hostname = 0; static char *password = 0; static unsigned int port = 0; @@ -368,18 +375,19 @@ int check_variable(MYSQL *mysql, char *variable, char *value) MYSQL *test_connect(struct my_tests_st *test) { MYSQL *mysql; char query[255]; - int i= 1; + int i= 0; + int timeout= 10; + int truncation_report= 1; if (!(mysql = mysql_init(NULL))) { diag("%s", "mysql_init failed - exiting"); return(NULL); } - mysql_options(mysql, MYSQL_REPORT_DATA_TRUNCATION, &i); - mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&i); + mysql_options(mysql, MYSQL_REPORT_DATA_TRUNCATION, &truncation_report); + mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); /* option handling */ if (test && test->options) { - int i=0; while (test->options[i].option) { @@ -403,6 +411,8 @@ MYSQL *test_connect(struct my_tests_st *test) { /* change database or create if it doesn't exist */ if (mysql_select_db(mysql, schema)) { + diag("Error number: %d", mysql_errno(mysql)); + if(mysql_errno(mysql) == 1049) { sprintf(query, "CREATE DATABASE %s", schema); if (mysql_query(mysql, query)) { @@ -425,11 +435,6 @@ static int reset_connection(MYSQL *mysql) { rc= mysql_change_user(mysql, username, password, schema); check_mysql_rc(rc, mysql); - if (mysql_get_server_version(mysql) < 50400) - rc= mysql_query(mysql, "SET table_type='MyISAM'"); - else - rc= mysql_query(mysql, "SET storage_engine='MyISAM'"); - check_mysql_rc(rc, mysql); rc= mysql_query(mysql, "SET sql_mode=''"); check_mysql_rc(rc, mysql); @@ -452,6 +457,8 @@ void get_envvars() { password= envvar; if (!schema && (envvar= getenv("MYSQL_TEST_DB"))) schema= envvar; + if (!schema) + schema= "testc"; if (!port && (envvar= getenv("MYSQL_TEST_PORT"))) port= atoi(envvar); if (!socketname && (envvar= getenv("MYSQL_TEST_SOCKET"))) @@ -470,7 +477,8 @@ void run_tests(struct my_tests_st *test) { if ((mysql_default= test_connect(NULL))) { diag("Testing against MySQL Server %s", mysql_get_server_info(mysql_default)); - diag("Host %s", mysql_get_host_info(mysql_default)); + diag("Host: %s", mysql_get_host_info(mysql_default)); + diag("Client library: %s", mysql_get_client_info()); } else { diff --git a/unittest/libmariadb/ps.c b/unittest/libmariadb/ps.c index 1c96baca..85e5528c 100644 --- a/unittest/libmariadb/ps.c +++ b/unittest/libmariadb/ps.c @@ -64,7 +64,10 @@ static int test_conc83(MYSQL *my) /* 1. Status is inited, so prepare should work */ rc= mysql_kill(mysql, mysql_thread_id(mysql)); - sleep(2); + sleep(5); + + rc= mysql_ping(mysql); + check_mysql_rc(rc, mysql); rc= mysql_stmt_prepare(stmt, query, strlen(query)); check_stmt_rc(rc, stmt); diff --git a/unittest/libmariadb/ps_bugs.c b/unittest/libmariadb/ps_bugs.c index bd072646..b7d54d26 100644 --- a/unittest/libmariadb/ps_bugs.c +++ b/unittest/libmariadb/ps_bugs.c @@ -528,9 +528,8 @@ static int test_bug12744(MYSQL *mysql) rc= mysql_options(mysql, MYSQL_OPT_RECONNECT, "1"); check_mysql_rc(rc, mysql); rc= mysql_kill(mysql, mysql_thread_id(mysql)); - check_mysql_rc(rc, mysql); - sleep(2); + sleep(4); rc= mysql_ping(mysql); check_mysql_rc(rc, mysql);