header header header
the book
the book
projects  Run Time Access
 Event Handler
articles contact

Run Time Access

Quick Start

This page gives a brief tutorial on the use of the RTA package by building a trivial application which uses RTA. We define the task, define the tables, give the code, describe how to build and link the application, and describe how to the test the application using the database interface.



Problem Statement

We want to build an application in which we expose an internal array of data structures as both a database and as a file system. The array has twenty structures and each structure has a writeable integer, a writeable float, a writeable string, and a read-only string. Both strings are thirty characters in length. A callback on the writeable string does two things: it replaces any '<' and '>' characters with a '.', and it copies the reversed string into the the read-only string.

The structure would be defined as:

#define NOTE_LEN   30
struct MyData {
    int    myint;
    float  myfloat;
    char   notes[NOTE_LEN];
    char   seton[NOTE_LEN];
};

We allocate storage for the data as:

#define ROW_COUNT  20
struct MyData mydata[ROW_COUNT];

Externally, we want this array of structures to be seen as a Postgres table called "mytable" or as a directory called "mytable" mounted under "/tmp/mydir".


Table Definitions

We need to tell the RTA package about our table. To do this we need to build a RTA_COLDEF structure for each of the four columns, and to build a RTA_TBLDEF structure for "mydata", the array of structures.

We use an array of four RTA_COLDEFs to describe our columns. This is pretty simple:

RTA_COLDEF mycolumns[] = {
  {
    "mytable",          /* the table name */
    "myint",            /* the column name */
    RTA_INT,            /* it is an integer */
    sizeof(int),        /* number of bytes */
    offsetof(struct MyData, myint), /* location in struct */
    0,                  /* no flags */
    (int (*)()) 0,      /* called before read */
    (int (*)()) 0,      /* called after write */
    "A sample integer in a table"
  },
  {
    "mytable",          /* the table name */
    "myfloat",          /* the column name */
    RTA_FLOAT,          /* it is a float */
    sizeof(float),      /* number of bytes */
    offsetof(struct MyData, myfloat), /* location in struct */
    0,                  /* no flags */
    (int (*)()) 0,      /* called before read */
    (int (*)()) 0,      /* called after write */
    "A sample float in a table"
  },
  {
    "mytable",          /* the table name */
    "notes",            /* the column name */
    RTA_STR,            /* it is a string */
    NOTE_LEN,           /* number of bytes */
    offsetof(struct MyData, notes), /* location in struct */
    0,                  /* no flags */
    (int (*)()) 0,      /* called before read */
    reverse_str,        /* called after write */
    "A sample note string in a table"
  },
  {
    "mytable",          /* the table name */
    "seton",            /* the column name */
    RTA_STR,            /* it is a string */
    NOTE_LEN,           /* number of bytes */
    offsetof(struct MyData, seton), /* location in struct */
    RTA_READONLY,       /* a read-only column */
    (int (*)()) 0,      /* called before read */
    (int (*)()) 0,      /* called after write */
    "Another sample note string in a table"
  },
};


For each of the four structure elements we gave the associated table name, column name, type, size, position in the struct, flags, read and write callbacks, and a short string to describe it.

We define the table in a similar way:

RTA_TBLDEF mytbldef {
    "mytable",           /* table name */
    mydata,              /* address of table */
    sizeof(struct MyData), /* length of each row */
    ROW_COUNT,           /* number of rows */
    mycolumns,           /* array of column defs */
    sizeof(mycolumns) / sizeof(RTA_COLDEF),
                         /* the number of columns */
    "",                  /* no save file */
    "A sample table"
};

Note the double quotes to specify no save file. This field is a pointer-to-string and the pointer can not be null, although the string can be.


Database Interface

C Code

The source code for our simple application is available here as myappdb.c. The first section of code of note is the list of include files:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>             /* for 'offsetof' */
#include <string.h>             /* for 'strlen' */
#include <unistd.h>             /* for 'read/write/close' */
#include <sys/socket.h>
#include <netinet/in.h>
#include "../src/rta.h"

You will need to set the path to the rta.h file to reflect where you install it.

We have already presented the code for the structures and arrays of structures. Let's look at the code in the main() program to perform all initialization:

int main()
{
    int   i;                   /* a loop counter */
    int   srvfd;               /* FD for our server socket */
    int   connfd;              /* FD for conn to client */
    struct sockaddr_in srvskt; /* server listen socket */
    struct sockaddr_in cliskt; /* socket to the UI/DB client */
    int   adrlen;
    char  inbuf[INSZ];         /* Buffer for incoming SQL commands */
    char  outbuf[OUTSZ];       /* response back to the client */
    int   incnt;               /* SQL command input count */
    int   outcnt;              /* SQL command output count */
    int   dbret;               /* return value from SQL command */

    /* init mydata */
    for (i=0; i<ROW_COUNT; i++) {
        mydata[i].myint    = 0;
        mydata[i].myfloat  = 0.0;
        mydata[i].notes[0] = (char) 0;
        mydata[i].seton[0] = (char) 0;
    }

    /* init the RTA package and tell it about mydata */
    rta_init();
    if (rta_add_table(&mytbldef) != RTA_SUCCESS) {
        fprintf(stderr, "Table definition error!\n");
        exit(1);
    }
 

This is pretty standard stuff. We allocate our socket structures and other local variables. We initialize the RTA package and add our one table.

The last piece of initialization is to set up the socket to listen for incoming client connections. Remember that each UI program will treat our program as if it were a Postgres database. We have to accept TCP connections from Postgres clients.

By-the-way: the following code is pretty horrendous. It uses blocking IO, ignores error conditions, and makes wildly optimistic assumptions about socket IO. My goal is to make the code understandable by getting it into as few lines as possible.

    /* We now need to open a socket to listen for incoming
     * client connections. */
    adrlen = sizeof (struct sockaddr_in);
    (void) memset ((void *) &srvskt, 0, (size_t) adrlen);
    srvskt.sin_family = AF_INET;
    srvskt.sin_addr.s_addr = INADDR_ANY;
    srvskt.sin_port = htons (8888);
    srvfd = socket(AF_INET, SOCK_STREAM, 0); /* no error checks! */
    bind(srvfd, (struct sockaddr *) &srvskt, adrlen);
    listen (srvfd, 4);

The main loop in our program waits for a TCP connection from a client, then loops reading the Postgres encoded stream of SQL commands, processing them with rta_dbcommand(), and then writing any results back to the client. While a connection can close for errors, we normally expect the client to request an orderly close to the connection.:

    /* Loop forever accepting client connections */
    while (1) {
        connfd = accept(srvfd, (struct sockaddr *) &cliskt, &adrlen);
        if (connfd < 0) {
            fprintf(stderr, "Error on socket/bind/listen/accept\n");
            exit(1);
        }
        incnt = 0;
        while (connfd >= 0) {
            incnt = read(connfd, &inbuf[incnt], INSZ-incnt);
            if (incnt <= 0) {
                close(connfd);
                connfd = -1;
            }
            outcnt = OUTSZ;
            dbret = rta_dbcommand(inbuf, &incnt, outbuf, &outcnt);
            switch (dbret) {
                case RTA_SUCCESS:
                    write(connfd, outbuf, (OUTSZ - outcnt));
                    incnt = 0;
                    break;
                case RTA_NOCMD:
                    break;
                case RTA_CLOSE:
                    close(connfd);
                    connfd = -1;
                    break;
                case RTA_NOBUF:
                    close(connfd);
                    connfd = -1;
                    break;
            }
        }
    }

The callback function is fairly straightforward. Remember that the write callback is called on a column after the write of all update data to the columns. Our write callback on the 'notes' field is called after the data has been written. It loops through the string replacing greater-than and less-than symbols with a period, and copying a reverse of the string into the 'seton' field.

int reverse_str(char *tbl, char *col, char *sql, void *pr,
                int rowid, void *poldrow)
{
    int   i,j;                 /* loop counters */

    i = strlen(mydata[rowid].notes) -1;  /* -1 to ignore NULL */
    for(j=0 ; i>=0; i--,j++) {
        if (mydata[rowid].notes[i] == '<' ||
            mydata[rowid].notes[i] == '>')
            mydata[rowid].notes[i] = '.';
        mydata[rowid].seton[j] = mydata[rowid].notes[i];
    }
    mydata[rowid].seton[j] = (char) 0;

    return(0);   /* no errors */
}

Build and Link

The information in this section should be sufficient to download, build, and run the sample application.

Download the RTA package from the download page given above. Untar it. Go to the src directory and do a make.

    # tar -xzf rta-0.8.0.tgz
    # cd rta-0.8.0
    # cd src
    # make

Download the myappdb.c file (available here). Place the file in the test directory. Build it with the command:

    # gcc myappdb.c -o myappdb -L../src -lrtadb

Note that we are telling the application that the library it needs is in the sibling src directory.

To run the application we will need to tell it where to find the shared object library. From the test directory enter:

    # export LD_LIBRARY_PATH=/home/rta-0.8.0/src
    # ./myappdb &

That is all there is to it. Please use the contact page to report any problems you find.


Test

If all has gone well, we now have an application running which pretends to be a Postgres database server. Only instead of a database, it is our sample application offering up its internal tables for use by various Postgres clients.

psql

The first client to try is the Postgres browser tool, psql. This tool is part of the base Postgres install. Remember that we have our application table, mytable, as well as the RTA internal tables, rta_tables, rta_columns, rta_dbg, and rta_stat. See the API section for a description of these tables.

To start psql to our application enter:

    # psql -h localhost -p 8888

Postgres should respond with:

    Welcome to psql, the PostgreSQL interactive terminal.

    Type:  \copyright for distribution terms
           \h for help with SQL commands
           \? for help on internal slash commands
           \g or terminate with semicolon to execute query
           \q to quit

(The PostgreSQL 8.x shell client, psql, will give a warning that the 8.x client might not be compatible with a 7.x server. You can safely ignore this error message.)

Let's try some SQL commands to play with the tables.

Give me a list of the tables:

    SELECT name FROM rta_tables;
        name     
    -------------
     rta_tables
     rta_columns
     rta_dbg
     rta_stat
     mytable
    (5 rows)

You do not need to use upper case for the SQL keywords. The psql program accepts both upper and lower case.

Display the contents of mytable:

    SELECT * FROM mytable;
     myint |       myfloat        | notes | seton 
    -------+----------------------+-------+-------
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
     0     |         0.0000000000 |       | 
    (20 rows)

Set all of the myint values to 44 and display the new table:

    UPDATE mytable SET myint=44;
    UPDATE 20
    SELECT * FROM mytable;

Note that psql tells us how many rows were modified.

Set the notes field to "hi mom!"

    UPDATE mytable SET notes="hi mom!";
    UPDATE 20

Set the notes field of only row 0 to "<b>hi mom!</b>":

    UPDATE mytable SET notes="<b>hi mom!</b>" LIMIT 1 OFFSET 0;
    UPDATE 1

The LIMIT says how many rows to change, and the OFFSET tells where to start the changes. The default LIMIT is all rows, and the default OFFSET is zero.

Turn on the trace of all SQL commands and send the output to both stderr and syslog:

    UPDATE rta_dbg SET trace=1, target=3;

You should now see all the commands echoed on the standard error output of the program. The commands are also being sent to your syslog facility.

C

The C program presented here, rta_client.c, illustrates how to get data out of our application from a C program. You need the postgresql-devel package to build this program. Build and run the program with:

    # gcc rta_client.c -o rta_client -lpq
    # ./rta_client

The program sets the field myint to a value of 44 and then gets and prints the fields myint, myfloat, and notes for all twenty rows in the table.

Note that we test for success in different ways depending on whether or not we expect data back from the command. Note also that the returned data is always a string. You need to scan it into a variable for other processing.

PHP

The PHP program presented here, rta_client.php, illustrates how to get data out of our application from a PHP program. You need the php-pgsql package to run this program. If you are running Apache, check for the PHP interpreter in modules directory. You can verify that the PHP-Postgres library is loaded by checking for pgsql.so in the /usr/lib/php5 directory. Clearly your configuration for PHP and Postgres may be different than what is described here.

This program gets and displays the fields myint, myfloat, and notes for all twenty rows in the table.

Perhaps a better way to evaluate PHP is to install the table editor which comes with the RTA package. Just put the four .php files onto your web server (being sure that PHP and php-pgsql are installed). The table editor is the application running on the Live Demo link above.