Client/Server C/C++ Application (Windows/MS Visual Code)

Introduction

Now that you've gotten comfortable with creating a database in the How to Create a Schema section, lets build an application that will allow you to read and write to a database.

We recommend getting familiar with some of the concepts and tools that you will be using in this tutorial. We will be covering: interacting with the Transaction File Server (TFS), inserting a row with the function call rdm_dbInsertRow(), and reading rows with the function call rdm_cursorReadRow(). You may also find it useful to read about the DOCROOT as well.

  • The TFS is responsible for safely storing and retrieving objects. It is like a key/value store, but very fast and transactionally safe. The TFS owns and is co-located with the database files.
  • In this example rdm_dbInsertRow() will be used to insert a new row into two of our tables: AUTHOR and BOOK.
  • rdm_cursorReadRow() reads all columns from a row specified by the RDM_CURSOR and places the contents inside of the designated buffer.
  • The DOCROOT is a directory which is designated for holding databases available to a Transaction File Server (TFS). The concept is similar to a web server document root for storing web pages

Prerequisites:

The example below used VSCodeSetup-x64-1.43.2 and vs_buildtools__811756485.1585097604 packages from Microsoft.

Steps:

  1. Open the Visual Studio "x64 Native Tools Command Prompt"
  2. Create an empty folder call "bookStore", navigate to it, and open VS Code (code) in that folder (.) by entering the following commands:
$> mkdir bookStore
$> cd bookStore
$> code . 
  1. Right-click in the BOOKSTORE section of the EXPLORER window and select New File:

  1. Right-click in the BOOKSTORE section of the EXPLORER window and select New File:
  2. Enter bookStore.c and then edit to look like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "rdm.h"
#include "bookStore_structs.h"

/* Write a row to from author and book tables */
static RDM_RETCODE writeARow (RDM_DB db)
{
    RDM_RETCODE rc;
    RDM_TABLE_ID tables[] = {TABLE_BOOK, TABLE_AUTHOR};

    char last_name[13];
    char full_name[35];
    char bookid[14];
    char title[105];
    char price[10];
    RDM_BCD_T dPrice;
    char *inputs[5] = {last_name, full_name, bookid, title, price};
    unsigned int len;
    int ii = 0;

    printf ("ADDING AUTHOR TO DATABASE\nAuthor Last Name:\n");
    fgets (last_name, 13, stdin);
    printf ("Author Full Name:\n");
    fgets (full_name, 35, stdin);
    printf ("ADDING BOOK TO DATABASE\nTitle of Book:\n");
    fgets (title, 105, stdin);
    printf ("Enter id located on book:\n");
    fgets (bookid, 14, stdin);
    printf ("Price:\n");
    fgets (price, 10, stdin);

    /* Remove newline character from fgets */
    while (ii < (sizeof (inputs) / sizeof (inputs[0])))
    {
        len = strlen (inputs[ii]);
        if (inputs[ii][len - 1] == '\n')
        {
            inputs[ii][len - 1] = '\0';
        }
        ii++;
    }

    /* Start an update transaction and lock the table */
    rc = rdm_dbStartUpdate (db, tables, RDM_LEN(tables), NULL, 0, NULL);
    if (rc == sOKAY)
    {
        AUTHOR authInsert; /* Row buffer */
        BOOK bookInsert;   /* Row buffer */

        /* Populate the columns in the AUTHOR Row buffer */
        strcpy (authInsert.LAST_NAME, last_name);
        strcpy (authInsert.FULL_NAME, full_name);
        authInsert._FULL_NAME_has_value = RDM_COL_HAS_VALUE;

        /* Insert a row into the table */
        rc = rdm_dbInsertRow (db, TABLE_AUTHOR, &authInsert, sizeof (authInsert), NULL);

        if (rc == sOKAY)
        {
            /* Convert the string to a binary-coded decimal */
            rdm_bcdFromString (price, &dPrice);

            /* Populate the columns in the BOOK Row buffer */
            strcpy (bookInsert.TITLE, title);
            bookInsert._TITLE_has_value = RDM_COL_HAS_VALUE;
            strcpy (bookInsert.BOOKID, bookid);
            bookInsert.PRICE = dPrice;
            bookInsert._PRICE_has_value = RDM_COL_HAS_VALUE;

            /* Insert a row into the table */
            rc = rdm_dbInsertRow (db, TABLE_BOOK, &bookInsert, sizeof (bookInsert), NULL);
        }
        if (rc == sOKAY)
        {
            /* Commit a transaction */
            printf ("The book %s by %s at a price of $%s was added to the database successfully.\n",
                    title, full_name, price);
            rc = rdm_dbEnd (db);
        }
        else
        {
            /* Abort the transaction */
            fprintf (stderr, "A problem occurred when adding data to the database.\n");
            fprintf (stderr, "Error: %s (%d): %s\n", rdm_retcodeGetName (rc), rc, rdm_retcodeGetDescription (rc));
            rdm_dbEndRollback (db);
        }
    }
    return rc;
}

/* Read rows from author table */
static RDM_RETCODE readRows (RDM_DB db)
{
    RDM_RETCODE rc;
    RDM_TABLE_ID tables[] = {TABLE_AUTHOR};
    AUTHOR authRead;
    RDM_CURSOR cursor = NULL;

    rc = rdm_dbStartRead (db, tables, RDM_LEN(tables), NULL);
    if (rc == sOKAY)
    {
        rc = rdm_dbGetRows (db, TABLE_AUTHOR, &cursor);
        if (rc == sOKAY)
        {
            /* Navigate to the first row in the cursor */
            printf ("Displaying all of the authors in the author table\n");
            rc = rdm_cursorMoveToFirst (cursor);
            while (rc == sOKAY)
            {
                /* Read the row column values */
                rc = rdm_cursorReadRow (cursor, &authRead, sizeof (authRead), NULL);
                if (rc == sOKAY)
                {
                    printf ("%s", authRead.LAST_NAME);

                    /* Move to the next row in the cursor */
                    rc = rdm_cursorMoveToNext (cursor);
                }
            }

            /* We expect to break out of the loop with a sENDOFCURSOR code*/
            if (rc == sENDOFCURSOR)
            {
                rc = sOKAY;
            }

            /* Free the cursor allocated in rdm_dbGetRows */
            rdm_cursorFree (cursor);
        }

        /* release the read locks */
        rdm_dbEnd (db);
    }
    return rc;
}

int main (int argc, const char* const* argv)
{
    RDM_RETCODE rc; /* Status/Error Return Code */
    RDM_TFStfs;     /* TFS Handle */
    RDM_DBdb;       /* Database Handle */

    /* Allocate a TFS Handle */
    rc = rdm_rdmAllocTFS (&tfs);
    if (rc == sOKAY)
    {
        rc = rdm_tfsSetOption (tfs, "tfstype", "remote");
        if (rc == sOKAY)
        {
            rc = rdm_tfsInitialize (tfs);
            if (rc == sOKAY)
            {
                /* Allocate a database handle */
                rc = rdm_tfsAllocDatabase (tfs, &db);
                if (rc == sOKAY)
                {
                    /* Open the database */
                    rc = rdm_dbOpen (db, "tfs://localhost:21553/bookStore", RDM_OPEN_SHARED);
                    if (rc == sOKAY)
                    {
                        /* Insert a row to database */
                        rc = writeARow (db);
                        if (rc == sOKAY)
                        {
                            /* Read the rows */
                            rc = readRows (db);
                        }
                        rdm_dbClose (db);
                    }
                    else
                    {
                        fprintf (stderr, "\nSorry, can't open bookStore database.\n");
                        fprintf (stderr, "Error: %s (%d): %s\n", rdm_retcodeGetName (rc), rc, rdm_retcodeGetDescription (rc));
                    }
                    rdm_dbFree (db);
                }
            }
        }
        rdm_tfsFree (tfs);
    }

    return rc == sOKAY ? EXIT_SUCCESS : EXIT_FAILURE;
}

Compared to the same embedded example in Embedded C/C++ Application (Windows/MS Visual Code), the only modifications needed to have this program access a remote TFS were on lines 147 and 155 above. The TFS allocation specifies that the TFS type will be "remote" and the database open specifies where to open the database using a URI string format.

  1. Select Terminal->Configure Tasks ... and select C/C++: cl.exe build active file
  2. Change the contents of the generated tasks.json file to:
{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "shell",
            "label": "cl.exe build active file",
            "command": "cl.exe",
            "args": [
                "/IC:\\Raima\\RDM\\15.2\\include",
                "bookstore.c",
                "/link",
                "/LIBPATH:C:\\Raima\\RDM\\15.2\\lib",
                "/MACHINE:X64",
                "rdmtfs-15.lib",
                "rdmrdm-15.lib",
                "rdmbase-15.lib",
				"rdmtx-15.lib"
            ],
            "problemMatcher": [
                "$msCompile"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}
  1. Right-click on the .vscode entry in the BOOKSTORE section of the EXPLORER window and select New File. Add the file c_cpp_properties.json file with the following contents:
{
    "configurations": [
        {
            "name": "Win32",
            "includePath": [
                "${workspaceFolder}/**",
                "C:/Raima/RDM/15.2/include"
            ],
            "defines": [
                "_DEBUG",
            ],
            "windowsSdkVersion": "10.0.18362.0",
            "compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/VC/Tools/MSVC/14.25.28610/bin/Hostx64/x64/cl.exe",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "msvc-x64"
        }
    ],
    "version": 4
}
  1. Right-click on the .vscode entry in the BOOKSTORE section of the EXPLORER window and select New File. Add the file launch.json file with the following contents:
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "cl.exe - Build and debug active file",
            "type": "cppvsdbg",
            "request": "launch",
            "program": "${fileDirname}\\bookStore.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": true,
            "preLaunchTask": "cl.exe build active file"
        }
    ]
}
  1. Our file structure should be all set up and will look like this:

  1. From the command line, navigate to the directory where your database bookStore.rdm is stored and run the command rdm-tfs.
  2. Now you are ready to interact with the remote database.
  3. Pressing the F5 key in the VS Code environment should compile and launch the program.

Conclusion

Your program should be fully functional at this point. You set up a Remote TFS which is used when you are interacting with a database through server processes, and you wrote and read rows from the database by interacting with the TFS.

Next, you might want to add some more functionality by deleting a row with rdm_cursorDeleteRow() or update a row with rdm_cursorUpdateRow(). Some other concepts to look over might be:

The full source file can be found here: learn/bookStore_client.c