Archives
 
 
 
  Special
 
 
 
  About Us
 
 
 

Newsletter
Free E-mail Newsletter from BYTE.com

 
    
           
Visit the home page Browse the four-year online archive Download platform-neutral CPU/FPU benchmarks Find information for advertisers, authors, vendors, subscribers Request free information on products written about or advertised in BYTE Submit a press release, or scan recent announcements Talk with BYTE's staff and readers about products and technologies

ArticlesPoet in Motion


May 1994 / Reviews / Poet in Motion

Poet 2.1 combines an object-oriented model with the best features of a multiuser database

Rick Grehan

With everything in the programming world turning, it seems, into an object of one sort or another, it's inevitable that the trend of OOP (object-oriented programming) should spill into the database world. You would hope, of course, that such spillage would occur with a little thought behind it, not just as a consequence of technological inertia. Poet 2.1 puts the object-oriented model to good use and includes all the accoutrements you'll find in a full-blown multiuser database: compound indexes, locks, even multilevel transactions.

I explored the single-user personal edition for Windows 3.1 and Microsoft Visual C++. Versions are available for other compilers and platforms, including NT, OS/2, Mac System 7, NextStep, and several Unix va riants. A professional edition (multiuser) is available on most of these platforms.

Beyond Relational Data

As justification for the move to ODBMSes (object-oriented database management systems), proponents claim that certain problems crop up when trying to manipulate a relational DBMS from within a C++ or Pascal program. In particular, there has appeared the notion of impedance mismatching, a term borrowed from the electronics world. Working with a relational database from within a language such as C++ forces your data to undergo a structural transformation when it passes from your application to the database or vice versa.

For example, suppose you have a database consisting of an EMPLOYEE table and a DEPARTMENT table. In your program, you might be tempted to lay out structures that will hold rows fetched from each table:

struct employee {

char name[20];

date birthdate;

struct dept* department;

};

struct dept {

char name[10];

struct employee* depthead;

};

This scheme could produce a number of problems. First, the C++ structures represent the connections between employees and departments using pointers, while the database system (if it is relational) will handle these connections via foreign keys, which will likely be stored as strings.

Next, the employee structure includes a member of type date, which could be a class for which you've built methods that allow your program to easily perform sorting or comparison operations on calendar dates. If the internal storage format of a date as handled by the database is different, you'll have to build conversion functions to move data from one format to the other.

Poet seeks to alleviate these problems by creating a DML (database management language) within the structure of the C++ class syntax. At the heart of this idea is the notion of persistent objects: objects that have the look and feel of instantiations of C++ classes but can be placed in permanent storage in a database.

Poet manages the storage and retrieval of contained or referenced objects invisibly. When you load an object from the database, all referenced objects are also loaded, with pointers properly "wired."

The Front End

From the programmer's point of view, working with Poet is a matter of dealing with the preprocessor, PTXX. Actually more than just a preprocessor, PTXX takes a special header file (identified by a .hcd extension) into which you have placed the definitions of the classes whose objects will reside in your database and, from that file, generates all the C++ source needed to manipulate those objects.

Suppose you're working for an organization that has need of a client database and you decide to use Poet for the task. Since this is, after all, OOP, I'll assume that you've defined a class called Client that will hold each client's data. I'll also assume that you're a Poet pro: You've built your application's source code, you've fed everything to PTXX, and you're rea dy to include what PTXX has given you into your C++ program and hand it all to the compiler. What, precisely, has PTXX given you?

First, it has made a container class called ClientAllSet, a potentially humongous set whose elements are of class Client. Member functions allow you to traverse the set in various ways, retrieve elements from it, store new elements into it, delete elements, and so on. ClientAllSet is attached to the database of persistent objects out on disk. (This process of attaching a class to a database amounts to issuing a call to "connect" the ClientAllSet class to a named database. From that point on, Poet handles the traffic flow of objects between disk and memory automatically.)

Second, PTXX defines a query class called ClientQuery. The member functions of ClientQuery allow you to specify a query's parameters. These specifications get assembled into an object of class ClientQuery, which you then pass to the Query member function of ClientAllSet.

Finally, PTXX defines a nother container class, ClientSet. The behavior of ClientSet is much like that of ClientAllSet; its function is to hold an arbitrary number of objects of class Client, and you usually use ClientSet objects as a repository for query results. It is, however, not persistent.

All these classes are placed in .hxx and .cxx files, which you tie into your program via include statements. Simplest among the database manipulation methods are Put() and Get(), which, respectively, store objects into and retrieve objects from the database. Simple as they seem, however, things can get complex. An object of one class may contain as a member an object of another class. So an operation that appears to fetch a single object may actually fetch a nested Russian doll of objects containing objects. Furthermore, objects of class A may contain a pointer to an object of class B; fetching a classmember of A would require fetching the proper classmember B and resolving A's pointer to B. Poet handles it all, loading referenced obj ects and resolving pointers. Poet even allows you to control how much gets loaded.

For example, in the preceding case, you might want a fetch operation to retrieve only object A--leaving the pointer to B dangling--and retrieve object B only when you specifically issue a call to do so. Poet allows this via the ondemand keyword: The system will not retrieve an object of type ondemand unless it is explicitly told to.

Queries and Sorting

Querying is handled by the query class that Poet automatically builds for every persistent object class. Poet fills query classes with methods corresponding to each member of its parent class. If you define

persistent class Client {

short clientid;

char name[30];

};

Poet builds the following query class (I've left out some of the details of the member functions for clarity's sake):

class ClientsQuery: public PtQuery

{

public:

Setclientid(...);

Setname(...);

};

You can see that each member of Client has caused Poet to generate corresponding Setxxx functions. To locate the client whose ID is 47, the query would look like this:

ClientAllSet *clall =

new ClientAllSet(objbase);

ClientSet *result=new ClientSet;

ClientQuery clquery;

clquery.Setclientid(47,PtEQ);

clall->Query(&clquery,result);

The Setclientid() member function of clquery lets you specify the parameters of the query. You then pass a pointer to this query object to the Query member function of clall, which performs the query and places the results in result, a container class that can hold an arbitrary number of Client objects. Once the query has completed, you can use member functions defined for objects of class ClientSet to "browse" the query's results.

Finally, Poet also builds query class member functions that allow you to sort the query results. So, if you've built a query that locates all client IDs greater than 100 and you want to sort by name , you simply issue clall.SortByName(PtASCENDING) before building the query. The items will be loaded into the result set in ascending order by client name.

Indexes

Indexes can significantly speed the querying and sorting process. If you create a class to which you wish to attach an index, you must build one or more class members of type useindex. The name of that member will appear elsewhere, in an indexdef definition. An indexdef looks like a typical C++-derived class definition, but it contains no methods--only data members. This is best illustrated by the following code:

persistent class Client {

short clientid;

char name[30];

Address address;

Phone phonenum;

useindex IdIndex;

useindex NameIndex;

...

};

indexdef IdIndex: ClubMember {

clientid;

};

indexdef NameIndex: ClubMember {

name[[20]];

clientid;

};

Objects of class Client have two indexes associat ed: IdIndex and NameIndex. The first has only one component, clientid (in this example, a unique ID number assigned to each client). The second is a compound index, consisting of name and clientid. Notice the double brackets in the name member of NameIndex. This tells Poet to use only the first 20 characters of the name field in constructing the index. Poet is intelligent enough to use indexes whenever such use would speed a query. For example, Poet would use Id-Index for handling a query along the lines of "Which client's ID number is 400?"

Transactions

All Poet operations include some form of transactioning. Whenever you store an object, you may also be storing all other objects referenced by that object. (I say may because how much is actually stored can be controlled by the depth mode parameter in the store operation. This depth indicator has four settings, ranging from "store only the object itself" to "store the object and all objects it references.") When you issue a store operation that trig gers placing multiple objects into the database, Poet stores either everything or--if any part of the operation fails--nothing.

If you're particularly antsy about the safety of your data, Poet does provide an optional two-phase commit. In this case, Poet builds a forward recovery file--a file that contains the write operations Poet intends to perform on the database--before making any updates. Even if the system crashes, Poet can use the forward recovery file to rebuild the database. Of course, enabling the two-phase commit option means that database I/O runs more slowly, since it requires twice the usual write operations.

The transactions I've described are system-level transactions. Poet also supports user-level transactions, which are handled using the time-honored begin/commit bracketing structures. Specifically, once you've connected to a database, you can issue a BeginTransaction() method call, carry on database operations, and conclude with a CommitTransaction() call. All operations betwe en the two calls are posted to the database only when Commit-Transaction() executes. If something goes awry during the transaction, you can issue an AbortTransaction() call and the database will be left in its original state.

Locks

Where transactions provide one form of concurrency mechanism, locks provide another. It's important to note that some locking goes on "under the sheets" when you enable transactions. Specifically, if you store or delete an object from within a transaction, Poet automatically places a transaction lock on that object to ensure that no other processes will mess with the object until the transaction is safely committed.

In the case of explicit locks, Poet provides seven lock types, ranging from no locking to exclusive locking. In between, locks are classified based on what your process intends to do, along with what your process wants to prevent other processes from doing. For example, the PtLK_READvWRITE lock level indicates that your process will be reading the locke d object so no other processes should try to write (or delete) it.

Since Poet is an object-oriented system, the question arises of how much in the database is being locked. Poet has four lock depths, from flat (which locks only the object being locked) to deep (which locks the object and all its referenced objects).

Events

Suppose you've initiated a query on a database that's likely to take a long time. It would be nice if you could keep the user apprised of the progress of the query and allow her to cancel the query if she decides it's taking too long.

Associated with each Poet database is an exception manager, and within the exception manager are methods that let you install callback functions for progress monitoring (a callback function being a routine in your application that Poet calls). You can build callback functions that are triggered at the beginning, during, and at the conclusion of a database operation. If you really want a spyglass into your database, every persistent clas s that is built by Poet inherits member functions Watch() and Notify(), which your program can use to track what operations other processes may be performing on an object. The Watch() member function accepts a watch specification as its argument; if the conditions of the watch specification are satisfied, the object's Notify() function is called. Again, some code will clarify this:

persistent class Client {

short clientid;

char name[30];

public:

Client(short clientid,

char* cname);

Client();

virtual int Notify(PtOnDemand

*Object, PtOnDemand *Root,

PtWatchMode Action);

};

...

Client MyClient(44,"Bob");

PtWatchSpec WatchDel

(PtWATCH_DELETE,PtDEEP);

MyClient.Watch(&WatchDel);

...

In this example, I've defined the persistent class Client and overridden its virtual Notify() member function. Later in this hypothetical application, I've created a new Cl ient object, "Bob," to which is attached a watch specification. The parameters of the specification tell Poet that I want my Notify() routine called whenever someone tries to delete "Bob."

You can create more than one watch specification for a given object. Poet allows you to set watches for store, update, lock, and unlock operations. Furthermore, you can set the depth of the watch, in fashion and function similar to setting the depth of a lock operation, which I described earlier.

Coda

Hard-core, meat-eating C++ programmers faced with their next database project would do well to examine the possibilities offered by Poet, since it allows them to use the structures they'll be coding into their programs as the same structures that will feed the database. If you couple Poet with a good class library that allows the rapid construction of GUI objects (e.g., Microsoft Foundation Classes), you've got a platform that approaches--and in terms of flexibility, probably exceeds--many of the self-proclaim ed fourth-generation GUI/database applications generators that are on the market today.


The Facts



Poet Personal Edition for Windows 2.1
(as tested)                                 $499
Requires Windows 3.1, 4 MB of RAM, and 5 MB of free disk space; versions available for Microsoft Visual C++, Borland C++, and Symantec C++. Other platforms supported.


Poet Professional Edition SDK
(multiuser), compatible with Windows
for Workgroups and Microsoft Visual C++     $1995
Other platforms are supported; contact Poet Software for prices.


Poet Software Corp.
4633 Old Ironsides Dr., Suite 110
Santa Clara, CA 95054
(800) 950-8845
(408) 970-4640
fax: (408) 970-4630


Illustration: Poet 2.1 is a true object-oriented database that uses the same structures as C++ programs.
Illustration: Retrieving information from a typical relational database (a) usually means pulling a row from a table structure, disassembling the fields, and translat ing them into variables your program can use. Poet (b) lets you pull objects from the database and place them directly into C++ class structures--all pointers and references intact.
Rick Grehan is technical director of the BYTE Lab. You can reach him on the Internet or BIX at rick_g@bix.com .

Up to the Reviews section contentsGo to previous article: Without PeerGo to next article: Desktop DictationSearchSend a comment on this articleSubscribe to BYTE or BYTE on CD-ROM  
Flexible C++
Matthew Wilson
My approach to software engineering is far more pragmatic than it is theoretical--and no language better exemplifies this than C++.

more...

BYTE Digest

BYTE Digest editors every month analyze and evaluate the best articles from Information Week, EE Times, Dr. Dobb's Journal, Network Computing, Sys Admin, and dozens of other CMP publications—bringing you critical news and information about wireless communication, computer security, software development, embedded systems, and more!

Find out more

BYTE.com Store

BYTE CD-ROM
NOW, on one CD-ROM, you can instantly access more than 8 years of BYTE.
 
The Best of BYTE Volume 1: Programming Languages
The Best of BYTE
Volume 1: Programming Languages
In this issue of Best of BYTE, we bring together some of the leading programming language designers and implementors...

Copyright © 2005 CMP Media LLC, Privacy Policy, Your California Privacy rights, Terms of Service
Site comments: webmaster@byte.com
SDMG Web Sites: BYTE.com, C/C++ Users Journal, Dr. Dobb's Journal, MSDN Magazine, New Architect, SD Expo, SD Magazine, Sys Admin, The Perl Journal, UnixReview.com, Windows Developer Network