MySQL Cluster NDB API Hello World!
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.
- MySQL NDB Cluster installed. Community distributions are here (I like to work with the self contained TAR).
- The cluster up and running, with a basic configuration (1 management node, 1 SQL node and 2 data nodes)
- The C++ source code containing the application logic
- 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.
ndbConnect
creates the connection to the cluster with the connection string provided. With that, it instantiates theNdb
objectprepare
connects to the SQL node using MySQL C API and is used to create the schemandb_examples
and the NDB tablegreetings
, which I will use in the exampleinsert
method, which creates a transaction and uses it to insert a record into the tableread
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
usingautotools
- Compile “manually” using
g++
- Generate the
Makefile
usingCmake
(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:
aclocal
autoconf
touch NEWS README AUTHORS ChangeLog
automake -a -c
./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.