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