From 1d05b8998070a23dd6f49a4885fe09f4cbc1fb8c Mon Sep 17 00:00:00 2001 From: "sasha@mysql.sashanet.com" <> Date: Sat, 2 Jun 2001 20:02:43 -0600 Subject: [PATCH] some code for replication awareness support - this is a temporary commit - it compiles, and test suite still passes, but it is not finished . The purpose of the commit is backup/code sharing/code review --- include/mysql.h | 41 ++++++++++ libmysql/libmysql.c | 183 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 223 insertions(+), 1 deletion(-) diff --git a/include/mysql.h b/include/mysql.h index b5d918a98a..5d06f96d22 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -127,6 +127,15 @@ typedef struct st_mysql_data { struct st_mysql_options { unsigned int connect_timeout,client_flag; my_bool compress,named_pipe; + my_bool rpl_probe; /* on connect, find out the replication + role of the server, and establish connections + to all the peers */ + my_bool rpl_parse; /* each call to mysql_real_query() will parse + it to tell if it is a read or a write, and + direct it to the slave or the master */ + my_bool no_master_reads; /* if set, never read from + a master,only from slave, when doing + a read that is replication-aware */ unsigned int port; char *host,*init_command,*user,*password,*unix_socket,*db; char *my_cnf_file,*my_cnf_group, *charset_dir, *charset_name; @@ -168,6 +177,13 @@ typedef struct st_mysql { char scramble_buff[9]; struct charset_info_st *charset; unsigned int server_language; + + /* pointers to the master, and the next slave + connections, points to itself if lone connection */ + struct st_mysql* master, *next_slave; + + struct st_mysql* last_used_slave; /* needed for round-robin slave pick */ + my_bool is_slave; /* will be false for a lone connection */ } MYSQL; @@ -242,6 +258,31 @@ int STDCALL mysql_send_query(MYSQL *mysql, const char *q, int STDCALL mysql_read_query_result(MYSQL *mysql); int STDCALL mysql_real_query(MYSQL *mysql, const char *q, unsigned int length); +/* perform query on master */ +int STDCALL mysql_master_query(MYSQL *mysql, const char *q, + unsigned int length); +/* perform query on slave */ +int STDCALL mysql_slave_query(MYSQL *mysql, const char *q, + unsigned int length); + +/* enable/disable parsing of all queries to decide + if they go on master or slave */ +void STDCALL mysql_enable_rpl_parse(MYSQL* mysql); +void STDCALL mysql_disable_rpl_parse(MYSQL* mysql); +/* get the value of the parse flag */ +int STDCALL mysql_rpl_parse_enabled(MYSQL* mysql); + +/* enable/disable reads from master */ +void STDCALL mysql_enable_reads_from_master(MYSQL* mysql); +void STDCALL mysql_disable_reads_from_master(MYSQL* mysql); +/* get the value of the master read flag */ +int STDCALL mysql_reads_from_master_enabled(MYSQL* mysql); + +int STDCALL mysql_query_goes_to_master(const char* q, int len); + +/* discover the master and its slaves */ +int STDCALL mysql_rpl_probe(MYSQL* mysql); + int STDCALL mysql_create_db(MYSQL *mysql, const char *DB); int STDCALL mysql_drop_db(MYSQL *mysql, const char *DB); int STDCALL mysql_shutdown(MYSQL *mysql); diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index d9e7b28d46..c45dce981a 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -678,7 +678,8 @@ static const char *default_options[]= "init-command", "host", "database", "debug", "return-found-rows", "ssl-key" ,"ssl-cert" ,"ssl-ca" ,"ssl-capath", "character-set-dir", "default-character-set", "interactive-timeout", - "connect_timeout", + "connect_timeout", "replication-probe", "enable-reads-from-master", + "repl-parse-query", NullS }; @@ -812,6 +813,15 @@ static void mysql_read_default_options(struct st_mysql_options *options, case 19: /* Interactive-timeout */ options->client_flag|=CLIENT_INTERACTIVE; break; + case 21: /* replication probe */ + options->rpl_probe = 1; + break; + case 22: /* enable-reads-from-master */ + options->rpl_parse = 1; + break; + case 23: /* repl-parse-query */ + options->no_master_reads = 0; + break; default: DBUG_PRINT("warning",("unknown option: %s",option[0])); } @@ -987,6 +997,175 @@ read_one_row(MYSQL *mysql,uint fields,MYSQL_ROW row, ulong *lengths) return 0; } +/* perform query on master */ +int STDCALL mysql_master_query(MYSQL *mysql, const char *q, + unsigned int length) +{ + if(!length) + length = strlen(q); + return mysql_real_query(mysql->master, q, length); +} + +/* perform query on slave */ +int STDCALL mysql_slave_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)) + slave_to_use = last_used_slave->next_slave; + else + slave_to_use = mysql->next_slave; + /* 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; + if(!length) + length = strlen(q); + return mysql_real_query(slave_to_use, q, length); +} + +/* enable/disable parsing of all queries to decide + if they go on master or slave */ +void STDCALL mysql_enable_rpl_parse(MYSQL* mysql) +{ + mysql->options.rpl_parse = 1; +} + +void STDCALL mysql_disable_rpl_parse(MYSQL* mysql) +{ + mysql->options.rpl_parse = 0; +} + +/* get the value of the parse flag */ +int STDCALL mysql_rpl_parse_enabled(MYSQL* mysql) +{ + return mysql->options.rpl_parse; +} + +/* enable/disable reads from master */ +void STDCALL mysql_enable_reads_from_master(MYSQL* mysql) +{ + mysql->options.no_master_reads = 0; +} + +void STDCALL mysql_disable_reads_from_master(MYSQL* mysql) +{ + mysql->options.no_master_reads = 1; +} + +/* get the value of the master read flag */ +int STDCALL mysql_reads_from_master_enabled(MYSQL* mysql) +{ + return !(mysql->options.no_master_reads); +} + +/* 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) + 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; + mysql->master = master; + return 0; +} + +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)) + return 1; + /* more to be written */ + return 0; +} + +int STDCALL mysql_rpl_probe(MYSQL* mysql) +{ + MYSQL_RES* res; + MYSQL_ROW row; + int error = 1; + /* first determine the replication role of the server we connected to + the most reliable way to do this is to run SHOW SLAVE STATUS and see + if we have a non-empty master host. This is still not fool-proof - + it is not a sin to have a master that has a dormant slave thread with + 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") || + !(res = mysql_store_result(mysql))) + return 1; + + if(!(row = mysql_fetch_row(res))) + goto err; + + /* check master host for emptiness/NULL */ + 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)) + goto err; + } + else + { + mysql->master = mysql; + if(get_slaves_from_master(mysql)) + goto err; + } + + error = 0; +err: + mysql_free_result(res); + return error; +} + + +/* make a not so fool-proof decision on where the query should go, to + the master or the slave. Ideally the user should always make this + decision himself with mysql_master_query() or mysql_slave_query(). + However, to be able to more easily port the old code, we support the + option of an educated guess - this should work for most applications, + however, it may make the wrong decision in some particular cases. If + that happens, the user would have to change the code to call + 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) +{ + const char* q_end; + q_end = (len) ? q + len : strend(q); + for(; q < q_end; ++q) + { + char c; + if(isalpha(c=*q)) + switch(tolower(c)) + { + case 'i': /* insert */ + case 'u': /* update or unlock tables */ + case 'l': /* lock tables or load data infile */ + case 'd': /* drop or delete */ + case 'a': /* alter */ + return 1; + default: + return 0; + } + } + + return 0; +} + + /**************************************************************************** ** Init MySQL structure or allocate one ****************************************************************************/ @@ -1005,6 +1184,8 @@ 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_slave = 0; #if defined(SIGPIPE) && defined(THREAD) if (!((mysql)->client_flag & CLIENT_IGNORE_SIGPIPE)) (void) signal(SIGPIPE,pipe_sig_handler); -- 2.30.9