diff options
-rw-r--r-- | include/errmsg.h | 4 | ||||
-rw-r--r-- | include/mysql.h | 52 | ||||
-rw-r--r-- | include/sql_common.h | 1 | ||||
-rw-r--r-- | libmysql/client_settings.h | 1 | ||||
-rw-r--r-- | libmysql/errmsg.c | 16 | ||||
-rw-r--r-- | libmysql/libmysql.c | 579 | ||||
-rw-r--r-- | sql-common/client.c | 98 |
7 files changed, 510 insertions, 241 deletions
diff --git a/include/errmsg.h b/include/errmsg.h index 8f3ddfa9796..16f220a7ee2 100644 --- a/include/errmsg.h +++ b/include/errmsg.h @@ -67,7 +67,7 @@ extern const char *client_errors[]; /* Error messages */ /* new 4.1 error codes */ #define CR_NULL_POINTER 2028 #define CR_NO_PREPARE_STMT 2029 -#define CR_NOT_ALL_PARAMS_BOUND 2030 +#define CR_PARAMS_NOT_BOUND 2030 #define CR_DATA_TRUNCATED 2031 #define CR_NO_PARAMETERS_EXISTS 2032 #define CR_INVALID_PARAMETER_NO 2033 @@ -87,3 +87,5 @@ extern const char *client_errors[]; /* Error messages */ #define CR_CONN_UNKNOW_PROTOCOL 2046 #define CR_INVALID_CONN_HANDLE 2047 #define CR_SECURE_AUTH 2048 +#define CR_FETCH_CANCELLED 2049 +#define CR_NO_DATA 2050 diff --git a/include/mysql.h b/include/mysql.h index 2b4153bb140..1bcc2a58534 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -256,6 +256,11 @@ typedef struct st_mysql LIST *stmts; /* list of all statements */ const struct st_mysql_methods *methods; void *thd; + /* + Points to boolean flag in MYSQL_RES or MYSQL_STMT. We set this flag + from mysql_stmt_close if close had to cancel result set of this object. + */ + my_bool *unbuffered_fetch_owner; } MYSQL; typedef struct st_mysql_res { @@ -270,6 +275,8 @@ typedef struct st_mysql_res { MYSQL_ROW row; /* If unbuffered read */ MYSQL_ROW current_row; /* buffer to current row */ my_bool eof; /* Used by mysql_fetch_row */ + /* mysql_stmt_close() had to cancel this result */ + my_bool unbuffered_fetch_cancelled; const struct st_mysql_methods *methods; } MYSQL_RES; @@ -479,7 +486,11 @@ my_bool STDCALL mysql_read_query_result(MYSQL *mysql); */ /* statement state */ -enum PREP_STMT_STATE { MY_ST_UNKNOWN, MY_ST_PREPARE, MY_ST_EXECUTE }; +enum enum_mysql_stmt_state +{ + MYSQL_STMT_INIT_DONE= 1, MYSQL_STMT_PREPARE_DONE, MYSQL_STMT_EXECUTE_DONE, + MYSQL_STMT_FETCH_DONE +}; /* client TIME structure to handle TIME, DATE and TIMESTAMP directly in @@ -525,31 +536,34 @@ typedef struct st_mysql_bind /* statement handler */ typedef struct st_mysql_stmt { - MYSQL *mysql; /* connection handle */ - MYSQL_BIND *params; /* input parameters */ - MYSQL_RES *result; /* resultset */ - MYSQL_BIND *bind; /* row binding */ - MYSQL_FIELD *fields; /* prepare meta info */ + MEM_ROOT mem_root; /* root allocations */ LIST list; /* list to keep track of all stmts */ - unsigned char *current_row; /* unbuffered row */ - unsigned char *last_fetched_buffer; /* last fetched column buffer */ - char *query; /* query buffer */ - MEM_ROOT mem_root; /* root allocations */ - my_ulonglong last_fetched_column; /* last fetched column */ + MYSQL *mysql; /* connection handle */ + MYSQL_BIND *params; /* input parameters */ + MYSQL_BIND *bind; /* output parameters */ + MYSQL_FIELD *fields; /* result set metadata */ + MYSQL_RES *result; /* cached result set */ /* copy of mysql->affected_rows after statement execution */ my_ulonglong affected_rows; + /* + mysql_stmt_fetch() calls this function to fetch one row (it's different + for buffered, unbuffered and cursor fetch). + */ + int (*read_row_func)(struct st_mysql_stmt *stmt, + unsigned char **row); unsigned long stmt_id; /* Id for prepared statement */ unsigned int last_errno; /* error code */ - unsigned int param_count; /* parameters count */ - unsigned int field_count; /* fields count */ - enum PREP_STMT_STATE state; /* statement state */ + unsigned int param_count; /* inpute parameters count */ + unsigned int field_count; /* number of columns in result set */ + enum enum_mysql_stmt_state state; /* statement state */ char last_error[MYSQL_ERRMSG_SIZE]; /* error message */ char sqlstate[SQLSTATE_LENGTH+1]; - my_bool long_alloced; /* flag to indicate long alloced */ - my_bool send_types_to_server; /* Types sent to server */ - my_bool param_buffers; /* param bound buffers */ - my_bool res_buffers; /* output bound buffers */ - my_bool result_buffered; /* Results buffered */ + /* Types of input parameters should be sent to server */ + my_bool send_types_to_server; + my_bool bind_param_done; /* input buffers were supplied */ + my_bool bind_result_done; /* output buffers were supplied */ + /* mysql_stmt_close() had to cancel this result */ + my_bool unbuffered_fetch_cancelled; } MYSQL_STMT; diff --git a/include/sql_common.h b/include/sql_common.h index cde53786f83..3f50008a922 100644 --- a/include/sql_common.h +++ b/include/sql_common.h @@ -25,6 +25,7 @@ extern "C" { MYSQL_FIELD *unpack_fields(MYSQL_DATA *data,MEM_ROOT *alloc,uint fields, my_bool default_value, uint server_capabilities); void free_rows(MYSQL_DATA *cur); +void flush_use_result(MYSQL *mysql); my_bool mysql_autenticate(MYSQL *mysql, const char *passwd); void free_old_query(MYSQL *mysql); void end_server(MYSQL *mysql); diff --git a/libmysql/client_settings.h b/libmysql/client_settings.h index e4475d76958..9fc8e67df27 100644 --- a/libmysql/client_settings.h +++ b/libmysql/client_settings.h @@ -22,7 +22,6 @@ extern my_string mysql_unix_port; CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION) sig_handler pipe_sig_handler(int sig __attribute__((unused))); -my_bool stmt_close(MYSQL_STMT *stmt, my_bool skip_free); void read_user_name(char *name); my_bool send_file_to_server(MYSQL *mysql, const char *filename); diff --git a/libmysql/errmsg.c b/libmysql/errmsg.c index 569267ddb37..e651c13897f 100644 --- a/libmysql/errmsg.c +++ b/libmysql/errmsg.c @@ -54,7 +54,7 @@ const char *client_errors[]= "Malformed packet", "Invalid use of null pointer", "Statement not prepared", - "Not all parameters data supplied", + "Parameters data was not supplied", "Data truncated", "No parameters exists in the statement", "Invalid parameter number", @@ -72,7 +72,9 @@ const char *client_errors[]= "Can't open shared memory. Can't send the request event to server (%lu)", "Wrong or unknown protocol", "Invalid connection handle", - "Connection using old (pre 4.1.1) authentication protocol refused (client option 'secure_auth' enabled)" + "Connection using old (pre 4.1.1) authentication protocol refused (client option 'secure_auth' enabled)", + "Row retrieval was cancelled by mysql_stmt_close() call", + "Attempt to read column without prior row fetch" }; /* Start of code added by Roberto M. Serqueira - martinsc@uol.com.br - 05.24.2001 */ @@ -110,7 +112,7 @@ const char *client_errors[]= "Malformed packet", "Invalid use of null pointer", "Statement not prepared", - "Not all parameters data supplied", + "Parameters data was not supplied", "Data truncated", "No parameters exists in the statement", "Invalid parameter number", @@ -128,7 +130,9 @@ const char *client_errors[]= "Can't open shared memory. Can't send the request event to server (%lu)", "Wrong or unknown protocol", "Invalid connection handle", - "Connection using old (pre 4.1.1) authentication protocol refused (client option 'secure_auth' enabled)" + "Connection using old (pre 4.1.1) authentication protocol refused (client option 'secure_auth' enabled)", + "Row retrieval was cancelled by mysql_stmt_close() call", + "Attempt to read column without prior row fetch" }; #else /* ENGLISH */ @@ -182,7 +186,9 @@ const char *client_errors[]= "Can't open shared memory. Can't send the request event to server (%lu)", "Wrong or unknown protocol", "Invalid connection handle", - "Connection using old (pre 4.1.1) authentication protocol refused (client option 'secure_auth' enabled)" + "Connection using old (pre 4.1.1) authentication protocol refused (client option 'secure_auth' enabled)", + "Row retrieval was cancelled by mysql_stmt_close() call", + "Attempt to read column without prior row fetch" }; #endif diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index 0f274021f20..91927e44e49 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -638,6 +638,7 @@ int cli_read_change_user_result(MYSQL *mysql, char *buff, const char *passwd) return 0; } + my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, const char *passwd, const char *db) { @@ -866,6 +867,7 @@ STDCALL mysql_set_master(MYSQL* mysql, const char* host, return 0; } + int STDCALL mysql_add_slave(MYSQL* mysql, const char* host, unsigned int port, @@ -987,6 +989,7 @@ mysql_list_tables(MYSQL *mysql, const char *wild) DBUG_RETURN (mysql_store_result(mysql)); } + MYSQL_FIELD *cli_list_fields(MYSQL *mysql) { MYSQL_DATA *query; @@ -1062,6 +1065,7 @@ mysql_list_processes(MYSQL *mysql) DBUG_RETURN(mysql_store_result(mysql)); } + #ifdef USE_OLD_FUNCTIONS int STDCALL mysql_create_db(MYSQL *mysql, const char *db) @@ -1099,6 +1103,7 @@ mysql_refresh(MYSQL *mysql,uint options) DBUG_RETURN(simple_command(mysql,COM_REFRESH,(char*) bits,1,0)); } + int STDCALL mysql_kill(MYSQL *mysql,ulong pid) { @@ -1126,6 +1131,7 @@ mysql_dump_debug_info(MYSQL *mysql) DBUG_RETURN(simple_command(mysql,COM_DEBUG,0,0,0)); } + const char *cli_read_statistics(MYSQL *mysql) { mysql->net.read_pos[mysql->packet_length]=0; /* End of stat string */ @@ -1139,6 +1145,7 @@ const char *cli_read_statistics(MYSQL *mysql) return (char*) mysql->net.read_pos; } + const char * STDCALL mysql_stat(MYSQL *mysql) { @@ -1576,6 +1583,10 @@ static my_bool my_realloc_str(NET *net, ulong length) Prepare related implementations ********************************************************************/ +static int stmt_read_row_unbuffered(MYSQL_STMT *stmt, unsigned char **row); +static int stmt_read_row_buffered(MYSQL_STMT *stmt, unsigned char **row); +static int stmt_read_row_no_data(MYSQL_STMT *stmt, unsigned char **row); + /* Read the prepared statement results .. @@ -1591,13 +1602,12 @@ static my_bool my_realloc_str(NET *net, ulong length) my_bool cli_read_prepare_result(MYSQL *mysql, MYSQL_STMT *stmt) { uchar *pos; - uint field_count; - ulong length, param_count; + uint field_count, param_count; MYSQL_DATA *fields_data; DBUG_ENTER("read_prepare_result"); mysql= mysql->last_used_con; - if ((length= net_safe_read(mysql)) == packet_error) + if (net_safe_read(mysql) == packet_error) DBUG_RETURN(1); pos= (uchar*) mysql->net.read_pos; @@ -1634,7 +1644,7 @@ MYSQL_STMT * STDCALL mysql_prepare(MYSQL *mysql, const char *query, stmt= mysql_stmt_init(mysql); if (stmt && mysql_stmt_prepare(stmt, query, query_length)) { - stmt_close(stmt, 0); + mysql_stmt_close(stmt); DBUG_RETURN(0); } DBUG_RETURN(stmt); @@ -1646,6 +1656,7 @@ MYSQL_STMT * STDCALL mysql_prepare(MYSQL *mysql, const char *query, SYNOPSIS mysql_stmt_init() mysql connection handle + RETURN VALUE statement structure upon success and NULL if out of memory @@ -1664,20 +1675,35 @@ mysql_stmt_init(MYSQL *mysql) DBUG_RETURN(0); } - init_alloc_root(&stmt->mem_root,8192,0); + init_alloc_root(&stmt->mem_root, 2048, 2048); mysql->stmts= list_add(mysql->stmts, &stmt->list); stmt->list.data= stmt; - stmt->state= MY_ST_UNKNOWN; + stmt->state= MYSQL_STMT_INIT_DONE; stmt->mysql= mysql; + stmt->read_row_func= stmt_read_row_no_data; + /* The rest of statement members was bzeroed inside malloc */ DBUG_RETURN(stmt); } /* - Prepare server side statement with query. + Prepare server side statement with query: + SYNOPSIS + mysql_stmt_prepare() + query statement to prepare + length statement length + + DESCRIPTION + - if this is a re-prepare of the statement, first close previous data + structure on the server and free old statement data + - send the query to server and get back number of placeholders, + number of columns in result set (if any), and result set metadata. + At the same time allocate memory for input and output parameters + to have less checks in mysql_stmt_bind_{param, result}. - Also update the total parameter count along with resultset - metadata information by reading from server + RETURN VALUES + 0 success + !0 error */ @@ -1687,17 +1713,44 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length) MYSQL *mysql= stmt->mysql; DBUG_ENTER("mysql_stmt_prepare"); - DBUG_ASSERT(mysql != 0); - - /* In case we reprepare this handle with another statement */ - free_root(&stmt->mem_root, MYF(0)); - - /* stmt->query is never used yet */ - if (!(stmt->query= strmake_root(&stmt->mem_root, query, length))) + if (!mysql) { - set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate); + /* mysql can be reset in mysql_close called from mysql_reconnect */ + set_stmt_error(stmt, CR_SERVER_LOST, unknown_sqlstate); DBUG_RETURN(1); } + + if ((int) stmt->state > (int) MYSQL_STMT_INIT_DONE) + { + /* This is second prepare with another statement */ + char buff[4]; + + mysql_stmt_free_result(stmt); + /* + These members must be reset for API to + function in case of error or misuse. + */ + stmt->bind_param_done= stmt->bind_result_done= FALSE; + stmt->param_count= stmt->field_count= 0; + stmt->last_errno= 0; + stmt->last_error[0]= '\0'; + free_root(&stmt->mem_root, MYF(MY_KEEP_PREALLOC)); + + int4store(buff, stmt->stmt_id); + /* + If there was a 'use' result from another statement, or from + mysql_use_result it won't be freed in mysql_stmt_free_result and + we should get 'Commands out of sync' here. + */ + if (simple_command(mysql, COM_CLOSE_STMT, buff, 4, 1)) + { + set_stmt_errmsg(stmt, mysql->net.last_error, mysql->net.last_errno, + mysql->net.sqlstate); + DBUG_RETURN(1); + } + stmt->state= MYSQL_STMT_INIT_DONE; + } + if (simple_command(mysql, COM_PREPARE, query, length, 1)) { set_stmt_errmsg(stmt, mysql->net.last_error, mysql->net.last_errno, @@ -1706,8 +1759,18 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length) } if ((*mysql->methods->read_prepare_result)(mysql, stmt)) + { + set_stmt_errmsg(stmt, mysql->net.last_error, mysql->net.last_errno, + mysql->net.sqlstate); DBUG_RETURN(1); + } + /* + alloc_root will return valid address even in case param_count + and field_count are zero. Thus we should never rely on stmt->bind + or stmt->params when checking for existence of placeholders or + result set. + */ if (!(stmt->params= (MYSQL_BIND *) alloc_root(&stmt->mem_root, sizeof(MYSQL_BIND)* (stmt->param_count + @@ -1717,26 +1780,22 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length) DBUG_RETURN(1); } stmt->bind= stmt->params + stmt->param_count; - stmt->state= MY_ST_PREPARE; - mysql->status= MYSQL_STATUS_READY; + stmt->state= MYSQL_STMT_PREPARE_DONE; DBUG_PRINT("info", ("Parameter count: %ld", stmt->param_count)); DBUG_RETURN(0); } /* Get the execute query meta information for non-select - statements (on demand). + statements. */ -unsigned int alloc_stmt_fields(MYSQL_STMT *stmt) +static unsigned int alloc_stmt_fields(MYSQL_STMT *stmt) { MYSQL_FIELD *fields, *field, *end; MEM_ROOT *alloc= &stmt->mem_root; MYSQL *mysql= stmt->mysql->last_used_con; - if (stmt->state != MY_ST_EXECUTE || !mysql->field_count) - return 0; - stmt->field_count= mysql->field_count; /* @@ -1782,20 +1841,25 @@ mysql_stmt_result_metadata(MYSQL_STMT *stmt) MYSQL_RES *result; DBUG_ENTER("mysql_stmt_result_metadata"); - if (!stmt->field_count || !stmt->fields) + /* + stmt->fields is only defined if stmt->field_count is not null; + stmt->field_count is initialized in prepare. + */ + if (!stmt->field_count) + DBUG_RETURN(0); + + if (!(result=(MYSQL_RES*) my_malloc(sizeof(*result), + MYF(MY_WME | MY_ZEROFILL)))) { - if (!alloc_stmt_fields(stmt)) - DBUG_RETURN(0); - } - if (!(result=(MYSQL_RES*) my_malloc(sizeof(*result)+ - sizeof(ulong)*stmt->field_count, - MYF(MY_WME | MY_ZEROFILL)))) - return 0; + set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate); + DBUG_RETURN(0); + } - result->methods= stmt->mysql->methods; - result->eof=1; /* Marker for buffered */ + result->methods= stmt->mysql->methods; + result->eof= 1; /* Marker for buffered */ result->fields= stmt->fields; result->field_count= stmt->field_count; + /* The rest of members of 'result' was bzeroed inside malloc */ DBUG_RETURN(result); } @@ -1989,11 +2053,9 @@ static void store_param_null(NET *net, MYSQL_BIND *param) client application */ - static my_bool store_param(MYSQL_STMT *stmt, MYSQL_BIND *param) { - MYSQL *mysql= stmt->mysql; - NET *net = &mysql->net; + NET *net= &stmt->mysql->net; DBUG_ENTER("store_param"); DBUG_PRINT("enter",("type: %d, buffer:%lx, length: %lu is_null: %d", param->buffer_type, @@ -2059,6 +2121,12 @@ int cli_stmt_execute(MYSQL_STMT *stmt) uint null_count; my_bool result; + if (!stmt->bind_param_done) + { + set_stmt_error(stmt, CR_PARAMS_NOT_BOUND, unknown_sqlstate); + DBUG_RETURN(1); + } + net_clear(net); /* Sets net->write_pos */ /* Reserve place for null-marker bytes */ null_count= (stmt->param_count+7) /8; @@ -2093,7 +2161,6 @@ int cli_stmt_execute(MYSQL_STMT *stmt) set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate); DBUG_RETURN(1); } - net->write_pos= net->buff; /* Reset for net_write() */ result= execute(stmt, param_data, length); stmt->send_types_to_server=0; my_free(param_data, MYF(MY_WME)); @@ -2103,21 +2170,145 @@ int cli_stmt_execute(MYSQL_STMT *stmt) } /* + Read one row from buffered result set. Result set is created by prior + call to mysql_stmt_store_result(). + SYNOPSIS + stmt_read_row_buffered() + + RETURN VALUE + 0 - success; *row is set to valid row pointer (row data + is stored in result set buffer) + MYSQL_NO_DATA - end of result set. *row is set to NULL +*/ + +static int stmt_read_row_buffered(MYSQL_STMT *stmt, unsigned char **row) +{ + MYSQL_RES *result= stmt->result; + + if (result && result->data_cursor) + { + *row= (uchar *) result->data_cursor->data; + result->data_cursor= result->data_cursor->next; + return 0; + } + *row= 0; + return MYSQL_NO_DATA; +} + +/* + Read one row from network: unbuffered non-cursor fetch. + If last row was read, or error occured, erase this statement + from record pointing to object unbuffered fetch is performed from. + + SYNOPSIS + stmt_read_row_unbuffered() + stmt statement handle + row pointer to write pointer to row data; + + RETURN VALUE + 0 - success; *row contains valid address of a row; + row data is stored in network buffer + 1 - error; error code is written to + stmt->last_{errno,error}; *row is not changed + MYSQL_NO_DATA - end of file was read from network; + *row is to NULL +*/ + +static int stmt_read_row_unbuffered(MYSQL_STMT *stmt, unsigned char **row) +{ + int rc= 1; + MYSQL *mysql= stmt->mysql; + /* + This function won't be called if stmt->field_count is zero + or execution wasn't done: this is ensured by mysql_stmt_execute. + */ + if (!mysql) + { + set_stmt_error(stmt, CR_SERVER_LOST, unknown_sqlstate); + return 1; + } + if (mysql->status != MYSQL_STATUS_GET_RESULT) + { + set_stmt_error(stmt, stmt->unbuffered_fetch_cancelled ? + CR_FETCH_CANCELLED : CR_COMMANDS_OUT_OF_SYNC, + unknown_sqlstate); + goto error; + } + if ((*mysql->methods->unbuffered_fetch)(mysql, (char**) row)) + { + set_stmt_errmsg(stmt, mysql->net.last_error, mysql->net.last_errno, + mysql->net.sqlstate); + goto error; + } + if (!*row) + { + mysql->status= MYSQL_STATUS_READY; + rc= MYSQL_NO_DATA; + goto error; + } + return 0; +error: + if (mysql->unbuffered_fetch_owner == &stmt->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner= 0; + return rc; +} + +/* + Default read row function to not SIGSEGV in client in + case of wrong sequence of API calls. +*/ + +static int +stmt_read_row_no_data(MYSQL_STMT *stmt __attribute__((unused)), + unsigned char **row __attribute__((unused))) +{ + if ((int) stmt->state < (int) MYSQL_STMT_PREPARE_DONE) + { + set_stmt_error(stmt, CR_NO_PREPARE_STMT, unknown_sqlstate); + return 1; + } + return MYSQL_NO_DATA; +} + +/* Execute the prepared query */ int STDCALL mysql_stmt_execute(MYSQL_STMT *stmt) { + MYSQL *mysql= stmt->mysql; DBUG_ENTER("mysql_stmt_execute"); - if ((*stmt->mysql->methods->stmt_execute)(stmt)) + if (!mysql) + { + set_stmt_error(stmt, CR_SERVER_LOST, unknown_sqlstate); DBUG_RETURN(1); + } + + mysql_stmt_free_result(stmt); + /* + No need to check for stmt->state: if the statement wasn't + prepared we'll get 'unknown statemenet handler' error from server. + */ + if (mysql->methods->stmt_execute(stmt)) + DBUG_RETURN(1); + if (!stmt->field_count && mysql->field_count) + { + /* + This is 'SHOW'/'EXPLAIN'-like query. Current implementation of + prepared statements can't send result set metadata for this queries + on prepare stage. Read it now. + */ + alloc_stmt_fields(stmt); + } - stmt->state= MY_ST_EXECUTE; - mysql_free_result(stmt->result); - stmt->result= (MYSQL_RES *)0; - stmt->result_buffered= 0; - stmt->current_row= 0; + stmt->state= MYSQL_STMT_EXECUTE_DONE; + if (stmt->field_count) + { + stmt->mysql->unbuffered_fetch_owner= &stmt->unbuffered_fetch_cancelled; + stmt->unbuffered_fetch_cancelled= FALSE; + stmt->read_row_func= stmt_read_row_unbuffered; + } DBUG_RETURN(0); } @@ -2155,6 +2346,16 @@ my_bool STDCALL mysql_stmt_bind_param(MYSQL_STMT *stmt, MYSQL_BIND * bind) MYSQL_BIND *param, *end; DBUG_ENTER("mysql_stmt_bind_param"); + if (!stmt->param_count) + { + if ((int) stmt->state < (int) MYSQL_STMT_PREPARE_DONE) + { + set_stmt_error(stmt, CR_NO_PREPARE_STMT, unknown_sqlstate); + DBUG_RETURN(1); + } + DBUG_RETURN(0); + } + /* Allocated on prepare */ memcpy((char*) stmt->params, (char*) bind, sizeof(MYSQL_BIND) * stmt->param_count); @@ -2166,13 +2367,6 @@ my_bool STDCALL mysql_stmt_bind_param(MYSQL_STMT *stmt, MYSQL_BIND * bind) param->param_number= count++; param->long_data_used= 0; - /* - If param->length is not given, change it to point to buffer_length. - This way we can always use *param->length to get the length of data - */ - if (!param->length) - param->length= ¶m->buffer_length; - /* If param->is_null is not set, then the value can never be NULL */ if (!param->is_null) param->is_null= &int_is_null_false; @@ -2239,10 +2433,16 @@ my_bool STDCALL mysql_stmt_bind_param(MYSQL_STMT *stmt, MYSQL_BIND * bind) param->buffer_type, count); DBUG_RETURN(1); } + /* + If param->length is not given, change it to point to buffer_length. + This way we can always use *param->length to get the length of data + */ + if (!param->length) + param->length= ¶m->buffer_length; } /* We have to send/resendtype information to MySQL */ - stmt->send_types_to_server= 1; - stmt->param_buffers= 1; + stmt->send_types_to_server= TRUE; + stmt->bind_param_done= TRUE; DBUG_RETURN(0); } @@ -2276,6 +2476,16 @@ mysql_stmt_send_long_data(MYSQL_STMT *stmt, uint param_number, DBUG_ASSERT(stmt != 0); DBUG_PRINT("enter",("param no : %d, data : %lx, length : %ld", param_number, data, length)); + + /* + We only need to check for stmt->param_count, if it's not null + prepare was done. + */ + if (param_number >= stmt->param_count) + { + set_stmt_error(stmt, CR_INVALID_PARAMETER_NO, unknown_sqlstate); + DBUG_RETURN(1); + } param= stmt->params+param_number; if (param->buffer_type < MYSQL_TYPE_TINY_BLOB || @@ -2846,9 +3056,20 @@ my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind) DBUG_ENTER("mysql_stmt_bind_result"); DBUG_ASSERT(stmt != 0); - if (!(bind_count= stmt->field_count) && - !(bind_count= alloc_stmt_fields(stmt))) + if (!stmt->field_count) + { + if ((int) stmt->state < (int) MYSQL_STMT_PREPARE_DONE) + { + set_stmt_error(stmt, CR_NO_PREPARE_STMT, unknown_sqlstate); + } DBUG_RETURN(0); + } + bind_count= stmt->field_count; + + /* + We only need to check that stmt->field_count - if it is not null + stmt->bind was initialized in mysql_stmt_prepare + */ memcpy((char*) stmt->bind, (char*) bind, sizeof(MYSQL_BIND)*bind_count); @@ -2929,7 +3150,7 @@ my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind) DBUG_RETURN(1); } } - stmt->res_buffers= 1; + stmt->bind_result_done= TRUE; DBUG_RETURN(0); } @@ -2943,9 +3164,18 @@ static int stmt_fetch_row(MYSQL_STMT *stmt, uchar *row) MYSQL_BIND *bind, *end; MYSQL_FIELD *field, *field_end; uchar *null_ptr, bit; + /* + Precondition: if stmt->field_count is zero or row is NULL, read_row_* + function must return no data. + */ + DBUG_ASSERT(stmt->field_count); + DBUG_ASSERT(row); - if (!row || !stmt->res_buffers) + if (!stmt->bind_result_done) + { + /* If output parameters were not bound we should just return success */ return 0; + } null_ptr= row; row+= (stmt->field_count+9)/8; /* skip null bits */ @@ -2994,57 +3224,22 @@ int cli_unbuffered_fetch(MYSQL *mysql, char **row) int STDCALL mysql_stmt_fetch(MYSQL_STMT *stmt) { - MYSQL *mysql= stmt->mysql; + int rc; uchar *row; DBUG_ENTER("mysql_stmt_fetch"); - stmt->last_fetched_column= 0; /* reset */ - if (stmt->result_buffered) /* buffered */ + if ((rc= (*stmt->read_row_func)(stmt, &row)) || + (rc= stmt_fetch_row(stmt, row))) { - MYSQL_RES *res; - - if (!(res= stmt->result)) - goto no_data; - - if (!res->data_cursor) - { - stmt->current_row= 0; - goto no_data; - } - row= (uchar *)res->data_cursor->data; - res->data_cursor= res->data_cursor->next; + stmt->state= MYSQL_STMT_PREPARE_DONE; /* XXX: this is buggy */ + stmt->read_row_func= stmt_read_row_no_data; } - else /* un-buffered */ + else { - if (mysql->status != MYSQL_STATUS_GET_RESULT) - { - if (!stmt->field_count) - goto no_data; - - set_stmt_error(stmt, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); - DBUG_RETURN(1); - } - - if ((*mysql->methods->unbuffered_fetch)(mysql, (char**) &row)) - { - set_stmt_errmsg(stmt, mysql->net.last_error, mysql->net.last_errno, - mysql->net.sqlstate); - DBUG_RETURN(1); - } - if (!row) - { - mysql->status= MYSQL_STATUS_READY; - stmt->current_row= 0; - goto no_data; - } + /* This is to know in mysql_stmt_fetch_column that data was fetched */ + stmt->state= MYSQL_STMT_FETCH_DONE; } - - stmt->current_row= row; - DBUG_RETURN(stmt_fetch_row(stmt, row)); - -no_data: - DBUG_PRINT("info", ("end of data")); - DBUG_RETURN(MYSQL_NO_DATA); /* no more data */ + DBUG_RETURN(rc); } @@ -3065,13 +3260,22 @@ no_data: */ int STDCALL mysql_stmt_fetch_column(MYSQL_STMT *stmt, MYSQL_BIND *bind, - uint column, ulong offset) + uint column, ulong offset) { MYSQL_BIND *param= stmt->bind+column; DBUG_ENTER("mysql_stmt_fetch_column"); - if (!stmt->current_row) - goto no_data; + if ((int) stmt->state < (int) MYSQL_STMT_FETCH_DONE) + { + set_stmt_error(stmt, CR_NO_DATA, unknown_sqlstate); + return 1; + } + if (column >= stmt->field_count) + { + set_stmt_error(stmt, CR_INVALID_PARAMETER_NO, unknown_sqlstate); + DBUG_RETURN(1); + } + if (param->null_field) { @@ -3092,10 +3296,6 @@ int STDCALL mysql_stmt_fetch_column(MYSQL_STMT *stmt, MYSQL_BIND *bind, fetch_results(bind, field, &row); } DBUG_RETURN(0); - -no_data: - DBUG_PRINT("info", ("end of data")); - DBUG_RETURN(MYSQL_NO_DATA); /* no more data */ } @@ -3111,7 +3311,7 @@ MYSQL_DATA *cli_read_binary_rows(MYSQL_STMT *stmt) MYSQL_DATA *result; MYSQL_ROWS *cur, **prev_ptr; NET *net = &mysql->net; - DBUG_ENTER("read_binary_rows"); + DBUG_ENTER("cli_read_binary_rows"); mysql= mysql->last_used_con; if ((pkt_len= net_safe_read(mysql)) == packet_error) @@ -3181,33 +3381,37 @@ int STDCALL mysql_stmt_store_result(MYSQL_STMT *stmt) if (!stmt->field_count) DBUG_RETURN(0); - if (mysql->status != MYSQL_STATUS_GET_RESULT) + if ((int) stmt->state < (int) MYSQL_STMT_EXECUTE_DONE || + mysql->status != MYSQL_STATUS_GET_RESULT) { set_stmt_error(stmt, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); DBUG_RETURN(1); } - mysql->status= MYSQL_STATUS_READY; /* server is ready */ - if (!(result= (MYSQL_RES*) my_malloc((uint) (sizeof(MYSQL_RES)+ - sizeof(ulong) * - stmt->field_count), + if (!(result= (MYSQL_RES*) my_malloc(sizeof(MYSQL_RES), MYF(MY_WME | MY_ZEROFILL)))) { set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate); DBUG_RETURN(1); } result->methods= mysql->methods; - stmt->result_buffered= 1; - if (!(result->data= (*stmt->mysql->methods->read_binary_rows)(stmt))) + if ((result->data= (*mysql->methods->read_binary_rows)(stmt))) + { + result->row_count= result->data->rows; + result->data_cursor= result->data->data; + } + else if (stmt->last_errno) { my_free((gptr) result,MYF(0)); - DBUG_RETURN(0); + DBUG_RETURN(1); } - mysql->affected_rows= result->row_count= result->data->rows; - stmt->affected_rows= result->row_count; - result->data_cursor= result->data->data; + mysql->affected_rows= stmt->affected_rows= result->row_count; result->fields= stmt->fields; result->field_count= stmt->field_count; + /* The rest of MYSQL_RES members were bzeroed inside my_malloc */ stmt->result= result; + stmt->read_row_func= stmt_read_row_buffered; + mysql->unbuffered_fetch_owner= 0; /* set in stmt_execute */ + mysql->status= MYSQL_STATUS_READY; /* server is ready */ DBUG_RETURN(0); /* Data buffered, must be fetched with mysql_stmt_fetch() */ } @@ -3292,6 +3496,40 @@ my_ulonglong STDCALL mysql_stmt_num_rows(MYSQL_STMT *stmt) DBUG_RETURN(0); } +my_bool STDCALL mysql_stmt_free_result(MYSQL_STMT *stmt) +{ + DBUG_ENTER("mysql_stmt_free_result"); + + DBUG_ASSERT(stmt != 0); + + if ((int) stmt->state > (int) MYSQL_STMT_INIT_DONE) + { + MYSQL *mysql= stmt->mysql; + + if (stmt->result) + { + /* Result buffered */ + mysql_free_result(stmt->result); + stmt->result= 0; + } + else if (mysql && stmt->field_count + && (int) stmt->state > (int) MYSQL_STMT_PREPARE_DONE) + { + if (mysql->unbuffered_fetch_owner == &stmt->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner= 0; + if (mysql->status != MYSQL_STATUS_READY) + { + /* There is a result set and it belongs to this statement */ + flush_use_result(mysql); + mysql->status= MYSQL_STATUS_READY; + } + } + stmt->state= MYSQL_STMT_PREPARE_DONE; + stmt->read_row_func= stmt_read_row_no_data; + } + DBUG_RETURN(0); +} + /******************************************************************** statement error handling and close *********************************************************************/ @@ -3300,82 +3538,55 @@ my_ulonglong STDCALL mysql_stmt_num_rows(MYSQL_STMT *stmt) Close the statement handle by freeing all alloced resources SYNOPSIS - mysql_stmt_free_result() + mysql_stmt_close() stmt Statement handle - skip_list Flag to indicate delete from list or not + RETURN VALUES 0 ok 1 error */ -my_bool STDCALL mysql_stmt_free_result(MYSQL_STMT *stmt) -{ - MYSQL *mysql; - DBUG_ENTER("mysql_stmt_free_result"); - - DBUG_ASSERT(stmt != 0); - - mysql= stmt->mysql; - if (mysql->status != MYSQL_STATUS_READY) - { - /* Clear the current execution status */ - DBUG_PRINT("warning",("Not all packets read, clearing them")); - for (;;) - { - ulong pkt_len; - if ((pkt_len= net_safe_read(mysql)) == packet_error) - break; - if (pkt_len <= 8 && mysql->net.read_pos[0] == 254) - break; - } - mysql->status= MYSQL_STATUS_READY; - } - mysql_free_result(stmt->result); - stmt->result= 0; - stmt->result_buffered= 0; - stmt->current_row= 0; - DBUG_RETURN(0); -} - - -my_bool stmt_close(MYSQL_STMT *stmt, my_bool skip_free) +my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt) { - MYSQL *mysql; - DBUG_ENTER("stmt_close"); + MYSQL *mysql= stmt->mysql; + int rc= 0; + DBUG_ENTER("mysql_stmt_close"); - DBUG_ASSERT(stmt != 0); - - if (!(mysql= stmt->mysql)) - { - if (!skip_free) - my_free((gptr) stmt, MYF(MY_WME)); - DBUG_RETURN(0); - } - mysql_stmt_free_result(stmt); - if (stmt->state == MY_ST_PREPARE || stmt->state == MY_ST_EXECUTE) + mysql_free_result(stmt->result); /* if result is buffered */ + free_root(&stmt->mem_root, MYF(0)); + + if (mysql) { - char buff[4]; - int4store(buff, stmt->stmt_id); - if (skip_free || simple_command(mysql, COM_CLOSE_STMT, buff, 4, 1)) + mysql->stmts= list_delete(mysql->stmts, &stmt->list); + if ((int) stmt->state > (int) MYSQL_STMT_INIT_DONE) { - set_stmt_errmsg(stmt, mysql->net.last_error, mysql->net.last_errno, - mysql->net.sqlstate); - stmt->mysql= NULL; /* connection isn't valid anymore */ - DBUG_RETURN(1); + char buff[4]; + + if (mysql->unbuffered_fetch_owner == &stmt->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner= 0; + if (mysql->status != MYSQL_STATUS_READY) + { + /* + Flush result set of the connection. If it does not belong + to this statement, set a warning. + */ + flush_use_result(mysql); + if (mysql->unbuffered_fetch_owner) + *mysql->unbuffered_fetch_owner= TRUE; + mysql->status= MYSQL_STATUS_READY; + } + int4store(buff, stmt->stmt_id); + if ((rc= simple_command(mysql, COM_CLOSE_STMT, buff, 4, 1))) + { + set_stmt_errmsg(stmt, mysql->net.last_error, mysql->net.last_errno, + mysql->net.sqlstate); + } } } - stmt->field_count= 0; - free_root(&stmt->mem_root, MYF(0)); - mysql->stmts= list_delete(mysql->stmts, &stmt->list); - mysql->status= MYSQL_STATUS_READY; - my_free((gptr) stmt, MYF(MY_WME)); - DBUG_RETURN(0); -} + my_free((gptr) stmt, MYF(MY_WME)); -my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt) -{ - return stmt_close(stmt, 0); + DBUG_RETURN(test(rc)); } /* @@ -3388,6 +3599,10 @@ my_bool STDCALL mysql_stmt_reset(MYSQL_STMT *stmt) MYSQL *mysql; DBUG_ENTER("mysql_stmt_reset"); DBUG_ASSERT(stmt != 0); + + /* If statement hasnt been prepared there is nothing to reset */ + if ((int) stmt->state < (int) MYSQL_STMT_PREPARE_DONE) + DBUG_RETURN(0); mysql= stmt->mysql->last_used_con; int4store(buff, stmt->stmt_id); /* Send stmt id to server */ diff --git a/sql-common/client.c b/sql-common/client.c index 5d2df4a0ddf..6d0da338543 100644 --- a/sql-common/client.c +++ b/sql-common/client.c @@ -712,6 +712,34 @@ void set_mysql_error(MYSQL *mysql, int errcode, const char *sqlstate) net->last_errno= errcode; strmov(net->last_error, ER(errcode)); strmov(net->sqlstate, sqlstate); + + DBUG_VOID_RETURN; +} + +/* + Flush result set sent from server +*/ + +void flush_use_result(MYSQL *mysql) +{ + /* Clear the current execution status */ + DBUG_PRINT("warning",("Not all packets read, clearing them")); + for (;;) + { + ulong pkt_len; + if ((pkt_len=net_safe_read(mysql)) == packet_error) + break; + if (pkt_len <= 8 && mysql->net.read_pos[0] == 254) + { + if (protocol_41(mysql)) + { + char *pos= (char*) mysql->net.read_pos; + mysql->warning_count=uint2korr(pos); pos+=2; + mysql->server_status=uint2korr(pos); pos+=2; + } + break; /* End of data */ + } + } } @@ -752,26 +780,16 @@ mysql_free_result(MYSQL_RES *result) DBUG_PRINT("enter",("mysql_res: %lx",result)); if (result) { - if (result->handle && result->handle->status == MYSQL_STATUS_USE_RESULT) + MYSQL *mysql= result->handle; + if (mysql) { - DBUG_PRINT("warning",("Not all rows in set where read; Ignoring rows")); - for (;;) + if (mysql->unbuffered_fetch_owner == &result->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner= 0; + if (mysql->status == MYSQL_STATUS_USE_RESULT) { - ulong pkt_len; - if ((pkt_len=net_safe_read(result->handle)) == packet_error) - break; - if (pkt_len <= 8 && result->handle->net.read_pos[0] == 254) - { - if (protocol_41(result->handle)) - { - char *pos= (char*) result->handle->net.read_pos; - result->handle->warning_count=uint2korr(pos); pos+=2; - result->handle->server_status=uint2korr(pos); pos+=2; - } - break; /* End of data */ - } + flush_use_result(mysql); + mysql->status=MYSQL_STATUS_READY; } - result->handle->status=MYSQL_STATUS_READY; } free_rows(result->data); if (result->fields) @@ -2177,12 +2195,13 @@ void STDCALL mysql_close(MYSQL *mysql) #ifdef MYSQL_CLIENT if (mysql->stmts) { - /* Free any open prepared statements */ - LIST *element, *next_element; - for (element= mysql->stmts; element; element= next_element) + /* Reset connection handle in all prepared statements. */ + LIST *element; + for (element= mysql->stmts; element; element= element->next) { - next_element= element->next; - stmt_close((MYSQL_STMT *)element->data, 1); + MYSQL_STMT *stmt= (MYSQL_STMT *) element->data; + stmt->mysql= 0; + /* No need to call list_delete for statement here */ } mysql->stmts= 0; } @@ -2372,9 +2391,10 @@ MYSQL_RES * STDCALL mysql_store_result(MYSQL *mysql) result->fields= mysql->fields; result->field_alloc= mysql->field_alloc; result->field_count= mysql->field_count; - result->current_field=0; - result->current_row=0; /* Must do a fetch first */ + /* The rest of result members is bzeroed in malloc */ mysql->fields=0; /* fields is now in result */ + /* just in case this was mistakenly called after mysql_stmt_execute() */ + mysql->unbuffered_fetch_owner= 0; DBUG_RETURN(result); /* Data fetched */ } @@ -2423,6 +2443,7 @@ static MYSQL_RES * cli_use_result(MYSQL *mysql) result->current_row= 0; mysql->fields=0; /* fields is now in result */ mysql->status=MYSQL_STATUS_USE_RESULT; + mysql->unbuffered_fetch_owner= &result->unbuffered_fetch_cancelled; DBUG_RETURN(result); /* Data is read to be fetched */ } @@ -2439,19 +2460,30 @@ mysql_fetch_row(MYSQL_RES *res) { /* Unbufferred fetch */ if (!res->eof) { - if (!(read_one_row(res->handle,res->field_count,res->row, res->lengths))) + MYSQL *mysql= res->handle; + if (mysql->status != MYSQL_STATUS_USE_RESULT) { - res->row_count++; - DBUG_RETURN(res->current_row=res->row); + set_mysql_error(mysql, + res->unbuffered_fetch_cancelled ? + CR_FETCH_CANCELLED : CR_COMMANDS_OUT_OF_SYNC, + unknown_sqlstate); } - else + else if (!(read_one_row(mysql, res->field_count, res->row, res->lengths))) { - DBUG_PRINT("info",("end of data")); - res->eof=1; - res->handle->status=MYSQL_STATUS_READY; - /* Don't clear handle in mysql_free_results */ - res->handle=0; + res->row_count++; + DBUG_RETURN(res->current_row=res->row); } + DBUG_PRINT("info",("end of data")); + res->eof=1; + mysql->status=MYSQL_STATUS_READY; + /* + Reset only if owner points to us: there is a chance that somebody + started new query after mysql_stmt_close(): + */ + if (mysql->unbuffered_fetch_owner == &res->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner= 0; + /* Don't clear handle in mysql_free_result */ + res->handle=0; } DBUG_RETURN((MYSQL_ROW) NULL); } |