diff options
Diffstat (limited to 'libmysql/libmysql.c')
-rw-r--r-- | libmysql/libmysql.c | 1198 |
1 files changed, 824 insertions, 374 deletions
diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index b3f4ccc4b4d..e63a583a0dd 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -89,7 +89,6 @@ static void append_wild(char *to,char *end,const char *wild); sig_handler pipe_sig_handler(int sig); static ulong mysql_sub_escape_string(CHARSET_INFO *charset_info, char *to, const char *from, ulong length); -my_bool stmt_close(MYSQL_STMT *stmt, my_bool skip_list); static my_bool mysql_client_init= 0; static my_bool org_my_init_done= 0; @@ -259,7 +258,8 @@ mysql_debug(const char *debug __attribute__((unused))) #else { char buff[80]; - strmov(strmov(buff,"libmysql: "),env); + buff[sizeof(buff)-1]= 0; + strxnmov(buff,sizeof(buff)-1,"libmysql: ", env, NullS); MessageBox((HWND) 0,"Debugging variable MYSQL_DEBUG used",buff,MB_OK); } #endif @@ -603,16 +603,97 @@ mysql_connect(MYSQL *mysql,const char *host, #endif +#ifdef CHECK_LICENSE +/* + Check server side variable 'license'. + If the variable does not exist or does not contain 'Commercial', + we're talking to non-commercial server from commercial client. + SYNOPSIS + check_license() + RETURN VALUE + 0 success + !0 network error or the server is not commercial. + Error code is saved in mysql->net.last_errno. +*/ + +static int check_license(MYSQL *mysql) +{ + MYSQL_ROW row; + MYSQL_RES *res; + NET *net= &mysql->net; + static const char query[]= "SELECT @@license"; + static const char required_license[]= LICENSE; + + if (mysql_real_query(mysql, query, sizeof(query)-1)) + { + if (net->last_errno == ER_UNKNOWN_SYSTEM_VARIABLE) + { + net->last_errno= CR_WRONG_LICENSE; + sprintf(net->last_error, ER(net->last_errno), required_license); + } + return 1; + } + if (!(res= mysql_use_result(mysql))) + return 1; + row= mysql_fetch_row(res); + /* + If no rows in result set, or column value is NULL (none of these + two is ever true for server variables now), or column value + mismatch, set wrong license error. + */ + if (!net->last_errno && + (!row || !row[0] || + strncmp(row[0], required_license, sizeof(required_license)))) + { + net->last_errno= CR_WRONG_LICENSE; + sprintf(net->last_error, ER(net->last_errno), required_license); + } + mysql_free_result(res); + return net->last_errno; +} +#endif /* CHECK_LICENSE */ + + /************************************************************************** Change user and database **************************************************************************/ +int cli_read_change_user_result(MYSQL *mysql, char *buff, const char *passwd) +{ + NET *net= &mysql->net; + ulong pkt_length; + + pkt_length= net_safe_read(mysql); + + if (pkt_length == packet_error) + return 1; + + if (pkt_length == 1 && net->read_pos[0] == 254 && + mysql->server_capabilities & CLIENT_SECURE_CONNECTION) + { + /* + By sending this very specific reply server asks us to send scrambled + password in old format. The reply contains scramble_323. + */ + scramble_323(buff, mysql->scramble, passwd); + if (my_net_write(net, buff, SCRAMBLE_LENGTH_323 + 1) || net_flush(net)) + { + net->last_errno= CR_SERVER_LOST; + strmov(net->sqlstate, unknown_sqlstate); + strmov(net->last_error,ER(net->last_errno)); + return 1; + } + /* Read what server thinks about out new auth message report */ + if (net_safe_read(mysql) == packet_error) + return 1; + } + return 0; +} + my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, const char *passwd, const char *db) { char buff[512],*end=buff; - NET *net= &mysql->net; - ulong pkt_length; DBUG_ENTER("mysql_change_user"); if (!user) @@ -646,31 +727,8 @@ my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, /* Write authentication package */ simple_command(mysql,COM_CHANGE_USER, buff,(ulong) (end-buff),1); - pkt_length= net_safe_read(mysql); - - if (pkt_length == packet_error) - goto error; - - if (pkt_length == 1 && net->read_pos[0] == 254 && - mysql->server_capabilities & CLIENT_SECURE_CONNECTION) - { - /* - By sending this very specific reply server asks us to send scrambled - password in old format. The reply contains scramble_323. - */ - scramble_323(buff, mysql->scramble, passwd); - if (my_net_write(net, buff, SCRAMBLE_LENGTH_323 + 1) || net_flush(net)) - { - net->last_errno= CR_SERVER_LOST; - strmov(net->sqlstate, unknown_sqlstate); - strmov(net->last_error,ER(net->last_errno)); - goto error; - } - /* Read what server thinks about out new auth message report */ - if (net_safe_read(mysql) == packet_error) - goto error; - } - + if ((*mysql->methods->read_change_user_result)(mysql, buff, passwd)) + DBUG_RETURN(1); /* Free old connect information */ my_free(mysql->user,MYF(MY_ALLOW_ZERO_PTR)); my_free(mysql->passwd,MYF(MY_ALLOW_ZERO_PTR)); @@ -681,9 +739,6 @@ my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, mysql->passwd=my_strdup(passwd,MYF(MY_WME)); mysql->db= db ? my_strdup(db,MYF(MY_WME)) : 0; DBUG_RETURN(0); - -error: - DBUG_RETURN(1); } #if defined(HAVE_GETPWUID) && defined(NO_GETPWUID_DECL) @@ -692,7 +747,7 @@ char* getlogin(void); #endif #if defined(__NETWARE__) -/* default to "root" on NetWare */ +/* Default to value of USER on NetWare, if unset use "UNKNOWN_USER" */ void read_user_name(char *name) { char *str=getenv("USER"); @@ -739,45 +794,63 @@ void read_user_name(char *name) #endif -my_bool send_file_to_server(MYSQL *mysql, const char *filename) +my_bool handle_local_infile(MYSQL *mysql, const char *net_filename) { - int fd, readcount; my_bool result= 1; uint packet_length=MY_ALIGN(mysql->net.max_packet-16,IO_SIZE); - char *buf, tmp_name[FN_REFLEN]; NET *net= &mysql->net; - DBUG_ENTER("send_file_to_server"); + int readcount; + void *li_ptr; /* pass state to local_infile functions */ + char *buf; /* buffer to be filled by local_infile_read */ + struct st_mysql_options *options= &mysql->options; + DBUG_ENTER("handle_local_infile"); - if (!(buf=my_malloc(packet_length,MYF(0)))) + /* check that we've got valid callback functions */ + if (!(options->local_infile_init && + options->local_infile_read && + options->local_infile_end && + options->local_infile_error)) + { + /* if any of the functions is invalid, set the default */ + mysql_set_local_infile_default(mysql); + } + + /* copy filename into local memory and allocate read buffer */ + if (!(buf=my_malloc(packet_length, MYF(0)))) { strmov(net->sqlstate, unknown_sqlstate); strmov(net->last_error, ER(net->last_errno=CR_OUT_OF_MEMORY)); DBUG_RETURN(1); } - fn_format(tmp_name,filename,"","",4); /* Convert to client format */ - if ((fd = my_open(tmp_name,O_RDONLY, MYF(0))) < 0) + /* initialize local infile (open file, usually) */ + if ((*options->local_infile_init)(&li_ptr, net_filename)) { my_net_write(net,"",0); /* Server needs one packet */ net_flush(net); strmov(net->sqlstate, unknown_sqlstate); - net->last_errno=EE_FILENOTFOUND; - my_snprintf(net->last_error,sizeof(net->last_error)-1, - EE(net->last_errno),tmp_name, errno); + net->last_errno= (*options->local_infile_error)(li_ptr, + net->last_error, + sizeof(net->last_error)-1); goto err; } - while ((readcount = (int) my_read(fd,(byte*) buf,packet_length,MYF(0))) > 0) + /* read blocks of data from local infile callback */ + while ((readcount = + (*options->local_infile_read)(li_ptr, buf, + packet_length)) > 0) { if (my_net_write(net,buf,readcount)) { - DBUG_PRINT("error",("Lost connection to MySQL server during LOAD DATA of local file")); + DBUG_PRINT("error", + ("Lost connection to MySQL server during LOAD DATA of local file")); strmov(net->sqlstate, unknown_sqlstate); net->last_errno=CR_SERVER_LOST; strmov(net->last_error,ER(net->last_errno)); goto err; } } + /* Send empty packet to mark end of file */ if (my_net_write(net,"",0) || net_flush(net)) { @@ -786,24 +859,186 @@ my_bool send_file_to_server(MYSQL *mysql, const char *filename) sprintf(net->last_error,ER(net->last_errno),errno); goto err; } + if (readcount < 0) { - strmov(net->sqlstate, unknown_sqlstate); - net->last_errno=EE_READ; /* the errmsg for not entire file read */ - my_snprintf(net->last_error,sizeof(net->last_error)-1, - tmp_name,errno); + net->last_errno= (*options->local_infile_error)(li_ptr, + net->last_error, + sizeof(net->last_error)-1); goto err; } + result=0; /* Ok */ err: - if (fd >= 0) - (void) my_close(fd,MYF(0)); - my_free(buf,MYF(0)); + /* free up memory allocated with _init, usually */ + (*options->local_infile_end)(li_ptr); DBUG_RETURN(result); } +/**************************************************************************** + Default handlers for LOAD LOCAL INFILE +****************************************************************************/ + +typedef struct st_default_local_infile +{ + int fd; + int error_num; + const char *filename; + char error_msg[LOCAL_INFILE_ERROR_LEN]; +} default_local_infile_data; + + +/* + Open file for LOAD LOCAL INFILE + + SYNOPSIS + default_local_infile_init() + ptr Store pointer to internal data here + filename File name to open. This may be in unix format ! + + + NOTES + Even if this function returns an error, the load data interface + guarantees that default_local_infile_end() is called. + + RETURN + 0 ok + 1 error +*/ + +static int default_local_infile_init(void **ptr, const char *filename) +{ + default_local_infile_data *data; + char tmp_name[FN_REFLEN]; + + if (!(*ptr= data= ((default_local_infile_data *) + my_malloc(sizeof(default_local_infile_data), MYF(0))))) + return 1; /* out of memory */ + + data->error_msg[0]= 0; + data->error_num= 0; + data->filename= filename; + + fn_format(tmp_name, filename, "", "", MY_UNPACK_FILENAME); + if ((data->fd = my_open(tmp_name, O_RDONLY, MYF(0))) < 0) + { + data->error_num= my_errno; + my_snprintf(data->error_msg, sizeof(data->error_msg)-1, + EE(EE_FILENOTFOUND), tmp_name, data->error_num); + return 1; + } + return 0; /* ok */ +} + + +/* + Read data for LOAD LOCAL INFILE + + SYNOPSIS + default_local_infile_read() + ptr Points to handle allocated by _init + buf Read data here + buf_len Ammount of data to read + + RETURN + > 0 number of bytes read + == 0 End of data + < 0 Error +*/ + +static int default_local_infile_read(void *ptr, char *buf, uint buf_len) +{ + int count; + default_local_infile_data*data = (default_local_infile_data *) ptr; + + if ((count= (int) my_read(data->fd, (byte *) buf, buf_len, MYF(0))) < 0) + { + data->error_num= EE_READ; /* the errmsg for not entire file read */ + my_snprintf(data->error_msg, sizeof(data->error_msg)-1, + EE(EE_READ), + data->filename, my_errno); + } + return count; +} + + +/* + Read data for LOAD LOCAL INFILE + + SYNOPSIS + default_local_infile_end() + ptr Points to handle allocated by _init + May be NULL if _init failed! + + RETURN +*/ + +static void default_local_infile_end(void *ptr) +{ + default_local_infile_data *data= (default_local_infile_data *) ptr; + if (data) /* If not error on open */ + { + if (data->fd >= 0) + my_close(data->fd, MYF(MY_WME)); + my_free(ptr, MYF(MY_WME)); + } +} + + +/* + Return error from LOAD LOCAL INFILE + + SYNOPSIS + default_local_infile_end() + ptr Points to handle allocated by _init + May be NULL if _init failed! + error_msg Store error text here + error_msg_len Max lenght of error_msg + + RETURN + error message number +*/ + +static int +default_local_infile_error(void *ptr, char *error_msg, uint error_msg_len) +{ + default_local_infile_data *data = (default_local_infile_data *) ptr; + if (data) /* If not error on open */ + { + strmake(error_msg, data->error_msg, error_msg_len); + return data->error_num; + } + /* This can only happen if we got error on malloc of handle */ + strmov(error_msg, ER(CR_OUT_OF_MEMORY)); + return CR_OUT_OF_MEMORY; +} + + +void +mysql_set_local_infile_handler(MYSQL *mysql, + int (*local_infile_init)(void **, const char *), + int (*local_infile_read)(void *, char *, uint), + void (*local_infile_end)(void *), + int (*local_infile_error)(void *, char *, uint)) +{ + mysql->options.local_infile_init= local_infile_init; + mysql->options.local_infile_read= local_infile_read; + mysql->options.local_infile_end= local_infile_end; + mysql->options.local_infile_error= local_infile_error; +} + + +void mysql_set_local_infile_default(MYSQL *mysql) +{ + mysql->options.local_infile_init= default_local_infile_init; + mysql->options.local_infile_read= default_local_infile_read; + mysql->options.local_infile_end= default_local_infile_end; + mysql->options.local_infile_error= default_local_infile_error; +} + + /************************************************************************** Do a query. If query returned rows, free old rows. Read data by mysql_store_result or by repeat call of mysql_fetch_row @@ -863,6 +1098,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, @@ -984,6 +1220,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; @@ -1059,6 +1296,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) @@ -1096,6 +1334,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) { @@ -1123,7 +1362,8 @@ mysql_dump_debug_info(MYSQL *mysql) DBUG_RETURN(simple_command(mysql,COM_DEBUG,0,0,0)); } -const char *cli_read_statistic(MYSQL *mysql) + +const char *cli_read_statistics(MYSQL *mysql) { mysql->net.read_pos[mysql->packet_length]=0; /* End of stat string */ if (!mysql->net.read_pos[0]) @@ -1136,13 +1376,14 @@ const char *cli_read_statistic(MYSQL *mysql) return (char*) mysql->net.read_pos; } + const char * STDCALL mysql_stat(MYSQL *mysql) { DBUG_ENTER("mysql_stat"); if (simple_command(mysql,COM_STATISTICS,0,0,0)) return mysql->net.last_error; - DBUG_RETURN((*mysql->methods->read_statistic)(mysql)); + DBUG_RETURN((*mysql->methods->read_statistics)(mysql)); } @@ -1541,21 +1782,6 @@ void set_stmt_errmsg(MYSQL_STMT * stmt, const char *err, int errcode, } -/* - Set the internal error message to mysql handler -*/ - -static void set_mysql_error(MYSQL * mysql, int errcode, const char *sqlstate) -{ - DBUG_ENTER("set_mysql_error"); - DBUG_PRINT("enter", ("error :%d '%s'", errcode, ER(errcode))); - DBUG_ASSERT(mysql != 0); - - mysql->net.last_errno= errcode; - strmov(mysql->net.last_error, ER(errcode)); - strmov(mysql->net.sqlstate, sqlstate); -} - /* Reallocate the NET package to be at least of 'length' bytes @@ -1588,6 +1814,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 .. @@ -1603,13 +1833,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; @@ -1617,6 +1846,16 @@ my_bool cli_read_prepare_result(MYSQL *mysql, MYSQL_STMT *stmt) field_count= uint2korr(pos); pos+= 2; param_count= uint2korr(pos); pos+= 2; + if (param_count != 0) + { + MYSQL_DATA *param_data; + + /* skip parameters data: we don't support it yet */ + if (!(param_data= (*mysql->methods->read_rows)(mysql, (MYSQL_FIELD*)0, 7))) + DBUG_RETURN(1); + free_rows(param_data); + } + if (field_count != 0) { if (!(mysql->server_status & SERVER_STATUS_AUTOCOMMIT)) @@ -1636,76 +1875,170 @@ my_bool cli_read_prepare_result(MYSQL *mysql, MYSQL_STMT *stmt) DBUG_RETURN(0); } +#ifdef HAVE_DEPRECATED_411_API +MYSQL_STMT * STDCALL mysql_prepare(MYSQL *mysql, const char *query, + unsigned long query_length) +{ + MYSQL_STMT *stmt; + DBUG_ENTER("mysql_prepare"); + + stmt= mysql_stmt_init(mysql); + if (stmt && mysql_stmt_prepare(stmt, query, query_length)) + { + mysql_stmt_close(stmt); + DBUG_RETURN(0); + } + DBUG_RETURN(stmt); +} +#endif /* - Prepare the query and return the new statement handle to - caller. + Allocate memory and init prepared statement structure + SYNOPSIS + mysql_stmt_init() + mysql connection handle - Also update the total parameter count along with resultset - metadata information by reading from server + RETURN VALUE + statement structure upon success and NULL if out of + memory */ - -MYSQL_STMT *STDCALL -mysql_prepare(MYSQL *mysql, const char *query, ulong length) +MYSQL_STMT * STDCALL +mysql_stmt_init(MYSQL *mysql) { - MYSQL_STMT *stmt; - DBUG_ENTER("mysql_prepare"); - DBUG_ASSERT(mysql != 0); + MYSQL_STMT *stmt; + DBUG_ENTER("mysql_stmt_init"); if (!(stmt= (MYSQL_STMT *) my_malloc(sizeof(MYSQL_STMT), - MYF(MY_WME | MY_ZEROFILL))) || - !(stmt->query= my_strdup_with_length((byte *) query, length, MYF(0)))) + MYF(MY_WME | MY_ZEROFILL)))) { - my_free((gptr) stmt, MYF(MY_ALLOW_ZERO_PTR)); set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); DBUG_RETURN(0); } + + init_alloc_root(&stmt->mem_root, 2048, 2048); + init_alloc_root(&stmt->result.alloc, 4096, 4096); + stmt->result.alloc.min_malloc= sizeof(MYSQL_ROWS); + mysql->stmts= list_add(mysql->stmts, &stmt->list); + stmt->list.data= stmt; + 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: + 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}. + + RETURN VALUES + 0 success + !0 error +*/ + + +int STDCALL +mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length) +{ + MYSQL *mysql= stmt->mysql; + DBUG_ENTER("mysql_stmt_prepare"); + + if (!mysql) + { + /* 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)) { - stmt_close(stmt, 1); - DBUG_RETURN(0); + set_stmt_errmsg(stmt, mysql->net.last_error, mysql->net.last_errno, + mysql->net.sqlstate); + DBUG_RETURN(1); } - init_alloc_root(&stmt->mem_root,8192,0); if ((*mysql->methods->read_prepare_result)(mysql, stmt)) { - stmt_close(stmt, 1); - DBUG_RETURN(0); + 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 + stmt->field_count)))) { set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate); - DBUG_RETURN(0); + DBUG_RETURN(1); } stmt->bind= stmt->params + stmt->param_count; - stmt->state= MY_ST_PREPARE; - stmt->mysql= mysql; - mysql->stmts= list_add(mysql->stmts, &stmt->list); - mysql->status= MYSQL_STATUS_READY; - stmt->list.data= stmt; + stmt->state= MYSQL_STMT_PREPARE_DONE; DBUG_PRINT("info", ("Parameter count: %ld", stmt->param_count)); - DBUG_RETURN(stmt); + 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; /* @@ -1746,25 +2079,30 @@ unsigned int alloc_stmt_fields(MYSQL_STMT *stmt) */ MYSQL_RES * STDCALL -mysql_get_metadata(MYSQL_STMT *stmt) +mysql_stmt_result_metadata(MYSQL_STMT *stmt) { MYSQL_RES *result; - DBUG_ENTER("mysql_get_metadata"); + 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); } @@ -1774,9 +2112,9 @@ mysql_get_metadata(MYSQL_STMT *stmt) */ MYSQL_RES * STDCALL -mysql_param_result(MYSQL_STMT *stmt) +mysql_stmt_param_metadata(MYSQL_STMT *stmt) { - DBUG_ENTER("mysql_param_result"); + DBUG_ENTER("mysql_stmt_param_metadata"); if (!stmt->param_count) DBUG_RETURN(0); @@ -1958,11 +2296,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, @@ -1996,21 +2332,24 @@ static my_bool execute(MYSQL_STMT * stmt, char *packet, ulong length) { MYSQL *mysql= stmt->mysql; NET *net= &mysql->net; - char buff[MYSQL_STMT_HEADER]; + char buff[4 /* size of stmt id */ + + 5 /* execution flags */]; DBUG_ENTER("execute"); DBUG_PRINT("enter",("packet: %s, length :%d",packet ? packet :" ", length)); mysql->last_used_con= mysql; int4store(buff, stmt->stmt_id); /* Send stmt id to server */ - if (cli_advanced_command(mysql, COM_EXECUTE, buff, - MYSQL_STMT_HEADER, packet, - length, 1) || + buff[4]= (char) 0; /* no flags */ + int4store(buff+5, 1); /* iteration count */ + if (cli_advanced_command(mysql, COM_EXECUTE, buff, sizeof(buff), + packet, length, 1) || (*mysql->methods->read_query_result)(mysql)) { set_stmt_errmsg(stmt, net->last_error, net->last_errno, net->sqlstate); DBUG_RETURN(1); } stmt->affected_rows= mysql->affected_rows; + stmt->insert_id= mysql->insert_id; DBUG_RETURN(0); } @@ -2028,6 +2367,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; @@ -2062,7 +2407,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)); @@ -2072,21 +2416,143 @@ 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) +{ + if (stmt->data_cursor) + { + *row= (uchar *) stmt->data_cursor->data; + stmt->data_cursor= stmt->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_execute(MYSQL_STMT *stmt) +int STDCALL mysql_stmt_execute(MYSQL_STMT *stmt) { - DBUG_ENTER("mysql_execute"); + 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); } @@ -2095,9 +2561,9 @@ int STDCALL mysql_execute(MYSQL_STMT *stmt) Return total parameters count in the statement */ -ulong STDCALL mysql_param_count(MYSQL_STMT * stmt) +ulong STDCALL mysql_stmt_param_count(MYSQL_STMT * stmt) { - DBUG_ENTER("mysql_param_count"); + DBUG_ENTER("mysql_stmt_param_count"); DBUG_RETURN(stmt->param_count); } @@ -2111,6 +2577,15 @@ my_ulonglong STDCALL mysql_stmt_affected_rows(MYSQL_STMT *stmt) } +/* + Return last inserted id for auto_increment columns +*/ + +my_ulonglong STDCALL mysql_stmt_insert_id(MYSQL_STMT *stmt) +{ + return stmt->insert_id; +} + static my_bool int_is_null_true= 1; /* Used for MYSQL_TYPE_NULL */ static my_bool int_is_null_false= 0; @@ -2118,11 +2593,21 @@ static my_bool int_is_null_false= 0; Setup the parameter data buffers from application */ -my_bool STDCALL mysql_bind_param(MYSQL_STMT *stmt, MYSQL_BIND * bind) +my_bool STDCALL mysql_stmt_bind_param(MYSQL_STMT *stmt, MYSQL_BIND * bind) { uint count=0; MYSQL_BIND *param, *end; - DBUG_ENTER("mysql_bind_param"); + 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, @@ -2135,13 +2620,6 @@ my_bool STDCALL mysql_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; @@ -2208,10 +2686,16 @@ my_bool STDCALL mysql_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); } @@ -2224,7 +2708,7 @@ my_bool STDCALL mysql_bind_param(MYSQL_STMT *stmt, MYSQL_BIND * bind) Send long data in pieces to the server SYNOPSIS - mysql_send_long_data() + mysql_stmt_send_long_data() stmt Statement handler param_number Parameter number (0 - N-1) data Data to send to server @@ -2237,14 +2721,24 @@ my_bool STDCALL mysql_bind_param(MYSQL_STMT *stmt, MYSQL_BIND * bind) my_bool STDCALL -mysql_send_long_data(MYSQL_STMT *stmt, uint param_number, +mysql_stmt_send_long_data(MYSQL_STMT *stmt, uint param_number, const char *data, ulong length) { MYSQL_BIND *param; - DBUG_ENTER("mysql_send_long_data"); + DBUG_ENTER("mysql_stmt_send_long_data"); 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 || @@ -2807,17 +3301,28 @@ static void fetch_result_str(MYSQL_BIND *param, uchar **row) Setup the bind buffers for resultset processing */ -my_bool STDCALL mysql_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind) +my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind) { MYSQL_BIND *param, *end; ulong bind_count; uint param_count= 0; - DBUG_ENTER("mysql_bind_result"); + 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); @@ -2826,7 +3331,7 @@ my_bool STDCALL mysql_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind) { /* Set param->is_null to point to a dummy variable if it's not set. - This is to make the excute code easier + This is to make the execute code easier */ if (!param->is_null) param->is_null= ¶m->internal_is_null; @@ -2898,7 +3403,7 @@ my_bool STDCALL mysql_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind) DBUG_RETURN(1); } } - stmt->res_buffers= 1; + stmt->bind_result_done= TRUE; DBUG_RETURN(0); } @@ -2912,9 +3417,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 */ @@ -2961,59 +3475,24 @@ int cli_unbuffered_fetch(MYSQL *mysql, char **row) Fetch and return row data to bound buffers, if any */ -int STDCALL mysql_fetch(MYSQL_STMT *stmt) +int STDCALL mysql_stmt_fetch(MYSQL_STMT *stmt) { - MYSQL *mysql= stmt->mysql; + int rc; uchar *row; - DBUG_ENTER("mysql_fetch"); + 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); } @@ -3021,10 +3500,10 @@ no_data: Fetch data for one specified column data SYNOPSIS - mysql_fetch_column() + mysql_stmt_fetch_column() stmt Prepared statement handler bind Where data should be placed. Should be filled in as - when calling mysql_bind_result() + when calling mysql_stmt_bind_result() column Column to fetch (first column is 0) ulong offset Offset in result data (to fetch blob in pieces) This is normally 0 @@ -3033,14 +3512,23 @@ no_data: 1 error */ -int STDCALL mysql_fetch_column(MYSQL_STMT *stmt, MYSQL_BIND *bind, - uint column, ulong offset) +int STDCALL mysql_stmt_fetch_column(MYSQL_STMT *stmt, MYSQL_BIND *bind, + uint column, ulong offset) { MYSQL_BIND *param= stmt->bind+column; - DBUG_ENTER("mysql_fetch_column"); + DBUG_ENTER("mysql_stmt_fetch_column"); + + 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 (!stmt->current_row) - goto no_data; if (param->null_field) { @@ -3061,10 +3549,6 @@ int STDCALL mysql_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 */ } @@ -3072,70 +3556,46 @@ no_data: Read all rows of data from server (binary format) */ -MYSQL_DATA *cli_read_binary_rows(MYSQL_STMT *stmt) +int cli_read_binary_rows(MYSQL_STMT *stmt) { ulong pkt_len; uchar *cp; MYSQL *mysql= stmt->mysql; - MYSQL_DATA *result; - MYSQL_ROWS *cur, **prev_ptr; + MYSQL_DATA *result= &stmt->result; + MYSQL_ROWS *cur, **prev_ptr= &result->data; NET *net = &mysql->net; - DBUG_ENTER("read_binary_rows"); - - mysql= mysql->last_used_con; - if ((pkt_len= net_safe_read(mysql)) == packet_error) - { - set_stmt_errmsg(stmt, mysql->net.last_error, mysql->net.last_errno, - mysql->net.sqlstate); - - DBUG_RETURN(0); - } - if (mysql->net.read_pos[0] == 254) /* end of data */ - return 0; + DBUG_ENTER("cli_read_binary_rows"); - if (!(result=(MYSQL_DATA*) my_malloc(sizeof(MYSQL_DATA), - MYF(MY_WME | MY_ZEROFILL)))) - { - set_stmt_errmsg(stmt, ER(CR_OUT_OF_MEMORY), CR_OUT_OF_MEMORY, - unknown_sqlstate); - DBUG_RETURN(0); - } - init_alloc_root(&result->alloc,8192,0); /* Assume rowlength < 8192 */ - result->alloc.min_malloc= sizeof(MYSQL_ROWS); - prev_ptr= &result->data; - result->rows= 0; + mysql= mysql->last_used_con; - while (*(cp=net->read_pos) != 254 || pkt_len >= 8) + while ((pkt_len= net_safe_read(mysql)) != packet_error) { - result->rows++; - - if (!(cur= (MYSQL_ROWS*) alloc_root(&result->alloc,sizeof(MYSQL_ROWS))) || - !(cur->data= ((MYSQL_ROW) alloc_root(&result->alloc, pkt_len)))) + cp= net->read_pos; + if (cp[0] != 254 || pkt_len >= 8) { - free_rows(result); - set_stmt_errmsg(stmt, ER(CR_OUT_OF_MEMORY), CR_OUT_OF_MEMORY, - unknown_sqlstate); - DBUG_RETURN(0); + if (!(cur= (MYSQL_ROWS*) alloc_root(&result->alloc, + sizeof(MYSQL_ROWS) + pkt_len - 1))) + { + set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate); + DBUG_RETURN(1); + } + cur->data= (MYSQL_ROW) (cur+1); + *prev_ptr= cur; + prev_ptr= &cur->next; + memcpy((char *) cur->data, (char *) cp+1, pkt_len-1); + ++result->rows; } - *prev_ptr= cur; - prev_ptr= &cur->next; - memcpy(cur->data, (char*)cp+1, pkt_len-1); - - if ((pkt_len=net_safe_read(mysql)) == packet_error) + else { - free_rows(result); + /* end of data */ + *prev_ptr= 0; + mysql->warning_count= uint2korr(cp+1); + mysql->server_status= uint2korr(cp+3); DBUG_RETURN(0); } } - *prev_ptr= 0; - if (pkt_len > 1) - { - mysql->warning_count= uint2korr(cp+1); - mysql->server_status= uint2korr(cp+3); - DBUG_PRINT("info",("warning_count: %ld", mysql->warning_count)); - } - DBUG_PRINT("exit",("Got %d rows",result->rows)); - DBUG_RETURN(result); + set_stmt_errmsg(stmt, net->last_error, net->last_errno, net->sqlstate); + DBUG_RETURN(1); } @@ -3146,41 +3606,40 @@ MYSQL_DATA *cli_read_binary_rows(MYSQL_STMT *stmt) int STDCALL mysql_stmt_store_result(MYSQL_STMT *stmt) { MYSQL *mysql= stmt->mysql; - MYSQL_RES *result; + MYSQL_DATA *result= &stmt->result; DBUG_ENTER("mysql_stmt_store_result"); mysql= mysql->last_used_con; 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), - MYF(MY_WME | MY_ZEROFILL)))) + if (result->data) { - set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate); - DBUG_RETURN(1); + free_root(&result->alloc, MYF(MY_KEEP_PREALLOC)); + result->data= NULL; + result->rows= 0; + stmt->data_cursor= NULL; } - result->methods= mysql->methods; - stmt->result_buffered= 1; - if (!(result->data= (*stmt->mysql->methods->read_binary_rows)(stmt))) + if ((*mysql->methods->read_binary_rows)(stmt)) { - my_free((gptr) result,MYF(0)); - DBUG_RETURN(0); + free_root(&result->alloc, MYF(MY_KEEP_PREALLOC)); + result->data= NULL; + result->rows= 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; - result->fields= stmt->fields; - result->field_count= stmt->field_count; - stmt->result= result; - DBUG_RETURN(0); /* Data buffered, must be fetched with mysql_fetch() */ + + stmt->data_cursor= result->data; + mysql->affected_rows= stmt->affected_rows= result->rows; + 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() */ } @@ -3191,19 +3650,11 @@ int STDCALL mysql_stmt_store_result(MYSQL_STMT *stmt) MYSQL_ROW_OFFSET STDCALL mysql_stmt_row_seek(MYSQL_STMT *stmt, MYSQL_ROW_OFFSET row) { - MYSQL_RES *result; + MYSQL_ROW_OFFSET offset= stmt->data_cursor; DBUG_ENTER("mysql_stmt_row_seek"); - if ((result= stmt->result)) - { - MYSQL_ROW_OFFSET return_value= result->data_cursor; - result->current_row= 0; - result->data_cursor= row; - DBUG_RETURN(return_value); - } - - DBUG_PRINT("exit", ("stmt doesn't contain any resultset")); - DBUG_RETURN(0); + stmt->data_cursor= row; + DBUG_RETURN(offset); } @@ -3216,11 +3667,7 @@ mysql_stmt_row_tell(MYSQL_STMT *stmt) { DBUG_ENTER("mysql_stmt_row_tell"); - if (stmt->result) - DBUG_RETURN(stmt->result->data_cursor); - - DBUG_PRINT("exit", ("stmt doesn't contain any resultset")); - DBUG_RETURN(0); + DBUG_RETURN(stmt->data_cursor); } @@ -3231,20 +3678,13 @@ mysql_stmt_row_tell(MYSQL_STMT *stmt) void STDCALL mysql_stmt_data_seek(MYSQL_STMT *stmt, my_ulonglong row) { - MYSQL_RES *result; + MYSQL_ROWS *tmp= stmt->result.data; DBUG_ENTER("mysql_stmt_data_seek"); DBUG_PRINT("enter",("row id to seek: %ld",(long) row)); - if ((result= stmt->result)) - { - MYSQL_ROWS *tmp= 0; - if (result->data) - for (tmp=result->data->data; row-- && tmp ; tmp = tmp->next) ; - result->current_row= 0; - result->data_cursor= tmp; - } - else - DBUG_PRINT("exit", ("stmt doesn't contain any resultset")); + for (; tmp && row; --row, tmp= tmp->next) + ; + stmt->data_cursor= tmp; DBUG_VOID_RETURN; } @@ -3257,10 +3697,44 @@ my_ulonglong STDCALL mysql_stmt_num_rows(MYSQL_STMT *stmt) { DBUG_ENTER("mysql_stmt_num_rows"); - if (stmt->result) - DBUG_RETURN(stmt->result->row_count); + DBUG_RETURN(stmt->result.rows); +} + +my_bool STDCALL mysql_stmt_free_result(MYSQL_STMT *stmt) +{ + MYSQL_DATA *result= &stmt->result; + DBUG_ENTER("mysql_stmt_free_result"); + + DBUG_ASSERT(stmt != 0); - DBUG_PRINT("exit", ("stmt doesn't contain any resultset")); + if ((int) stmt->state > (int) MYSQL_STMT_INIT_DONE) + { + MYSQL *mysql= stmt->mysql; + + if (result->data) + { + /* Result buffered */ + free_root(&result->alloc, MYF(MY_KEEP_PREALLOC)); + result->data= NULL; + result->rows= 0; + stmt->data_cursor= NULL; + } + + 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); } @@ -3274,81 +3748,53 @@ my_ulonglong STDCALL mysql_stmt_num_rows(MYSQL_STMT *stmt) SYNOPSIS 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_list) +my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt) { - MYSQL *mysql; + MYSQL *mysql= stmt->mysql; + int rc= 0; DBUG_ENTER("mysql_stmt_close"); - DBUG_ASSERT(stmt != 0); - - if (!(mysql= stmt->mysql)) - { - 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) + free_root(&stmt->result.alloc, MYF(0)); + free_root(&stmt->mem_root, MYF(0)); + + if (mysql) { - char buff[4]; - int4store(buff, stmt->stmt_id); - if (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)); - if (!skip_list) - mysql->stmts= list_delete(mysql->stmts, &stmt->list); - mysql->status= MYSQL_STATUS_READY; - my_free((gptr) stmt->query, MYF(MY_WME)); - 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)); } /* @@ -3361,6 +3807,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 */ |