Bug#32265 Server returns different metadata if prepared statement is used

Executing a prepared statement associated with a materialized
cursor yields to the client a metadata packet with wrong table
and database names. The problem was occurring because the server
was sending the the name of the temporary table used by the cursor
instead of the table name of the original table. The same problem
occurs when selecting from views, in which case the table name was
being sent and not the name of the view.
  
The solution is to fill the list item from the temporary table but
preserving the table and database names of the original fields. This
is achieved by tweaking the Select_materialize to accept a pointer to
the Materialized_cursor class which contains the item list to be filled.
parent 23d670c3
...@@ -88,6 +88,7 @@ class Materialized_cursor: public Server_side_cursor ...@@ -88,6 +88,7 @@ class Materialized_cursor: public Server_side_cursor
public: public:
Materialized_cursor(select_result *result, TABLE *table); Materialized_cursor(select_result *result, TABLE *table);
int fill_item_list(THD *thd, List<Item> &send_fields);
virtual bool is_open() const { return table != 0; } virtual bool is_open() const { return table != 0; }
virtual int open(JOIN *join __attribute__((unused))); virtual int open(JOIN *join __attribute__((unused)));
virtual void fetch(ulong num_rows); virtual void fetch(ulong num_rows);
...@@ -109,6 +110,7 @@ class Select_materialize: public select_union ...@@ -109,6 +110,7 @@ class Select_materialize: public select_union
{ {
select_result *result; /* the result object of the caller (PS or SP) */ select_result *result; /* the result object of the caller (PS or SP) */
public: public:
Materialized_cursor *materialized_cursor;
Select_materialize(select_result *result_arg) :result(result_arg) {} Select_materialize(select_result *result_arg) :result(result_arg) {}
virtual bool send_fields(List<Item> &list, uint flags); virtual bool send_fields(List<Item> &list, uint flags);
}; };
...@@ -152,7 +154,7 @@ int mysql_open_cursor(THD *thd, uint flags, select_result *result, ...@@ -152,7 +154,7 @@ int mysql_open_cursor(THD *thd, uint flags, select_result *result,
if (! (sensitive_cursor= new (thd->mem_root) Sensitive_cursor(thd, result))) if (! (sensitive_cursor= new (thd->mem_root) Sensitive_cursor(thd, result)))
{ {
delete result; delete result_materialize;
return 1; return 1;
} }
...@@ -174,13 +176,13 @@ int mysql_open_cursor(THD *thd, uint flags, select_result *result, ...@@ -174,13 +176,13 @@ int mysql_open_cursor(THD *thd, uint flags, select_result *result,
/* /*
Possible options here: Possible options here:
- a sensitive cursor is open. In this case rc is 0 and - a sensitive cursor is open. In this case rc is 0 and
result_materialize->table is NULL, or result_materialize->materialized_cursor is NULL, or
- a materialized cursor is open. In this case rc is 0 and - a materialized cursor is open. In this case rc is 0 and
result_materialize->table is not NULL result_materialize->materialized is not NULL
- an error occured during materializaton. - an error occurred during materialization.
result_materialize->table is not NULL, but rc != 0 result_materialize->materialized_cursor is not NULL, but rc != 0
- successful completion of mysql_execute_command without - successful completion of mysql_execute_command without
a cursor: rc is 0, result_materialize->table is NULL, a cursor: rc is 0, result_materialize->materialized_cursor is NULL,
sensitive_cursor is not open. sensitive_cursor is not open.
This is possible if some command writes directly to the This is possible if some command writes directly to the
network, bypassing select_result mechanism. An example of network, bypassing select_result mechanism. An example of
...@@ -191,7 +193,7 @@ int mysql_open_cursor(THD *thd, uint flags, select_result *result, ...@@ -191,7 +193,7 @@ int mysql_open_cursor(THD *thd, uint flags, select_result *result,
if (sensitive_cursor->is_open()) if (sensitive_cursor->is_open())
{ {
DBUG_ASSERT(!result_materialize->table); DBUG_ASSERT(!result_materialize->materialized_cursor);
/* /*
It's safer if we grab THD state after mysql_execute_command It's safer if we grab THD state after mysql_execute_command
is finished and not in Sensitive_cursor::open(), because is finished and not in Sensitive_cursor::open(), because
...@@ -202,18 +204,10 @@ int mysql_open_cursor(THD *thd, uint flags, select_result *result, ...@@ -202,18 +204,10 @@ int mysql_open_cursor(THD *thd, uint flags, select_result *result,
*pcursor= sensitive_cursor; *pcursor= sensitive_cursor;
goto end; goto end;
} }
else if (result_materialize->table) else if (result_materialize->materialized_cursor)
{ {
Materialized_cursor *materialized_cursor; Materialized_cursor *materialized_cursor=
TABLE *table= result_materialize->table; result_materialize->materialized_cursor;
MEM_ROOT *mem_root= &table->mem_root;
if (!(materialized_cursor= new (mem_root)
Materialized_cursor(result, table)))
{
rc= 1;
goto err_open;
}
if ((rc= materialized_cursor->open(0))) if ((rc= materialized_cursor->open(0)))
{ {
...@@ -229,8 +223,6 @@ int mysql_open_cursor(THD *thd, uint flags, select_result *result, ...@@ -229,8 +223,6 @@ int mysql_open_cursor(THD *thd, uint flags, select_result *result,
err_open: err_open:
DBUG_ASSERT(! (sensitive_cursor && sensitive_cursor->is_open())); DBUG_ASSERT(! (sensitive_cursor && sensitive_cursor->is_open()));
delete sensitive_cursor; delete sensitive_cursor;
if (result_materialize->table)
free_tmp_table(thd, result_materialize->table);
end: end:
delete result_materialize; delete result_materialize;
return rc; return rc;
...@@ -544,6 +536,51 @@ Materialized_cursor::Materialized_cursor(select_result *result_arg, ...@@ -544,6 +536,51 @@ Materialized_cursor::Materialized_cursor(select_result *result_arg,
} }
/**
Preserve the original metadata that would be sent to the client.
@param thd Thread identifier.
@param send_fields List of fields that would be sent.
*/
int Materialized_cursor::fill_item_list(THD *thd, List<Item> &send_fields)
{
Query_arena backup_arena;
int rc;
List_iterator_fast<Item> it_org(send_fields);
List_iterator_fast<Item> it_dst(item_list);
Item *item_org;
Item *item_dst;
thd->set_n_backup_active_arena(this, &backup_arena);
if ((rc= table->fill_item_list(&item_list)))
goto end;
DBUG_ASSERT(send_fields.elements == item_list.elements);
/*
Unless we preserve the original metadata, it will be lost,
since new fields describe columns of the temporary table.
Allocate a copy of the name for safety only. Currently
items with original names are always kept in memory,
but in case this changes a memory leak may be hard to notice.
*/
while ((item_dst= it_dst++, item_org= it_org++))
{
Send_field send_field;
Item_ident *ident= static_cast<Item_ident *>(item_dst);
item_org->make_field(&send_field);
ident->db_name= thd->strdup(send_field.db_name);
ident->table_name= thd->strdup(send_field.table_name);
}
end:
thd->restore_active_arena(this, &backup_arena);
/* Check for thd->is_error() in case of OOM */
return rc || thd->net.report_error;
}
int Materialized_cursor::open(JOIN *join __attribute__((unused))) int Materialized_cursor::open(JOIN *join __attribute__((unused)))
{ {
THD *thd= fake_unit.thd; THD *thd= fake_unit.thd;
...@@ -552,8 +589,7 @@ int Materialized_cursor::open(JOIN *join __attribute__((unused))) ...@@ -552,8 +589,7 @@ int Materialized_cursor::open(JOIN *join __attribute__((unused)))
thd->set_n_backup_active_arena(this, &backup_arena); thd->set_n_backup_active_arena(this, &backup_arena);
/* Create a list of fields and start sequential scan */ /* Create a list of fields and start sequential scan */
rc= (table->fill_item_list(&item_list) || rc= (result->prepare(item_list, &fake_unit) ||
result->prepare(item_list, &fake_unit) ||
table->file->ha_rnd_init(TRUE)); table->file->ha_rnd_init(TRUE));
thd->restore_active_arena(this, &backup_arena); thd->restore_active_arena(this, &backup_arena);
if (rc == 0) if (rc == 0)
...@@ -664,6 +700,24 @@ bool Select_materialize::send_fields(List<Item> &list, uint flags) ...@@ -664,6 +700,24 @@ bool Select_materialize::send_fields(List<Item> &list, uint flags)
if (create_result_table(unit->thd, unit->get_unit_column_types(), if (create_result_table(unit->thd, unit->get_unit_column_types(),
FALSE, thd->options | TMP_TABLE_ALL_COLUMNS, "")) FALSE, thd->options | TMP_TABLE_ALL_COLUMNS, ""))
return TRUE; return TRUE;
materialized_cursor= new (&table->mem_root)
Materialized_cursor(result, table);
if (! materialized_cursor)
{
free_tmp_table(table->in_use, table);
table= 0;
return TRUE;
}
if (materialized_cursor->fill_item_list(unit->thd, list))
{
delete materialized_cursor;
table= 0;
materialized_cursor= 0;
return TRUE;
}
return FALSE; return FALSE;
} }
...@@ -16152,6 +16152,87 @@ static void test_bug31669() ...@@ -16152,6 +16152,87 @@ static void test_bug31669()
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
/**
Bug#32265 Server returns different metadata if prepared statement is used
*/
static void test_bug32265()
{
int rc, i;
MYSQL_STMT *stmt;
MYSQL_FIELD *field;
DBUG_ENTER("test_bug32265");
myheader("test_bug32265");
rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1");
myquery(rc);
rc= mysql_query(mysql, "CREATE TABLE t1 (a INTEGER)");
myquery(rc);
rc= mysql_query(mysql, "INSERT INTO t1 VALUES (1)");
myquery(rc);
rc= mysql_query(mysql, "CREATE VIEW v1 AS SELECT * FROM t1");
myquery(rc);
stmt= open_cursor("SELECT * FROM t1");
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
field= stmt->mysql->fields;
DIE_UNLESS(strcmp(field->table, "t1") == 0);
DIE_UNLESS(strcmp(field->org_table, "t1") == 0);
DIE_UNLESS(strcmp(field->db, "client_test_db") == 0);
mysql_stmt_close(stmt);
stmt= open_cursor("SELECT a '' FROM t1 ``");
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
field= stmt->mysql->fields;
DIE_UNLESS(strcmp(field->table, "") == 0);
DIE_UNLESS(strcmp(field->org_table, "t1") == 0);
DIE_UNLESS(strcmp(field->db, "client_test_db") == 0);
mysql_stmt_close(stmt);
stmt= open_cursor("SELECT a '' FROM t1 ``");
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
field= stmt->mysql->fields;
DIE_UNLESS(strcmp(field->table, "") == 0);
DIE_UNLESS(strcmp(field->org_table, "t1") == 0);
DIE_UNLESS(strcmp(field->db, "client_test_db") == 0);
mysql_stmt_close(stmt);
stmt= open_cursor("SELECT * FROM v1");
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
field= stmt->mysql->fields;
DIE_UNLESS(strcmp(field->table, "v1") == 0);
DIE_UNLESS(strcmp(field->org_table, "t1") == 0);
DIE_UNLESS(strcmp(field->db, "client_test_db") == 0);
mysql_stmt_close(stmt);
stmt= open_cursor("SELECT * FROM v1 /* SIC */ GROUP BY 1");
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
field= stmt->mysql->fields;
DIE_UNLESS(strcmp(field->table, "v1") == 0);
DIE_UNLESS(strcmp(field->org_table, "t1") == 0);
DIE_UNLESS(strcmp(field->db, "client_test_db") == 0);
mysql_stmt_close(stmt);
rc= mysql_query(mysql, "DROP VIEW v1");
myquery(rc);
rc= mysql_query(mysql, "DROP TABLE t1");
myquery(rc);
DBUG_VOID_RETURN;
}
/* /*
Read and parse arguments and MySQL options from my.cnf Read and parse arguments and MySQL options from my.cnf
*/ */
...@@ -16446,6 +16527,7 @@ static struct my_tests_st my_tests[]= { ...@@ -16446,6 +16527,7 @@ static struct my_tests_st my_tests[]= {
{ "test_bug29948", test_bug29948 }, { "test_bug29948", test_bug29948 },
{ "test_bug29306", test_bug29306 }, { "test_bug29306", test_bug29306 },
{ "test_bug31669", test_bug31669 }, { "test_bug31669", test_bug31669 },
{ "test_bug32265", test_bug32265 },
{ 0, 0 } { 0, 0 }
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment