summaryrefslogtreecommitdiff
path: root/libmysql
diff options
context:
space:
mode:
authorAlexander Nozdrin <alexander.nozdrin@oracle.com>2011-05-06 17:39:20 +0400
committerAlexander Nozdrin <alexander.nozdrin@oracle.com>2011-05-06 17:39:20 +0400
commitcd501675d8b88698d2a4a0d24ec638ed7e78a95b (patch)
treede9a20a6c8fddf18d59a1dd66965c327e19b2505 /libmysql
parentbc4095643b955e76f349daaf7472f33da7638597 (diff)
downloadmariadb-git-cd501675d8b88698d2a4a0d24ec638ed7e78a95b.tar.gz
Patch for Bug#12374486 - SEVERE MEMORY LEAK IN PREPARED STATEMENTS
THAT CALL STORED PROCEDURES. The bug was introduced by WL#4435. The problem was that if a stored procedure generated a few result sets with different set of columns, a new memory would be allocated after every EXECUTE for every result set. The fix is to introduce a new memory root in scope of MYSQL_STMT, and to store result-set metadata in that memory root.
Diffstat (limited to 'libmysql')
-rw-r--r--libmysql/libmysql.c75
1 files changed, 54 insertions, 21 deletions
diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c
index f802387cf9a..ec48720a2f5 100644
--- a/libmysql/libmysql.c
+++ b/libmysql/libmysql.c
@@ -94,6 +94,11 @@ sig_handler my_pipe_sig_handler(int sig);
static my_bool mysql_client_init= 0;
static my_bool org_my_init_done= 0;
+typedef struct st_mysql_stmt_extension
+{
+ MEM_ROOT fields_mem_root;
+} MYSQL_STMT_EXT;
+
/*
Initialize the MySQL client library
@@ -1480,11 +1485,16 @@ mysql_stmt_init(MYSQL *mysql)
MYSQL_STMT *stmt;
DBUG_ENTER("mysql_stmt_init");
- if (!(stmt= (MYSQL_STMT *) my_malloc(sizeof(MYSQL_STMT),
+ if (!(stmt=
+ (MYSQL_STMT *) my_malloc(sizeof (MYSQL_STMT),
+ MYF(MY_WME | MY_ZEROFILL))) ||
+ !(stmt->extension=
+ (MYSQL_STMT_EXT *) my_malloc(sizeof (MYSQL_STMT_EXT),
MYF(MY_WME | MY_ZEROFILL))))
{
set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate);
- DBUG_RETURN(0);
+ my_free(stmt);
+ DBUG_RETURN(NULL);
}
init_alloc_root(&stmt->mem_root, 2048, 2048);
@@ -1499,6 +1509,8 @@ mysql_stmt_init(MYSQL *mysql)
strmov(stmt->sqlstate, not_error_sqlstate);
/* The rest of statement members was bzeroed inside malloc */
+ init_alloc_root(&stmt->extension->fields_mem_root, 2048, 0);
+
DBUG_RETURN(stmt);
}
@@ -1571,6 +1583,7 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length)
stmt->bind_param_done= stmt->bind_result_done= FALSE;
stmt->param_count= stmt->field_count= 0;
free_root(&stmt->mem_root, MYF(MY_KEEP_PREALLOC));
+ free_root(&stmt->extension->fields_mem_root, MYF(0));
int4store(buff, stmt->stmt_id);
@@ -1631,21 +1644,21 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length)
static void alloc_stmt_fields(MYSQL_STMT *stmt)
{
MYSQL_FIELD *fields, *field, *end;
- MEM_ROOT *alloc= &stmt->mem_root;
+ MEM_ROOT *fields_mem_root= &stmt->extension->fields_mem_root;
MYSQL *mysql= stmt->mysql;
- DBUG_ASSERT(mysql->field_count);
+ DBUG_ASSERT(stmt->field_count);
- stmt->field_count= mysql->field_count;
+ free_root(fields_mem_root, MYF(0));
/*
Get the field information for non-select statements
like SHOW and DESCRIBE commands
*/
- if (!(stmt->fields= (MYSQL_FIELD *) alloc_root(alloc,
+ if (!(stmt->fields= (MYSQL_FIELD *) alloc_root(fields_mem_root,
sizeof(MYSQL_FIELD) *
stmt->field_count)) ||
- !(stmt->bind= (MYSQL_BIND *) alloc_root(alloc,
+ !(stmt->bind= (MYSQL_BIND *) alloc_root(fields_mem_root,
sizeof(MYSQL_BIND) *
stmt->field_count)))
{
@@ -1658,18 +1671,36 @@ static void alloc_stmt_fields(MYSQL_STMT *stmt)
field && fields < end; fields++, field++)
{
*field= *fields; /* To copy all numeric parts. */
- field->catalog= strmake_root(alloc, fields->catalog,
+ field->catalog= strmake_root(fields_mem_root,
+ fields->catalog,
fields->catalog_length);
- field->db= strmake_root(alloc, fields->db, fields->db_length);
- field->table= strmake_root(alloc, fields->table, fields->table_length);
- field->org_table= strmake_root(alloc, fields->org_table,
+ field->db= strmake_root(fields_mem_root,
+ fields->db,
+ fields->db_length);
+ field->table= strmake_root(fields_mem_root,
+ fields->table,
+ fields->table_length);
+ field->org_table= strmake_root(fields_mem_root,
+ fields->org_table,
fields->org_table_length);
- field->name= strmake_root(alloc, fields->name, fields->name_length);
- field->org_name= strmake_root(alloc, fields->org_name,
+ field->name= strmake_root(fields_mem_root,
+ fields->name,
+ fields->name_length);
+ field->org_name= strmake_root(fields_mem_root,
+ fields->org_name,
fields->org_name_length);
- field->def= fields->def ? strmake_root(alloc, fields->def,
- fields->def_length) : 0;
- field->def_length= field->def ? fields->def_length : 0;
+ if (fields->def)
+ {
+ field->def= strmake_root(fields_mem_root,
+ fields->def,
+ fields->def_length);
+ field->def_length= fields->def_length;
+ }
+ else
+ {
+ field->def= NULL;
+ field->def_length= 0;
+ }
field->extension= 0; /* Avoid dangling links. */
field->max_length= 0; /* max_length is set in mysql_stmt_store_result() */
}
@@ -2387,6 +2418,9 @@ static void reinit_result_set_metadata(MYSQL_STMT *stmt)
prepared statements can't send result set metadata for these queries
on prepare stage. Read it now.
*/
+
+ stmt->field_count= stmt->mysql->field_count;
+
alloc_stmt_fields(stmt);
}
else
@@ -2404,7 +2438,7 @@ static void reinit_result_set_metadata(MYSQL_STMT *stmt)
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);
}
}
@@ -4605,6 +4639,7 @@ my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt)
free_root(&stmt->result.alloc, MYF(0));
free_root(&stmt->mem_root, MYF(0));
+ free_root(&stmt->extension->fields_mem_root, MYF(0));
if (mysql)
{
@@ -4639,6 +4674,7 @@ my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt)
}
}
+ my_free(stmt->extension);
my_free(stmt);
DBUG_RETURN(test(rc));
@@ -4805,16 +4841,13 @@ int STDCALL mysql_stmt_next_result(MYSQL_STMT *stmt)
stmt->state= MYSQL_STMT_EXECUTE_DONE;
stmt->bind_result_done= FALSE;
+ stmt->field_count= mysql->field_count;
if (mysql->field_count)
{
alloc_stmt_fields(stmt);
prepare_to_fetch_result(stmt);
}
- else
- {
- stmt->field_count= mysql->field_count;
- }
DBUG_RETURN(0);
}