Building the Example Programs

In this section, we will begin constructing the application base on the Requirements that we have already covered. We will be using the schema file (helloworld.sdl) that we created in the Database Design section. The following steps will assume the reader has knowledge of how to create a C/C++ program in the host environment that they are working in. This example will be written in a platform agnostic way and should work for Linux/macOS/Windows users.

The example programs covered in this section will demonstrate how the components of the RaimaDB system work together for a standalone application with RaimaDB into the application process and for a client/server like application where there is a communication link between the application process and the TFS process.

Creating the RaimaDB Interface Files

Using the schema file we created in the Database Design,we will use the rdm-compile utility to create the C/C++ source files needed to interface to the RaimaDB Core API and create the database if it does not already exist. The rdm-compile utility will create the support files in the current working directory. For this example, we will use the following command line:

$> rdm-compile --c-structs --catalog helloworld.sdl

The execution of this command will create the three files listed below:

helloworld_structs.h Generated RaimaDB Struct Definitions for helloworld. This file is created because of the --c-structs option. The file contains mnemonics representing the TABLES and COLUMNS of the schema for interfacing with the Core API as well as C structures the contain the row layouts.
helloworld_cat.h Compiled Catalog Header File.
helloworld_cat.c Compiled Catalog Source File. This is a binary representation of the schema file that will be used by the database open function to create the database image if it does not already exist. The compiled catalog files are created when the --catalog option is given.

Writing the Example 1 Application

This example application will fulfill the requirements for the HelloWorld Example 1 requirements in the Requirements section.

  • The application will instantiate a handle for communicating with the TFS.
  • The application will open the database and will create the database if it does not exist.
  • The application will insert a populated row into the database.
  • The application will read all of the rows currently in the database.

For the purpose of simplicity in documentation, the application will be presented in six(6) files:

  • The three files created in the Creating the RaimaDB Interface Files section above.
  • helloworld.c will contain the "main" function that will connect to the TFS, open the database, call the insert and read modules, and finally shutdown.
  • insertRowIntoDb.c will contain the function that will insert one row into the helloworld database.
  • readRowsFromDb.c will contain the function that will read all the rows from the helloworld database.

Steps to Open the Database

Below is a listing for the helloworld.c example. This portion of the example will make the connection to the TFS, open the database, and then call the insert and read functions that will be described later in this document. Following the calls to the insert and read, we will cleanly shutdown the database operations in RaimaDB.

The documentation will describe the RaimaDB functions used in the order that they appear in this file.

helloworld.c

#include <stdio.h>

#include "rdm.h"
#include "helloworld_structs.h"
#include "helloworld_cat.h"

extern RDM_RETCODE insertRowIntoDb(RDM_DB hDB);
extern RDM_RETCODE readRowsFromDb(RDM_DB hDB);

int main(int argc, char* argv[])
{
    RDM_RETCODE rc;
    RDM_TFS hTFS;
    RDM_DB hDB;

    rc = rdm_rdmAllocTFS(&hTFS);
    if (rc == sOKAY)
    {
        rc = rdm_tfsSetOption (hTFS, "docroot", "/RaimaData"); 
        if (rc == sOKAY)
        {
            rc = rdm_tfsInitialize(hTFS);
        }
        if (rc == sOKAY)
        {
            rc = rdm_tfsAllocDatabase (hTFS, &hDB);
        }
        if (rc == sOKAY)
        {
            rc = rdm_dbSetCatalog(hDB, helloworld_cat);
            if (rc == sOKAY)
            {
                rc = rdm_dbOpen (hDB, "helloworld", RDM_OPEN_SHARED);             }
            if (rc == sOKAY)
            {
                rc = insertRowIntoDb(hDB);
                if (rc == sOKAY)
                {
                    rc = readRowsFromDb(hDB);
                }
                rdm_dbClose(hDB);
            }
            rdm_dbFree(hDB);
        }
        rdm_tfsFree(hTFS);
    }
}

Step 1: Allocating a TFS Handle

Before we can open the database, we need to connect to the TFS module that will be serving up the database information to our application. The interface to the TFS, whether or not it is embedded or remote, is the same.

For this example, we will be using an RaimaDB TFS library that contains ALL of the TFS capabilities. Other RaimaDB TFS libraries available in the package a subset of the full TFS feature set. See RaimaDB Libraries section for a description of the RaimaDB library components and the dependency tree.

The TFS interaction using a handle to reference the TFS functionality. It is possible to have multiple TFS instances running on the same machine and in the same process space as long as those TFS instances are not sharing the same docroot or communication ports (if listening is enabled).

The functions that will be described in this section are:

rdm_rdmAllocTFS()

The first step in opening a database in the RaimaDB system is to allocate a handle for interfacing with the TFS functionality. This function call will allocate a handle to access the internals that determine what type of TFS we will be using as well as methods for communicating with that TFS.

rdm_tfsSetOption()

Once we have the handle allocated, we can modify the default behavior of the TFS by setting options. The list of available options for the TFS can be found in the TFS Configuration Options section.

The default docroot for an embedded TFS instance is the current working directory. Since our stated requirement is to locate the database in a specific location ("/RaimaData"), we will use an function to set the docroot location prior to instantiating the embedded TFS. The docroot cannot be altered AFTER the TFS has been started.

rdm_tfsInitialize()

Now that the TFS handle has been allocated, the next function will setup a TFS interface based on our settings (and the default settings of the TFS library type we are linking with). For this application, the function will start a TFS Thread running in our process that we can access using the TFS handle.

This function allocates all of the resources necessary to support this TFS type and starts the TFS Thread running in the background. At this point, the docroot has been locked to prevent another TFS from using it.

RaimaDB requires only one directory to be defined for the database engine (TFS). The required directory is the location of the root database directory referred to as the docroot in this manual. If the docroot is not defined, it will be assumed to be the current working directory of the application process executing the TFS process. For a disk based TFS, the docroot cannot be the root directory of a file system. For an inmemory only TFS, no directories will be required since RaimaDB will not have access to a file system.

Step 2: Opening the Database

Now that we have a TFS handle, we can start associating databases with that handle. One TFS handle can have multiple databases associated with it. The first step is to allocate a database handle for the database that we want to open.

Only one database can be opened on a handle. If multiple databases need to be opened simultaneously, a handle will need to allocated for each database.

The functions that will be described in this section are:

rdm_tfsAllocDatabase()

rdm_dbSetCatalog()

rdm_dbOpen()

rc = rdm_tfsAllocDatabase (hTFS, &hDB);

In the above line, we are allocating a new handle (hDB) associated with the previously allocated TFS handle (hTFS) from above.

Once we have the database handle allocated, we can modify the default behavior by setting database options. The list of available options for the database handle can be found in the Database Configuration Options section. For this example, we will leave database options at their default settings but we will associate the database catalog we created in the Creating the RaimaDB Interface Files section.

rc = rdm_dbSetCatalog (hDB, helloworld_cat);

The name of the catalog for the schema we created will be found in the helloworld_cat.h file.

The database can now be opened. The following call will attempt the database open on the TFS handle associated with the database handle. If the database cannot be found, the database image will be created with an empty database.

rc = rdm_dbOpen (db, "helloworld", RDM_OPEN_SHARED);

The open command provides the Database Identifier (db-uri) and the mode that the database will be opened in. In this example, TFS URI portion of the DB URI is empty and only includes "helloworld" as the name of the database. The empty TFS URI indicates that the connection will be made to the EMBED TFS. The mode will be a 'shared' open mode. The 'shared' mode indicates that another database handle connected to the same EMBED TFS could also have the database open at the same time. Other modes include: RDM_OPEN_EXCLUSIVE and RDM_OPEN_READONLY.

The database name does not need to be the same as the schema name. The database name could be named "foo" and that would be perfectly acceptable.

A successful return from the rdm_dbOpen() function indicates that the database is open and ready for reading or writing.

rdm_dbClose()

rdm_tfsFree()

When the application has closed the database, the resources allocated for the TFS can be freed. If the TFS allocated is an Embedded TFS, the TFS Thread will be shutdown.

Inserting a Row into the Database

insertRowIntoDb.c

#include <string.h>

#include "rdm.h"
#include "helloworld_structs.h"

RDM_RETCODE insertRowIntoDb(RDM_DB hDB)
{
    RDM_RETCODE rc;

    rc = rdm_dbStartUpdate(hDB, RDM_LOCK_ALL, 0, NULL, 0, NULL);
    if (rc == sOKAY)
    {
        WORLD rowValues = { 0 };
        strncpy(rowValues.HELLO, "Hello World", sizeof(rowValues.HELLO));

        rc = rdm_dbInsertRow(hDB, TABLE_WORLD, &rowValues, sizeof(rowValues), NULL);
        if (rc == sOKAY)
        {
            rc = rdm_dbEnd(hDB);
        }
        else {
            rc = rdm_dbEndRollback(hDB);
        }
    }
    return rc;
}

Reading the Rows from the Database

readRowsFromDb.c

#include <stdio.h>

#include "rdm.h"
#include "helloworld_structs.h"

RDM_RETCODE readRowsFromDb(RDM_DB hDB)
{
    RDM_RETCODE rc;
    RDM_CURSOR hCursor;
    WORLD rowValues = { 0 };

    rc = rdm_dbStartRead(hDB, RDM_LOCK_ALL, 0, NULL);
    if (rc == sOKAY)
    {
        rc = rdm_dbAllocCursor(hDB, &hCursor);
        if (rc == sOKAY)
        {
            rc = rdm_dbGetRows(hDB, TABLE_WORLD, &hCursor);
            if (rc == sOKAY)
            {
                for (rc = rdm_cursorMoveToFirst(hCursor); rc == sOKAY; rc = rdm_cursorMoveToNext(hCursor))
                {
                    rc = rdm_cursorReadRow(hCursor, &rowValues, sizeof(rowValues), NULL);
                    if (rc == sOKAY)
                    {
                        puts(rowValues.HELLO);
                    }
                }
                /* if we exited the for loop with sEOS, that is expected */
                if (rc == sEOS)
                {
                    rc = sOKAY;
                }
            }
            rdm_cursorFree(hCursor);
        }
        rdm_dbEnd(hDB);
    }
    return rc;
}

Compiling and Linking the Application

The compiling process requires the generated header files and the RaimaDB "include" directory to be in the INCLUDE path for the compiler toolchain. Unless otherwise instructed, no other DEFINES need to be specified to compile your source files.

To link your compiled objects into an executable, you will need to include the RaimaDB libraries associated with the RaimaDB APIs you are using in your application. For example, the rdm_dbStartRead() function used in the previous code snippet is located in the rdm library. On Linux, you would need to include the librdmrdm.so library. The reference section of this manual contains the names of the libraries that your application will need to include based on the API you are using.

For the rdm_dbStartRead() function we just referenced, you will notice under the Detailed Description on that function group page the name of the library.

For applications using static libraries, the RaimaDB Libraries section will contain all of the dependent RaimaDB libraries that will need to be included to link your application.

Linking with an Embedded TFS

There are multiple TFS libraries to pick from, but ONLY include one!

For the embedded TFS capability needed to fulfill our requirements for this example, we will need to include the tfs_embed library. On Linux, you would need to include the librdmtfs_embed.so library.

Writing the Example 2 Application

This example application will fulfill the requirements for the HelloWorld Example 2 requirements in the Requirements section.

  • The application will instantiate a handle for communicating with the TFS.
  • The application will open the database on a TFS running on localhost using port number 21553 and will create the database on that TFS if it does not exist.
  • The application will insert a populated row into the database.
  • The application will read all of the rows currently in the database.

For the purpose of simplicity in documentation, the application will be presented in six(6) files:

  • The three files created in the Creating the RaimaDB Interface Files section above.
  • helloworld.c will contain the "main" function that will connect to the TFS, open the database, call the insert and read modules, and finally shutdown.
  • insertRowIntoDb.c will contain the function that will insert one row into the helloworld database.
  • readRowsFromDb.c will contain the function that will read all the rows from the helloworld database.

Steps to Open the Database

Below is a listing for the helloworld.c example. This portion of the example will make the connection to the TFS, open the database, and then call the insert and read functions that will be described later in this document. Following the calls to the insert and read, we will cleanly shutdown the database operations in RaimaDB.

The documentation will describe the RaimaDB functions used in the order that they appear in this file.

helloworld.c

#include <stdio.h>

#include "rdm.h"
#include "helloworld_structs.h"
#include "helloworld_cat.h"

extern RDM_RETCODE insertRowIntoDb(RDM_DB hDB);
extern RDM_RETCODE readRowsFromDb(RDM_DB hDB);

int main(int argc, char* argv[])
{
    RDM_RETCODE rc;
    RDM_TFS hTFS;
    RDM_DB hDB;

    rc = rdm_rdmAllocTFS(&hTFS);
    if (rc == sOKAY)
    {
        if (rc == sOKAY)
        {
            rc = rdm_tfsInitialize(hTFS);
        }
        if (rc == sOKAY)
        {
            rc = rdm_tfsAllocDatabase (hTFS, &hDB);
        }
        if (rc == sOKAY)
        {
            rc = rdm_dbSetCatalog(hDB, helloworld_cat);
            if (rc == sOKAY)
            {
                rc = rdm_dbOpen (hDB, "tfs://localhost:21553/helloworld", RDM_OPEN_SHARED); 
            }
            if (rc == sOKAY)
            {
                rc = insertRowIntoDb(hDB);
                if (rc == sOKAY)
                {
                    rc = readRowsFromDb(hDB);
                }
                rdm_dbClose(hDB);
            }
            rdm_dbFree(hDB);
        }
        rdm_tfsFree(hTFS);
    }
}

Step 1: Allocating a TFS Handle

Before we can open the database, we need to connect to the TFS module that will be serving up the database information to our application. The interface to the TFS, whether or not it is embedded or remote, is the same. The difference is the options that are defined prior to the initialization.

For this example, we will be using a RaimaDB TFS library that contains ALL of the TFS capabilities. Other RaimaDB TFS libraries available in the package a subset of the full TFS feature set. See RaimaDB Libraries section for a description of the RaimaDB library components and the dependency tree.

The TFS interaction using a handle to reference the TFS functionality. It is possible to have multiple TFS instances running on the same machine and in the same process space as long as those TFS instances are not sharing the same docroot or communication ports (if listening is enabled).

The functions that will be described in this section are:

rdm_rdmAllocTFS()

The first step in opening a database in the RaimaDB system is to allocate a handle for interfacing with the TFS functionality. This function call will allocate a handle to access the internals that determine what type of TFS we will be using as well as methods for communicating with that TFS.

rdm_tfsSetOption()

Once we have the handle allocated, we can modify the default behavior of the TFS by setting options. The list of available options for the TFS can be found in the TFS Configuration Options section.

The default docroot for an embedded TFS instance is the current working directory. Since our stated requirement is to locate the database in a specific location ("/RaimaData"), we will use an function to set the docroot location prior to instantiating the embedded TFS. The docroot cannot be altered AFTER the TFS has been started.

rdm_tfsInitialize()

Now that the TFS handle has been allocated, the next function will setup a TFS interface based on our settings (and the default settings of the TFS library type we are linking with). For this application, the function will start a TFS Thread running in our process that we can access using the TFS handle.

This function allocates all of the resources necessary to support this TFS type and starts the TFS Thread running in the background. At this point, the docroot has been locked to prevent another TFS from using it.

RaimaDB requires only one directory to be defined for the database engine (TFS). The required directory is the location of the root database directory referred to as the docroot in this manual. If the docroot is not defined, it will be assumed to be the current working directory of the application process executing the TFS process. For a disk based TFS, the docroot cannot be the root directory of a file system. For an inmemory only TFS, no directories will be required since RaimaDB will not have access to a file system.

Step 2: Opening the Database

Now that we have a TFS handle, we can start associating databases with that handle. One TFS handle can have multiple databases associated with it. The first step is to allocate a database handle for the database that we want to open.

Only one database can be opened on a handle. If multiple databases need to be opened simultaneously, a handle will need to allocated for each database.

The functions that will be described in this section are:

rdm_tfsAllocDatabase()

rdm_dbSetCatalog()

rdm_dbOpen()

rc = rdm_tfsAllocDatabase (hTFS, &hDB);

In the above line, we are allocating a new handle (hDB) associated with the previously allocated TFS handle (hTFS) from above.

Once we have the database handle allocated, we can modify the default behavior by setting database options. The list of available options for the database handle can be found in the Database Configuration Options section. For this example, we will leave database options at their default settings but we will associate the database catalog we created in the Creating the RaimaDB Interface Files section.

rc = rdm_dbSetCatalog (hDB, helloworld_cat);

The name of the catalog for the schema we created will be found in the helloworld_cat.h file.

The database can now be opened. The following call will attempt the database open on the TFS handle associated with the database handle. If the database cannot be found, the database image will be created with an empty database.

rc = rdm_dbOpen (db, "tfs-tcp://localhost:21553/helloworld", RDM_OPEN_SHARED);

The open command provides the Database Identifier (db-uri) and the mode that the database will be opened in. In this example, TFS URI portion of the DB URI includes the address and communication protocol to access the "helloworld" database. The mode will be a 'shared' open mode. The 'shared' mode indicates that another database handle connected to the same REMOTE TFS could also have the database open at the same time. Other modes include: RDM_OPEN_EXCLUSIVE and RDM_OPEN_READONLY.

The database name does not need to be the same as the schema name. The database name could be named "foo" and that would be perfectly acceptable.

A successful return from the rdm_dbOpen() function indicates that the database is open and ready for reading or writing.

rdm_dbClose()

rdm_tfsFree()

When the application has closed the database, the resources allocated for the TFS can be freed. If the TFS allocated is an Embedded TFS, the TFS Thread will be shutdown.

Inserting a Row into the Database

insertRowIntoDb.c

#include <string.h>

#include "rdm.h"
#include "helloworld_structs.h"

RDM_RETCODE insertRowIntoDb(RDM_DB hDB)
{
    RDM_RETCODE rc;

    rc = rdm_dbStartUpdate(hDB, RDM_LOCK_ALL, 0, NULL, 0, NULL);
    if (rc == sOKAY)
    {
        WORLD rowValues = { 0 };
        strncpy(rowValues.HELLO, "Hello World", sizeof(rowValues.HELLO));

        rc = rdm_dbInsertRow(hDB, TABLE_WORLD, &rowValues, sizeof(rowValues), NULL);
        if (rc == sOKAY)
        {
            rc = rdm_dbEnd(hDB);
        }
        else {
            rc = rdm_dbEndRollback(hDB);
        }
    }
    return rc;
}

Reading the Rows from the Database

readRowsFromDb.c

#include <stdio.h>

#include "rdm.h"
#include "helloworld_structs.h"

RDM_RETCODE readRowsFromDb(RDM_DB hDB)
{
    RDM_RETCODE rc;
    RDM_CURSOR hCursor;
    WORLD rowValues = { 0 };

    rc = rdm_dbStartRead(hDB, RDM_LOCK_ALL, 0, NULL);
    if (rc == sOKAY)
    {
        rc = rdm_dbAllocCursor(hDB, &hCursor);
        if (rc == sOKAY)
        {
            rc = rdm_dbGetRows(hDB, TABLE_WORLD, &hCursor);
            if (rc == sOKAY)
            {
                for (rc = rdm_cursorMoveToFirst(hCursor); rc == sOKAY; rc = rdm_cursorMoveToNext(hCursor))
                {
                    rc = rdm_cursorReadRow(hCursor, &rowValues, sizeof(rowValues), NULL);
                    if (rc == sOKAY)
                    {
                        puts(rowValues.HELLO);
                    }
                }
                /* if we exited the for loop with sEOS, that is expected */
                if (rc == sEOS)
                {
                    rc = sOKAY;
                }
            }
            rdm_cursorFree(hCursor);
        }
        rdm_dbEnd(hDB);
    }
    return rc;
}

Compiling and Linking the Application

The compiling process requires the generated header files and the RaimaDB "include" directory to be in the INCLUDE path for the compiler toolchain. Unless otherwise instructed, no other DEFINES need to be specified to compile your source files.

To link your compiled objects into an executable, you will need to include the RaimaDB libraries associated with the RaimaDB APIs you are using in your application. For example, the rdm_dbStartRead() function used in the previous code snippet is located in the rdm library. On Linux, you would need to include the librdmrdm.so library. The reference section of this manual contains the names of the libraries that your application will need to include based on the API you are using.

For the rdm_dbStartRead() function we just referenced, you will notice under the Detailed Description on that function group page the name of the library.

For applications using static libraries, the RaimaDB Libraries section will contain all of the dependent RaimaDB libraries that will need to be included to link your application.

Linking with an Embedded TFS

There are multiple TFS libraries to pick from, but ONLY include one!

For the embedded TFS capability needed to fulfill our requirements for this example, we will need to include the tfs_client library. On Linux, you would need to include the librdmtfs_client.so library.