1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-27 18:02:13 +03:00

Bug#27430 "Crash in subquery code when in PS and table DDL changed after

PREPARE", review fixes:
- make the patch follow the specification of WL#4166 and remove  
the new error that was originally introduced.
Now the client never gets an error from reprepare, unless it failed.
I.e. even if the statement at hand returns a completely different
result set, this is not considered a server error.
The C API library, that can not handle this situation, was modified to
return a client error.
Added additional test coverage.


include/errmsg.h:
  Add a new client side error: now when we automatically
  reprepare a statement, the new result set may contain a different
  number of columns.
include/mysql_com.h:
  Add a new server status to be sent to the client if the 
  number of columns in the result set is different.
libmysql/errmsg.c:
  Add a new error message.
libmysql/libmysql.c:
  Make the client library robust against a result set that
  contains a different number of columns from prepare time.
  Previously that could never happen, and we simply had an assert.
  That means in particular that all clients are advised to upgrade
  with transition to 5.1, if they are using prepared statements C API.
  Make mysql_stmt_store_result() and mysql_stmt_execute() robust against 
  "broken" statement handles (those that have an error).
sql/sql_parse.cc:
  Clear transient server status flags at start of statement more 
  systematically.
sql/share/errmsg.txt:
  Remove an error that is unused and is not part of any public release.
sql/sql_prepare.cc:
  Instead of returning an error in case the number of result set columns
  has changed, simply update the client in server status.
  That will allow modern clients automatically recover from an error.
tests/mysql_client_test.c:
  Add additional coverage to the cases when the number of result
  set columns changed as a result of reprepare.
  Cover conversion and truncation of result set columns.
This commit is contained in:
unknown
2008-05-20 20:36:26 +04:00
parent 4175806efc
commit 02c901ee5e
8 changed files with 229 additions and 64 deletions

View File

@ -1706,6 +1706,7 @@ static my_bool setup_one_fetch_function(MYSQL_BIND *, MYSQL_FIELD *field);
#define RESET_SERVER_SIDE 1
#define RESET_LONG_DATA 2
#define RESET_STORE_RESULT 4
#define RESET_CLEAR_ERROR 8
static my_bool reset_stmt_handle(MYSQL_STMT *stmt, uint flags);
@ -2090,7 +2091,7 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length)
To be removed when all commands will fully support prepared mode.
*/
static unsigned int alloc_stmt_fields(MYSQL_STMT *stmt)
static void alloc_stmt_fields(MYSQL_STMT *stmt)
{
MYSQL_FIELD *fields, *field, *end;
MEM_ROOT *alloc= &stmt->mem_root;
@ -2108,7 +2109,10 @@ static unsigned int alloc_stmt_fields(MYSQL_STMT *stmt)
!(stmt->bind= (MYSQL_BIND *) alloc_root(alloc,
sizeof(MYSQL_BIND) *
stmt->field_count)))
return 0;
{
set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate, NULL);
return;
}
for (fields= mysql->fields, end= fields+stmt->field_count,
field= stmt->fields;
@ -2127,13 +2131,15 @@ static unsigned int alloc_stmt_fields(MYSQL_STMT *stmt)
field->def = fields->def ? strdup_root(alloc,fields->def): 0;
field->max_length= 0;
}
return stmt->field_count;
}
/*
/**
Update result set columns metadata if it was sent again in
reply to COM_STMT_EXECUTE.
@note If the new field count is different from the original one,
an error is set and no update is performed.
*/
static void update_stmt_fields(MYSQL_STMT *stmt)
@ -2143,7 +2149,22 @@ static void update_stmt_fields(MYSQL_STMT *stmt)
MYSQL_FIELD *stmt_field= stmt->fields;
MYSQL_BIND *my_bind= stmt->bind_result_done ? stmt->bind : 0;
DBUG_ASSERT(stmt->field_count == stmt->mysql->field_count);
if (stmt->field_count != stmt->mysql->field_count)
{
/*
The tables used in the statement were altered,
and the query now returns a different number of columns.
There is no way to continue without reallocating the bind
array:
- if the number of columns increased, mysql_stmt_fetch()
will write beyond allocated memory
- if the number of columns decreased, some user-bound
buffers will be left unassigned without user knowing
that.
*/
set_stmt_error(stmt, CR_NEW_STMT_METADATA, unknown_sqlstate, NULL);
return;
}
for (; field < field_end; ++field, ++stmt_field)
{
@ -2792,6 +2813,50 @@ my_bool STDCALL mysql_stmt_attr_get(MYSQL_STMT *stmt,
}
/**
Update statement result set metadata from with the new field
information sent during statement execute.
@pre mysql->field_count is not zero
@retval TRUE if error: out of memory or the new
result set has a different number of columns
@retval FALSE success
*/
static void reinit_result_set_metadata(MYSQL_STMT *stmt)
{
/* Server has sent result set metadata */
if (stmt->field_count == 0)
{
/*
This is 'SHOW'/'EXPLAIN'-like query. Current implementation of
prepared statements can't send result set metadata for these queries
on prepare stage. Read it now.
*/
alloc_stmt_fields(stmt);
}
else
{
/*
Update result set metadata if it for some reason changed between
prepare and execute, i.e.:
- in case of 'SELECT ?' we don't know column type unless data was
supplied to mysql_stmt_execute, so updated column type is sent
now.
- if data dictionary changed between prepare and execute, for
example a table used in the query was altered.
Note, that now (4.1.3) we always send metadata in reply to
COM_STMT_EXECUTE (even if it is not necessary), so either this or
previous branch always works.
TODO: send metadata only when it's really necessary and add a warning
'Metadata changed' when it's sent twice.
*/
update_stmt_fields(stmt);
}
}
/*
Send placeholders data to server (if there are placeholders)
and execute prepared statement.
@ -2847,7 +2912,7 @@ int STDCALL mysql_stmt_execute(MYSQL_STMT *stmt)
DBUG_RETURN(1);
}
if (reset_stmt_handle(stmt, RESET_STORE_RESULT))
if (reset_stmt_handle(stmt, RESET_STORE_RESULT | RESET_CLEAR_ERROR))
DBUG_RETURN(1);
/*
No need to check for stmt->state: if the statement wasn't
@ -2855,40 +2920,10 @@ int STDCALL mysql_stmt_execute(MYSQL_STMT *stmt)
*/
if (mysql->methods->stmt_execute(stmt))
DBUG_RETURN(1);
stmt->state= MYSQL_STMT_EXECUTE_DONE;
if (mysql->field_count)
{
/* Server has sent result set metadata */
if (stmt->field_count == 0)
{
/*
This is 'SHOW'/'EXPLAIN'-like query. Current implementation of
prepared statements can't send result set metadata for these queries
on prepare stage. Read it now.
*/
alloc_stmt_fields(stmt);
}
else
{
/*
Update result set metadata if it for some reason changed between
prepare and execute, i.e.:
- in case of 'SELECT ?' we don't know column type unless data was
supplied to mysql_stmt_execute, so updated column type is sent
now.
- if data dictionary changed between prepare and execute, for
example a table used in the query was altered.
Note, that now (4.1.3) we always send metadata in reply to
COM_STMT_EXECUTE (even if it is not necessary), so either this or
previous branch always works.
TODO: send metadata only when it's really necessary and add a warning
'Metadata changed' when it's sent twice.
*/
update_stmt_fields(stmt);
}
}
stmt->state= MYSQL_STMT_EXECUTE_DONE;
if (stmt->field_count)
{
reinit_result_set_metadata(stmt);
if (stmt->server_status & SERVER_STATUS_CURSOR_EXISTS)
{
mysql->status= MYSQL_STATUS_READY;
@ -2903,7 +2938,7 @@ int STDCALL mysql_stmt_execute(MYSQL_STMT *stmt)
network or b) is more efficient if all (few) result set rows are
precached on client and server's resources are freed.
*/
DBUG_RETURN(mysql_stmt_store_result(stmt));
mysql_stmt_store_result(stmt);
}
else
{
@ -2912,7 +2947,7 @@ int STDCALL mysql_stmt_execute(MYSQL_STMT *stmt)
stmt->read_row_func= stmt_read_row_unbuffered;
}
}
DBUG_RETURN(0);
DBUG_RETURN(test(stmt->last_errno));
}
@ -4766,6 +4801,12 @@ int STDCALL mysql_stmt_store_result(MYSQL_STMT *stmt)
DBUG_RETURN(1);
}
if (stmt->last_errno)
{
/* An attempt to use an invalid statement handle. */
DBUG_RETURN(1);
}
if (mysql->status == MYSQL_STATUS_READY &&
stmt->server_status & SERVER_STATUS_CURSOR_EXISTS)
{
@ -4973,9 +5014,10 @@ static my_bool reset_stmt_handle(MYSQL_STMT *stmt, uint flags)
stmt->state= MYSQL_STMT_INIT_DONE;
return 1;
}
stmt_clear_error(stmt);
}
}
if (flags & RESET_CLEAR_ERROR)
stmt_clear_error(stmt);
stmt->state= MYSQL_STMT_PREPARE_DONE;
}
return 0;
@ -4986,7 +5028,8 @@ 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 | RESET_STORE_RESULT));
DBUG_RETURN(reset_stmt_handle(stmt, RESET_LONG_DATA | RESET_STORE_RESULT |
RESET_CLEAR_ERROR));
}
/********************************************************************
@ -5067,7 +5110,9 @@ my_bool STDCALL mysql_stmt_reset(MYSQL_STMT *stmt)
DBUG_RETURN(1);
}
/* Reset the client and server sides of the prepared statement */
DBUG_RETURN(reset_stmt_handle(stmt, RESET_SERVER_SIDE | RESET_LONG_DATA));
DBUG_RETURN(reset_stmt_handle(stmt,
RESET_SERVER_SIDE | RESET_LONG_DATA |
RESET_CLEAR_ERROR));
}
/*