Building a C++ interface for your embedded DBMS - Embedded.com

Building a C++ interface for your embedded DBMS

In this Product How-To article, Raima’s Jeff Parsons describes the procedures he went through to build a C++ interface for the company's RDM Embedded 10.1 embedded DBMS.

I recently finished a project implementing a new C++ interface for the RDM Embedded 10.1 release. As a programmer that typically works on the internals of an embedded database engine it had been years since I had seriously worked with C++.

Having the chance to code in this language gave me an opportunity to discover how an elegant design can be implemented simply in a higher level language, a stark contrast from my previous experiences with C++.

The last major C++ project I worked on was Raima Object Manager (ROM) the original RDMe C++ interface. Working with ROM soured me a little on the practicality of using C++ in an embedded application. While ROM was powerful it was over engineered containing a dozen classes that had to be combined together in various ways to perform any database related operation.

Unfortunately it was often easier to use the low-level core API to accomplish something than it was to use the higher-level class libraries and the core API was always more efficient. To be fair ROM was designed during the infancy of C++ and object orientated programming so the original developers did not have 15 years of best practices to help them in their design decisions.

Early on we decided to avoid the pitfalls of ROM by keeping the new C++ interface clean and simple. In fact the interface contains only two base classes – the Db interface and the Cursor interface. These base classes contain methods common to all databases and records. The RDM ddlp tool is responsible for generating derived classes containing methods that are specific to a particular schema.

Db Class
There is one Db class generated for each database definition and it derives from the base Db interface class. This class provides methods that allow the programmer to

* Open the database
* Create new records
* Obtain record scan cursors (collections ordered by location in a data file)
* Obtain key scan cursors (collections ordered by key value)
* Start read transactions
* Start write transactions
* Pre-commit transaction
* Commit transactions
* Abort transactions
* Start a read-only snap shot

Cursor Interface
There is one Cursor class generated for each record defined in a schema. These Cursor classes derive from the base Cursor interface class and provide methods that allow the programmer to

* Delete Records
* Disconnect Records from a set owner
* Connect Records to a set owner
* Reconnect Records to a new set owner
* Navigate through the records in the cursor
* Obtain record scan cursors (collections ordered by file location)
* Obtain key scan cursors (collections order by key value)
* Obtain a cursor containing member records (collections related by set owner)
* Obtain a singleton cursor containing an owner record
* Update individual field values
* Update all field values
* Read individual field values
* Read all field values
* Find records by a key value

Creating a C++ interface for your database
When you compile a schema using the 10.1 version of ddlp an option flag (-cpp) will instruct the utility to generate a C++ API specific to your database definition.

As described previously, this API contains one Db class for your database and one Cursor class for each record defined in your schema. Using the methods provided by these classes, a developer is able to create, retrieve, update, and delete data in their database without having to bother with all of the details required by the low-level core API.

As an example, consider the following database definition for a simple database to record data about sensors and measurements:

database simpleDB
{
data file “simpleDB.d00” contains sensor;
data file “simpleDB.d01” contains measurement;
key file “simpleDB.k00” contains sensor.name;
key file “simpleDB.k01” contains measurement.k_reading;
blob file “simpleDB.b00” contains measurement.raw_data;

record sensor {
unique key char name[32];
int32_t status;
}

record measurement {
int32_t time;
int32_t value;
blob_id raw_data;

compound key k_reading {
time;
value;
}
}

set sensor_measurement {
order last;
owner sensor;
member measurement;
}
}

Example Schema
Compile the schema using the –cpp option to generate the C++ API files.

ddlp -cpp -d simpleDB.ddl

Database Definition Language Processor Utility
RDM Embedded 10.1.0 Build 1000 [12-30-2010] http://www.raima.com/Copyright (c) 2010 Raima Inc., All rights reserved.

0 errors detected

DDLP output creating C++ API
This command will produce the source and header that comprise the RDMe C++ API for the measurements database

simpleDB.h                   RDMe database header file
simpleDB_api.h           RDMe C++ interface header file
simpleDB_api.cpp       RDMe C++ interface implementation file

All source files that want to use the RDMe Embedded C++ API for the simpleDB database will need to include the simpleDB_api.h header file. The simpleDB_api.cpp source file will need to be compiled with the application code.

In our example three classes will be defined and implemented in the simpleDB_api interface

* Db_simpleDB                     A DB interface for the simpleDB database
* Cursor_sensor                  A Cursor interface for the sensor record
* Cursor_measurement     A Cursor interface for the measurement record

Using the C++ Interface
The classes generated by DDLP are used by the application to perform database operations.

Creating Data. The schema declares two record types (sensor and measurement). The Db_simpleDB class contains generated methods to add new instances to those record types

* Cursor_sensor New_sensor_record (void);
* Cursor_sensor New_sensor_recordWithFieldValues (struct sensor fields);
* Cursor_measurement New_measurement_record (void);
* Cursor_measurement New_measurement_recordWithFieldValues (struct measurement *fields);

(Db Methods for creating new Record Instances )

There is a set relationship between the sensor and measurement records called sensor_measurement. The measurement class contains a method to associate a measurement to a particular sensor.

* void Connect_sensor_measurement (const Cursor_sensor &owner);
(Cursor_measurement method to associate a measurement with a sensor )

Retrieving Data. The Db_simpleDB class also contains methods for obtaining collections of records. There is a Get method for each record defined that will retrieve a collection in raw database order. There is also a GetBy method defined for each key to obtain collections in key order.

* Cursor_sensor Get_sensor_records ();
* Cursor_measurement Get_measurement_records ();
* Cursor_sensor Get_sensor_recordsBy_name ();
* Cursor_measurement Get_measurement_recordsBy_k_reading ();

(Db Methods for getting collections of records )

The Cursor classes also contain methods related to data retrieval. For each key defined in a record a KeyFindBy method will be generated.

* void KeyFindBy_name (char name_val[32]);
(Cursor_sensor method to locate a sensor based on the name_val field )

* void KeyFindBy_k_reading (measurement_k_reading *k_reading_val);
(Cursor_measurement method to locate a measurement based on the reading key )

* Cursor_measurement Get_sensor_measurement_members (void);
(Cursor_sensor method to get a collected measurements from the sensor )

* Cursor_sensor Get_sensor_measurement_owner (void)
(Cursor_measurement method to get sensor taking the measurement )

Once you have found data, you can use the Cursor interface to read individual fields or the entire record.

* void GetFieldValues (struct sensor *fields);
* void Get_name (char name_val[32]);
* void Get_status (int32_t *status_val);

(Cursor_sensor methods for reading data )

* void GetFieldValues (struct measurement *fields);
* void Get_time (int32_t *time_val);
* void Get_value (int32_t *value_val);

(Cursor_measurement methods for reading data )

Updating Data
Each Cursor class will contain methods to update individual fields as well as the entire record.

* void SetFieldValues (const struct sensor *fields);
* void Set_name (const char name_val[32]);
* void Set_status (int32_t status_val);

(Cursor_sensor methods for updating data )

* void SetFieldValues (const struct measurement *fields);
* void Set_time (int32_t time_val);
* void Set_value (int32_t value_val);

(Cursor_measurement methods for updating data )

The Cursor_measurement class has a method used to associate a measurement with a different sensor through the sensor_measurement set.

* void Reconnect_sensor_measurement (const Cursor_sensor &owner);
(Cursor_measurement method linking measurement with a different sensor )

Deleting Data
The Cursor base class contains methods to delete records and to delete records after removing any set relationships.

* Cursor Delete (void);
* Cursor DisconnectAndDelete (void);

(Cursor methods to remove records from the database )

The Cursor_measurement class contains a generated method to disassociate a measurement from a sensor.

* void Disconnect_sensor_measurement (void)
(Cursor_measurement method to disconnect a measurement from a sensor )

Transaction Processing
The Db base class contains methods for transaction processing and concurrency control. While concurrency control and transaction processing can be handled implicitly by the C++ API, it is considered best practice to have the application use these methods to explicitly control transaction scope.

* void BeginRead (const recordType types[]= NULL, int16_t num = 0);
* void BeginUpdate (const recordType types[]= NULL, int16_t num = 0);
* BeginSnapshot ();
* void Precommit (void)
* void End (void);
* void Abort (void);

(Db class methods to handle transaction processing )

While we have completed development on the initial version released in the 10.1 package we have numerous ideas and plans for enhancements and refinements to be included in subsequent patches and releases.

Jeff Parsons is currently a Senior Sales Engineer at Raima Inc. and has worked for a total of 16 years at this company, as a technical support engineer, a consulting engineer and as a sales and development engineer. His expertise resides with the RDMe core engine, RDMe C++ API and RDMe legacy interfaces, but has worked in all areas of both of Raima’s products, RDMe and RDMs.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.