August 30, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Simplifying Access to BREW Databases

  • May 18, 2005
  • By Ray Rischpater
  • Send Email »
  • More Articles »

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.





Page 1 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel