DatabaseSimplifying Access to BREW Databases

Simplifying Access to BREW Databases

Since QUALCOMM BREW’s inception, BREW has provided a trio of interfaces (IDBMgr, IDatabase, and IDBRecord) that permit random access to a series of records within a data store on the handset’s flash file system. Although efficient, the interfaces suffer from complexity that can be hard to manage in user-interface applications, especially those utilizing the Model-View-Controller (MVC) pattern. On a recent project, I stumbled across a solution that solved the complexity problem well: using the fields of the model as the data store for the underlying database record prior to serialization, and hiding the database record interface itself as a private member of the model.

In this article, I show you how you can use the same trick when writing in C for BREW, so that your application’s views and controller can largely treat your application’s data model as a purely in-heap object, while creating a manageable interface to your object’s persistent representation that builds on the best aspects of the BREW IDatabase and IDBRecord interfaces. Even if you haven’t worked with BREW databases before, chances are you’ll learn something, because under the hood, this solution makes use of the entire repertoire of BREW interfaces.

Inside BREW’s Data Storage Model

Before I begin, it’s worth taking a minute and reviewing the basics of using a BREW database. BREW provides three interfaces for you to use when creating and manipulating databases:

  • The IDBMgr interface, which lets you create, open, close, and delete databases.
  • The IDatabase interface, which lets you add records, iterate over records as well as fetch a record by its BREW-assigned ID.
  • The IDBRecord interface, which lets you add and remove fields in a record, as well as remove a record from the database.

The BREW database implementation is emphatically a no-frills implementation, because on-disk performance is paramount. There’s no interface to perform sorts or queries; it’s up to you to add that logic after you define your database schema. Under the hood, a database is a file (or pair of files, in older versions of BREW) with an index to each record and the data of each record, stored in record fields whose representation closely mirrors that of an individual database record as it’s created.

Tip: QUALCOMM has not chosen to release the BREW database format, and for that reason it’s ill-advised to write applications that access BREW databases that don’t directly use the BREW database interfaces! Although the underlying implementation may appear simple (I regularly use the UNIX od facility during unit testing to eyeball database changes), it’s subject to change without notice.

What puzzles many newcomers to BREW most is the iterative nature of the IDBRecord interface. Veterans of small platforms are used to having to write their own search and sort facilities, but most lightweight database implementations let you specify the structure of a record’s fields as a C structure or similar object. Not so with BREW! When you create a record, it’s your responsibility to specify the number of fields in a record, and then create an array of AEEDBField objects, with one entry for each field. Each AEEDBField object indicates your name for the field (an unsigned word), along with the type of the field (such as byte, binary array, double-byte string, or short or long word), a pointer to a buffer containing the data, and the size of the data. For example, consider a three-field record that describes a task (say, in your personal information manager), storing its name, when it’s due, and whether or not you’ve completed the task. Its representation in C might look something like this:

Listing 1

typedef struct _ITaskRecord
{
   AECHAR wszTitle[ITASK_TEXT_MAXLENGTH+1];
   uint32 dwDue;
   boolean bComplete;
} ITaskRecord;

To create one of these records, you invoke IDATABASE_CreateRecord with an AEEDBField structure like you see in Listing 1.

When fetching data from a record, you must iterate over the fields in the record, obtaining the name, type, and size of a field each pass, and then choosing whether or not to fetch the data associated with the field. This isn’t such a simple matter, either, as you can see from Listing 2.

Listing 2

AEEDBFieldName name;
   uint16 nLen = 0;
   boolean *pbComplete = NULL;
   int type = IDBRECORD_NextField( pRecord->private.pIDBRecord,
                                   &name, &nLen );
   while( AEEDB_FT_NONE != type )
   {
      switch( name )
      {
      case TASKRECORDFIELD_TITLE:
         WSTRCOPYGUARDTOFIXED( pRecord->public.wszTitle,
                               IDBRECORD_GetFieldString
                               ( pRecord-private.pIDBRecord ));

        break;

      case TASKRECORDFIELD_DUE:
         IDBRECORD_GetFieldDWord( pRecord->private.pIDBRecord,
                                  &pRecord- public.dwDue );

         break;


      case TASKRECORDFIELD_COMPLETE
         pbComplete = IDBRECORD_GetField( pRecord->private.pIDBRecord,
                                          &name, &type, &nLen );
         pRecord->public.bComplete = pbComplete ? *pbComplete : FALSE;
         break;


      default:
         break;
      }
      type = IDBRECORD_NextField( pRecord->private.pIDBRecord,
                                  &name, &nLen );
   }

The BREW database interface is elegant because it frees the platform from having to do tricky things such as mapping memory in C structures to the stream-based file system, but it’s definitely counterintuitive looking down from the perspective of the application developer, especially when most developers are used to working with database elements that closely resemble C structures.

Sharing Database Fields with Object Attributes

The obvious thing to do is to wrap the code that glues the application data model to the underlying BREW database, if only so that it’s easy to find. Most BREW application developers, working in C, would be tempted to simply create a module to convert from one representation to another. There is a better way, however, that closely resembles how most C++ developers would tackle the problem, and it’s easily implemented in C.

The approach builds on the BREW component model, making the application data model—in my example, the ITaskRecord—act like a BREW interface with methods, but share its data members with the underlying AEEDBField array. Doing this has three key advantages over a more traditional C module, or even a typical C++ class:

  • For users of the record component as a model, the component can retain its appearance as a conventional C structure, completely hiding its nature as a client of the uglier aspects of the IDBRecord interface.
  • The object can implement the remainder of the IDBRecord interface, and a second component can extend the IDatabase interface, providing a well-defined interface to the data store that’s understood by most BREW developers with little implementation difficulty.
  • The overhead of a virtual table for the record component is typically much smaller than the code and memory overhead associated with the skullduggery typically performed when converting a C structure to an IDBRecord and back.
  • Because the model and its hooks to the database are well-isolated, it’s easy to add new fields to the database record implementation as you flesh out the requirements for your application.

To users of the object, the interface becomes one that looks like a BREW extension (see my previous article), as you see in Listing 3. The only significant addition to the ITaskRecord you saw previously is a pointer to the object’s virtual table (also known as a vtable) that implements the appropriate subset of IDBRecord methods, including AddRef, Release, UpdateRecord, RemoveRecord, and an additional method, SetContentsOfRecord, that several of my developers found helpful when using the object. Of course, I accompany the vtable with the usual helper macros, making it easy to access each of the methods directly. In essence, by sharing the data field with the underlying IDBRecord implementation the C structure provides a proxy to the underlying database representation.

In designing the vtable, you might wonder why I didn’t provide accessors and mutators for each of the fields in the class. To be sure, the idea crossed my mind, and in many settings it’s probably a very good one. However, in designing this interface I wanted an object with as close an appearance as possible to a C structure. Furthermore, I didn’t want the overhead—on the part of either the programmer or the source code base—of getters and setters for each field. Although not onerous for a three-tuple, I’ve applied this trick with thrice as many fields, and hand-coding and unit testing the accessors and mutators in C for each field is almost as error-prone as letting other developers have direct access to the data fields. It’s admittedly a compromise, and if I had to do it again, I might choose to do it differently; similarly, you should consider whether to provide raw access to fields or protect access through methods when designing your record object as well.

Under the hood, the data representation of the ITaskRecord is a trifle more complex because in addition to carrying around the fields of a task record, it also carries around the necessary data to perform as a BREW database record, including the AEEDBField array used when creating or updating the record.

Listing 3

typedef struct _STaskRecord_private
{
   IDBRecord            *pIDBRecord;
   AEEDBField           aFields[TASKRECORD_MAXFIELDCOUNT];
   uint32               nRefs;
} STaskRecord_private;

typedef struct _STaskRecordData
{
   ITaskRecord          public;
   STaskRecord_private  private;
   ITaskRecordVtbl      vt;
} STaskRecordData;

The private implementation of an ITaskRecord must be cast-compatible with the public interface, so the private representation STaskRecordData ensures this by placing the public ITaskRecord first, followed by a second structure for its private data and finally its vtable. (I could have chosen to simply lump the private fields after the public member, but encapsulating the private data in its own structure adds clarity at the expense of a little typing.) The private data must carry the record’s BREWuff of database fields (an AEEDBField array) along with the IDBRecord associated with the record; in addition, the ITaskRecord implements the full BREW IBase reference counting schema, so it must carry a reference count as well.

The implementation of the actual ITaskRecord class is straightforward, as you can see from Listing 4. Rather than walk through each method, I invite you to examine the listing and see how it works for yourself; only a few additional explanations are necessary.

First, because each ITaskRecord carries its AEEDBField array along with it, the ITaskRecord_Update method is simply a wrapper around the IDBRecord_Update method, which commits each field (pointing back to the public members of the ITaskRecord structure) to the data store.

Second, ITaskRecord_Remove asserts that the reference count of a record to be deleted is unity, so that a programmer doesn’t accidentally pull the rug out from the application when more than one part of the application is relying on the record. Using ASSERT is a bit of a cop-out, however, because it only points out programming errors in debug builds; it’s still a potential issue in production releases. Frankly, I never did decide whether I wanted to let other developers hang themselves by removing a record in use in multiple places in the code; the design of the application is such that it would be nigh impossible. Bear in mind when crafting your own solution, though, that it’s an area that deserves some additional analysis.

Finally, ITaskRecord_Release is a little tricky because it must decrement both the ITaskRecord’s reference count and the underlying IDBRecord, and free the ITaskRecord only if its reference count is zero. Note that ITaskRecord_Release doesn’t update changes to the data store—doing so would violate the contract specified by the IDBRecord interface, which only changes the contents of its records when you invoke IDBRecord_Update.

Inheriting the BREW IDatabase Interface

All of the methods in Listing 4 assume a well-formed initialized ITaskRecord instance. Constructing each instance is the responsibility of the ITaskDatabase interface, which inherits its interface from IDatabase. Inheriting the IDatabase interface gives clients a well-understood way to create and fetch records from the database, and structuring ITaskDatabase as an extension provides a clear delineation of responsibilities for the users of the interface. The internal representation of ITaskDatabase is pretty simple because it needs only carry around the IDatabase instance in addition to the baggage most extensions carry:

Listing 4

typedef struct _SITaskDatabaseData
{
   ITaskDatabaseVtbl *pvt;
   IModule *pIModule;
   IShell *pIShell;
   uint32 nRefs;
   IDatabase *pIDatabase;
} SITaskDatabaseData;

ITaskDatabase_CreateRecord creates a new ITaskRecord just as IDATABASE_CreateRecord creates a new IDBRecord:

Listing 5

static ITaskRecord *ITaskDatabase_CreateRecord(ITaskDatabase *p)
{
   SITaskDatabaseData *pThis = (SITaskDatabaseData *)p;
   STaskRecordData *pNewRecord;

   if ( !pThis || !pThis->pIDatabase ) return NULL;

   pNewRecord = MALLOC( sizeof( STaskRecordData ) );
   if ( !pNewRecord ) return NULL;

   InitRecord( p, pNewRecord );

   pNewRecord->private.pIDBRecord =
      IDATABASE_CreateRecord( pThis->pIDatabase,
                              pNewRecord->private.aFields,
                              TASKRECORD_MAXFIELDCOUNT );
   if ( !pNewRecord->private.pIDBRecord ) FREEIF( pNewRecord );

   return (ITaskRecord *)pNewRecord;
}

Not surprisingly, ITaskDatabase_CreateRecord doesn’t do much. First, it creates the necessary memory to hold a new ITaskRecord (including its private data, of course), and then invokes InitRecord to connect up the AEEDBField structure and the public data, as well as initialize the vtable:

Listing 6

static void InitRecord( ITaskDatabase *p, STaskRecordData *pRecord )
{
   UNUSED( p );
   pRecord->private.aFields[ TASKRECORDFIELD_TITLE ].fType =
      AEEDB_FT_STRING;
   pRecord->private.aFields[ TASKRECORDFIELD_TITLE ].fName =
      TASKRECORDFIELD_TITLE;
   pRecord->private.aFields[ TASKRECORDFIELD_TITLE ].wDataLen =
      sizeof(pRecord->public.wszTitle);
   pRecord->private.aFields[ TASKRECORDFIELD_TITLE ].pBuffer =
      &pRecord->public.wszTitle;

   pRecord->private.aFields[ TASKRECORDFIELD_DUE ].fType =
      AEEDB_FT_DWORD;
   pRecord->private.aFields[ TASKRECORDFIELD_DUE ].fName =
      TASKRECORDFIELD_DUE;
   pRecord->private.aFields[ TASKRECORDFIELD_DUE ].wDataLen =
      sizeof( uint32);
   pRecord->private.aFields[ TASKRECORDFIELD_DUE ].pBuffer =
      &pRecord->public.dwDue;

   pRecord->private.aFields[ TASKRECORDFIELD_COMPLETE ].fType =
      AEEDB_FT_BYTE;
   pRecord->private.aFields[ TASKRECORDFIELD_COMPLETE ].fName =
      TASKRECORDFIELD_COMPLETE;
   pRecord->private.aFields[ TASKRECORDFIELD_COMPLETE ].wDataLen =
      sizeof( boolean );
   pRecord->private.aFields[ TASKRECORDFIELD_COMPLETE ].pBuffer =
      &pRecord->public.bComplete;

   pRecord->public.pvt = &pRecord->vt;
   pRecord->public.pvt->Update  = ITaskRecord_Update;
   pRecord->public.pvt->Remove  = ITaskRecord_Remove;
   pRecord->public.pvt->Release = ITaskRecord_Release;
   pRecord->public.pvt->AddRef  = ITaskRecord_AddRef;
   pRecord->public.pvt->SetContentsOfRecord = ITaskRecord_SetContents;
   // Initialize the private data
   pRecord->private.nRefs = 1;
}

InitTaskRecord is one of those long, boring functions in C that’s literally all assignments, a necessary evil. Once this bookkeeping is done, however, the remainder of the work is straightforward; simply create the record in the database using the IDATABASE_CreateRecord function and return the new object.

InitRecord is also used when fetching a record, either from ITaskDatabase_GetRecordByID or ITaskDatabase_GetNextRecord, both of which call LoadRecord:

Listing 7

static int LoadRecord( ITaskRecord *pRecord )
{
   AEEDBFieldName name;
   uint16 nLen = 0;
   boolean *pbComplete = NULL;
   int type = IDBRECORD_NextField( pRecord->private.pIDBRecord,
                                   &name, &nLen );

   while( AEEDB_FT_NONE != type )
   {
      switch( name )
      {
         case TASKRECORDFIELD_TITLE:
         WSTRCOPYGUARDTOFIXED( pRecord->public.wszTitle,
                               IDBRECORD_GetFieldString(
                                  pRecord->private.pIDBRecord ));
         break;

      case TASKRECORDFIELD_DUE:
        IDBRECORD_GetFieldDWord( pRecord->private.pIDBRecord,
                                 &pRecord->public.dwDue );
        break;

      case TASKRECORDFIELD_COMPLETE
         pbComplete = IDBRECORD_GetField( pRecord->
                      private.pIDBRecord, &name, &type, &nLen );
         pRecord->public.bComplete = pbComplete ? *pbComplete :
                                     FALSE;
         break;

      default:
        break;
      }
      type = IDBRECORD_NextField( pRecord->private.pIDBRecord,
                                  &name, &nLen );
   }
   return SUCCESS;
}

LoadRecord is a typical BREW database record accessor function, looping through each of the fields and storing the data associated with the field along the way.

The remainder of the ITaskDatabase implementation—including ITaskDatabase_GetRecordByID and ITaskDatabase_GetNexRecord, which invoke the aforementioned InitRecord and LoadRecord functions—merely wraps around the corresponding IDatabase methods, as you can see in Listing 5.

Conclusion

Although many BREW developers don’t take a strong object-oriented approach to BREW development when coding in C, one area where such an approach can pay off is when working with objects that must persist between application invocations in a BREW database. By creating a class object that inherits the BREW IDBRecord interface and links its fields to AEEDBField entries, your application can treat data that persists as if it were local in-memory when accessing and mutating fields, while relying on the BREW database to handle object persistence.

For Further Reading

An introduction to Model-View-Controller: http://c2.com/cgi/wiki?ModelViewController

Writing BREW Extensions

Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.
Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.

Latest Posts

Related Stories