Bug #5254 Prepared Statement C API does not work
Submitted: 27 Aug 2004 15:13 Modified: 7 Sep 2004 15:48
Reporter: Rick Robinson Email Updates:
Status: Not a Bug Impact on me:
None 
Category:MySQL Server Severity:S2 (Serious)
Version:4.1.3 beta OS:Solaris (Solaris 9 Sparc)
Assigned to: Konstantin Osipov CPU Architecture:Any

[27 Aug 2004 15:13] Rick Robinson
Description:
The prepared statement C API does not work for simple select.  The code listed below shows the example.  The code listed below receives an error "MySQL client run out of memory" - but should just get a one row result.  In other tests (I have not listed code here), a very similar example receives a core dump.

Some of the relevant details:
Solaris 9 Sparc, gcc 3.3.2, using 32 bit, max binary version of 4.1.3 beta MySQL for Solaris 9, linked with reentrant client lib (libmyclient_r), using a my-medium.cnf config.

How to repeat:
create table cfg_2
(config_key varchar(30) not null,
section_key varchar(30) not null,
property_key varchar(255) not null,
property_value varchar(255),
primary key(config_key, section_key, property_key)
) engine=myisam;
insert into cfg_2 values('svp001','stats','number.of.threads','2');

<code>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include "mysql.h"
using namespace std;

int main(int argc, char *argv[])
{
    cout << "start up" << endl;
    MYSQL * connection;
    connection = mysql_init(0);
    // update for your own environment
    connection = mysql_real_connect(connection, 0, "sfwd", "sfwd", "sfwd", 0, 0,0);
    if (0 == connection)
    {
        cerr << "connect failed:" << mysql_error(connection) << endl;
        return -1;
    }
    static const char * sql_statement =
        "select property_value from cfg_2 "
        "where config_key=? and section_key=? and property_key=?";
    MYSQL_STMT * stmt_handle = mysql_stmt_init(connection);
    if (0 == stmt_handle)
    {
        cerr << "stmt init failed:" << mysql_error(connection) << endl;
        return -1;
    }
    int mysql_return_code = mysql_stmt_prepare(stmt_handle,
                                               sql_statement,
                                               strlen(sql_statement)
                                               );
    if (0 != mysql_return_code)
    {
        cerr << "stmt prepare failed:" << mysql_stmt_error(stmt_handle) << endl;
        mysql_stmt_close(stmt_handle);
        return -1;
    }

    MYSQL_BIND bind_var[3];
    unsigned long var_length[3];
    my_bool not_null = 0;
    char config_key[31] = {'\0'};
    char section_key[31] = {'\0'};
    char property_key[256] = {'\0'};

    bind_var[0].buffer_type = MYSQL_TYPE_VAR_STRING;
    bind_var[0].buffer = (char *)config_key;
    bind_var[0].buffer_length = sizeof(config_key);
    bind_var[0].length = &var_length[0];
    bind_var[0].is_null = &not_null;

    bind_var[1].buffer_type = MYSQL_TYPE_VAR_STRING;
    bind_var[1].buffer = (char *)section_key;
    bind_var[1].buffer_length = sizeof(section_key);
    bind_var[1].length = &var_length[1];
    bind_var[1].is_null = &not_null;

    bind_var[2].buffer_type = MYSQL_TYPE_VAR_STRING;
    bind_var[2].buffer = (char *)property_key;
    bind_var[2].buffer_length = sizeof(property_key);
    bind_var[2].length = &var_length[2];
    bind_var[2].is_null = &not_null;

    strncpy(config_key, "svp001", sizeof(section_key)-1);
    strncpy(section_key, "stats", sizeof(section_key)-1);
    strncpy(property_key, "number.of.threads", sizeof(property_key)-1);

    mysql_return_code = mysql_stmt_bind_param(stmt_handle, bind_var);
    if (0 != mysql_return_code)
    {
        cerr << "stmt bind failed:" << mysql_stmt_error(stmt_handle) << endl;
        mysql_stmt_close(stmt_handle);
        return -1;
    }

    mysql_return_code = mysql_stmt_execute(stmt_handle);
    if (0 != mysql_return_code)
    {
        cerr << "stmt execute failed:" << mysql_stmt_error(stmt_handle) << endl;
        mysql_stmt_close(stmt_handle);
        return -1;
    }

    char property_value[256] = {'\0'};
    MYSQL_BIND result_var[1];
    unsigned long result_var_length[1];
    my_bool is_null[1];

    result_var[0].buffer_type = MYSQL_TYPE_VAR_STRING;
    result_var[0].buffer = (char *)property_value;
    result_var[0].buffer_length = sizeof(property_value)-1;
    result_var[0].is_null = &is_null[0];
    result_var[0].length = &result_var_length[0];

    mysql_return_code = mysql_stmt_bind_result(stmt_handle, result_var);
    if (0 != mysql_return_code)
    {
        cerr << "stmt bind rslt failed:" << mysql_stmt_error(stmt_handle) << endl;
        mysql_stmt_close(stmt_handle);
        return -1;
    }

    mysql_return_code = mysql_stmt_store_result(stmt_handle);
    if (0 != mysql_return_code)
    {
        cerr << "stmt store rslt failed:" << mysql_stmt_error(stmt_handle) << endl;
        mysql_stmt_close(stmt_handle);
        return -1;
    }

    mysql_return_code = mysql_stmt_fetch(stmt_handle);
    if (0 != mysql_return_code && MYSQL_NO_DATA != mysql_return_code)
    {
        cerr << "stmt fetch failed:" << mysql_stmt_error(stmt_handle) << endl;
        mysql_stmt_free_result(stmt_handle);
        mysql_stmt_close(stmt_handle);
        return -1;
    }

    if (MYSQL_NO_DATA != mysql_return_code)
    {
        cout << "stmt - got: " << property_value << endl;
    }
    mysql_stmt_free_result(stmt_handle);
    mysql_stmt_close(stmt_handle);
    cout << "done" << endl;
    return 0;
}
</code>

Suggested fix:
Review the prepared statement API code, fix the prepared statement API code, and improve the doc for prepared statement C API usage.
[30 Aug 2004 20:02] Hartmut Holzgraefe
Verified with gcc 3.3.1 (SuSE 9.0).

Happens with libmysqlclient_r while regular libmysqlclient works fine.
[30 Aug 2004 22:50] Rick Robinson
I see that the problem was replicated - but it begs the question; is this a problem with just prepared statements and the reentrant lib or should I be concerned about using the reentrant lib with 4.1.3 for anything else?

Thx,
R
[7 Sep 2004 15:48] Konstantin Osipov
Hello Rick.

After fixing a few bugs in your test case, I wasn't able to repeat the bug.
Both, libmysqlclient_r and libmysqlclient work fine.
In contrary, if I try the original test case, it fails, no matter how it's compiled.
See the modified test case below. You shouldn't have set bind[i].length to point
to uninitialized lengths - on input, bind[i].length should be set only if length varies
from execute to execute. If this is the case, then you can reset lengths
before calling mysql_stmt_execute, without having to rebind parameters each time.

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include "mysql.h"
using namespace std;

int main(int argc, char *argv[])
{
  cout << "start up" << endl;
  MYSQL * connection;
  connection = mysql_init(0);
  // update for your own environment
  connection = mysql_real_connect(connection, 0, "root", "", "test", 0,
                                  "/opt/local/var/mysql/mysql.sock", 0);
  if (0 == connection)
  {
    cerr << "connect failed:" << mysql_error(connection) << endl;
    return -1;
  }
  static const char * sql_statement =
    "select property_value from cfg_2 "
    "where config_key=? and section_key=? and property_key=?";
  MYSQL_STMT * stmt_handle = mysql_stmt_init(connection);
  if (0 == stmt_handle)
  {
    cerr << "stmt init failed:" << mysql_error(connection) << endl;
    return -1;
  }
  int mysql_return_code = mysql_stmt_prepare(stmt_handle,
                                             sql_statement,
                                             strlen(sql_statement)
                                            );
 if (0 != mysql_return_code)
  {
    cerr << "stmt prepare failed:" << mysql_stmt_error(stmt_handle) <<
      endl;
    mysql_stmt_close(stmt_handle);
    return -1;
  }

  MYSQL_BIND bind_var[3];
  unsigned long var_length[3];
  my_bool not_null = 0;
  char config_key[31] = {'\0'};
  char section_key[31] = {'\0'};
  char property_key[256] = {'\0'};

  bzero(bind_var, sizeof(bind_var));
  bind_var[0].buffer_type = MYSQL_TYPE_VAR_STRING;
  bind_var[0].buffer = (char *)config_key;
  bind_var[0].buffer_length = sizeof(config_key);
  bind_var[0].is_null = &not_null;

  bind_var[1].buffer_type = MYSQL_TYPE_VAR_STRING;
  bind_var[1].buffer = (char *)section_key;
  bind_var[1].buffer_length = sizeof(section_key);
  bind_var[1].is_null = &not_null;

  bind_var[2].buffer_type = MYSQL_TYPE_VAR_STRING;
  bind_var[2].buffer = (char *)property_key;
  bind_var[2].buffer_length = sizeof(property_key);
  bind_var[2].is_null = &not_null;

  strncpy(config_key, "svp001", sizeof(section_key)-1);
  strncpy(section_key, "stats", sizeof(section_key)-1);
  strncpy(property_key, "number.of.threads", sizeof(property_key)-1);
 mysql_return_code = mysql_stmt_bind_param(stmt_handle, bind_var);
  if (0 != mysql_return_code)
  {
    cerr << "stmt bind failed:" << mysql_stmt_error(stmt_handle) << endl;
    mysql_stmt_close(stmt_handle);
    return -1;
  }

  mysql_return_code = mysql_stmt_execute(stmt_handle);
  if (0 != mysql_return_code)
  {
    cerr << "stmt execute failed:" << mysql_stmt_error(stmt_handle) <<
      endl;
    mysql_stmt_close(stmt_handle);
    return -1;
  }

  char property_value[256] = {'\0'};
  MYSQL_BIND result_var[1];
  unsigned long result_var_length[1];
  my_bool is_null[1];

  bzero(result_var, sizeof(result_var));
  result_var[0].buffer_type = MYSQL_TYPE_VAR_STRING;
  result_var[0].buffer = (char *)property_value;
  result_var[0].buffer_length = sizeof(property_value)-1;
  result_var[0].is_null = &is_null[0];
  result_var[0].length = &result_var_length[0];

  mysql_return_code = mysql_stmt_bind_result(stmt_handle, result_var);
  if (0 != mysql_return_code)
  {
    cerr << "stmt bind rslt failed:" << mysql_stmt_error(stmt_handle) <<
      endl;
    mysql_stmt_close(stmt_handle);
    return -1;
  }

  mysql_return_code = mysql_stmt_store_result(stmt_handle);
  if (0 != mysql_return_code)
  {
    cerr << "stmt store rslt failed:" << mysql_stmt_error(stmt_handle) <<
      endl;
    mysql_stmt_close(stmt_handle);
    return -1;
  }

  mysql_return_code = mysql_stmt_fetch(stmt_handle);
  if (0 != mysql_return_code && MYSQL_NO_DATA != mysql_return_code)
  {
    cerr << "stmt fetch failed:" << mysql_stmt_error(stmt_handle) << endl;
    mysql_stmt_free_result(stmt_handle);
    mysql_stmt_close(stmt_handle);
    return -1;
  }

  if (MYSQL_NO_DATA != mysql_return_code)
  {
    cout << "stmt - got: " << property_value << endl;
  }
  mysql_stmt_free_result(stmt_handle);
  mysql_stmt_close(stmt_handle);
  mysql_close(connection);
  mysql_server_end();
  cout << "done" << endl;
  return 0;
}

Note, that the sample is still wrong, as either buffer_length or length should reflect
real lengths of values, not lengths of buffers.

kostja@oak:~/work/mysql-4.1-5254/tests> g++  5254.cc -I../include -L../libmysql/.libs/ -lmysqlclient -lz
kostja@oak:~/work/mysql-4.1-5254/tests> ./5254 
start up
done
kostja@oak:~/work/mysql-4.1-5254/tests>