MySQL Cluster NDB API Hello World!

MySQL NDB API

In this post I will show how to program a C++ client for MySQL NDB Cluster. I have already presented MySQL Cluster, the distributed database using the in-memory storage engine, in several occasions. You may have learnt how to configure and start MySQL Cluster, so I will assume that a cluster is up and running. If you want to develop a simple C++ client, just run the cluster in a single host, which may be your very laptop. In order to compile an NDB API client, you will need.

  1. MySQL NDB Cluster installed. Community distributions are here (I like to work with the self contained TAR).
  2. The cluster up and running, with a basic configuration (1 management node, 1 SQL node and 2 data nodes)
  3. The C++ source code containing the application logic
  4. A Makefile

NDB API client source code

The following code (copy and paste into a main.cpp file) implements:

  • NDB API, to connect to the cluster thus bypassing the MySQL Server (aka SQL node). We can operate NDB storage engine tables using SQL statements via the SQL node, but the purpose of this how-to is to allow high performance connections by connecting directly to the data nodes, thus using NDB as a key-value storage.
  • MySQL C API, to connect to SQL nodes and execute standard SQL statements.

Here is a brief description of the methods implemented.

  1. ndbConnect creates the connection to the cluster with the connection string provided. With that, it instantiates the Ndb object
  2. prepare connects to the SQL node using MySQL C API and is used to create the schema ndb_examples and the NDB table greetings, which I will use in the example
  3. insert method, which creates a transaction and uses it to insert a record into the table
  4. read method, to read the record just stored
#include <iostream>
#include <stdio.h>
#include <NdbApi.hpp>
#include <mysql.h>
#include <mysqld_error.h>

#define NDB_CONNECT "localhost:1186"
#define MYSQL_HOST "127.0.0.1"
#define MYSQL_PORT 3306
#define MYSQL_USER "mcmd"
#define MYSQL_PASSWORD "super"

using namespace std;

Ndb *myNdb=NULL;

#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); }

void insert(){
    const NdbDictionary::Dictionary *dict = myNdb->getDictionary();
    if (dict == NULL) {
        APIERROR(dict->getNdbError());
    }

    const NdbDictionary::Table *greetingsTable = dict->getTable("greetings");
    if (greetingsTable == NULL) {
        APIERROR(dict->getNdbError());
    }

    NdbTransaction *trans = myNdb->startTransaction();
    if (trans == NULL) {
        APIERROR(trans->getNdbError());
    }

    NdbOperation *myOperation = trans->getNdbOperation(greetingsTable);
    if ( myOperation == NULL) {
        APIERROR(myOperation->getNdbError());
    }

    myOperation->insertTuple();

    char message[20];
    sprintf(message, "Hello World!");

    myOperation->setValue("id", 1);
    myOperation->setValue("msg", message);

    if(trans->execute(NdbTransaction::Commit) != 0) {
        APIERROR(trans->getNdbError());
    }

    myNdb->closeTransaction(trans);
    cout << "Insert operation was successful" << endl;
}


void read(){
    const NdbDictionary::Dictionary *dict = myNdb->getDictionary();
    if (dict == NULL) {
        APIERROR(dict->getNdbError());
    }

    const NdbDictionary::Table *greetingsTable = dict->getTable("greetings");
    if (greetingsTable == NULL) {
        APIERROR(dict->getNdbError());
    }

    NdbTransaction *trans = myNdb->startTransaction();
    if (trans == NULL) {
        APIERROR(myNdb->getNdbError());
    }

    NdbOperation *myOperation = trans->getNdbOperation(greetingsTable);
    if ( myOperation == NULL) {
        APIERROR(trans->getNdbError());
    }

    myOperation->readTuple(NdbOperation::LM_Read);
    myOperation->equal("id", 1);

    NdbRecAttr *myRecAttr= myOperation->getValue("msg", NULL);
    if (myRecAttr == NULL) {
        APIERROR(trans->getNdbError());
    }

    if(trans->execute(NdbTransaction::NoCommit) != 0) {
        APIERROR(trans->getNdbError());
    }

    cout << endl << myRecAttr->aRef() << endl;

    myNdb->closeTransaction(trans);
}


void ndbConnect(Ndb_cluster_connection *cluster_connection){
    cluster_connection = new Ndb_cluster_connection(NDB_CONNECT);

    if(cluster_connection->connect(5,5,1)) {
        cout << "Cannot connect to Cluster using connectstring: "<< NDB_CONNECT << endl;
        exit(EXIT_FAILURE);
    }

    if(cluster_connection->wait_until_ready(30,0) < 0) {
        cout << "Cluster was not ready within 30 seconds" << endl;
        exit(EXIT_FAILURE);
    }

    myNdb = new Ndb(cluster_connection, "ndb_examples");
    if(myNdb->init(1024) == -1){
        APIERROR(myNdb->getNdbError());
    }

    cout << "Connected to NDB Cluster" << endl;
}


void prepare(){
    MYSQL *mysql=new MYSQL();
    if ( !mysql_init(mysql) ) {
      std::cout << "mysql_init failed\n";
      exit(EXIT_FAILURE);
    }

    if ( !mysql_real_connect(mysql, MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, "", MYSQL_PORT, NULL, 0))
        MYSQLERROR(*mysql);

    mysql_query(mysql, "CREATE DATABASE IF NOT EXISTS ndb_examples");

    if (mysql_select_db(mysql, "ndb_examples")!=0){
        MYSQLERROR(*mysql);
    }

    mysql_query(mysql, "DROP TABLE IF EXISTS greetings");
    mysql_query(mysql,  "CREATE TABLE greetings(id int primary key, msg char(20) NOT NULL) "
                        "DEFAULT CHARSET=latin1 ENGINE=NDB;");
    mysql_close(mysql);

    cout << "Connected to SQL node" << endl;
}


int main(int argc, char **argv) {
    Ndb_cluster_connection *cluster_connection=NULL;
	cout << "NDB api test application" << endl;

    if(ndb_init())
        exit(EXIT_FAILURE);

    ndbConnect(cluster_connection);
    prepare();
    insert();
    read();

    ndb_end(0);

    delete myNdb;
    delete cluster_connection;

    return EXIT_SUCCESS;
}

Compile the NDB API program

In order to compile the program, you can follow any of the three approaches.

  • Generate the Makefile using autotools
  • Compile “manually” using g++
  • Generate the Makefile using Cmake (not discussed in this post)

Generate the Makefile using autotools

Following the instructions from the documentation, it is sufficient to create in the folder of the source code, the following files:

  • acinclude.m4
  • Makefile.am
  • configure.in

acinclude.m4

dnl
dnl configure.in helper macros
dnl

AC_DEFUN([WITH_MYSQL], [
  AC_MSG_CHECKING(for mysql_config executable)

  AC_ARG_WITH(mysql, [  --with-mysql=PATH path to mysql_config binary or mysql prefix dir], [
  if test -x $withval -a -f $withval
    then
      MYSQL_CONFIG=$withval
    elif test -x $withval/bin/mysql_config -a -f $withval/bin/mysql_config
    then
     MYSQL_CONFIG=$withval/bin/mysql_config
    fi
  ], [
  if test -x /usr/local/mysql/bin/mysql_config -a -f /usr/local/mysql/bin/mysql_config
    then
      MYSQL_CONFIG=/usr/local/mysql/bin/mysql_config
    elif test -x /usr/bin/mysql_config -a -f /usr/bin/mysql_config
    then
      MYSQL_CONFIG=/usr/bin/mysql_config
    fi
  ])

  if test "x$MYSQL_CONFIG" = "x"
  then
    AC_MSG_RESULT(not found)
    exit 3
  else
    AC_PROG_CC
    AC_PROG_CXX

    # add regular MySQL C flags
    ADDFLAGS=`$MYSQL_CONFIG --cflags`

    # add NDB API specific C flags
    IBASE=`$MYSQL_CONFIG --include`
    ADDFLAGS="$ADDFLAGS $IBASE/storage/ndb"
    ADDFLAGS="$ADDFLAGS $IBASE/storage/ndb/ndbapi"
    ADDFLAGS="$ADDFLAGS $IBASE/storage/ndb/mgmapi"

    CFLAGS="$CFLAGS $ADDFLAGS"
    CXXFLAGS="$CXXFLAGS $ADDFLAGS"

    LDFLAGS="$LDFLAGS "`$MYSQL_CONFIG --libs_r`" -lndbclient"
    LDFLAGS="$LDFLAGS "`$MYSQL_CONFIG --libs_r`" -lndbclient"

    AC_MSG_RESULT($MYSQL_CONFIG)
  fi
])

Makefile.am

bin_PROGRAMS = apitest
apitest_SOURCES = main.cpp

configure.in

AC_INIT(apitest, 1.0)
AM_INIT_AUTOMAKE(apitest, 1.0)
WITH_MYSQL()
AC_OUTPUT(Makefile)

Once created the files, it is possible to proceed as follows:

  1. aclocal
  2. autoconf
  3. touch NEWS README AUTHORS ChangeLog
  4. automake -a -c
  5. ./configure --with-mysql=<PATH_TO_MYSQL_CONFIG>/bin/mysql_config

Once the steps are done, you will find your Makefile in the folder, ready to be used. So, compile with:

make

Compile “manually” using g++

If you want to go ahead and compile quickly your source code, you can simply use the following (note that I have a whole distribution under a single path, because I am using a TAR binary distribution).

g++ -w -g -c  -O0 -W -I<DISTRIBUTION_PATH>/include/storage/ndb/ndbapi/ -I<DISTRIBUTION_PATH>/include/storage/ndb -I<DISTRIBUTION_PATH>/lib/private -I<DISTRIBUTION_PATH>/include/ -o main.o main.cpp

g++ -w -g  -o apitest  main.o   -L<DISTRIBUTION_PATH>/lib/ -L<DISTRIBUTION_PATH>/lib/private -lmysqlclient -lndbclient -lpthread -lm -lrt -lcrypto -lssl -ldl -lresolv

Hello World!

If you followed the steps correctly (or you managed to fix issues), you can now launch you NDB API “Hello World!” client.

./apitest 
NDB api test application
Connected to NDB Cluster
Connected to SQL node
Insert operation was successful

Hello World!

Make sure you learn how to code more complex programs from the NDB API examples page.

Leave A Comment