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