Description:
When inserting a blob that is larger than 8k, MyODBC crashes when reallocating memory to store the larger blob.
How to repeat:
See the attached Visual Studio 6.0 project that reproduces this bug.
Create the following database to test:
CREATE DATABASE test;
USE test;
CREATE TABLE blob_data
(
blob_id int not null primary key auto_increment,
data blob not null
);
(Update the username/password in the connection string in CChildView::OnTest() to connect to your database.)
Suggested fix:
The problem seems to be that the memory is reallocated using a copy of the MYSQL structure instead of the original object. I was able to solve this by using pointers in the following two functions. Modified code with the original in comments is marked:
static SQLRETURN copy_rowdata(STMT FAR *stmt, PARAM_BIND param,
NET **net, SQLCHAR **to)
{
SQLCHAR *orig_to= *to;
// PHH modified from:
// MYSQL mysql= stmt->dbc->mysql;
// to:
MYSQL* mysql= &stmt->dbc->mysql;
// Comments: This is required so that if the net->buff grows
// the pointer in the original MYSQL struct will be updated
// instead of one in a copy of the object.
SQLUINTEGER length= *(param.actual_len)+1;
DBUG_PRINT("copy_rowdata",("buffer: '%s', length: %d, actual: %d", *to, length, sizeof(**to)));
if (!(*to= (SQLCHAR *) extend_buffer(*net,(char*) *to,length)))
return set_error(stmt,MYERR_S1001,NULL,4001);
// PHH modified from:
// if (!(*to= (SQLCHAR*) insert_param(&mysql, (char*) *to, ¶m)))
// to:
if (!(*to= (SQLCHAR*) insert_param(mysql, (char*) *to, ¶m)))
return set_error(stmt,MYERR_S1001,NULL,4001);
/* We have to remove zero bytes or we have problems! */
while ((*to > orig_to) && (*((*to) - 1) == (SQLCHAR) 0)) (*to)--;
/* insert "," */
param.SqlType= SQL_INTEGER;
param.CType= SQL_C_CHAR;
param.buffer= (gptr) ",";
*param.actual_len= 1;
// PHH modified from:
// if (!(*to= (SQLCHAR*) insert_param(&mysql,(char*) *to, ¶m)))
// to:
if (!(*to= (SQLCHAR*) insert_param(mysql,(char*) *to, ¶m)))
return set_error(stmt,MYERR_S1001,NULL,4001);
return(SQL_SUCCESS);
}
static SQLRETURN batch_insert(STMT FAR *stmt, SQLUSMALLINT irow,
DYNAMIC_STRING *ext_query)
{
MYSQL_RES *result= stmt->result;
SQLUINTEGER insert_count= 1;
SQLUINTEGER count= 0;
SQLINTEGER length;
NET *net;
SQLUSMALLINT ncol;
SQLCHAR *to;
ulong query_length= 0;
my_bool break_insert= FALSE;
// PHH modified from:
// MYSQL mysql= stmt->dbc->mysql ;
// to:
MYSQL* mysql= &stmt->dbc->mysql ;
PARAM_BIND param;
if (!irow && stmt->stmt_options.rows_in_set > 1) /* batch wise */
{
insert_count= stmt->stmt_options.rows_in_set;
query_length= ext_query->length;
}
do
{
if (break_insert)
{
/*
If query exceeded its length, then set the query
from begining..
*/
ext_query->length= query_length;
}
while (count < insert_count)
{
// PHH modified from:
// net= mysql->net;
// to:
net= &(mysql->net);
to = net->buff;
dynstr_append_mem(ext_query,"(", 1);
for (ncol= 0; ncol < result->field_count; ncol++)
{
SQLUINTEGER transfer_length,precision,display_size;
MYSQL_FIELD *field= mysql_fetch_field_direct(result,ncol);
BIND *bind= stmt->bind+ncol;
param.SqlType= unireg_to_sql_datatype(stmt,field,0,
&transfer_length,&precision,
&display_size);
param.CType = bind->fCType;
param.buffer= (gptr) bind->rgbValue+count*bind->cbValueMax;
if (param.buffer)
{
if (bind->pcbValue)
{
if (*bind->pcbValue == SQL_NTS)
length= strlen(param.buffer);
else if (*bind->pcbValue == SQL_COLUMN_IGNORE)
length= SQL_NULL_DATA;
else length= *bind->pcbValue;
}
else length= bind->cbValueMax;
}
else length= SQL_NULL_DATA;
param.actual_len= &length;
if (copy_rowdata(stmt,param,&net,&to) != SQL_SUCCESS)
return(SQL_ERROR);
} /* END OF for (ncol= 0; ncol < result->field_count; ncol++) */
length= (uint) ((char *)to - (char*) net->buff);
dynstr_append_mem(ext_query, (char*) net->buff, length-1);
dynstr_append_mem(ext_query, "),", 2);
count++;
if (ext_query->length+length >= net_buffer_length)
{
break_insert= TRUE;
break;
}
} /* END OF while(count < insert_count) */
ext_query->str[--ext_query->length]= '\0';
DBUG_PRINT("batch_insert:",("%s",ext_query->str));
if (exec_stmt_query(stmt, ext_query->str, ext_query->length) !=
SQL_SUCCESS)
return(SQL_ERROR);
} while (break_insert && count < insert_count);
stmt->affected_rows= stmt->dbc->mysql.affected_rows= insert_count;
if (stmt->stmt_options.rowStatusPtr)
{
for (count= insert_count; count--;)
stmt->stmt_options.rowStatusPtr[count]= SQL_ROW_ADDED;
}
return(SQL_SUCCESS);
}