From: Date: May 12 2005 9:16am Subject: bk commit into 5.0 tree (konstantin:1.1938) BUG#9478 List-Archive: http://lists.mysql.com/internals/24804 X-Bug: 9478 Message-Id: <20050512071621.42FFAA900@dragonfly.local> Below is the list of changes that have just been committed into a local 5.0 repository of kostja. When kostja does a push these changes will be propagated to the main repository and, within 24 hours after the push, to the public repository. For information on how to access the public repository see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html ChangeSet 1.1938 05/05/12 11:16:12 konstantin@stripped +9 -0 A fix and test case for Bug#9478 "mysql_stmt_attr_set mysql_stmt_execute" (crash on attempt to re-execute a statement with an open cursor) + post-review fixes. tests/mysql_client_test.c 1.116 05/05/12 11:16:08 konstantin@stripped +175 -22 A test case for Bug#9478, test the case of mysql_stmt_reset called for client-side cached result set and for the case with open cursor. All strcpy replaced with strmov (review request). sql/sql_select.h 1.85 05/05/12 11:16:07 konstantin@stripped +4 -0 A comment added. sql/sql_select.cc 1.330 05/05/12 11:16:07 konstantin@stripped +8 -10 free_items and free_root moved to Cursor::close(). sql/sql_prepare.cc 1.114 05/05/12 11:16:07 konstantin@stripped +20 -7 Return error when we fetch while there is no open cursor and when we call execute while there is a pending cursor. Fix mysql_stmt_reset to close the open cursor if there is any. sql/share/errmsg.txt 1.31 05/05/12 11:16:07 konstantin@stripped +8 -4 Fix format of ER_UNKNOWN_STMT_HANDLER error message (needs to be fixed separately in 4.1). Add two new error messages for the case when we fetch from when there is no cursor and for the case when we attempt to execute a statement while there is a cursor. sql-common/client.c 1.72 05/05/12 11:16:07 konstantin@stripped +2 -0 Fix one place where we flushed the pending result set of a statement, but didn't set unbuffered_fetch_cancelled flag. libmysql/libmysql.c 1.212 05/05/12 11:16:07 konstantin@stripped +71 -49 Move the code which frees result sets on client and closes the cursor on server, resets long data state on client and server. This makes one function out of two (mysql_stmt_reset and mysql_stmt_free_result), thus aggregating all related reset work in one place. libmysql/errmsg.c 1.34 05/05/12 11:16:06 konstantin@stripped +3 -0 Error message text for CR_NO_RESULT_SET include/errmsg.h 1.26 05/05/12 11:16:06 konstantin@stripped +2 -1 Add a special error message when we attempt to mysql_stmt_fetch from a statement which has no result set. # This is a BitKeeper patch. What follows are the unified diffs for the # set of deltas contained in the patch. The rest of the patch, the part # that BitKeeper cares about, is below these diffs. # User: konstantin # Host: dragonfly.local # Root: /media/sda1/mysql/mysql-5.0-9478 --- 1.25/include/errmsg.h 2004-12-31 01:50:26 +03:00 +++ 1.26/include/errmsg.h 2005-05-12 11:16:06 +04:00 @@ -95,6 +95,7 @@ #define CR_FETCH_CANCELED 2050 #define CR_NO_DATA 2051 #define CR_NO_STMT_METADATA 2052 -#define CR_ERROR_LAST /*Copy last error nr:*/ 2052 +#define CR_NO_RESULT_SET 2053 +#define CR_ERROR_LAST /*Copy last error nr:*/ 2053 /* Add error numbers before CR_ERROR_LAST and change it accordingly. */ --- 1.33/libmysql/errmsg.c 2004-12-31 01:45:44 +03:00 +++ 1.34/libmysql/errmsg.c 2005-05-12 11:16:06 +04:00 @@ -80,6 +80,7 @@ "Row retrieval was canceled by mysql_stmt_close() call", "Attempt to read column without prior row fetch", "Prepared statement contains no metadata", + "Attempt to read a row while there is no result set associated with the statement" "" }; @@ -141,6 +142,7 @@ "Row retrieval was canceled by mysql_stmt_close() call", "Attempt to read column without prior row fetch", "Prepared statement contains no metadata", + "Attempt to read a row while there is no result set associated with the statement" "" }; @@ -200,6 +202,7 @@ "Row retrieval was canceled by mysql_stmt_close() call", "Attempt to read column without prior row fetch", "Prepared statement contains no metadata", + "Attempt to read a row while there is no result set associated with the statement" "" }; #endif --- 1.211/libmysql/libmysql.c 2005-05-09 00:47:43 +04:00 +++ 1.212/libmysql/libmysql.c 2005-05-12 11:16:07 +04:00 @@ -1724,6 +1724,13 @@ static void stmt_update_metadata(MYSQL_STMT *stmt, MYSQL_ROWS *data); static my_bool setup_one_fetch_function(MYSQL_BIND *bind, MYSQL_FIELD *field); +/* Auxilary function used to reset statement handle. */ + +#define RESET_SERVER_SIDE 1 +#define RESET_LONG_DATA 2 + +static my_bool reset_stmt_handle(MYSQL_STMT *stmt, uint flags); + /* Maximum sizes of MYSQL_TYPE_DATE, MYSQL_TYPE_TIME, MYSQL_TYPE_DATETIME values stored in network buffer. @@ -2019,7 +2026,8 @@ /* This is second prepare with another statement */ char buff[MYSQL_STMT_HEADER]; /* 4 bytes - stmt id */ - mysql_stmt_free_result(stmt); + if (reset_stmt_handle(stmt, RESET_LONG_DATA)) + DBUG_RETURN(1); /* These members must be reset for API to function in case of error or misuse. @@ -2702,12 +2710,8 @@ 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; + set_stmt_error(stmt, CR_NO_RESULT_SET, unknown_sqlstate); + return 1; } @@ -2817,7 +2821,8 @@ DBUG_RETURN(1); } - mysql_stmt_free_result(stmt); + if (reset_stmt_handle(stmt, 0)) + DBUG_RETURN(1); /* No need to check for stmt->state: if the statement wasn't prepared we'll get 'unknown statement handler' error from server. @@ -4805,16 +4810,21 @@ 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); +/* + Free the client side memory buffers, reset long data state + on client if necessary, and reset the server side statement if + this has been requested. +*/ +static my_bool reset_stmt_handle(MYSQL_STMT *stmt, uint flags) +{ + /* If statement hasn't been prepared there is nothing to reset */ if ((int) stmt->state > (int) MYSQL_STMT_INIT_DONE) { MYSQL *mysql= stmt->mysql; + MYSQL_DATA *result= &stmt->result; + my_bool has_cursor= stmt->read_row_func == stmt_read_row_from_cursor; if (result->data) { @@ -4824,23 +4834,58 @@ result->rows= 0; stmt->data_cursor= NULL; } - - if (mysql && stmt->field_count && - (int) stmt->state > (int) MYSQL_STMT_PREPARE_DONE) + if (flags & RESET_LONG_DATA) { - if (mysql->unbuffered_fetch_owner == &stmt->unbuffered_fetch_cancelled) - mysql->unbuffered_fetch_owner= 0; - if (mysql->status != MYSQL_STATUS_READY) + MYSQL_BIND *param= stmt->params, *param_end= param + stmt->param_count; + /* Clear long_data_used flags */ + for (; param < param_end; param++) + param->long_data_used= 0; + } + stmt->read_row_func= stmt_read_row_no_data; + if (mysql) + { + if ((int) stmt->state > (int) MYSQL_STMT_PREPARE_DONE) { - /* There is a result set and it belongs to this statement */ - (*mysql->methods->flush_use_result)(mysql); - mysql->status= MYSQL_STATUS_READY; + if (mysql->unbuffered_fetch_owner == &stmt->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner= 0; + if (stmt->field_count && mysql->status != MYSQL_STATUS_READY) + { + /* There is a result set and it belongs to this statement */ + (*mysql->methods->flush_use_result)(mysql); + if (mysql->unbuffered_fetch_owner) + *mysql->unbuffered_fetch_owner= TRUE; + mysql->status= MYSQL_STATUS_READY; + } + } + if (has_cursor || (flags & RESET_SERVER_SIDE)) + { + /* + Reset the server side statement and close the server side + cursor if it exists. + */ + char buff[MYSQL_STMT_HEADER]; /* packet header: 4 bytes for stmt id */ + int4store(buff, stmt->stmt_id); + if ((*mysql->methods->advanced_command)(mysql, COM_RESET_STMT, buff, + sizeof(buff), 0, 0, 0)) + { + set_stmt_errmsg(stmt, mysql->net.last_error, mysql->net.last_errno, + mysql->net.sqlstate); + stmt->state= MYSQL_STMT_INIT_DONE; + return 1; + } } } stmt->state= MYSQL_STMT_PREPARE_DONE; - stmt->read_row_func= stmt_read_row_no_data; } - DBUG_RETURN(0); + return 0; +} + +my_bool STDCALL mysql_stmt_free_result(MYSQL_STMT *stmt) +{ + DBUG_ENTER("mysql_stmt_free_result"); + + /* Free the client side and close the server side cursor if there is one */ + DBUG_RETURN(reset_stmt_handle(stmt, RESET_LONG_DATA)); } /******************************************************************** @@ -4913,33 +4958,10 @@ my_bool STDCALL mysql_stmt_reset(MYSQL_STMT *stmt) { - char buff[MYSQL_STMT_HEADER]; /* packet header: 4 bytes for stmt id */ - MYSQL *mysql; - MYSQL_BIND *param, *param_end; 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 */ - if ((*mysql->methods->advanced_command)(mysql, COM_RESET_STMT, buff, - sizeof(buff), 0, 0, 0)) - { - set_stmt_errmsg(stmt, mysql->net.last_error, mysql->net.last_errno, - mysql->net.sqlstate); - DBUG_RETURN(1); - } - - /* Clear long_data_used for next call (as we do in mysql_stmt_execute() */ - for (param= stmt->params, param_end= param + stmt->param_count; - param < param_end; - param++) - param->long_data_used= 0; - - DBUG_RETURN(0); + /* Reset the client and server sides of the prepared statement */ + DBUG_RETURN(reset_stmt_handle(stmt, RESET_SERVER_SIDE | RESET_LONG_DATA)); } /* --- 1.329/sql/sql_select.cc 2005-05-09 00:41:58 +04:00 +++ 1.330/sql/sql_select.cc 2005-05-12 11:16:07 +04:00 @@ -1742,6 +1742,7 @@ /* XXX: thd->locked_tables is not changed. What problems can we have with it if cursor is open? + TODO: must be fixed because of the prelocked mode. */ /* TODO: grab thd->free_list here? @@ -1871,10 +1872,6 @@ } else if (error != NESTED_LOOP_KILLED) my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0)); - /* free cursor memory */ - free_items(free_list); - free_list= 0; - free_root(&main_mem_root, MYF(0)); } } @@ -1914,6 +1911,13 @@ } join= 0; unit= 0; + free_items(free_list); + free_list= 0; + /* + Must be last, as some memory might be allocated for free purposes, + like in free_tmp_table() (TODO: fix this issue) + */ + free_root(mem_root, MYF(0)); DBUG_VOID_RETURN; } @@ -1922,12 +1926,6 @@ { if (is_open()) close(); - free_items(free_list); - /* - Must be last, as some memory might be allocated for free purposes, - like in free_tmp_table() (TODO: fix this issue) - */ - free_root(&main_mem_root, MYF(0)); } /*********************************************************************/ --- 1.84/sql/sql_select.h 2005-05-09 13:26:47 +04:00 +++ 1.85/sql/sql_select.h 2005-05-12 11:16:07 +04:00 @@ -364,6 +364,10 @@ /* Server-side cursor (now stands only for basic read-only cursor) See class implementation in sql_select.cc + A cursor has its own runtime state - list of used items and memory root of + used memory - which is different from Prepared statement runtime: it must + be different at least for the purpose of reusing the same prepared + statement for many cursors. */ class Cursor: public Sql_alloc, public Item_arena --- 1.30/sql/share/errmsg.txt 2005-05-05 16:20:46 +04:00 +++ 1.31/sql/share/errmsg.txt 2005-05-12 11:16:07 +04:00 @@ -4766,13 +4766,13 @@ swe "Subquery returnerade mer än 1 rad" ukr "ð¦ÄÚÁÐÉÔ ÐÏ×ÅÒÔÁ¤ Â¦ÌØÛ ÎiÖ 1 ÚÁÐÉÓ" ER_UNKNOWN_STMT_HANDLER - dan "Unknown prepared statement handler (%ld) given to %s" + dan "Unknown prepared statement handler (%.*s) given to %s" eng "Unknown prepared statement handler (%.*s) given to %s" ger "Unbekannter Prepared-Statement-Handler (%.*s) für %s angegeben" por "Desconhecido manipulador de declaração preparado (%.*s) determinado para %s" - spa "Desconocido preparado comando handler (%ld) dado para %s" - swe "Okänd PREPARED STATEMENT id (%ld) var given till %s" - ukr "Unknown prepared statement handler (%ld) given to %s" + spa "Desconocido preparado comando handler (%.*s) dado para %s" + swe "Okänd PREPARED STATEMENT id (%.*s) var given till %s" + ukr "Unknown prepared statement handler (%.*s) given to %s" ER_CORRUPT_HELP_DB eng "Help database is corrupt or does not exist" ger "Die Hilfe-Datenbank ist beschädigt oder existiert nicht" @@ -5352,3 +5352,7 @@ eng "This routine is declared to be non-deterministic and to modify data and binary logging is enabled (you *might* want to use the less safe log_bin_trust_routine_creators variable)" ER_BINLOG_CREATE_ROUTINE_NEED_SUPER eng "You do not have SUPER privilege and binary logging is enabled (you *might* want to use the less safe log_bin_trust_routine_creators variable)" +ER_EXEC_STMT_WITH_OPEN_CURSOR + eng "You can't execute a prepared statement which has an open cursor associated with it. Reset the statement to re-execute it." +ER_STMT_HAS_NO_OPEN_CURSOR + eng "The statement (%d) has no open cursor." --- 1.71/sql-common/client.c 2005-04-13 14:22:15 +04:00 +++ 1.72/sql-common/client.c 2005-05-12 11:16:07 +04:00 @@ -864,6 +864,8 @@ { (*mysql->methods->flush_use_result)(mysql); mysql->status=MYSQL_STATUS_READY; + if (mysql->unbuffered_fetch_owner) + *mysql->unbuffered_fetch_owner= TRUE; } } free_rows(result->data); --- 1.113/sql/sql_prepare.cc 2005-04-30 09:54:29 +04:00 +++ 1.114/sql/sql_prepare.cc 2005-05-12 11:16:07 +04:00 @@ -135,7 +135,8 @@ if (stmt == 0 || stmt->type() != Item_arena::PREPARED_STATEMENT) { char llbuf[22]; - my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), 22, llstr(id, llbuf), where); + my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), sizeof(llbuf), llstr(id, llbuf), + where); return 0; } return (Prepared_statement *) stmt; @@ -1969,7 +1970,7 @@ { ulong stmt_id= uint4korr(packet); ulong flags= (ulong) ((uchar) packet[4]); - Cursor *cursor= 0; + Cursor *cursor; /* Query text for binary log, or empty string if the query is not put into binary log. @@ -1995,6 +1996,13 @@ DBUG_VOID_RETURN; } + cursor= stmt->cursor; + if (cursor && cursor->is_open()) + { + my_error(ER_EXEC_STMT_WITH_OPEN_CURSOR, MYF(0)); + DBUG_VOID_RETURN; + } + DBUG_ASSERT(thd->free_list == NULL); mysql_reset_thd_for_next_command(thd); if (flags & (ulong) CURSOR_TYPE_READ_ONLY) @@ -2013,7 +2021,7 @@ else { DBUG_PRINT("info",("Using READ_ONLY cursor")); - if (!stmt->cursor && + if (!cursor && !(cursor= stmt->cursor= new (&stmt->main_mem_root) Cursor())) DBUG_VOID_RETURN; /* If lex->result is set, mysql_execute_command will use it */ @@ -2208,13 +2216,15 @@ Statement *stmt; DBUG_ENTER("mysql_stmt_fetch"); - if (!(stmt= thd->stmt_map.find(stmt_id)) || - !stmt->cursor || - !stmt->cursor->is_open()) + if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_fetch"))) + DBUG_VOID_RETURN; + + if (!stmt->cursor || !stmt->cursor->is_open()) { - my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), stmt_id, "fetch"); + my_error(ER_STMT_HAS_NO_OPEN_CURSOR, MYF(0)); DBUG_VOID_RETURN; } + thd->current_arena= stmt; thd->set_n_backup_statement(stmt, &thd->stmt_backup); stmt->cursor->init_thd(thd); @@ -2265,6 +2275,9 @@ if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_reset"))) DBUG_VOID_RETURN; + + if (stmt->cursor && stmt->cursor->is_open()) + stmt->cursor->close(); stmt->state= Item_arena::PREPARED; --- 1.115/tests/mysql_client_test.c 2005-05-02 21:19:34 +04:00 +++ 1.116/tests/mysql_client_test.c 2005-05-12 11:16:08 +04:00 @@ -2269,7 +2269,7 @@ check_execute(stmt, rc); int_data= 1; - strcpy(str_data, "hh"); + strmov(str_data, "hh"); str_length= strlen(str_data); rc= mysql_stmt_execute(stmt); @@ -7288,7 +7288,7 @@ rc= mysql_stmt_bind_param(stmt, bind); check_execute(stmt, rc); - strcpy(data, "8.0"); + strmov(data, "8.0"); rc= mysql_stmt_execute(stmt); check_execute(stmt, rc); @@ -7306,7 +7306,7 @@ rc= mysql_stmt_fetch(stmt); DIE_UNLESS(rc == MYSQL_NO_DATA); - strcpy(data, "5.61"); + strmov(data, "5.61"); rc= mysql_stmt_execute(stmt); check_execute(stmt, rc); @@ -7331,7 +7331,7 @@ rc= mysql_stmt_fetch(stmt); DIE_UNLESS(rc == MYSQL_NO_DATA); - strcpy(data, "10.22"); is_null= 0; + strmov(data, "10.22"); is_null= 0; rc= mysql_stmt_execute(stmt); check_execute(stmt, rc); @@ -8510,13 +8510,13 @@ myquery(rc); /* PIPES_AS_CONCAT */ - strcpy(query, "SET SQL_MODE= \"PIPES_AS_CONCAT\""); + strmov(query, "SET SQL_MODE= \"PIPES_AS_CONCAT\""); if (!opt_silent) fprintf(stdout, "\n With %s", query); rc= mysql_query(mysql, query); myquery(rc); - strcpy(query, "INSERT INTO test_piping VALUES(?||?)"); + strmov(query, "INSERT INTO test_piping VALUES(?||?)"); if (!opt_silent) fprintf(stdout, "\n query: %s", query); stmt= mysql_simple_prepare(mysql, query); @@ -8542,7 +8542,7 @@ rc= mysql_stmt_bind_param(stmt, bind); check_execute(stmt, rc); - strcpy(c1, "My"); strcpy(c2, "SQL"); + strmov(c1, "My"); strmov(c2, "SQL"); rc= mysql_stmt_execute(stmt); check_execute(stmt, rc); @@ -8552,20 +8552,20 @@ rc= mysql_query(mysql, "DELETE FROM test_piping"); myquery(rc); - strcpy(query, "SELECT connection_id ()"); + strmov(query, "SELECT connection_id ()"); if (!opt_silent) fprintf(stdout, "\n query: %s", query); stmt= mysql_simple_prepare(mysql, query); check_stmt_r(stmt); /* ANSI */ - strcpy(query, "SET SQL_MODE= \"ANSI\""); + strmov(query, "SET SQL_MODE= \"ANSI\""); if (!opt_silent) fprintf(stdout, "\n With %s", query); rc= mysql_query(mysql, query); myquery(rc); - strcpy(query, "INSERT INTO test_piping VALUES(?||?)"); + strmov(query, "INSERT INTO test_piping VALUES(?||?)"); if (!opt_silent) fprintf(stdout, "\n query: %s", query); stmt= mysql_simple_prepare(mysql, query); @@ -8576,7 +8576,7 @@ rc= mysql_stmt_bind_param(stmt, bind); check_execute(stmt, rc); - strcpy(c1, "My"); strcpy(c2, "SQL"); + strmov(c1, "My"); strmov(c2, "SQL"); rc= mysql_stmt_execute(stmt); check_execute(stmt, rc); @@ -8584,7 +8584,7 @@ verify_col_data("test_piping", "name", "MySQL"); /* ANSI mode spaces ... */ - strcpy(query, "SELECT connection_id ()"); + strmov(query, "SELECT connection_id ()"); if (!opt_silent) fprintf(stdout, "\n query: %s", query); stmt= mysql_simple_prepare(mysql, query); @@ -8604,13 +8604,13 @@ mysql_stmt_close(stmt); /* IGNORE SPACE MODE */ - strcpy(query, "SET SQL_MODE= \"IGNORE_SPACE\""); + strmov(query, "SET SQL_MODE= \"IGNORE_SPACE\""); if (!opt_silent) fprintf(stdout, "\n With %s", query); rc= mysql_query(mysql, query); myquery(rc); - strcpy(query, "SELECT connection_id ()"); + strmov(query, "SELECT connection_id ()"); if (!opt_silent) fprintf(stdout, "\n query: %s", query); stmt= mysql_simple_prepare(mysql, query); @@ -9115,7 +9115,7 @@ /* This too should not hang but should return proper error */ rc= mysql_stmt_fetch(stmt); - DIE_UNLESS(rc == MYSQL_NO_DATA); + DIE_UNLESS(rc == 1); /* This too should not hang but should not bark */ rc= mysql_stmt_store_result(stmt); @@ -9124,7 +9124,7 @@ /* This should return proper error */ rc= mysql_stmt_fetch(stmt); check_execute_r(stmt, rc); - DIE_UNLESS(rc == MYSQL_NO_DATA); + DIE_UNLESS(rc == 1); mysql_stmt_close(stmt); @@ -10266,7 +10266,7 @@ my_bool my_null= FALSE; myheader("test_union_param"); - strcpy(my_val, "abc"); + strmov(my_val, "abc"); query= (char*)"select ? as my_col union distinct select ?"; stmt= mysql_simple_prepare(mysql, query); @@ -10548,14 +10548,14 @@ if (!opt_silent) printf("Concat result: '%s'\n", out_buff); check_execute(stmt, rc); - strcpy(canonical_buff, concat_arg0); + strmov(canonical_buff, concat_arg0); strcat(canonical_buff, "ONE"); DIE_UNLESS(strlen(canonical_buff) == out_length && strncmp(out_buff, canonical_buff, out_length) == 0); rc= mysql_stmt_fetch(stmt); check_execute(stmt, rc); - strcpy(canonical_buff + strlen(concat_arg0), "TWO"); + strmov(canonical_buff + strlen(concat_arg0), "TWO"); DIE_UNLESS(strlen(canonical_buff) == out_length && strncmp(out_buff, canonical_buff, out_length) == 0); if (!opt_silent) @@ -10852,7 +10852,8 @@ rc= mysql_stmt_prepare(stmt, query, strlen(query)); check_execute(stmt, rc); - strcpy(str_data, "TEST"); + strmov(str_data, "TEST"); + bzero(bind, sizeof(bind)); bind[0].buffer_type= FIELD_TYPE_STRING; bind[0].buffer= (char *)&str_data; bind[0].buffer_length= 50; @@ -10971,8 +10972,9 @@ " AENAME,T0001.DEPENDVARS AS DEPENDVARS,T0001.INACTIVE AS " " INACTIVE from LTDX T0001 where (T0001.SRTF2 = 0)"); myquery(rc); + bzero(bind, sizeof(bind)); for (i=0; i < 8; i++) { - strcpy(parms[i], "1"); + strmov(parms[i], "1"); bind[i].buffer_type = MYSQL_TYPE_VAR_STRING; bind[i].buffer = (char *)&parms[i]; bind[i].buffer_length = 100; @@ -11019,6 +11021,7 @@ myquery(rc); rc= mysql_query(mysql, "CREATE VIEW vt1 AS SELECT a FROM t1"); myquery(rc); + bzero(bind, sizeof(bind)); for (i= 0; i < 2; i++) { sprintf((char *)&parms[i], "%d", i); bind[i].buffer_type = MYSQL_TYPE_VAR_STRING; @@ -11084,6 +11087,7 @@ rc= mysql_stmt_prepare(select_stmt, query, strlen(query)); check_execute(select_stmt, rc); + bzero(bind, sizeof(bind)); bind[0].buffer_type = FIELD_TYPE_LONG; bind[0].buffer = (char *)&my_val; bind[0].length = &my_length; @@ -11187,6 +11191,7 @@ " F7F8 AS F7F8, F6N4 AS F6N4, F5C8 AS F5C8, F9D8 AS F9D8" " from t1 T0001"); + bzero(bind, sizeof(bind)); for (i= 0; i < 11; i++) { l[i]= 20; @@ -11194,7 +11199,7 @@ bind[i].is_null= 0; bind[i].buffer= (char *)&parm[i]; - strcpy(parm[i], "1"); + strmov(parm[i], "1"); bind[i].buffer_length= 2; bind[i].length= &l[i]; } @@ -12707,6 +12712,7 @@ rc= mysql_query(mysql, "DROP VIEW v1"); myquery(rc); rc= mysql_query(mysql, "DROP TABLE t1, t2"); + mysql_free_result(res); myquery(rc); } @@ -12924,6 +12930,152 @@ /* + We can't have more than one cursor open for a prepared statement. + Test re-executions of a PS with cursor; mysql_stmt_reset must close + the cursor attached to the statement, if there is one. +*/ + +static void test_bug9478() +{ + MYSQL_STMT *stmt; + MYSQL_BIND bind[1]; + char a[6]; + ulong a_len; + int rc, i; + + myheader("test_bug9478"); + + mysql_query(mysql, "drop table if exists t1"); + mysql_query(mysql, "create table t1 (id integer not null primary key, " + " name varchar(20) not null)"); + rc= mysql_query(mysql, "insert into t1 (id, name) values " + " (1, 'aaa'), (2, 'bbb'), (3, 'ccc')"); + myquery(rc); + + stmt= open_cursor("select name from t1 where id=2"); + + bzero(bind, sizeof(bind)); + bind[0].buffer_type= MYSQL_TYPE_STRING; + bind[0].buffer= (char*) a; + bind[0].buffer_length= sizeof(a); + bind[0].length= &a_len; + mysql_stmt_bind_result(stmt, bind); + + for (i= 0; i < 5; i++) + { + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + rc= mysql_stmt_fetch(stmt); + check_execute(stmt, rc); + if (!opt_silent && i == 0) + printf("Fetched row: %s\n", a); + + /* + The query above is a one-row result set. Therefore, there is no + cursor associated with it, as the server won't bother with opening + a cursor for a one-row result set. The first row was read from the + server in the fetch above. But there is eof packet pending in the + network. mysql_stmt_execute will flush the packet and successfully + execute the statement. + */ + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + rc= mysql_stmt_fetch(stmt); + check_execute(stmt, rc); + if (!opt_silent && i == 0) + printf("Fetched row: %s\n", a); + rc= mysql_stmt_fetch(stmt); + DIE_UNLESS(rc == MYSQL_NO_DATA); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + rc= mysql_stmt_fetch(stmt); + check_execute(stmt, rc); + if (!opt_silent && i == 0) + printf("Fetched row: %s\n", a); + + rc= mysql_stmt_reset(stmt); + check_execute(stmt, rc); + rc= mysql_stmt_fetch(stmt); + DIE_UNLESS(rc && mysql_stmt_errno(stmt)); + if (!opt_silent && i == 0) + printf("Got error (as expected): %s\n", mysql_stmt_error(stmt)); + } + rc= mysql_stmt_close(stmt); + DIE_UNLESS(rc == 0); + + /* Test the case with a server side cursor */ + stmt= open_cursor("select name from t1"); + + mysql_stmt_bind_result(stmt, bind); + + for (i= 0; i < 5; i++) + { + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + rc= mysql_stmt_fetch(stmt); + check_execute(stmt, rc); + if (!opt_silent && i == 0) + printf("Fetched row: %s\n", a); + /* + Although protocol-wise an attempt to execute a statement which + already has an open cursor associated with it will yield an error, + the client library behavior tested here is consistent with + the non-cursor execution scenario: mysql_stmt_execute will + silently close the cursor if necessary. + */ + { + char buff[9]; + bzero(buff, sizeof(buff)); + /* Fill in the execute packet */ + int4store(buff, stmt->stmt_id); + int4store(buff+5, 1); + rc= ((*mysql->methods->advanced_command)(mysql, COM_EXECUTE, buff, + sizeof(buff), 0,0,1) || + (*mysql->methods->read_query_result)(mysql)); + DIE_UNLESS(rc); + if (!opt_silent && i == 0) + printf("Got error (as expected): %s\n", mysql_error(mysql)); + } + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + while (! (rc= mysql_stmt_fetch(stmt))) + { + if (!opt_silent && i == 0) + printf("Fetched row: %s\n", a); + } + DIE_UNLESS(rc == MYSQL_NO_DATA); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + rc= mysql_stmt_fetch(stmt); + check_execute(stmt, rc); + if (!opt_silent && i == 0) + printf("Fetched row: %s\n", a); + + rc= mysql_stmt_reset(stmt); + check_execute(stmt, rc); + rc= mysql_stmt_fetch(stmt); + DIE_UNLESS(rc && mysql_stmt_errno(stmt)); + if (!opt_silent && i == 0) + printf("Got error (as expected): %s\n", mysql_stmt_error(stmt)); + } + + rc= mysql_stmt_close(stmt); + DIE_UNLESS(rc == 0); + + rc= mysql_query(mysql, "drop table t1"); + myquery(rc); +} + + +/* Read and parse arguments and MySQL options from my.cnf */ @@ -13153,6 +13305,7 @@ { "test_bug8880", test_bug8880 }, { "test_bug9159", test_bug9159 }, { "test_bug9520", test_bug9520 }, + { "test_bug9478", test_bug9478 }, { 0, 0 } };