[CONC-459] Prepared statement insertion of a NULL MYSQL_TYPE_NEWDECIMAL type fails if buffer length is not set to 0 Created: 2020-03-06  Updated: 2020-03-11  Resolved: 2020-03-07

Status: Closed
Project: MariaDB Connector/C
Component/s: None
Affects Version/s: 3.1.7
Fix Version/s: N/A

Type: Bug Priority: Minor
Reporter: David Ritter Assignee: Georg Richter
Resolution: Not a Bug Votes: 0
Labels: None


 Description   

I have discovered that if I attempt to send a NULL value into a column that is bound as a MYSQL_TYPE_NEWDECIMAL I receive an MySQL client run out of memory from the client.

Here is my test case:

#include <iomanip>
#include <iostream>
#include <vector>
#include <time.h>
 
#include <mysql.h>
 
using namespace std;
 
void
dropTable(MYSQL* mysql) {
    char query[] = "drop table testdfr";
    if (mysql_real_query(mysql, query, strlen(query))) {
      fprintf(stderr, "%s\n", mysql_error(mysql));
    }
}
 
void
createTable(MYSQL* mysql) {
    dropTable(mysql);
    {
        char query[] = "create table testdfr(col1 DECIMAL(10,4))";
        if (mysql_real_query(mysql, query, strlen(query))) {
          fprintf(stderr, "%s\n", mysql_error(mysql));
        }
    }
}
 
void 
testInsertPrepare(MYSQL* mysql) {
    MYSQL_STMT    *stmt;
    MYSQL_BIND    bind[1];
    unsigned long length[1];
    my_bool       is_null[1];
 
    stmt = mysql_stmt_init(mysql);
    if (!stmt) {
      fprintf(stderr, " mysql_stmt_init(), out of memory\n");
    }
    if (mysql_stmt_prepare(stmt, "INSERT INTO testdfr VALUES(?)", -1)) {
      fprintf(stderr, " %s\n", mysql_stmt_error(stmt));
    }
 
    memset(bind, 0, sizeof(bind));
    bind[0].buffer_type= MYSQL_TYPE_NEWDECIMAL;
    bind[0].buffer = "1.23";
    bind[0].buffer_length = 8000;
    is_null[0] = 1;
    bind[0].is_null= &is_null[0];
    //unsigned long len = 0;
    unsigned long len = 2147483648; // DFR If this length is sufficiently large an out of memory
    bind[0].length = &len;
 
    if (mysql_stmt_bind_param(stmt, bind)) {
      fprintf(stderr, " %s\n", mysql_stmt_error(stmt));
    }
 
    if (mysql_stmt_execute(stmt)) {
      fprintf(stderr, " %s\n", mysql_stmt_error(stmt));
    }
 
    if (mysql_stmt_close(stmt)) {
      fprintf(stderr, " %s\n", mysql_stmt_error(stmt));
    }
}
 
int
main()
{
    mysql_library_init(0, NULL, NULL);
 
    MYSQL* mysql_ = mysql_init(NULL);
    if(mysql_errno(mysql_)) {
        mysql_library_end();
        cerr << "??? MySQL Initialization Failed ???" << endl;
        return 1;
    } else if (mysql_ == NULL ) {
        mysql_library_end();
        cerr << "??? MySQL Initialization Failed to Allocate Connection Handle ???" << endl;
        return 1;
    }
 
    unsigned int timeout = 0;
    mysql_options(mysql_, MYSQL_OPT_CONNECT_TIMEOUT, (const char*)&timeout);
    mysql_options(mysql_, MYSQL_SET_CHARSET_NAME, MYSQL_AUTODETECT_CHARSET_NAME);
 
 
    if(!mysql_real_connect(mysql_,
                           "server",
                           "user",
                           "pass",
                           "db",
                           3306, NULL, 0)
                           )
    {
        cerr << "??? MySQL Connection Failed ??? " << endl << mysql_error(mysql_) << endl ;
        mysql_close(mysql_);
        return 1;
    }
 
    createTable(mysql_);
    testInsertPrepare(mysql_);
 
    mysql_close(mysql_);
    mysql_library_end();
 
    return 0;
}

Without setting the buffer length I see this error:

 MySQL client run out of memory

It seems like a bug that if NULL is being sent that the buffer length should be used at all.



 Comments   
Comment by Georg Richter [ 2020-03-07 ]

Your example code is buggy:

  • If you want to insert a NULL value, the correct buffer_type is MYSQL_TYPE_NULL
  • In bind->buffer you provide a string, but specify MYSQL_TYPE_NEWDECIMAL as buffer_type
  • While length of bind->buffer is 5, you provide a buffer_length of 8000
  • bind->is_null is used when fetching rows to indicate a NULL value
Comment by David Ritter [ 2020-03-11 ]

It doesn't make sense to me to change the buffer_type to MYSQL_TYPE_NULL if you are performing column wise binding. It seems like you would always want the C type to be the MYSQL_TYPE_<...> of the column type.

I have updated my testcase accordingly:

...
    memset(bind, 0, sizeof(bind));
    bind[0].buffer_type= MYSQL_TYPE_NEWDECIMAL;
    double d = 1.23;
    bind[0].buffer = &d;
    is_null[0] = 1;
    bind[0].is_null= &is_null[0];
    //unsigned long len = 0;
    unsigned long len = 2147483648; // DFR If this length is sufficiently large an out of memory
    bind[0].length = &len;
...

And I still see the client of memory error.

Fundamentally though because a numeric value is being inserted I do not understand why the length data member of the MYSQL_BIND struct is being used. buffer.length is defined to be used for character or binary C data length. Should we consider MYSQL_TYPE_NEWDECIMAL binary data?

Generated at Thu Feb 08 03:05:28 UTC 2024 using Jira 8.20.16#820016-sha1:9d11dbea5f4be3d4cc21f03a88dd11d8c8687422.