Bug #10562 Large blobs fail
Submitted: 11 May 2005 16:36 Modified: 20 Jul 2007 12:04
Reporter: Ian Klassen Email Updates:
Status: Closed Impact on me:
None 
Category:Connector / ODBC Severity:S2 (Serious)
Version:3.51.11 OS:Microsoft Windows (Windows XP SP1)
Assigned to: Jess Balint

[11 May 2005 16:36] Ian Klassen
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, &param)))
  // to:
  if (!(*to= (SQLCHAR*) insert_param(mysql, (char*) *to, &param)))
    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, &param)))
  // to:
  if (!(*to= (SQLCHAR*) insert_param(mysql,(char*) *to, &param)))
    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);
}
[11 May 2005 16:39] Ian Klassen
Visual Studio 6.0 Project

Attachment: myodbctest - blobs.zip (application/x-zip-compressed, text), 40.67 KiB.

[10 Jul 2007 21:21] Jess Balint
fix remaining (+other) errors reported by poster

Attachment: bug10562.patch (application/octet-stream, text), 3.10 KiB.

[10 Jul 2007 21:58] Jess Balint
Patch committed in rev559, will be release in 3.51.17.
[20 Jul 2007 12:04] MC Brown
A note has been added to the 3.51.17 changelog: 

When inserting a large BLOB field,
Connector/ODBC would crash due to a memory allocation error.