summaryrefslogtreecommitdiff
path: root/libmysql
diff options
context:
space:
mode:
authorsasha@mysql.sashanet.com <>2001-06-08 19:24:41 -0600
committersasha@mysql.sashanet.com <>2001-06-08 19:24:41 -0600
commit24f89c754ae9d7b8c40c7ae89857ce5d928fad9e (patch)
treef46251fc12dc5ff80de5c799174f04ad0fc2487d /libmysql
parent3aafd368a4ecf7264fa30fa5d9a3915da4aaa18b (diff)
downloadmariadb-git-24f89c754ae9d7b8c40c7ae89857ce5d928fad9e.tar.gz
auto-magic replication redirection logic support in the client,
compiles, passes test suite, does magic, has bugs, but none that I know at this point. Have not tested everything yet, though. Changed a lot of code in the client, but normal stuff appears to be working.
Diffstat (limited to 'libmysql')
-rw-r--r--libmysql/errmsg.c16
-rw-r--r--libmysql/libmysql.c286
2 files changed, 263 insertions, 39 deletions
diff --git a/libmysql/errmsg.c b/libmysql/errmsg.c
index 71fc25fd77c..f0a1692453a 100644
--- a/libmysql/errmsg.c
+++ b/libmysql/errmsg.c
@@ -16,7 +16,7 @@
MA 02111-1307, USA */
/* Error messages for MySQL clients */
-/* error messages for the demon is in share/language/errmsg.sys */
+/* error messages for the daemon is in share/language/errmsg.sys */
#include <global.h>
#include <my_sys.h>
@@ -47,6 +47,10 @@ const char *client_errors[]=
"Can't initialize character set %-.64s (path: %-.64s)",
"Got packet bigger than 'max_allowed_packet'",
"Embedded server",
+ "Error on SHOW SLAVE STATUS: %-.64s",
+ "Error on SHOW SLAVE HOSTS: %-.64s",
+ "Error connecting to slave: %-.64s",
+ "Error connecting to master: %-.64s"
};
/* Start of code added by Roberto M. Serqueira - martinsc@uol.com.br - 05.24.2001 */
@@ -76,6 +80,10 @@ const char *client_errors[]=
"Não pode inicializar conjunto de caracteres %-.64s (caminho %-.64s)",
"Obteve pacote maior do que 'max_allowed_packet'",
"Embedded server"
+ "Error on SHOW SLAVE STATUS: %-.64s",
+ "Error on SHOW SLAVE HOSTS: %-.64s",
+ "Error connecting to slave: %-.64s",
+ "Error connecting to master: %-.64s"
};
#else /* ENGLISH */
@@ -102,7 +110,11 @@ const char *client_errors[]=
"Can't set state of named pipe to host: %-.64s pipe: %-.32s (%lu)",
"Can't initialize character set %-.64s (path: %-.64s)",
"Got packet bigger than 'max_allowed_packet'",
- "Embedded server"
+ "Embedded server",
+ "Error on SHOW SLAVE STATUS:",
+ "Error on SHOW SLAVE HOSTS:",
+ "Error connecting to slave:",
+ "Error connecting to master:"
};
#endif
diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c
index c45dce981a8..d6f5b7c523f 100644
--- a/libmysql/libmysql.c
+++ b/libmysql/libmysql.c
@@ -109,6 +109,12 @@ static ulong mysql_sub_escape_string(CHARSET_INFO *charset_info, char *to,
#define reset_sigpipe(mysql)
#endif
+static MYSQL* spawn_init(MYSQL* parent, const char* host,
+ unsigned int port,
+ const char* user,
+ const char* passwd);
+
+
/****************************************************************************
* A modified version of connect(). connect2() allows you to specify
* a timeout value, in seconds, that we should wait until we
@@ -1001,15 +1007,36 @@ read_one_row(MYSQL *mysql,uint fields,MYSQL_ROW row, ulong *lengths)
int STDCALL mysql_master_query(MYSQL *mysql, const char *q,
unsigned int length)
{
- if(!length)
+ if(mysql_master_send_query(mysql, q, length))
+ return 1;
+ return mysql_read_query_result(mysql);
+}
+
+int STDCALL mysql_master_send_query(MYSQL *mysql, const char *q,
+ unsigned int length)
+{
+ MYSQL*master = mysql->master;
+ if (!length)
length = strlen(q);
- return mysql_real_query(mysql->master, q, length);
+ if (!master->net.vio && !mysql_real_connect(master,0,0,0,0,0,0,0))
+ return 1;
+ mysql->last_used_con = master;
+ return simple_command(master, COM_QUERY, q, length, 1);
}
+
/* perform query on slave */
int STDCALL mysql_slave_query(MYSQL *mysql, const char *q,
unsigned int length)
{
+ if(mysql_slave_send_query(mysql, q, length))
+ return 1;
+ return mysql_read_query_result(mysql);
+}
+
+int STDCALL mysql_slave_send_query(MYSQL *mysql, const char *q,
+ unsigned int length)
+{
MYSQL* last_used_slave, *slave_to_use = 0;
if((last_used_slave = mysql->last_used_slave))
@@ -1019,12 +1046,16 @@ int STDCALL mysql_slave_query(MYSQL *mysql, const char *q,
/* next_slave is always safe to use - we have a circular list of slaves
if there are no slaves, mysql->next_slave == mysql
*/
- mysql->last_used_slave = slave_to_use;
+ mysql->last_used_con = mysql->last_used_slave = slave_to_use;
if(!length)
length = strlen(q);
- return mysql_real_query(slave_to_use, q, length);
+ if(!slave_to_use->net.vio && !mysql_real_connect(slave_to_use, 0,0,0,
+ 0,0,0,0))
+ return 1;
+ return simple_command(slave_to_use, COM_QUERY, q, length, 1);
}
+
/* enable/disable parsing of all queries to decide
if they go on master or slave */
void STDCALL mysql_enable_rpl_parse(MYSQL* mysql)
@@ -1060,40 +1091,100 @@ int STDCALL mysql_reads_from_master_enabled(MYSQL* mysql)
return !(mysql->options.no_master_reads);
}
+/* We may get an error while doing replication internals.
+ In this case, we add a special explanation to the original
+ error
+*/
+static inline void expand_error(MYSQL* mysql, int error)
+{
+ char tmp[MYSQL_ERRMSG_SIZE];
+ char* p, *tmp_end;
+ tmp_end = strnmov(tmp, mysql->net.last_error, MYSQL_ERRMSG_SIZE);
+ p = strnmov(mysql->net.last_error, ER(error), MYSQL_ERRMSG_SIZE);
+ memcpy(p, tmp, tmp_end - tmp);
+ mysql->net.last_errno = error;
+}
+
/* This function assumes we have just called SHOW SLAVE STATUS and have
read the given result and row
*/
static inline int get_master(MYSQL* mysql, MYSQL_RES* res, MYSQL_ROW row)
{
MYSQL* master;
- if(mysql_num_rows(res) < 3)
+ if(mysql_num_fields(res) < 3)
return 1; /* safety */
- if(!(master = mysql_init(0)))
- return 1;
-
+
/* use the same username and password as the original connection */
- master->user = mysql->user;
- master->passwd = mysql->passwd;
- master->host = row[0];
- master->port = atoi(row[2]);
- master->db = mysql->db;
+ if(!(master = spawn_init(mysql, row[0], atoi(row[2]), 0, 0)))
+ return 1;
mysql->master = master;
return 0;
}
+/* assuming we already know that mysql points to a master connection,
+ retrieve all the slaves
+*/
static inline int get_slaves_from_master(MYSQL* mysql)
{
- if(!mysql->net.vio && !mysql_real_connect(mysql, mysql->host, mysql->user,
- mysql->passwd, mysql->db, mysql->port,
- mysql->unix_socket, mysql->client_flag))
+ MYSQL_RES* res = 0;
+ MYSQL_ROW row;
+ int error = 1;
+ int has_auth_info;
+ if (!mysql->net.vio && !mysql_real_connect(mysql,0,0,0,0,0,0,0))
+ {
+ expand_error(mysql, CR_PROBE_MASTER_CONNECT);
return 1;
- /* more to be written */
- return 0;
+ }
+
+ if (mysql_query(mysql, "SHOW SLAVE HOSTS") ||
+ !(res = mysql_store_result(mysql)))
+ {
+ expand_error(mysql, CR_PROBE_SLAVE_HOSTS);
+ return 1;
+ }
+
+ switch (mysql_num_fields(res))
+ {
+ case 3: has_auth_info = 0; break;
+ case 5: has_auth_info = 1; break;
+ default:
+ goto err;
+ }
+
+ while ((row = mysql_fetch_row(res)))
+ {
+ MYSQL* slave;
+ const char* tmp_user, *tmp_pass;
+
+ if (has_auth_info)
+ {
+ tmp_user = row[3];
+ tmp_pass = row[4];
+ }
+ else
+ {
+ tmp_user = mysql->user;
+ tmp_pass = mysql->passwd;
+ }
+
+ if(!(slave = spawn_init(mysql, row[1], atoi(row[2]),
+ tmp_user, tmp_pass)))
+ goto err;
+
+ /* Now add slave into the circular linked list */
+ slave->next_slave = mysql->next_slave;
+ mysql->next_slave = slave;
+ }
+ error = 0;
+err:
+ if(res)
+ mysql_free_result(res);
+ return error;
}
int STDCALL mysql_rpl_probe(MYSQL* mysql)
{
- MYSQL_RES* res;
+ MYSQL_RES* res = 0;
MYSQL_ROW row;
int error = 1;
/* first determine the replication role of the server we connected to
@@ -1103,30 +1194,34 @@ int STDCALL mysql_rpl_probe(MYSQL* mysql)
a non-empty master host. However, it is more reliable to check
for empty master than whether the slave thread is actually running
*/
- if(mysql_query(mysql, "SHOW SLAVE STATUS") ||
+ if (mysql_query(mysql, "SHOW SLAVE STATUS") ||
!(res = mysql_store_result(mysql)))
+ {
+ expand_error(mysql, CR_PROBE_SLAVE_STATUS);
return 1;
-
- if(!(row = mysql_fetch_row(res)))
+ }
+
+ if (!(row = mysql_fetch_row(res)))
goto err;
/* check master host for emptiness/NULL */
- if(row[0] && *(row[0]))
+ if (row[0] && *(row[0]))
{
/* this is a slave, ask it for the master */
- if(get_master(mysql, res, row) || get_slaves_from_master(mysql))
+ if (get_master(mysql, res, row) || get_slaves_from_master(mysql))
goto err;
}
else
{
mysql->master = mysql;
- if(get_slaves_from_master(mysql))
+ if (get_slaves_from_master(mysql))
goto err;
}
error = 0;
err:
- mysql_free_result(res);
+ if(res)
+ mysql_free_result(res);
return error;
}
@@ -1141,7 +1236,8 @@ err:
mysql_master_query() or mysql_slave_query() explicitly in the place
where we have made the wrong decision
*/
-int STDCALL mysql_query_goes_to_master(const char* q, int len)
+enum mysql_rpl_type
+STDCALL mysql_rpl_query_type(const char* q, int len)
{
const char* q_end;
q_end = (len) ? q + len : strend(q);
@@ -1156,9 +1252,17 @@ int STDCALL mysql_query_goes_to_master(const char* q, int len)
case 'l': /* lock tables or load data infile */
case 'd': /* drop or delete */
case 'a': /* alter */
- return 1;
+ return MYSQL_RPL_MASTER;
+ case 'c': /* create or check */
+ return tolower(q[1]) == 'h' ? MYSQL_RPL_ADMIN : MYSQL_RPL_MASTER ;
+ case 's': /* select or show */
+ return tolower(q[1] == 'h') ? MYSQL_RPL_ADMIN : MYSQL_RPL_SLAVE;
+ case 'f': /* flush */
+ case 'r': /* repair */
+ case 'g': /* grant */
+ return MYSQL_RPL_ADMIN;
default:
- return 0;
+ return MYSQL_RPL_SLAVE;
}
}
@@ -1184,8 +1288,12 @@ mysql_init(MYSQL *mysql)
else
bzero((char*) (mysql),sizeof(*(mysql)));
mysql->options.connect_timeout=CONNECT_TIMEOUT;
- mysql->next_slave = mysql->master = mysql;
+ mysql->last_used_con = mysql->next_slave = mysql->master = mysql;
mysql->last_used_slave = 0;
+ /* By default, we are a replication pivot. The caller must reset it
+ after we return if this is not the case.
+ */
+ mysql->rpl_pivot = 1;
#if defined(SIGPIPE) && defined(THREAD)
if (!((mysql)->client_flag & CLIENT_IGNORE_SIGPIPE))
(void) signal(SIGPIPE,pipe_sig_handler);
@@ -1723,6 +1831,9 @@ mysql_real_connect(MYSQL *mysql,const char *host, const char *user,
mysql->reconnect=reconnect;
}
+ if (mysql->options.rpl_probe && mysql_rpl_probe(mysql))
+ goto error;
+
DBUG_PRINT("exit",("Mysql handler: %lx",mysql));
reset_sigpipe(mysql);
DBUG_RETURN(mysql);
@@ -1864,6 +1975,21 @@ mysql_close(MYSQL *mysql)
/* ((VioConnectorFd*)(mysql->connector_fd))->delete();
mysql->connector_fd = 0;*/
#endif /* HAVE_OPENSSL */
+
+ /* free/close slave list */
+ if (mysql->rpl_pivot)
+ {
+ MYSQL* tmp;
+ for (tmp = mysql->next_slave; tmp != mysql; )
+ {
+ /* trick to avoid following freed pointer */
+ MYSQL* tmp1 = tmp->next_slave;
+ mysql_close(tmp);
+ tmp = tmp1;
+ }
+ }
+ if(mysql != mysql->master)
+ mysql_close(mysql->master);
if (mysql->free_me)
my_free((gptr) mysql,MYF(0));
}
@@ -1882,6 +2008,67 @@ mysql_query(MYSQL *mysql, const char *query)
return mysql_real_query(mysql,query, (uint) strlen(query));
}
+static MYSQL* spawn_init(MYSQL* parent, const char* host,
+ unsigned int port,
+ const char* user,
+ const char* passwd)
+{
+ MYSQL* child;
+ if (!(child = mysql_init(0)))
+ return 0;
+
+ child->options.user = my_strdup((user) ? user :
+ (parent->user ? parent->user :
+ parent->options.user), MYF(0));
+ child->options.password = my_strdup((passwd) ? passwd : (parent->passwd ?
+ parent->passwd :
+ parent->options.password), MYF(0));
+ child->options.port = port;
+ child->options.host = my_strdup((host) ? host : (parent->host ?
+ parent->host :
+ parent->options.host), MYF(0));
+ if(parent->db)
+ child->options.db = my_strdup(parent->db, MYF(0));
+ else if(parent->options.db)
+ child->options.db = my_strdup(parent->options.db, MYF(0));
+
+ child->options.rpl_parse = child->options.rpl_probe = child->rpl_pivot = 0;
+
+ return child;
+}
+
+
+int
+STDCALL mysql_set_master(MYSQL* mysql, const char* host,
+ unsigned int port,
+ const char* user,
+ const char* passwd)
+{
+ if (mysql->master != mysql && !mysql->master->rpl_pivot)
+ mysql_close(mysql->master);
+ if(!(mysql->master = spawn_init(mysql, host, port, user, passwd)))
+ return 1;
+ mysql->master->rpl_pivot = 0;
+ mysql->master->options.rpl_parse = 0;
+ mysql->master->options.rpl_probe = 0;
+ return 0;
+}
+
+int
+STDCALL mysql_add_slave(MYSQL* mysql, const char* host,
+ unsigned int port,
+ const char* user,
+ const char* passwd)
+{
+ MYSQL* slave;
+ if(!(slave = spawn_init(mysql, host, port, user, passwd)))
+ return 1;
+ slave->next_slave = mysql->next_slave;
+ mysql->next_slave = slave;
+ return 0;
+}
+
+
/*
Send the query and return so we can do something else.
Needs to be followed by mysql_read_query_result() when we want to
@@ -1891,6 +2078,20 @@ mysql_query(MYSQL *mysql, const char *query)
int STDCALL
mysql_send_query(MYSQL* mysql, const char* query, uint length)
{
+ if (mysql->options.rpl_parse && mysql->rpl_pivot)
+ {
+ switch (mysql_rpl_query_type(query, length))
+ {
+ case MYSQL_RPL_MASTER:
+ return mysql_master_send_query(mysql, query, length);
+ case MYSQL_RPL_SLAVE:
+ return mysql_slave_send_query(mysql, query, length);
+ case MYSQL_RPL_ADMIN: /*fall through */
+ }
+ }
+
+ mysql->last_used_con = mysql;
+
return simple_command(mysql, COM_QUERY, query, length, 1);
}
@@ -1902,6 +2103,11 @@ int STDCALL mysql_read_query_result(MYSQL *mysql)
uint length;
DBUG_ENTER("mysql_read_query_result");
+ /* read from the connection which we actually used, which
+ could differ from the original connection if we have slaves
+ */
+ mysql = mysql->last_used_con;
+
if ((length = net_safe_read(mysql)) == packet_error)
DBUG_RETURN(-1);
free_old_query(mysql); /* Free old result */
@@ -1948,7 +2154,8 @@ mysql_real_query(MYSQL *mysql, const char *query, uint length)
DBUG_ENTER("mysql_real_query");
DBUG_PRINT("enter",("handle: %lx",mysql));
DBUG_PRINT("query",("Query = \"%s\"",query));
- if (simple_command(mysql,COM_QUERY,query,length,1))
+
+ if (mysql_send_query(mysql,query,length))
DBUG_RETURN(-1);
DBUG_RETURN(mysql_read_query_result(mysql));
}
@@ -2020,6 +2227,9 @@ mysql_store_result(MYSQL *mysql)
MYSQL_RES *result;
DBUG_ENTER("mysql_store_result");
+ /* read from the actually used connection */
+ mysql = mysql->last_used_con;
+
if (!mysql->fields)
DBUG_RETURN(0);
if (mysql->status != MYSQL_STATUS_GET_RESULT)
@@ -2072,6 +2282,8 @@ mysql_use_result(MYSQL *mysql)
MYSQL_RES *result;
DBUG_ENTER("mysql_use_result");
+ mysql = mysql->last_used_con;
+
if (!mysql->fields)
DBUG_RETURN(0);
if (mysql->status != MYSQL_STATUS_GET_RESULT)
@@ -2525,32 +2737,32 @@ uint STDCALL mysql_field_tell(MYSQL_RES *res)
unsigned int STDCALL mysql_field_count(MYSQL *mysql)
{
- return mysql->field_count;
+ return mysql->last_used_con->field_count;
}
my_ulonglong STDCALL mysql_affected_rows(MYSQL *mysql)
{
- return (mysql)->affected_rows;
+ return mysql->last_used_con->affected_rows;
}
my_ulonglong STDCALL mysql_insert_id(MYSQL *mysql)
{
- return (mysql)->insert_id;
+ return mysql->last_used_con->insert_id;
}
uint STDCALL mysql_errno(MYSQL *mysql)
{
- return (mysql)->net.last_errno;
+ return mysql->net.last_errno;
}
char * STDCALL mysql_error(MYSQL *mysql)
{
- return (mysql)->net.last_error;
+ return mysql->net.last_error;
}
char *STDCALL mysql_info(MYSQL *mysql)
{
- return (mysql)->info;
+ return mysql->info;
}
ulong STDCALL mysql_thread_id(MYSQL *mysql)