Commit d0db7027 authored by unknown's avatar unknown
Browse files

A fix and test case for Bug#10729 "mysql_stmt_attr_set

CURSOR_TYPE_READ_ONLY". The bug was that we (me) don't perform proper
cleanups of the prepared statement when done fetching from a cursor.
Another patch.


sql/mysql_priv.h:
  Rename reset_stmt_for_execute to init_stmt_before_use (to correspond to
  cleanup_stmt_and_thd_after_use).
sql/sp_head.cc:
  Rename.
sql/sql_prepare.cc:
  Move common cleanup code to a cleanup function, call it when we close
  a cursor.
sql/sql_select.cc:
  Cleanup.
sql/sql_select.h:
  No need for init_thd, this code has been inlined in Cursor::open.
tests/mysql_client_test.c:
  Add a test case for Bug#10729 "mysql_stmt_attr_set CURSOR_TYPE_READ_ONLY"
  (problem reusing a prepared statemnt if there was a cursor)
parent baae624c
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -834,7 +834,7 @@ void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length);
void mysql_stmt_free(THD *thd, char *packet);
void mysql_stmt_reset(THD *thd, char *packet);
void mysql_stmt_get_longdata(THD *thd, char *pos, ulong packet_length);
void reset_stmt_for_execute(THD *thd, LEX *lex);
void reinit_stmt_before_use(THD *thd, LEX *lex);
void init_stmt_after_parse(THD*, LEX*);

/* sql_handler.cc */
+1 −1
Original line number Diff line number Diff line
@@ -1355,7 +1355,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
           implemented at the same time as ability not to store LEX for
           instruction if it is not really used.
  */
  reset_stmt_for_execute(thd, m_lex);
  reinit_stmt_before_use(thd, m_lex);

  /*
    If requested check whenever we have access to tables in LEX's table list
+34 −24
Original line number Diff line number Diff line
@@ -1656,6 +1656,17 @@ static bool init_param_array(Prepared_statement *stmt)
  return FALSE;
}


/* Cleanup PS after execute/prepare and restore THD state */

static void cleanup_stmt_and_thd_after_use(Statement *stmt, THD *thd)
{
  stmt->lex->unit.cleanup();
  cleanup_items(stmt->free_list);
  thd->rollback_item_tree_changes();
  thd->cleanup_after_query();
}

/*
  Given a query string with parameter markers, create a Prepared Statement
  from it and send PS info back to the client.
@@ -1760,12 +1771,9 @@ bool mysql_stmt_prepare(THD *thd, char *packet, uint packet_length,
    thd->lex->sphead= NULL;
  }
  lex_end(lex);
  lex->unit.cleanup();
  close_thread_tables(thd);
  cleanup_stmt_and_thd_after_use(stmt, thd);
  thd->restore_backup_statement(stmt, &thd->stmt_backup);
  cleanup_items(stmt->free_list);
  thd->rollback_item_tree_changes();
  thd->cleanup_after_query();
  thd->current_arena= thd;

  if (error)
@@ -1808,10 +1816,10 @@ void init_stmt_after_parse(THD *thd, LEX *lex)

/* Reinit prepared statement/stored procedure before execution */

void reset_stmt_for_execute(THD *thd, LEX *lex)
void reinit_stmt_before_use(THD *thd, LEX *lex)
{
  SELECT_LEX *sl= lex->all_selects_list;
  DBUG_ENTER("reset_stmt_for_execute");
  DBUG_ENTER("reinit_stmt_before_use");

  if (lex->empty_field_list_on_rset)
  {
@@ -2007,7 +2015,7 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length)
  thd->stmt_backup.set_statement(thd);
  thd->set_statement(stmt);
  thd->current_arena= stmt;
  reset_stmt_for_execute(thd, stmt->lex);
  reinit_stmt_before_use(thd, stmt->lex);
  /* From now cursors assume that thd->mem_root is clean */
  if (expanded_query.length() &&
      alloc_query(thd, (char *)expanded_query.ptr(),
@@ -2031,17 +2039,18 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length)

  if (cursor && cursor->is_open())
  {
    /*
      It's safer if we grab THD state after mysql_execute_command is
      finished and not in Cursor::open(), because currently the call to
      Cursor::open is buried deep in JOIN::exec of the top level join.
    */
    cursor->init_from_thd(thd);
    cursor->state= stmt->state;
  }
  else
  {
    thd->lex->unit.cleanup();
    cleanup_items(stmt->free_list);
    close_thread_tables(thd);
    cleanup_stmt_and_thd_after_use(stmt, thd);
    reset_stmt_params(stmt);
    close_thread_tables(thd);                   /* to close derived tables */
    thd->rollback_item_tree_changes();
    thd->cleanup_after_query();
  }

  thd->set_statement(&thd->stmt_backup);
@@ -2119,7 +2128,7 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt,
{
  DBUG_ENTER("execute_stmt");

  reset_stmt_for_execute(thd, stmt->lex);
  reinit_stmt_before_use(thd, stmt->lex);

  if (expanded_query->length() &&
      alloc_query(thd, (char *)expanded_query->ptr(),
@@ -2141,14 +2150,11 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt,
  if (!(specialflag & SPECIAL_NO_PRIOR))
    my_pthread_setprio(pthread_self(), WAIT_PRIOR);

  thd->lex->unit.cleanup();
  thd->current_arena= thd;
  cleanup_items(stmt->free_list);
  thd->rollback_item_tree_changes();
  reset_stmt_params(stmt);
  close_thread_tables(thd);                    // to close derived tables
  cleanup_stmt_and_thd_after_use(stmt, thd);
  reset_stmt_params(stmt);
  thd->set_statement(&thd->stmt_backup);
  thd->cleanup_after_query();
  thd->current_arena= thd;

  if (stmt->state == Item_arena::PREPARED)
    stmt->state= Item_arena::EXECUTED;
@@ -2171,7 +2177,7 @@ void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length)
  /* assume there is always place for 8-16 bytes */
  ulong stmt_id= uint4korr(packet);
  ulong num_rows= uint4korr(packet+4);
  Statement *stmt;
  Prepared_statement *stmt;
  DBUG_ENTER("mysql_stmt_fetch");

  if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_fetch")))
@@ -2185,7 +2191,6 @@ void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length)

  thd->current_arena= stmt;
  thd->set_n_backup_statement(stmt, &thd->stmt_backup);
  stmt->cursor->init_thd(thd);

  if (!(specialflag & SPECIAL_NO_PRIOR))
    my_pthread_setprio(pthread_self(), QUERY_PRIOR);
@@ -2197,11 +2202,16 @@ void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length)
  if (!(specialflag & SPECIAL_NO_PRIOR))
    my_pthread_setprio(pthread_self(), WAIT_PRIOR);

  /* Restore THD state */
  stmt->cursor->reset_thd(thd);
  thd->restore_backup_statement(stmt, &thd->stmt_backup);
  thd->current_arena= thd;

  if (!stmt->cursor->is_open())
  {
    /* We're done with the fetch: reset PS for next execution */
    cleanup_stmt_and_thd_after_use(stmt, thd);
    reset_stmt_params(stmt);
  }

  DBUG_VOID_RETURN;
}

+14 −21
Original line number Diff line number Diff line
@@ -1709,10 +1709,11 @@ Cursor::init_from_thd(THD *thd)
    We need to save and reset thd->mem_root, otherwise it'll be freed
    later in mysql_parse.

    We can't just change the thd->mem_root here as we want to keep the things
    that is already allocated in thd->mem_root for Cursor::fetch()
    We can't just change the thd->mem_root here as we want to keep the
    things that are already allocated in thd->mem_root for Cursor::fetch()
  */
  main_mem_root=  *thd->mem_root;
  state= thd->current_arena->state;
  /* Allocate new memory root for thd */
  init_sql_alloc(thd->mem_root,
                 thd->variables.query_alloc_block_size,
@@ -1735,24 +1736,6 @@ Cursor::init_from_thd(THD *thd)
    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?
  */
}


void
Cursor::init_thd(THD *thd)
{
  DBUG_ASSERT(thd->derived_tables == 0);
  thd->derived_tables= derived_tables;

  DBUG_ASSERT(thd->open_tables == 0);
  thd->open_tables= open_tables;

  DBUG_ASSERT(thd->lock== 0);
  thd->lock= lock;
  thd->query_id= query_id;
}


@@ -1828,6 +1811,13 @@ Cursor::fetch(ulong num_rows)
  DBUG_ENTER("Cursor::fetch");
  DBUG_PRINT("enter",("rows: %lu", num_rows));

  DBUG_ASSERT(thd->derived_tables == 0 && thd->open_tables == 0 &&
              thd->lock == 0);

  thd->derived_tables= derived_tables;
  thd->open_tables= open_tables;
  thd->lock= lock;
  thd->query_id= query_id;
  /* save references to memory, allocated during fetch */
  thd->set_n_backup_item_arena(this, &thd->stmt_backup);

@@ -1846,6 +1836,9 @@ Cursor::fetch(ulong num_rows)
#endif

  thd->restore_backup_item_arena(this, &thd->stmt_backup);
  DBUG_ASSERT(thd->free_list == 0);
  reset_thd(thd);

  if (error == NESTED_LOOP_CURSOR_LIMIT)
  {
    /* Fetch limit worked, possibly more rows are there */
@@ -1886,8 +1879,8 @@ Cursor::close()
    join->cleanup();
    delete join;
  }
  /* XXX: Another hack: closing tables used in the cursor */
  {
    /* XXX: Another hack: closing tables used in the cursor */
    DBUG_ASSERT(lock || open_tables || derived_tables);

    TABLE *tmp_derived_tables= thd->derived_tables;
+0 −2
Original line number Diff line number Diff line
@@ -386,8 +386,6 @@ class Cursor: public Sql_alloc, public Item_arena
  /* Temporary implementation as now we replace THD state by value */
  /* Save THD state into cursor */
  void init_from_thd(THD *thd);
  /* Restore THD from cursor to continue cursor execution */
  void init_thd(THD *thd);
  /* bzero cursor state in THD */
  void reset_thd(THD *thd);

Loading