Index: driver/prepare.c =================================================================== --- driver/prepare.c (revision 250) +++ driver/prepare.c (working copy) @@ -45,23 +45,36 @@ #include "myodbc3.h" #ifndef _UNIX_ - #include +# include #endif /* !_UNIX_ */ #include #include -/* - @type : ODBC 1.0 API - @purpose : prepares an SQL string for execution +/** + Prepare a statement for later execution. + + @param[in] hStmt Handle of the statement + @param[in] szSqlStr The statement to prepare + @param[in] cbSqlStr The length of the statement (or @c SQL_NTS if it is + NUL-terminated) */ - -SQLRETURN SQL_API SQLPrepare(SQLHSTMT hstmt,SQLCHAR FAR *szSqlStr, +SQLRETURN SQL_API SQLPrepare(SQLHSTMT hStmt, SQLCHAR *szSqlStr, SQLINTEGER cbSqlStr) { - MYODBCDbgEnter; - MYODBCDbgReturnReturn( my_SQLPrepare( hstmt, szSqlStr, cbSqlStr ) ); + STMT *stmt= (STMT *)hStmt; + MYODBCDbgEnter; + /* + We free orig_query here, instead of my_SQLPrepare, because + my_SQLPrepare is used by my_pos_update() when a statement requires + additional parameters. + */ + if (stmt->orig_query) + my_free(stmt->orig_query,MYF(0)); + + MYODBCDbgReturnReturn(my_SQLPrepare(hStmt, szSqlStr, cbSqlStr)); } + /* @type : myodbc3 internal @purpose : prepares an SQL string for execution @@ -185,6 +198,8 @@ *pcLastCloseBrace = ' '; stmt->param_count= param_count; + /* Reset current_param so that SQLParamData starts fresh. */ + stmt->current_param= 0; stmt->query_end= pos; stmt->state= ST_PREPARED; MYODBCDbgInfo( "Parameter count: %ld", stmt->param_count ); Index: driver/execute.c =================================================================== --- driver/execute.c (revision 250) +++ driver/execute.c (working copy) @@ -130,6 +130,18 @@ if ( query != stmt->query ) my_free((gptr) query,MYF(0)); + /* + If the original query was modified, we reset stmt->query so that the + next execution re-starts with the original query. + */ + if (stmt->orig_query) + { + my_free((gptr) stmt->query,MYF(0)); + stmt->query= stmt->orig_query; + stmt->query_end= stmt->orig_query_end; + stmt->orig_query= NULL; + } + MYODBCDbgReturnReturn( error ); } @@ -596,7 +608,7 @@ SQLRETURN my_SQLExecute( STMT FAR *pStmt ) { - char * query; + char *query, *cursor_pos; uint i; uint nIndex; PARAM_BIND *param; @@ -614,9 +626,21 @@ if ( !pStmt->query ) MYODBCDbgReturnReturn( set_error( pStmt, MYERR_S1010, "No previous SQLPrepare done", 0 ) ); - if ( check_if_positioned_cursor_exists( pStmt, &pStmtCursor ) ) + if (cursor_pos= check_if_positioned_cursor_exists(pStmt, &pStmtCursor)) { - MYODBCDbgReturnReturn( do_my_pos_cursor( pStmt, pStmtCursor ) ); + /* Save a copy of the query, because we're about to modify it. */ + pStmt->orig_query= my_strdup(pStmt->query, MYF(0)); + if (!pStmt->orig_query) + { + MYODBCDbgReturnReturn(set_error(pStmt,MYERR_S1001,NULL,4001)); + } + pStmt->orig_query_end= pStmt->orig_query + (pStmt->query_end - + pStmt->query); + + /* Chop off the 'WHERE CURRENT OF ...' */ + *(char *)cursor_pos= '\0'; + + MYODBCDbgReturnReturn(do_my_pos_cursor(pStmt, pStmtCursor)); } for ( nIndex= 0 ; nIndex < pStmt->param_count ; ) Index: driver/myodbc3.h =================================================================== --- driver/myodbc3.h (revision 250) +++ driver/myodbc3.h (working copy) @@ -335,6 +335,11 @@ STMT_OPTIONS stmt_options; const char *table_name; char *query,*query_end; + /* + We save a copy of the original query before we modify it for 'WHERE + CURRENT OF' cursor handling. + */ + char *orig_query,*orig_query_end; my_ulonglong affected_rows; long current_row; long cursor_row; Index: driver/cursor.c =================================================================== --- driver/cursor.c (revision 250) +++ driver/cursor.c (working copy) @@ -124,74 +124,71 @@ } -/* - @type : myodbc3 internal - @purpose : checks whether the SQL statement contains WHERE CURRENT OF CURSOR +/** + Check if a statement involves a positioned cursor using the WHERE CURRENT + OF syntax. + + @param[in] pStmt Handle of the statement + @param[out] pStmtCursor Pointer to the statement referred to by the cursor + + @return Pointer to the beginning of 'WHERE CURRENT OF' */ +char *check_if_positioned_cursor_exists(STMT *pStmt, STMT **pStmtCursor) +{ + if (pStmt->query && pStmt->query_end) + { + const char *pszQueryTokenPos= pStmt->query_end; + const char *pszCursor= mystr_get_prev_token((const char**)&pszQueryTokenPos, + pStmt->query); -my_bool check_if_positioned_cursor_exists( STMT FAR *pStmt, STMT FAR **pStmtCursor ) -{ - if ( pStmt->query && pStmt->query_end ) + if (!myodbc_casecmp(mystr_get_prev_token(&pszQueryTokenPos, + pStmt->query),"OF",2) && + !myodbc_casecmp(mystr_get_prev_token(&pszQueryTokenPos, + pStmt->query),"CURRENT",7) && + !myodbc_casecmp(mystr_get_prev_token(&pszQueryTokenPos, + pStmt->query),"WHERE",5) ) { - char * pszQueryTokenPos = pStmt->query_end; - const char *pszCursor = mystr_get_prev_token( (const char **)&pszQueryTokenPos, pStmt->query ); + LIST *list_element; + DBC *dbc= (DBC *)pStmt->dbc; + /* + Scan the list of statements for this connection and see if we + can find the cursor name this statement is referring to - it + must have a result set to count. + */ + for (list_element= dbc->statements; + list_element; + list_element= list_element->next) + { + *pStmtCursor= (HSTMT)list_element->data; + /* - Return TRUE if this statement is doing a 'WHERE CURRENT OF' - even when - we can not find the cursor this statement is referring to. + Even if the cursor name matches, the statement must have a + result set to count. */ - if ( !myodbc_casecmp(mystr_get_prev_token((const char **)&pszQueryTokenPos, - pStmt->query),"OF",2) && - !myodbc_casecmp(mystr_get_prev_token((const char **)&pszQueryTokenPos, - pStmt->query),"CURRENT",7) && - !myodbc_casecmp(mystr_get_prev_token((const char **)&pszQueryTokenPos, - pStmt->query),"WHERE",5) ) + if ((*pStmtCursor)->result && + (*pStmtCursor)->cursor.name && + !myodbc_strcasecmp((*pStmtCursor)->cursor.name, + pszCursor)) { - LIST * list_element; - LIST * next_element; - DBC FAR * dbc = (DBC FAR*)pStmt->dbc; + return (char *)pszQueryTokenPos; + } + } - /* - Scan the list of statements for this connection and see if we can - find the cursor name this statement is referring to - it must have - a result set to count. - */ - for ( list_element = dbc->statements; list_element; list_element = next_element ) - { - next_element = list_element->next; - *pStmtCursor = (HSTMT)list_element->data; + /* Did we run out of statements without finding a viable cursor? */ + if (!list_element) + { + char buff[200]; + strxmov(buff,"Cursor '", pszCursor, + "' does not exist or does not have a result set.", NullS); + set_stmt_error(pStmt, "34000", buff, ER_INVALID_CURSOR_NAME); + } - /* - Might have the cursor in the pStmt without any resultset, so - avoid crashes, by keeping check on (*pStmtCursor)->result) - */ - if ( (*pStmtCursor)->cursor.name && - !myodbc_strcasecmp( (*pStmtCursor)->cursor.name, pszCursor ) && - (*pStmtCursor)->result ) - { - /* - \todo - - Well we have found a match so we truncate the 'WHERE CURRENT OF cursorname' - but the truncation may be a bad way to go as it may make it broken for future - use. - */ - *pszQueryTokenPos = '\0'; - return ( TRUE ); - } - } - /* Did we run out of statements without finding a viable cursor? */ - if ( !list_element ) - { - char buff[200]; - strxmov( buff,"Cursor '", pszCursor, "' does not exist or does not have a result set.", NullS ); - set_stmt_error( pStmt, "34000", buff, ER_INVALID_CURSOR_NAME ); - } - return ( TRUE ); - } + return pszQueryTokenPos; } + } - return ( FALSE ); + return NULL; } @@ -957,10 +954,20 @@ pStmt->affected_rows = mysql_affected_rows( &pStmtTemp->dbc->mysql ); nReturn = update_status( pStmt, SQL_ROW_UPDATED ); } + else if (nReturn == SQL_NEED_DATA) + { + /* + Re-prepare the statement, which will leave us with a prepared + statement that is a non-positioned update. + */ + if (my_SQLPrepare(pStmt, (SQLCHAR *)dynQuery->str, dynQuery->length) != + SQL_SUCCESS) + return SQL_ERROR; + } my_SQLFreeStmt( pStmtTemp, SQL_DROP ); - return ( nReturn ); + return nReturn; } Index: driver/myutil.h =================================================================== --- driver/myutil.h (revision 250) +++ driver/myutil.h (working copy) @@ -84,8 +84,7 @@ SQLUSMALLINT irow,DYNAMIC_STRING *dynStr); SQLRETURN my_pos_update(STMT FAR *stmt,STMT FAR *stmtParam, SQLUSMALLINT irow,DYNAMIC_STRING *dynStr); -my_bool check_if_positioned_cursor_exists(STMT FAR *stmt, - STMT FAR **stmtNew); +char *check_if_positioned_cursor_exists(STMT FAR *stmt, STMT FAR **stmtNew); char *insert_param(MYSQL *mysql, char *to,PARAM_BIND *param); char *add_to_buffer(NET *net,char *to,char *from,ulong length); SQLRETURN copy_lresult(SQLSMALLINT HandleType, SQLHANDLE handle, Index: driver/handle.c =================================================================== --- driver/handle.c (revision 250) +++ driver/handle.c (working copy) @@ -462,7 +462,8 @@ /* At this point, only MYSQL_RESET and SQL_DROP left out */ x_free((gptr) stmt->query); - stmt->query= 0; + x_free((gptr) stmt->orig_query); + stmt->query= stmt->orig_query= 0; stmt->param_count= 0; if (fOption == MYSQL_RESET) Index: test/cursor/my_cursor.c =================================================================== --- test/cursor/my_cursor.c (revision 250) +++ test/cursor/my_cursor.c (working copy) @@ -1,27 +1,25 @@ -/*************************************************************************** - my_cursor.c - description - --------------------- - begin : Wed Sep 8 2001 - copyright : (C) MySQL AB 1995-2002, www.mysql.com - author : venu ( venu@mysql.com ) - ***************************************************************************/ +/* + Copyright (C) 1995-2007 MySQL AB -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. -/*************************************************************************** - * * - * This is a basic sample to demonstrate how to perform positioned * - * update and deletes using cursors * - * * - ***************************************************************************/ + There are special exceptions to the terms and conditions of the GPL + as it is applied to this software. View the full text of the exception + in file LICENSE.exceptions in the top-level directory of this software + distribution. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + #include "mytest3.h" /* MyODBC 3.51 sample utility header */ /******************************************************** @@ -259,13 +257,108 @@ mystmt(hstmt,rc); } + +/** + Bug #5853: Using Update with 'WHERE CURRENT OF' with binary data crashes +*/ +void t_bug5853(SQLHDBC hdbc, SQLHSTMT hstmt) +{ + SQLRETURN rc; + SQLHSTMT hstmt_pos; + SQLCHAR nData[3]; + SQLLEN nLen= SQL_DATA_AT_EXEC; + int i= 0; + + rc= SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt_pos); + mycon(hdbc, rc); + + rc= SQLExecDirect(hstmt,"DROP TABLE IF EXISTS t_bug5853",SQL_NTS); + mystmt(hstmt,rc); + + rc= SQLExecDirect(hstmt,"CREATE TABLE t_bug5853 (id INT AUTO_INCREMENT PRIMARY KEY, a VARCHAR(3))",SQL_NTS); + mystmt(hstmt,rc); + + rc= SQLExecDirect(hstmt,"INSERT INTO t_bug5853 (a) VALUES ('abc'),('def')",SQL_NTS); + mystmt(hstmt,rc); + + rc= SQLPrepare(hstmt,"INSERT INTO t_bug5853 VALUES(?)",SQL_NTS); + mystmt(hstmt,rc); + + rc= SQLSetStmtAttr(hstmt, SQL_ATTR_CURSOR_TYPE, + (SQLPOINTER)SQL_CURSOR_DYNAMIC,0); + mystmt(hstmt, rc); + + rc= SQLSetCursorName(hstmt, "bug5853", SQL_NTS); + mystmt(hstmt, rc); + + rc= SQLExecDirect(hstmt,"SELECT * FROM t_bug5853",SQL_NTS); + mystmt(hstmt,rc); + + rc= SQLPrepare(hstmt_pos, + "UPDATE t_bug5853 SET a = ? WHERE CURRENT OF bug5853", + SQL_NTS); + mystmt(hstmt_pos, rc); + + rc= SQLBindParameter(hstmt_pos, 1, SQL_PARAM_INPUT, SQL_VARCHAR, SQL_C_CHAR, + 0, 0, NULL, 0, &nLen); + mystmt(hstmt_pos,rc); + + while ((rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0)) != SQL_NO_DATA_FOUND) + { + char data[2][3] = { "uvw", "xyz" }; + + mystmt(hstmt,rc); + + rc= SQLExecute(hstmt_pos); + mystmt_err(hstmt_pos, rc == SQL_NEED_DATA, rc); + + while (rc == SQL_NEED_DATA) + { + SQLPOINTER token; + rc= SQLParamData(hstmt_pos, &token); + if (rc == SQL_NEED_DATA) + { + SQLRETURN rc2; + rc2= SQLPutData(hstmt_pos, data[i++ % 2], sizeof(data[0])); + mystmt(hstmt_pos,rc2); + } + } + mystmt(hstmt_pos,rc); + } + + rc= SQLFreeStmt(hstmt_pos, SQL_CLOSE); + mystmt(hstmt,rc); + + rc= SQLExecDirect(hstmt,"SELECT * FROM t_bug5853",SQL_NTS); + mystmt(hstmt,rc); + + rc= SQLBindCol(hstmt, 2, SQL_C_CHAR, nData, 3, &nLen); + mystmt(hstmt,rc); + + rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0); + mystmt(hstmt,rc); + my_assert(strncmp((char *)nData, "uvw", 3)); + + rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0); + mystmt(hstmt,rc); + my_assert(strncmp((char *)nData, "xyz", 3)); + + rc= SQLFetchScroll(hstmt, SQL_FETCH_NEXT, 0); + mystmt_err(hstmt, rc == SQL_NO_DATA_FOUND, rc); + + rc= SQLExecDirect(hstmt,"DROP TABLE IF EXISTS t_bug5853",SQL_NTS); + mystmt(hstmt,rc); + + //return OK; +} + /******************************************************** * main routine * *********************************************************/ int main(int argc, char *argv[]) { SQLHENV henv; - SQLHDBC hdbc; + SQLHDBC hdbc; SQLHSTMT hstmt; SQLINTEGER narg; @@ -283,41 +376,21 @@ myuid = argv[2]; else if ( narg == 3 ) mypwd = argv[3]; - } + } - /* - * connect to MySQL server - */ - myconnect(&henv,&hdbc,&hstmt); + myconnect(&henv,&hdbc,&hstmt); if (driver_supports_positioned_ops(hdbc)) { - - /* - * initialize table - */ my_init_table(hdbc, hstmt); - - /* - * positioned cursor update and delete - */ my_positioned_cursor(hdbc, hstmt); - - /* - * Update and Delete using SQLSetPos - */ my_setpos_cursor(hdbc, hstmt); + t_bug5853(hdbc, hstmt); } - /* - * disconnect from the server, by freeing all resources - */ mydisconnect(&henv,&hdbc,&hstmt); printMessageFooter( 1 ); return(0); -} - - - +} Index: ChangeLog =================================================================== --- ChangeLog (revision 250) +++ ChangeLog (working copy) @@ -3,6 +3,9 @@ Functionality added or changed: Bugs fixed: + * Statements that used "WHERE CURRENT OF" for positioned updates could + not be re-executed or used with parameters that were provided using + SQLPutData() and SQLParamData(). (Bug #5853) ----