Commit 2a8556f3 authored by unknown's avatar unknown
Browse files

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.


include/errmsg.h:
  Add a special error message when we attempt to mysql_stmt_fetch
  from a statement which has no result set.
libmysql/errmsg.c:
  Error message text for CR_NO_RESULT_SET
libmysql/libmysql.c:
  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.
sql-common/client.c:
  Fix one place where we flushed the pending result set of a statement,
  but didn't set unbuffered_fetch_cancelled flag.
sql/share/errmsg.txt:
  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/sql_prepare.cc:
  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/sql_select.cc:
  free_items and free_root moved to Cursor::close().
sql/sql_select.h:
  A comment added.
tests/mysql_client_test.c:
  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).
parent edcdc57b
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -95,6 +95,7 @@ extern const char *client_errors[]; /* Error messages */
#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. */
+3 −0
Original line number Diff line number Diff line
@@ -80,6 +80,7 @@ const char *client_errors[]=
  "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 @@ const char *client_errors[]=
  "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 @@ const char *client_errors[]=
  "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
+71 −49
Original line number Diff line number Diff line
@@ -1724,6 +1724,13 @@ static int stmt_read_row_no_data(MYSQL_STMT *stmt, unsigned char **row);
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 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length)
    /* 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,13 +2710,9 @@ 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);
  set_stmt_error(stmt, CR_NO_RESULT_SET, unknown_sqlstate);
  return 1;
}
  return MYSQL_NO_DATA;
}


/*
@@ -2817,7 +2821,8 @@ int STDCALL mysql_stmt_execute(MYSQL_STMT *stmt)
    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 @@ my_ulonglong STDCALL mysql_stmt_num_rows(MYSQL_STMT *stmt)
  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 @@ my_bool STDCALL mysql_stmt_free_result(MYSQL_STMT *stmt)
      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)
    {
      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)
      {
        if (mysql->unbuffered_fetch_owner == &stmt->unbuffered_fetch_cancelled)
          mysql->unbuffered_fetch_owner= 0;
      if (mysql->status != MYSQL_STATUS_READY)
        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_close(MYSQL_STMT *stmt)

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));
}

/*
+2 −0
Original line number Diff line number Diff line
@@ -864,6 +864,8 @@ mysql_free_result(MYSQL_RES *result)
      {
        (*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);
+8 −4
Original line number Diff line number Diff line
@@ -4766,13 +4766,13 @@ ER_SUBQUERY_NO_1_ROW 21000
	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) fr %s angegeben"
	por "Desconhecido manipulador de declarao preparado (%.*s) determinado para %s"
	spa "Desconocido preparado comando handler (%ld) dado para %s"
	swe "Oknd 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 "Oknd 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 beschdigt oder existiert nicht"
@@ -5352,3 +5352,7 @@ ER_BINLOG_UNSAFE_ROUTINE
	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."
Loading