Bug #70550 Data mismatch between C NDB API and MySQL CLI.
Submitted: 8 Oct 2013 1:14 Modified: 7 Nov 2014 18:30
Reporter: Mike Cress Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Cluster: NDB API Severity:S2 (Serious)
Version:NDB Cluster 7.3 OS:Any
Assigned to: Lakshmi Narayanan Sreethar CPU Architecture:Any
Tags: NDB Cluster NDBAPI

[8 Oct 2013 1:14] Mike Cress
Description:
A bug exists between the data accessed and manipulated via the C API (NDB API) and the MySQL CLI. Data queried via the CLI returns missing data in the string e.g. data written via the C API might be "Test String" and the post-update query via the CLI shows "est String".  Working the other direction (e.g. updating via the CLI and the querying the data via the C API) shows an upside down question mark preceding the strings.

How to repeat:
Create a test database called testdb via the MySQL CLI. Create a test db using the following instruction: 
create table test ( id int not null primary key auto_increment, name varchar(50) not null ) default character set = utf8,  engine=ndbcluster;

Insert the following data:
insert into test values (1,'Test 2222');

The result to notice is that there is a space preceding the output string "( Test 2222") instead of "(Test 2222"). Then query the database and observe the result ("set update2222").

Execute this program (using your own connection details), and then query the data via the CLI:

#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include "NdbApi.hpp"
#include "mysql.h"

#define PRINT_ERROR(code,msg) \
std::cout << "Error in " << __FILE__ << ", line: " << __LINE__ \
<< ", code: " << code \
<< ", msg: " << msg << "." << std::endl

#define MYSQLERROR(mysql) { \
PRINT_ERROR(mysql_errno(&mysql),mysql_error(&mysql)); \
exit(-1); }

#define APIERROR(error) { \
PRINT_ERROR(error.code,error.message); \
exit(-1); }

struct TestRowdata
{
    int32_t id;
    char name[50];
};

Ndb_cluster_connection* connect_to_cluster();
void disconnect_from_cluster(Ndb_cluster_connection *c);

Ndb_cluster_connection* connect_to_cluster()
{
    Ndb_cluster_connection* c;
    
    if(ndb_init())
        exit(EXIT_FAILURE);
    
    c= new Ndb_cluster_connection("192.168.1.135");
    
    if(c->connect(4, 5, 1))
    {
        fprintf(stderr, "Unable to connect to cluster within 30 seconds.\n\n");
        exit(EXIT_FAILURE);
    }
    
    if(c->wait_until_ready(30, 0) < 0)
    {
        fprintf(stderr, "Cluster was not ready within 30 seconds.\n\n");
        exit(EXIT_FAILURE);
    }
    
    return c;
}

void disconnect_from_cluster(Ndb_cluster_connection *c)
{
    delete c;
    
    ndb_end(0);
}

int main(int argc, char* argv[])
{
    Ndb_cluster_connection *ndb_connection= connect_to_cluster();
    
    printf("Connection Established.\n\n");
    
    
    
    //Execute a test query
    Ndb myNdb( ndb_connection, "testdb" );
    if (myNdb.init())
        APIERROR(myNdb.getNdbError());
    
    
    NdbDictionary::Dictionary* myDict= myNdb.getDictionary();
    const NdbDictionary::Table *myTable= myDict->getTable("test");
    
    NdbTransaction *myTransaction= myNdb.startTransaction();
    if (myTransaction == NULL)
        APIERROR(myNdb.getNdbError());
    
    NdbDictionary::RecordSpecification recordSpec[2];
    
    const NdbDictionary::Column* pkeyCol = myTable->getColumn("id");
    recordSpec[0].column = pkeyCol;
    recordSpec[0].offset = offsetof(TestRowdata,id);
    recordSpec[0].nullbit_byte_offset = 0;
    recordSpec[0].nullbit_bit_in_byte = 0;
    
    const NdbDictionary::Column *pnameCol = myTable->getColumn("name");
    recordSpec[1].column = pnameCol;
    recordSpec[1].offset = offsetof(TestRowdata,name);
    recordSpec[1].nullbit_byte_offset = 0;
    recordSpec[1].nullbit_bit_in_byte = 0;
    
    NdbDictionary::RecordSpecification* pkeyRecordSpec = &recordSpec[0];
    NdbDictionary::RecordSpecification* nameColRecordSpec = &recordSpec[1];
    
    
    struct TestRowdata r;
    r.id = 1;
    struct TestRowdata res;
    NdbRecord* pkeyRec = myDict->createRecord( myTable, pkeyRecordSpec /*recordSpec*/, 1, sizeof(NdbDictionary::RecordSpecification) /*sizeof(recordSpec[0])*/ );
    NdbRecord* nameRec = myDict->createRecord( myTable, /*nameColRecordSpec*/ recordSpec, 2, /*sizeof(NdbDictionary::RecordSpecification)*/ sizeof(recordSpec[0]) );
    const NdbOperation *pop = myTransaction->readTuple(pkeyRec, (char*) &r, nameRec, (char*) &res);
    if (pop==NULL)
        APIERROR(myTransaction->getNdbError());
    
    if(myTransaction->execute( NdbTransaction::NoCommit ) == -1)
        APIERROR(myTransaction->getNdbError());
    
    if( myTransaction->getNdbError().classification == NdbError::NoDataFound )
        printf("No data found.\r\n\r\n");
    else
        printf("Old data: %d - (%s)\r\n\r\n", res.id, res.name);
    
    bzero(&r, sizeof(struct TestRowdata));
    bzero(&res, sizeof(struct TestRowdata));
    
    r.id = 1;
    char msg[] = "Test update2222";
    strncpy( res.name, msg, sizeof( msg ) );
    const NdbOperation* updateOp =myTransaction->updateTuple( pkeyRec, (char*) &r, nameRec, (char*) &res );
    if( updateOp == NULL )
        APIERROR(myTransaction->getNdbError());
    
    if(myTransaction->execute( NdbTransaction::Commit ) == -1)
        APIERROR(myTransaction->getNdbError());
    
    myTransaction->close();
    
    
    disconnect_from_cluster(ndb_connection);
    
    return EXIT_SUCCESS;
}

Suggested fix:
I am not sure of the fix but a work-around would to be to exclusively use one API over the other to access the data in the database.
[9 Oct 2013 16:33] Mike Cress
The mystery character is apparently the length of the string. I'm not sure if this is intended to be a feature, but this should be better documented, illustrated in the NDB code examples, or perhaps solved within the NDB API itself ( preferred solution ).

Note to others: the length of the string being encoded in the VARCHAR field should be prepended with the length of the string.
[11 Oct 2013 7:13] MySQL Verification Team
Hello Mike,

Thank you for the bug report and test case.
Indeed, this kind of behavior should be documented.

My colleague pointed out similar kind of info here http://geert.vanderkelen.org/insert-data-into-a-varchar-field-using-ndb-api-a-solution/

Regards,
Umesh
[7 Nov 2014 18:30] Jon Stephens
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.

Documented as follows in the NDB 7.3.8 and 7.4.3 changelogs:

    New examples, demonstrating reads and writes of CHAR, VARCHAR, 
    and VARBINARY column values have been added to those found in
    storage/ndb/ndbapi-examples in the MySQL Cluster source tree. 
    For more information, including program listings, see "NDB API 
    Simple Array Example," and "NDB API Simple Array Example Using 
    Adapters," in the MySQL CLuster API Developer Guide.

Added code listings for the new examples to the API documentation.

Closed.

If necessary, you can access the source repository and build the latest available version, including the bug fix. More information about accessing the source trees is available at

    http://dev.mysql.com/doc/en/installing-source.html