diff --git a/include/ma_common.h b/include/ma_common.h index d99e81e21..ff238e8ab 100644 --- a/include/ma_common.h +++ b/include/ma_common.h @@ -84,6 +84,8 @@ struct st_mysql_options_extension { char *restricted_auth; char *rpl_host; unsigned short rpl_port; + void (*status_callback)(void *ptr, enum enum_mariadb_status_info type, ...); + void *status_data; }; typedef struct st_connection_handler diff --git a/include/mariadb_com.h b/include/mariadb_com.h index 3d7bde67e..58f44c4db 100644 --- a/include/mariadb_com.h +++ b/include/mariadb_com.h @@ -306,6 +306,13 @@ enum enum_mysql_set_option MYSQL_OPTION_MULTI_STATEMENTS_OFF }; +/* for status callback function */ +enum enum_mariadb_status_info +{ + STATUS_TYPE= 0, + SESSION_TRACK_TYPE +}; + enum enum_session_state_type { SESSION_TRACK_SYSTEM_VARIABLES= 0, diff --git a/include/mysql.h b/include/mysql.h index d87a4d66e..357a7a293 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -249,7 +249,8 @@ extern const char *SQLSTATE_UNKNOWN; MARIADB_OPT_IO_WAIT, MARIADB_OPT_SKIP_READ_RESPONSE, MARIADB_OPT_RESTRICTED_AUTH, - MARIADB_OPT_RPL_REGISTER_REPLICA + MARIADB_OPT_RPL_REGISTER_REPLICA, + MARIADB_OPT_STATUS_CALLBACK }; enum mariadb_value { diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c index 5006e02b6..9066908d7 100644 --- a/libmariadb/mariadb_lib.c +++ b/libmariadb/mariadb_lib.c @@ -1419,6 +1419,10 @@ mysql_real_connect(MYSQL *mysql, const char *host, const char *user, if (!mysql->methods) mysql->methods= &MARIADB_DEFAULT_METHODS; + /* set default */ + if (!mysql->options.extension || !mysql->options.extension->status_callback) + mysql_optionsv(mysql, MARIADB_OPT_STATUS_CALLBACK, NULL, NULL); + /* if host contains a semicolon, we need to parse connection string */ if (host && strchr(host, ';')) { @@ -2430,19 +2434,97 @@ mysql_query(MYSQL *mysql, const char *query) finish processing it. */ + + int STDCALL mysql_send_query(MYSQL* mysql, const char* query, unsigned long length) { return ma_simple_command(mysql, COM_QUERY, query, length, 1,0); } +void ma_save_session_track_info(void *ptr, enum enum_mariadb_status_info type, ...) +{ + MYSQL *mysql= (MYSQL *)ptr; + enum enum_session_state_type track_type; + va_list ap; + + DBUG_ASSERT(mysql != NULL); + + /* We only handle SESSION_TRACK_TYPE here */ + if (type != SESSION_TRACK_TYPE) + return; + + va_start(ap, type); + + track_type= va_arg(ap, enum enum_session_state_type); + + switch (track_type) { + case SESSION_TRACK_SCHEMA: + case SESSION_TRACK_STATE_CHANGE: + case SESSION_TRACK_TRANSACTION_CHARACTERISTICS: + case SESSION_TRACK_TRANSACTION_STATE: + case SESSION_TRACK_GTIDS: + case SESSION_TRACK_SYSTEM_VARIABLES: + { + LIST *session_item; + MYSQL_LEX_STRING *str; + char *tmp; + MARIADB_CONST_STRING *data1= va_arg(ap, MARIADB_CONST_STRING *); + + if (!(session_item= ma_multi_malloc(0, + &session_item, sizeof(LIST), + &str, sizeof(MYSQL_LEX_STRING), + &tmp, data1->length, + NULL))) + goto mem_error; + + str->str= tmp; + memcpy(str->str, data1->str, data1->length); + str->length= data1->length; + session_item->data= str; + mysql->extension->session_state[track_type].list= list_add(mysql->extension->session_state[track_type].list, + session_item); + if (track_type == SESSION_TRACK_SYSTEM_VARIABLES) + { + MARIADB_CONST_STRING *data2= va_arg(ap, MARIADB_CONST_STRING *); + if (!(session_item= ma_multi_malloc(0, + &session_item, sizeof(LIST), + &str, sizeof(MYSQL_LEX_STRING), + &tmp, data2->length, + NULL))) + goto mem_error; + + str->str= tmp; + memcpy(str->str, data2->str, data2->length); + str->length= data2->length; + session_item->data= str; + mysql->extension->session_state[track_type].list= list_add(mysql->extension->session_state[track_type].list, + session_item); + } + } + break; + } + return; + +mem_error: + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return; +} + int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length) { uchar *end= mysql->net.read_pos+length; size_t item_len; + unsigned int last_server_status= mysql->server_status; mysql->affected_rows= net_field_length_ll(&pos); mysql->insert_id= net_field_length_ll(&pos); mysql->server_status=uint2korr(pos); + + /* callback */ + if (mysql->options.extension->status_callback && + mysql->server_status != last_server_status) + mysql->options.extension->status_callback(mysql->options.extension->status_data, + STATUS_TYPE, mysql->server_status); pos+=2; mysql->warning_count=uint2korr(pos); pos+=2; @@ -2466,8 +2548,6 @@ int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length) int i; if (pos < end) { - LIST *session_item; - MYSQL_LEX_STRING *str= NULL; enum enum_session_state_type si_type; uchar *old_pos= pos; @@ -2483,7 +2563,7 @@ int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length) while (pos < end) { size_t plen; - char *data; + MYSQL_LEX_STRING data1, data2; si_type= (enum enum_session_state_type)net_field_length(&pos); switch(si_type) { @@ -2505,55 +2585,52 @@ int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length) plen= net_field_length(&pos); if (pos + plen > end) goto corrupted; - if (!(session_item= ma_multi_malloc(0, - &session_item, sizeof(LIST), - &str, sizeof(MYSQL_LEX_STRING), - &data, plen, - NULL))) - goto oom; - str->length= plen; - str->str= data; - memcpy(str->str, (char *)pos, plen); - pos+= plen; - session_item->data= str; - mysql->extension->session_state[si_type].list= list_add(mysql->extension->session_state[si_type].list, session_item); + data1.str= (char *)pos; + data1.length= plen; + if (si_type != SESSION_TRACK_SYSTEM_VARIABLES) + { + mysql->options.extension->status_callback(mysql->options.extension->status_data, + SESSION_TRACK_TYPE, si_type, + &data1); + if (mysql->net.last_errno) + goto oom; + } + pos+= plen; /* in case schema has changed, we have to update mysql->db */ if (si_type == SESSION_TRACK_SCHEMA) { free(mysql->db); mysql->db= malloc(plen + 1); - memcpy(mysql->db, str->str, plen); - mysql->db[plen]= 0; + memcpy(mysql->db, data1.str, data1.length); + mysql->db[data1.length]= 0; } else if (si_type == SESSION_TRACK_SYSTEM_VARIABLES) { my_bool set_charset= 0; /* make sure that we update charset in case it has changed */ - if (!strncmp(str->str, "character_set_client", str->length)) + if (!strncmp(data1.str, "character_set_client", plen)) set_charset= 1; plen= net_field_length(&pos); if (pos + plen > end) goto corrupted; - if (!(session_item= ma_multi_malloc(0, - &session_item, sizeof(LIST), - &str, sizeof(MYSQL_LEX_STRING), - &data, plen, - NULL))) - goto oom; - str->length= plen; - str->str= data; - memcpy(str->str, (char *)pos, plen); + data2.str= (char *)pos; + data2.length= plen; + + mysql->options.extension->status_callback(mysql->options.extension->status_data, + SESSION_TRACK_TYPE, si_type, + &data1, &data2); + if (mysql->net.last_errno) + goto oom; + pos+= plen; - session_item->data= str; - mysql->extension->session_state[si_type].list= list_add(mysql->extension->session_state[si_type].list, session_item); - if (set_charset && str->length < CHARSET_NAME_LEN && - strncmp(mysql->charset->csname, str->str, str->length) != 0) + if (set_charset && plen < CHARSET_NAME_LEN && + strncmp(mysql->charset->csname, data2.str, data2.length) != 0) { char cs_name[CHARSET_NAME_LEN]; const MARIADB_CHARSET_INFO *cs_info; - memcpy(cs_name, str->str, str->length); - cs_name[str->length]= 0; + memcpy(cs_name, data2.str, data2.length); + cs_name[plen]= 0; if ((cs_info = mysql_find_charset_name(cs_name))) mysql->charset= cs_info; } @@ -3663,6 +3740,19 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) OPT_SET_EXTENDED_VALUE(&mysql->options, rpl_port, (ushort)arg2); } break; + case MARIADB_OPT_STATUS_CALLBACK: + { + void *arg2= va_arg(ap, void *); + if (arg1) + { + OPT_SET_EXTENDED_VALUE(&mysql->options, status_callback, arg1); + OPT_SET_EXTENDED_VALUE(&mysql->options, status_data, arg2); + } else { + OPT_SET_EXTENDED_VALUE(&mysql->options, status_callback, ma_save_session_track_info); + OPT_SET_EXTENDED_VALUE(&mysql->options, status_data, mysql); + } + } + break; default: va_end(ap); SET_CLIENT_ERROR(mysql, CR_NOT_IMPLEMENTED, SQLSTATE_UNKNOWN, 0); diff --git a/unittest/libmariadb/connection.c b/unittest/libmariadb/connection.c index 93f7e1dbf..1a35cc1c6 100644 --- a/unittest/libmariadb/connection.c +++ b/unittest/libmariadb/connection.c @@ -2151,7 +2151,109 @@ static int test_conc365_reconnect(MYSQL *my) return rc; } +struct st_callback { + char autocommit; + char database[64]; + char charset[64]; +}; + +void my_status_callback(void *ptr, enum enum_mariadb_status_info type, ...) +{ + va_list ap; + struct st_callback *data= (struct st_callback *)ptr; + va_start(ap, type); + + switch(type) { + case STATUS_TYPE: + { + int status= va_arg(ap, int); + data->autocommit= status & SERVER_STATUS_AUTOCOMMIT; + } + break; + case SESSION_TRACK_TYPE: + { + enum enum_session_state_type track_type= va_arg(ap, enum enum_session_state_type); + switch (track_type) { + case SESSION_TRACK_SCHEMA: + { + MARIADB_CONST_STRING *str= va_arg(ap, MARIADB_CONST_STRING *); + strncpy(data->database, str->str, str->length); + } + break; + case SESSION_TRACK_SYSTEM_VARIABLES: + { + MARIADB_CONST_STRING *key= va_arg(ap, MARIADB_CONST_STRING *); + MARIADB_CONST_STRING *val= va_arg(ap, MARIADB_CONST_STRING *); + + if (!strncmp(key->str, "character_set_client", key->length)) + { + strncpy(data->charset, val->str, val->length); + } + } + break; + default: + break; + } + } + default: + break; + } + va_end(ap); +} + +static int test_status_callback(MYSQL *my __attribute__((unused))) +{ + MYSQL *mysql= mysql_init(NULL); + char tmp[64]; + int rc; + struct st_callback data= {0,"", ""}; + + rc= mysql_optionsv(mysql, MARIADB_OPT_STATUS_CALLBACK, my_status_callback, &data); + + if (!my_test_connect(mysql, hostname, username, + password, NULL, port, socketname, 0)) + { + diag("error1: %s", mysql_error(mysql)); + return FAIL; + } + + rc= mysql_autocommit(mysql, 0); + check_mysql_rc(rc, mysql); + rc= mysql_autocommit(mysql, 1); + check_mysql_rc(rc, mysql); + + if (!data.autocommit) + { + diag("autocommit not set"); + return FAIL; + } + diag("-------------------------"); + + sprintf(tmp, "USE %s", schema); + rc= mysql_query(mysql, tmp); + check_mysql_rc(rc, mysql); + + if (strcmp(data.database, schema)) + { + diag("Expected database: %s instead of %s", schema, data.database); + return FAIL; + } + + rc= mysql_query(mysql, "SET NAMES latin1"); + check_mysql_rc(rc, mysql); + + if (strcmp(data.charset, "latin1")) + { + diag("Expected charset latin1 instead of %s", data.charset); + return FAIL; + } + + mysql_close(mysql); + return OK; +} + struct my_tests_st my_tests[] = { + {"test_status_callback", test_status_callback, TEST_CONNECTION_NONE, 0, NULL, NULL}, {"test_conc365", test_conc365, TEST_CONNECTION_NONE, 0, NULL, NULL}, {"test_conc365_reconnect", test_conc365_reconnect, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"test_conn_str", test_conn_str, TEST_CONNECTION_NONE, 0, NULL, NULL},