Hello, Thank you for your interest in POET. For more information on POET please contact: POET Software Corporation 4633 Old Ironsides Drive, Suite #110 Santa Clara CA 95054 Sales (408) 970-4640 Fax (408) 970-4630 Best Regards, Brad Sturtevant POET Software Technical Support Compuserve: 70402.74 Internet: 70402.74@compuserve.com POET TECHNICAL OVERVIEW April 20, 1993 POET (Persistent Objects and Extended database Technology) is an innovative object-oriented database development system for C++ applications. POET has received a variety of awards, including Computer Language's Jolt award for product excellence, Computer Language's productivity award, and BYTE Magazine's award of distinction. POET is truly object oriented. It supports fully all aspects of C++ objects, including encapsulation, inheritance, polymorphism, object identity, and references among objects. POET also gives you full database functionality, including queries, device independent formats, and sharing of objects among programs in a multi-user environment. Database operations manage your C++ objects directly without programmer intervention. Instead of making you understand our database formats, we have taught our database to understand your objects. POET is available on MS-DOS, MS-Windows, MS-NT, Macintosh, OS/2, NeXT, SCO, Interactive, Sun, and various other flavors of UNIX. Both single-user and client/server versions are available. Heterogeneous networks are also supported--no matter what computer your application runs on, it can use any server on the network. Because POET uses the same database format for all systems, objects stored by one computer can be used on any other computer. OBJECTS NEED POET... Applications continue to become more complex, and often require immense effort to develop and maintain. Object oriented techniques let the structure of your program closely reflect the structure of the problem to be solved. Related code and data are combined into objects, and C++ has rich features for expressing the structural and semantic relationships among objects. Object oriented programs are modular, easy to understand, easy to maintain, and easy to extend. This significantly reduces both development and maintenance costs, which explains the rapid growth in object-oriented programming. Conventional database systems (relational database, ISAM, b-tree, etc.) manage their data in two dimensional tables. Complex structures must be broken up and distributed among these tables, which offer no real support for the object oriented programming model. As a result, many programs stop being object oriented whenever they use the database. The program and the database have two different designs, and the program must constantly convert back and forth. This significantly increases both program complexity and run time overhead. A conventional database is like a garage which forces you to take your car apart and store the pieces in little drawers. You can do it, but you probably don't want to. POET lets you store even complex C++ objects directly, without modification. Only object oriented databases can store complex objects 1:1. POET's database operations preserve all aspects of your C++ objects. References among objects are preserved, as is the full polymorphic structure of each object. Only one command is needed to store, read, or delete an object, now matter how complex the object is. For instance, you could use one command to store a CAD drawing with many shapes, a document with many paragraphs and images, or an invoice with many items. The individual shapes, paragraphs, images, or items are objects in their own right, and can be processed independently in other parts of your program. Database management with POET requires a fraction of the effort and a fraction of the code. Less code means less work--your programs are easier to develop and maintain. POET -- A THUMBNAIL SUMMARY POET Keywords. POET extends C++ syntax to allow you to specify database attributes directly in your class declarations. POET Precompiler. The precompiler translates POET's extended C++ declaration syntax into ANSI C++ header files which you include in your program. It also adds your class declarations to the class dictionary of the database POET Tool Classes. POET's tool classes provide the C++ interface to the database. In addition, they allow management of sets and query specification. POET Run Time System. The run time system manages the database and processes queries. In multi-user systems it is part of the server; in single-user systems it is linked to the application. PROGRAMMING WITH POET In the next section we would like to illustrate POET programming with an example. We will design an inventory management system for a computer supply store and show how it would be implemented with POET. Scenario : Inventory Management An inventory system must manage a wide variety of data. Our store sells hardware, software, literature, services, and accessories. We need to generate packing lists and invoices which list the items we sell. Our database must keep track of manufacturers, distributors, customers, and employees. The first step in object oriented design is often to list the objects in the system and to group related objects. Delivery notices and invoices are both documents. We will call anything that can be listed on an invoice an item. Manufacturers, customers, and employees all have names and addresses. For our purposes we can call them all persons. Our database will have to manage objects of the following types: Item Hardware, Software, Literature, Service, Accessory Document Delivery Notice, Invoice Person Manufacturer, Distributor, Customer, Employee Representing the individual objects is not enough; we must also represent the relationships among objects, and doing this naturally is central to the design of the program. Consider an invoice: An invoice contains the address of the customer and a list of items. Each item refers to its manufacturer. We want to represent these relationships directly in our database design. Direct Integration in C++ Direct Integration in C++ Because C++ is tightly integrated into the C++ programming language you do not need to convert C++ objects for the database. POET manages your C++ objects, and it carefully follows the semantics of C++. Database classes are defined using C++ class declarations with a few simple extensions. POET does not need a separate class definition; your class declarations are the only database schema you need. Your database objects are your program objects. This dramatically simplifies database design. Let's start with the base classes "Person" and "Item". These classes represent the Person and Item sets in the previous diagram. They will need data and functions for input and output. The declarations below are normal C++ class declarations except for one keyword: persistent. Any class declared with the persistent keyword can be stored in the database. persistent class Person { private: PtString Name; PtString Firstname; PtString Street; ... public: virtual int Input (..); virtual int Print (..); }; persistent class Item { private: int ItemNumber; PtString Description; ... public: virtual int Input (..); virtual int Print (..); }; Now POET has declarations for the set of all Items or the set of all Persons. As shown in the diagram, each of these sets has subsets. A Software is an Item, but not all Items are Software. All items have the data and the methods needed to list them on an invoice. A Manufacturer is a Person, but not all Persons are Manufacturers. All persons have addresses. How do we represent these subsets? C++ represents subsets by using inheritance. POET ensures that this inheritance always works properly for objects stored in the database. In the following declaration, persistent class Software: public Item means that a Software is an Item and has all functions and data that any other Item has. persistent class Software : public Item { private: PtString Release; public: ... virtual int Input (); virtual int Print(); }; persistent class Hardware : public Item { private: ... public: virtual int Input(); virtual int Print(); }; persistent class Manufacturer : public Person { ... cset Products; virtual int Input(); virtual int Print(); }; The "Manufacturer" class contains the statement cset Products;. This is a container which can hold a set of references to Items. These Items can be "Hardware", "Software", or any other kind of item -- even if it is a new kind of Item developed after this declaration was written! The above example shows how POET derives the structure of your objects and the relationships among them by reading your C++ class declarations. You simply add POET's keywords. Now let's see how the resulting database definition is integrated into your program. The POET Precompiler Unfortunately, no C++ compiler in the world understands our language extensions. Therefore, we have written a precompiler which produces code that can be compiled with any ANSI 2.0 C++ compiler. Persistent class declarations are usually placed in files with the .hcd extension. Our precompiler produces a header file with the .hxx extension. You include this file in your source files. Of course, the POET precompiler does a lot more than this behind the curtains. It manages the class dictionary of the database to ensure that all of your class declarations are registered an up to date. It also produces code to tell the POET run time system how to manage your objects. Less Code, Less Work Since POET can manage both your objects and their relationships without programmer intervention, your programs are smaller and simpler. POET applications are easy to write. A single line in a POET program is often equivalent to many lines of code in traditional database systems. Suppose our program has just changed the name and business form of a manufacturer and added a few items to their product line. Now we need to save these changes. In conventional systems these objects are stored in a series of two-dimensional tables (Persons, Manufacturers, Item, Software) and your program must know how to change your objects into the rows of these tables. In POET you need only one line: pManufacturer->Store(); POET's precompiler always generates a Store() member function for any persistent class, so you don't have to bother with the details. The object is completely stored, and the references to the items in the product line are automatically updated. Your application code is much shorter and easier to understand. Less code also means fewer programming bugs. Moreover, since the class dictionary resides in the server, you simply send exchange complete objects with the server instead of all the rows of individual tables needed for a conventional system. This reduces the communications overhead for client/server applications. Of course, you need an open database before you can store an object. The following code is all you need to connect to a server, open the database, and store an object: main() { // A PtBase manages the connection to the database. PtBase base; base.Connect ("Local"); base.Open ("test"); // Create a manufacturer and read its data Manufacturer * pManufacturer = new Manufacturer (base); pManufacturer->Input(); pManufacturer->Store(); base.Close(); base.DisConnect(); } That's all the code you need! pManufacturer->Store() is the member function that the POET precompiler automatically creates to store a persistent object. You need some way to input the data; in this example we assume you have written a member function to do this, and pManufacturer->Input() calls this function. PtBase is a POET class that manages the connection to the database. base.Connect() and base.DisConnect() are used to establish and break the connection to the server. In the single- user version you use the server name "Local". base.Open() and base.Close() open and close the database. POET's Object Management Model You have already seen that any C++ class declaration can be made persistent simply by adding one keyword to its class declaration. The precompiler translates your persistent class declarations into ANSI 2.0 C++ code. Once the database is open, storing a persistent object requires one line of code. However, database programming is not a matter of storing individual objects. A database system must provide a clear and consistent programming model for expressing and managing the relationships among objects. This is where POET really shines. When you program in POET you program in C++. POET carefully follows the semantics of C++ to provide powerful database functionality using the programming model that every C++ programmer already knows. C++ inheritance, polymorphism, and pointer references provide POET with an elegant model for database programming. All the classes you need to manage the database are provided, and are used like any other C++ class library. AllSets: Every Object for a Given Class Whenever the POET precompiler encounters a persistent class declaration it creates a set to hold all objects of this type. This set is called an AllSet, and the name of the AllSet depends on the name of the class. In our example we have defined the classes "Person", "Manufacturer", "Item", "Hardware", and "Software". The POET precompiler would generate the AllSets "PersonAllSet", "ManufacturerAllSet", 'ItemAllSet", "HardwareAllSet", and "SoftwareAllSet" to hold objects for these classes. When you store the object it is added to the appropriate classes. Since C++ objects can belong to several classes, a POET object may belong to several AllSets. Of course, an object can belong to more than one set. In our design every "Manufacturer" is also a "Person". Whenever a Manufacturer is stored it will be added to both the ManufacturerAllSet and the PersonAllSet. POET's AllSets, like all other sets, use the same inheritance as the objects they contain. since a "Manufacturer" is a "Person", a ManufacturerAllSet is a PersonAllSet. If you have a function that browses a PersonAllSet then you can also use this function with a ManufacturerAllSet. Reading Objects When POET creates an AllSet it automatically derives it from PtAllSet, giving it a variety of methods for managing objects of a given class. Suppose you want to read the first Person from the database. You simply call the Get() method of the PersonAllSet, telling it you want the first person: POET reads the first Person object from the database and builds this object in your program's memory. In the code below pPerson points to this first object: PersonAllSet * AllPersons = new PersonAllSet( base ); Person * pPerson = 0; ... AllPersons->Get( pPerson ); // read object from database ... pPerson->Print(); When you read an object from the database it behaves just like the object that was stored. Its member functions, data, and references are identical to the original object. Objects and Polymorphism If you ask two musicians to play a song, one may start squeezing an accordion while the other strums a guitar. They are both musicians, but they are different kinds of musicians and they respond to the same request in different ways. If they did not know how to play a song, though, they would not be musicians at all. Similarly, in C++ objects derived from the same base class may call completely different functions in response to the same member function call. This is known as polymorphism, and any C++ function can be used polymorphically if it is declared virtual and occurs in both a base class and derived classes. In practice, polymorphism can often simplify code. For instance, in our example we need to be able to compute tax for each item on an invoice. However, taxes are computed differently for different kinds of items. In the States there is no sales tax for services, but sales tax is charged for hardware or software. The easiest way to deal with this is to ask each kind of item to compute its own tax. If we want to compute the total tax for an invoice we could do it like this: double TotalTax = 0.0; Item * pItem; long i; for (i=0; Items.Get(pItem, i, PtSTART) == 0; i++) { TotalTax += pItem->ComputeTax(); Items->Unget(pItem); // removes the item from memory } Because C++ supports polymorphism, each kind of Item can have its own virtual ComputeTax() function, and we do not need to know the derived type to call this function. If the object is of type Hardware then hardware::ComputeTax() will be called, if it is of type Service then Service::ComputeTax() will be called. In fact, if a new tax category is introduced in the future then we can create a new kind of Item with a new ComputeTax() function--the above code will call it correctly without change and without recompiling! Note that you can write your C++ objects to the database and retrieve them without affecting polymorphism. When you read an object it has precisely the same member functions that it had when it was stored. Navigation The objects in your program can refer to each other, and so can the objects in your database. For instance, an invoice has a list of Items, and each item has a Manufacturer. Following the references among objects is called navigation. Suppose we want to print a description of the Customer for the second Invoice in the database or print a description for the manufacturer of the third item on this invoice. In a normal C++ program you would do this by following pointers in memory. You may be surprised to know that you can also do this with objects that you read from the database! When POET stores an object which has pointers it automatically converts these into database references. If you read this object from the database then POET loads the referenced objects and sets your pointers appropriately. Sets of references are also resolved automatically. To follow the relationships in your database, you simply follow the pointers in your C++ programs. This makes it quite easy to make hypertext-style programs which follow the references in a database. For our example, let's assume that an invoice has a pointer to the customer and a set of pointers to Items. An Item has a pointer to its manufacturer: persistent class Invoice { ... Customer *pCustomer; cset Items; ... }; persistent class Item { ... Manufacturer *pManufacturer; }; The pointers and sets of pointers in the above declarations will be resolved automatically by POET whenever objects of these classes are read from the database. This makes navigation quite simple: ... InvoicesAllSet *Invoices = new InvoicesAllSet(base); Invoice *pInvoice; if (Invoices.Get(pInvoice, 1, PtSTART) == 0) // get the second invoice { pInvoice->pCustomer->Print(); // print the customer Item *pItem; if (Items.Get(pItem, 2, PtSTART) == 0) // get the third item pItem->pManufacturer->Print(); // print the manufacturer } What if the referenced object is deleted? POET has referential integrity: it knows that the referenced object is gone, so it initializes the pointer to zero and returns a warning. What if you have large objects or large sets of references that you do not want loaded into memory? POET also supports other kinds of references that do not load the object until the programmer asks for it. Queries Queries allow you to find all objects that contain specific values. Suppose you want to list all customers whose names start with "A*". Query objects are used to specify queries. The POET precompiler generates a query specification class for every persistent class it encounters. The query specification class generated for Customer is called CustomerQuery. If you want to state that the Name field should start with A, you can do this using the SetName function. You can also tell the query how to sort the result set; for instance, we can also sort by Name. CustomerAllSet has a member function called Query which takes your query specification and builds a set of matching objects. The code might look like the following: CustomerAllSet * AllCustomers = new CustomerAllSet (base); CustomerSubSet * Result = new CustomerResultSet; CustomerQuery Query; Customer * pCustomer = 0; // Specify Query: All customers beginning with "A*" in ascending order. Query.SetName("A*", PtMATCHES); Query.SortByName(PtACSENDING); AllCustomers-> Query(Query, Result); Result->Get(pCustomer); The results of the query are placed in a set. You can read this set using Get(), just as you would with an AllSet. In fact, all sets are derived from the same base class, PtObjectSet, so you can write one browser that can be used for result sets, AllSets, or any other sets in your program. Intelligent Client / Server Architecture POET is available in both single-user and multi-user versions. The multi- user version of POET uses a client/server architecture. Application programs can run on any computer on the network. Access to the database is done by making requests to one central process, the database server. The server manages multi-user access and ensures that the database is kept consistent. Because the server runs on the computer where the database is stored it can access objects quickly, without network overhead. This computer is typically much more powerful than the computers that the client applications run on, which also improves system performance. Furthermore, since only the results of queries are sent to the clients network overhead is reduced significantly. Multi-user databases need to provide features to ensure that updates made by different processes do not make the database inconsistent. POET provides traditional features like locking and transactions. It also it offers more innovative features--a program can ask the server to notify it when one of its objects is modified by another process, or to notify it when certain events occur during processing. Locking A program can lock an object to ensure that no other process performs certain actions on that object. Different kinds of locks prohibit different kinds of actions, such as reading, writing, deleting, or setting locks on the object. A program can also lock a set, which means that a lock is set on all objects in the set. A lock can also be set implicitly when an object is read from a set. Transactions Transactions are used to ensure that a series of operations either succeed completely or fail completely without making any changes to the database. Every PtBase has a transaction manager which is accessed using the member function Transaction(). When you start a series of logically related operations you call the Begin() function of the transaction manager. Now you can perform any series of database operations. As far as your program is concerned the database acts as though all of these changes have actually been made, but in reality nothing has been written to the database. If operations succeed you can call the Commit() function to write all changes to the database. If not, you simply call the Abort() function; the database now looks just like it did when you called Begin(). PtBase base; Manufacturer * pMan = new Manufacturer (base); Customer * pCust = new Customer (base) pMan->Input(); pCust->Input(); // We want to store both objects. If one of the objects is locked then // store might fail. If it fails for one object, should fail for both. base->Transact()->Begin(); if ((pMan->Store() != 0) || (pCust->Store() != 0)) base->Transact()->Abort(); else base->Transact()->Commit(); Does your program have many levels of nested logic? No problem. Transactions can also be nested. Each Abort() or Commit() applies to the current transaction level. Incidentally, POET also has a second kind of transaction that is invisible to the user. Any operation which changes a database is performed using our file-level transaction manager. This ensures that changes either succeed completely or fail completely, and protects the integrity of your database. Watch & Notify Sometimes it is perfectly acceptable for other processes to modify the objects you are using, but you want to ensure that your process always has the most recent version. POET allows you to set a watch on object or set of objects; this means that the database will inform you whenever they are accessed or modified by another process. You can also ask POET to update the object in your memory if it is modified by another program. POET does this automatically without further program intervention. Suppose our inventory program is running on many different computers, and changes are being made to the database. If an item is discontinued then we may no longer sell it. We want to ensure that all programs using the database are informed instantly. The traditional approach is to force all applications to reread items from the database if they may have changed. This increases the number of database accesses that are needed, slowing down the programs and increasing the network traffic. Worse, the programmer has to ensure that the item is reread at many different points in the program. Any part of the code that uses an object in memory can cause errors if it is critical to use the most recent version of the object and the programmer forgets to reread it. In POET you can use the Watch() method to set a watch on an object or set of objects. UnsetWatch() removes the watch. The PtWatchSpec class is used to specify the operations that you want to know about, and the PtMethodRef class is used to specify the action that POET should take if this operation is performed by another program. The server can notify clients when an object is modified in the database. You can tell POET to update the object in your memory or to call a function in your program when this occurs. .... pDistributor->Display(); // Define the watch specification: if anybody updates the // distributor in the database, the POET server should call // our application's target function PtMethodRef Target ( pDistributor, (PtFunc) &Distributor::Display()): PtWatchSpec WatchSpec(PtWATCH_UPDATE, PtDEEP, Target); // Install the watch for the object. pDistributor->Watch (&WatchSpec); Let's assume we have two clients using the same database on a network. Client 1 executes the code in the previous example. Client 2 modifies the distributor and stores it. This is what happens: Time Client 1 Client 2 1 Installs watch using code from above example 2 modifies and stores the product line for a distributor 3 The POET server notices that the watched object has been modified, updates the distributor in this client's RAM, and calls the target function. Event Handling An application is usually blocked while a database operation is performed. If the operation takes a long time then the application may need to gain control from time to update the screen, process user input, or allow the user to cancel the operation. Event handling allows you to gain control when a database operation begins, ends, or when a certain number of objects have been processed. You simply give POET the address of a function which it calls when these events occur. POET examines the return value of your function to see if it should continue. In our example the application opens a window when the operation begins and periodically checks to see if the Cancel button has been pressed. If so, it aborts the database operation. When the operation is completed the window is closed. The event handler can only call functions that are member functions of classes derived from PtCallback. In our example we declare a class called ActionWindow. It has three member functions to handle the window when processing begins, during processing, and when the operation is finished. class ActionWindow : public PtCallback { public: int OpenWindow() { WindowSystem->OpenWindow(&MessageWindow); ... return PtEXCCONTINUE; // continue database operation } int CancelButton(int percent) { WindowSystem->Message(&MessageWindow, "%d percent finished", percent); ... if ( ! CANCELBUTTON ) return PtEXCCONTINUE; // continue database operation else return PtEXCABORT; // abort database operation } int CloseWindow() { WindowSystem->CloseWindow(&MessageWindow); ... return PtEXCCONTINUE; } } myActionWindow; Every PtBase has an exception manager which lets you install callback functions. We will use three exception manager functions: SetActionStarting()--Installs the callback function that will be called when a database operation begins. SetActionPending()--Installs the callback function that will be called periodically during database processing. SetActionTerminated()--Installs the callback function that will be called when a database operation is terminated. Because C++ member functions need to know the address of their objects when they are called, you must specify both the object and the member function when installing a callback function: ActionWindow myActionWindow; base->GetExcMgr()->SetActionStarting (&myActionWindow, (PtFunc) &ActionWindow::OpenWindow); base->GetExcMgr()->SetActionPending(&myActionWindow, (PtFuncInt) &ActionWindow::CancelButton); base->GetExcMgr()->SetActionTerminated(&myActionWindow, (PtFunc) &ActionWindow::CloseWindow); That's all there is to it. Your functions will be called automatically when a database operation starts, during processing, and when the operation completes. POET: An Overview C++ Language Support o Tight semantic integration with C++. o Any C++ object or structure can be made persistent by adding the persistent keyword. o Storing and reading a C++ object does not change its state or behavior. o Full support for C++ encapsulation, object identity, inheritance, and polymorphism. o C++ pointers and references are automatically converted to database references when storing objects. o Database references are automatically converted to C++ pointers and references when reading objects. o All database definition is done through a small extension to C++ declaration syntax. Database Functionality o Navigation o Queries o Sorting o Indexes o Single-user operation o Multi-user operation using client/server architecture o Flexible locking for objects and sets o Nested transactions o Watch & notify for objects and sets o Event handling o Database size limited only by hard disk size C++ Language Extensions o Persistence o Indexes o Transient data elements in persistent classes o Sets o Dependent objects PTXX-Precompiler o Automatically converts extended C++ class declarations into ANSI 2.0 code o Registers classes in the class dictionary o Automatically generates POET Tool Classes o Provides class versioning Predefined C++ Classes o Predefined classes for date, time, strings, and BLOBS (binary large objects) can save you development time Portability o All platforms are source-code compatible o Any POET database may be read by any computer o Full support for heterogeneous networks Platforms o Available for MS-DOS / MS-Windows (Borland C++, Microsoft C++, Zortech C++), OS/2 (Borland C++), Novell, Macintosh (MPW), and various Unix systems, including NeXT (NeXTStep), SCO (Liant C++), and Sun OS (Sun C++). POET Keywords o persistent--Defines database classes. Instances of a persistent class can be stored in the POET database. o transient--Defines non-persistent members of a persistent class. Transient members are not stored in the database. o depend--Defines dependent sub-objects. o indexdef, useindex--Defines indices. If available, indices are automatically used for query optimization. o ondemand--Defines special references among objects. Ondemand references are explicitly loaded after object is read. o cset, lset, hset--Define object containers that may or may not swap objects to disk POET Tool Classes o PtBase--Used for database management. o PtObject--Management of persistence and database characteristics. o PtAllSet--Provides access to all objects of a given class. o PtSet--Sets which can be used for one to many relationships among objects or to hold the results of a query. o PtOndemand--An optimization class for references to objects which are to be explicitly loaded. o PtQuery--A class to define and hold query specifications. POET Data Types o int, long, char, float, double--All of the standard C++ data types are supported. o class[n]--Arrays are fully supported. o PtString--A variable length string is provided. o PtDate--Date type. o PtTime--Time type. o PtBlob--BLOBs--Binary Large OBjects