Bug #1664 mysql_send_long_data() API call is completely busted
Submitted: 26 Oct 2003 3:38 Modified: 4 May 2004 18:07
Reporter: Dmitry Lenev Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Server Severity:S2 (Serious)
Version:4.1 OS:
Assigned to: Dmitry Lenev CPU Architecture:Any

[26 Oct 2003 3:38] Dmitry Lenev
Description:
mysql_send_long_data() is completely broken because:

1) Real API doesn't correspond manual (some described features are missing and some crucial issues are not described)

2) Behavior of libmysql is incosistent with server behavior 
(for example in certain situations server will expect from client description of only one field but will receive two of them)

3) Current mysql_send_long_data() is broken by itself since there is no way to 
say to server that we want reset our long data parameter to empty string...
And by the way if server will once mark parameter as long data parameter it will treat this parameter as such forever. Client have no means to change this.

How to repeat:
Demo code snippet from tests/client_test.c

/********************************************************
* to test various long data bugs                        *
*********************************************************/

static void test_bug_long_data()
{
  MYSQL_STMT *stmt;
  int        rc, int_data;
  char       *data=NullS;
  MYSQL_RES  *result;
  MYSQL_BIND bind[2];

  myheader("test_bug_long_data");

  rc= mysql_autocommit(mysql,TRUE);
  myquery(rc);

  rc= mysql_query(mysql,"DROP TABLE IF EXISTS test_long_data");
  myquery(rc);

  rc= mysql_commit(mysql);
  myquery(rc);

  rc= mysql_query(mysql,"CREATE TABLE test_long_data(col1 int, col2 long varchar)");
  myquery(rc);

  rc= mysql_commit(mysql);
  myquery(rc);

  strmov(query,"INSERT INTO test_long_data(col2, col1) VALUES(?,?)");
  stmt= mysql_prepare(mysql, query, strlen(query));
  mystmt_init(stmt);

  verify_param_count(stmt,2);

  /* according to manual this should work */
  bind[0].buffer_type= FIELD_TYPE_STRING;
  bind[0].is_null= 0;
  bind[0].buffer_length= 0;
#ifdef THIS_DOES_NOT_COMPILE
  /* This even doesn't compile !!! But it should according to manual */
  bind[0].length= MYSQL_LONG_DATA;
  /* And this too !!! But it should according to manual too */
  bind[0].is_long_data= 1;
#else
  bind[0].length= 0;
#endif
  /* 
    Ok we implicitly mark parameter as long data on first
    mysql_send_long_data call but it is not documented anyhwhere
  */

  bind[1].buffer= (char *)&int_data;
  bind[1].buffer_type= FIELD_TYPE_LONG;
  bind[1].is_null= 0;

  rc= mysql_bind_param(stmt,bind);
  mystmt(stmt, rc);

  int_data= 1;

  /* 
    Now we are supplying empty long_data and this just doesn't work
    Moreover it will completely broke following execution
  */
  data= (char *)"";
  rc= mysql_send_long_data(stmt,0,data,strlen(data));
  mystmt(stmt,rc);

  /* execute */
  rc= mysql_execute(stmt);
  fprintf(stdout," mysql_execute() returned %d\n",rc);
  mystmt(stmt,rc);

#ifdef THIS_WILL_BREAK_TEST
  /* We have trash in col1 now */
  verify_col_data("test_long_data","col1","1");
  verify_col_data("test_long_data","col2","");
#endif

  /* clean-up */
  mysql_stmt_close(stmt);
  rc= mysql_query(mysql,"DELETE FROM test_long_data");
  myquery(rc);

  /* This should pass OK */

  /* This will resurrect statement */
  stmt= mysql_prepare(mysql, query, strlen(query));
  mystmt_init(stmt);
  verify_param_count(stmt,2);
  rc= mysql_bind_param(stmt,bind);
  mystmt(stmt, rc);
  
  data= (char *)"Data";
  rc= mysql_send_long_data(stmt,0,data,strlen(data));
  mystmt(stmt,rc);
  
  rc= mysql_execute(stmt);
  fprintf(stdout," mysql_execute() returned %d\n",rc);
  mystmt(stmt,rc);

  verify_col_data("test_long_data","col1","1");
  verify_col_data("test_long_data","col2","Data");
  
  /* clean up */
  rc= mysql_query(mysql,"DELETE FROM test_long_data");
  myquery(rc);
  
  /*  
    Now we are changing int parameter and don't do anything 
    with first parameter. 
    But! mysql_execute resets long_data_used member of internal param desriptor 
    to 0!!! So library send two parameters, But server remembers that first 
    parameter wants long data and reads only second parameter which it assumes 
    is int so we have trash in int field...
    Actually I think the same will happen if we even will call bind() with other 
    params. Server will treat first parameter as long_data until we close statement
  */
    
  int_data= 2;
  /* execute */
  rc = mysql_execute(stmt);
  fprintf(stdout," mysql_execute() returned %d\n",rc);
  mystmt(stmt,rc);
  
#ifdef THIS_WILL_BREAK_TEST
  /* Here we have trash in col1 so this will fail test */
  verify_col_data("test_long_data","col1","1");
  verify_col_data("test_long_data","col2","Data");
#endif
  
  /* clean up */
  rc= mysql_query(mysql,"DELETE FROM test_long_data");
  myquery(rc);

  /* And now my favorite one - What happens if we want set other long data ? */

  data= (char *)"SomeOtherData";
  rc= mysql_send_long_data(stmt,0,data,strlen(data));
  mystmt(stmt,rc);

  rc= mysql_execute(stmt);
  fprintf(stdout," mysql_execute() returned %d\n",rc);
  mystmt(stmt,rc);

#ifdef THIS_WILL_BREAK_TEST
  /*
    Ooops we have DataSomeOtherData in col2 and NO way except of
    repreparing of our statement to reset it...
  */
  verify_col_data("test_long_data","col1","2");
  verify_col_data("test_long_data","col2","SomeOtherData");
#endif

  mysql_stmt_close(stmt);
}

Suggested fix:
Rethink current API and implementation
[22 Jan 2004 6:04] Dmitry Lenev
Actually there is way to reset long data parameter. It is mysql_stmt_reset() (but it is not documented).
[4 May 2004 18:07] Dmitry Lenev
Thank you for your bug report. This issue has been committed to our
source repository of that product and will be incorporated into the
next release.

If necessary, you can access the source repository and build the latest
available version, including the bugfix, yourself. More information 
about accessing the source trees is available at
    http://www.mysql.com/doc/en/Installing_source_tree.html

Additional info:

The fix will appear in 4.1.2