Chapter 5: ODMG C++/VERSANT Usage

 

Important Notes

Beginning with Release 5.2, the standard C++ binding is merged with the ODMG binding.

 

Library Notes

The C++/VERSANT library is the same as the ODMG C++/VERSANT library.

Methods which are defined in the ODMG standard but not in C++/VERSANT have been added to the standard classes, so that the C++/VERSANT library is exactly the same as the the ODMG C++ library.

For new applications, include odmg.h and link with the C++/VERSANT library.

For new applications, to use ODMG C++/VERSANT methods:

1.  Include <odmg/odmg.h>.

2.  Link with a C++/VERSANT library.

The ODMG library will be dropped in future releases.

 

Method Notes

Due to the differences between the ODMG and previous VERSANT interfaces, some methods have been changed to follow the ODMG standard. We have tried to make the changes be as effortless as possible for you, but there are occasions when you may have to change your code.

The following methods are changed to follow the ODMG standard.

 

PString

Old PString methods

New PString methods

operator char*() const;

operator char *();

operator const char *() const;

char &operator[](o_u4b) const;

char &operator[](o_4b) const;

char &operator[](int i) const;

char &operator[](o_u4b);

const char &operator[](o_u4b) const;

 
 

VString

Old VString methods

New VString methods

operator char*() const;

operator char *();

operator const char *() const;

char &operator[](o_u4b) const;

char &operator[](o_u4b);

const char &operator[](o_u4b) const;

 
 

LinkAny

Changed return types

The following two methods now return int instead of o_bool:

int is_null();

int operator!();

New methods

The following methods have been added:

LinkAny(d_Object*);

LinkAny &operator=(d_Object *);

New constructor

The following copy constructor has been added:

LinkAny(d_Object* baseLink );

There may be some conflicts with the old code due to this constructor. Code such as:

LinkAny link=NULL;

will no longer work, because the compiler cannot resolve the overloaded functions.

This means that you will either have to be more specific in specifying the constructor or change the previous code to:

LinkAny link;

In the above, the default constructor will set the value to NULL anyway.

 

Using C++/VERSANT in ODMG Applications

 

ODMG/C++ Overview

In general, ODMG C++/VERSANT implements Release 1.2 of the ODMG-93 C++ binding. It complies to standards set in Chapter 5 of The Object Database Standard: ODMG - 93, Release 1.2.

ODMG C++/VERSANT also incorporates the ODMG 2.0 C++ binding standard for the d_Database and d_Transaction classes. The ODMG 2.0 C++ binding standard does not specify how a transaction is associated with a database, thereby allowing access to only one database at a time. The VERSANT implementation provides multi-threaded transactions capabilities with access to single and multi-databases.

ODMG C++/VERSANT is a thin layer on top of C++/VERSANT which implements the ODMG object model. When you use ODMG C++/VERSANT, you can still use C++/VERSANT and/or C/VERSANT methods not provided by the ODMG-93 standard.

 

VERSANT Classes and their Usability in ODMG Applications

Following is a list of VERSANT classes and their usability in ODMG applications.

Although most VERSANT classes are supported, we suggest that you follow ODMG standards and use VERSANT classes as extensions only as needed.

The signatures of ODMG methods may differ from those of equivalent VERSANT methods.

VERSANT CLASS

USABLE IN AN ODMG APPLICATION?

 
Primary Interface Classes

PClass Yes
PDOM Yes
PObject No — use d_Object
VSession Yes
 
Link and Link Vstr Classes

BiLink<type> Yes — or use d_Rel_Ref<type>
BiLinkVstr<type> Yes — or use d_Rel_Set<type> or d_Rel_List<type>
Link<type> Yes — alias for d_Ref<type>
LinkAny Yes — alias for d_Ref_Any
LinkVstr<type> Yes
LinkVstrAny Yes
PVirtual Yes — alias for d_Object
VCursor Yes
Vstr<type> Yes
VstrAny Yes
PList<type> Yes

PItem<type>

Yes

 
Also

Vstr<d_Ref><type>>> Yes — all vstr methods

Vstr<d_Ref_Any>

Yes — all vstr methods

 

Primary Helper Classes

PClassObject<type> Yes
PClassObjectIterator Yes
PCODIterator Yes
PError Yes — alias for d_Error
PLock Yes

PResult

Yes

 
Helper Types

o_1b Yes
o_2b Yes
o_4b Yes
o_8b Yes
o_bcd Yes
o_be_profile Yes
o_bool Yes
o_clsname Yes
o_clsname_const Yes
o_connectinfo Yes
o_date Yes
o_dblistdesc Yes
o_dbname Yes
o_dbname_const Yes
o_double Yes
o_envinfo Yes
o_event_msg Yes
o_fe_alias Yes
o_fe_profile Yes
o_float Yes
o_indextype Yes
o_interval Yes
o_lockmode Yes
o_opts Yes
o_time Yes
o_timestamp Yes
o_u1b Yes
o_u2b Yes
o_u4b Yes
o_u8b Yes
o_udllockmode Yes
o_userdesc Yes
o_vsnstatus Yes

vpp_dbname

Yes

 
Helper Macros

AS() Yes
assertClass() Yes
assertSpecies() Yes
EOS Yes
L_AS() Yes
NULL Yes
NULL_LINK Yes
NULL_PREDICATE Yes
NULL_VSTR Yes
O_COMBINE_LOCK() Yes
O_EXTERNAL_LOCK() Yes
O_INTERNAL_LOCK() Yes
O_IS_EXPLICIT_LOCK() Yes
O_NEW_PERSISTENT() Yes
O_NEW_PERSISTENT1() Yes
O_NEW_PERSISTENT2() Yes
O_SET_EXPLICIT_LOCK() Yes
VPP_CLASS_CONSTRUCTOR1() Yes
VPP_CLASS_CONSTRUCTOR2() Yes

vppConstruct()

Yes

 
Error Functions, Macros, Types, and Variables

Error Functions  
set_panic() Yes
set_terminate() Yes
set_unexpected() Yes
terminate() Yes
unexpected() Yes
vpp_break() Yes

vpp_panic()

Yes

Error Macros  

__HERE__

Yes

Error Types  
o_err Yes

o_error

Yes

Error Variables  
VPP_PANIC_HANDLE Yes
VPP_PFV Yes
vpp_terminate_where Yes

vpp_terminate_why

Yes

 
Query Classes

d_VQL_Query Yes — similar to OQL
PAttribute Yes
PBuffer Yes
PPredicate Yes

PPredTerm

Yes

 
Query Functions

!=() Yes
<() Yes
<=() Yes
==() Yes
>() Yes

>=()

Yes

 
Support Classes

PString Yes — alias for d_String
VString Yes
VDate Yes — or use d_Date

VTime

Yes — or use d_Time or d_Timestamp

 
Collections and Containers

VElemental Yes
Container Yes
V*Iterator<type> Yes
V??Dictionary<k,v> Yes
V??DictionaryIterator<k,v> Yes
V?Array<type> Yes — or use d_Varray<type>
V?ArrayIterator<type> Yes — or use d_Varray<type>
V?List<type> Yes — or use d_List<type>
V?ListIterator<type> Yes — or use d_List<type>
V?Set<type> Yes — or use d_Set<type>
V?SetIterator<type> Yes — or use d_Set<type>

 

can also use d_Bag<type>

 
Run-time Classes

PCustomizeFundamental Yes
VRTAttr Yes
VRTAttrDesc Yes
VRTBufDesc Yes
VRTClass Yes
VRTClassDesc Yes

VRTSuperDesc

Yes

 
 

Using ODMG Classes in ODMG C++/VERSANT Applications

 

ODMG Classes and their Usability in ODMG C++/VERSANT Applications

Following is a list of ODMG C++ classes and methods and comments relevant to their use in an ODMG C++/VERSANT applications. The following is not a complete list: classes and methods not listed here can be used in ODMG C++/VERSANT applications.

The ODMG classes have all ODMG specific methods and may have additional VERSANT methods. The signatures of ODMG methods may differ from those of equivalent VERSANT methods.

ODMG CLASS

RELATIONSHIP TO ODMG C++/VERSANT

 
Primary Interface Classes  

d_Database  
d_Object Aliased to PVirtual

d_Transaction

PDOM or VSession

 
Link and Link Vstr Classes  

d_Ref_Any Aliased to LinkAny
d_Ref<type> Aliased to Link<type>
 
Relationship Classes  

d_Rel_List<type, member> Extended by VERSANT
d_Rel_Ref<type, member> Extended by VERSANT

d_Rel_Set<type, member>

Extended by VERSANT

 
Primary Helper Classes  

d_Error

Aliased to PError

 
Support Classes  

d_Date — or use VDate
d_Time — or use VTime
d_Timestamp — or use VTime
d_String Aliased to PString

d_Interval

— —

 
Collection and Container Classes  

d_Bag<type> Extended by VERSANT
d_Iterator<type> Extended by VERSANT
d_List<type> Extended by VERSANT

d_VArray<type>

Extended by VERSANT

 
•    Using VERSANT non-ODMG C++ binding compliant classes and methods in your ODMG applications requires careful handling. If you elect to utilize them in your application, it is your responsibility to handle the effects with appropriate safeguards. Please read the section entitled "ODMG C++/VERSANT Restrictions, Variations, and Usage" before creating applications with C++/VERSANT classes and methods.

Besides using ODMG C++ classes, you can access VERSANT features in the following four ways.

•    You can invoke standard VERSANT database management methods through the pointer returned from the get_session() method in the d_Transaction class.

•    You can use extended VERSANT methods in otherwise standard ODMG classes.

•    You can also use C++/VERSANT specific classes.

•    You can invoke standard VERSANT database methods defined on PDOM by using the global variable ::dom.

 

ODMG C++ Access to VERSANT Using VSession

You can access C++/VERSANT database management methods on class VSession by using a get_session() method on d_Transaction.

For example:

d_Database *db1 = new d_Database;
db1->open("mydb");
d_Transaction *txction = new d_Transaction;
txction->begin();
/* create a Vstr of 5 elements */
LinkVstr<ClassA> v;
d_Ref<ClassA> r;
for (int i = 0; i < 5; ++i)
{  r = new (db1, "ClassA") ClassA();
   v.add(r);
}
/* use PDOM::gwriteobjs() */
txction->get_session()->gwriteobjs(v, O_FREE_OBJECTS);
txction->checkpoint();
/* use PDOM::gdeleteobjs() */
LinkVstrAny failed, timestamp;
txction->get_session()->gdeleteobjs(
    v,
    db1->get_this_db_name(),
    &failed, ×tamp, TRUE);
txction -> commit();
delete txction;
db1->close();

 

ODMG C++ Access to VERSANT Extended Methods

Some ODMG classes have VERSANT specific methods implemented. For example, each ODMG collection class implements the select() method, which is not defined in ODMG but is similar to the select() method in the VERSANT collection classes.

The following example shows use of VERSANT extended methods.

#include "ClassA.h"
#include <odmg/set.h>
#include <iostream.h>
extern "C" void exit(int);
int ClassA::count = 0;
main(int argc, char *argv[])
{  if ( argc < 2 )
   {  cout << "Usage: "
           << argv[0]
           << " dbname [ message ... ]"
           << endl;
      exit(1);
   }
   d_Database test_db;
   test_db.open (argv[1]);
   d_Transaction *txction = new d_Transaction ();
   txction->begin ();
   /* create a set with 5 elements
    */
   d_Ref<d_Set<d_Ref<ClassA> > > setRef =
       new (test_db, "d_Set<d_Ref<ClassA>>") d_Set<d_Ref<ClassA> >();
   for (i = 0; i < 5; ++i)
   {  d_Ref<ClassA> r = new (test_db, "ClassA") ClassA;
      setRef->insert_element(r);
   }
   /* commit the transaction
    */
   txction->commit();
   d_Set<d_Ref<ClassA> > set2;
   /* do a select on the d_Set<d_Ref<ClassA> > object
      and put the results into another d_Set<d_Ref<ClassA> > object
    */
   set2 = (VstrAny) setRef->select(&test_db, 0, 0, NULL_PREDICATE);
   iter = set2.create_iterator();
   for (; iter.not_done(); iter++)
   {  cout << (*iter)->id << endl;
   }
   txction -> commit();
   test_db.close ();
   delete txction;
}

 

ODMG C++ Access to VERSANT Specific Classes

Since most C++/VERSANT classes are shared in the ODMG implementation, you can use them as usual.

For example, to iterate through the COD table:

PCODIterator cod_iter;
int no_of_cod = cod_iter.getnumberofcods();
cout << "# of PCOD: " << no_of_cod << endl;
for (i = 0; i < no_of_cod; ++i)
        (void) cod_iter.get_next();
cod_iter.reset();

 

ODMG C++ Access to VERSANT using ::dom

You can access C++/VERSANT database management methods on class PDOM by using a global variable name ::dom. The ::dom global variable is created for you the first time you call d_Transaction::begin(). However, if you are using multiple sessions and multiple threads, always use the get_session() method instead of the ::dom global variable for thread protection.

For example:

d_Database *db1 = new d_Database;
db1->open("mydb");
d_Transaction *txction = new d_Transaction;
txction->begin();
/* create a Vstr of 5 elements */
LinkVstr<ClassA> v;
d_Ref<ClassA> r;
for (int i = 0; i < 5; ++i)
{  r = new (db1, "ClassA") ClassA();
   v.add(r);
}
/* use PDOM::gwriteobjs() */
::dom->gwriteobjs(v, O_FREE_OBJECTS);
txction->checkpoint();
/* use PDOM::gdeleteobjs() */
LinkVstrAny failed, timestamp;
::dom->gdeleteobjs(
    v,
    db1->get_this_db_name(),
    &failed, ×tamp, TRUE);
txction -> commit();
delete txction;
db1->close();

 

ODMG C++/VERSANT Restrictions, Variations, and Usage

Restrictions and variations inevitably arise, because ODMG has not defined standards in all areas related to databases. For example, ODMG doesn't define multiple database connections, but VERSANT does, so there are special VERSANT semantics needed to support distributed databases.

 

Multi-database and Multi-transaction Support to ODMG

•    The ODMG 2.0 C++ binding standard does not specify how a transaction is associated with a database, thereby allowing access to only one database at a time. The VERSANT implementation provides multi-threaded transactions capabilities with accesss to single and multi-databases.

When a transaction begin() is called for the first time, a hidden VSession object is created for the transaction to allow multiple threads to operate on the transaction. This means that each transaction has its own object cache. Each thread joining the same transaction shares the same VERSANT session. When a d_transaction is destroyed, the session is also ended.

To support multiple databases with multiple transactions, we have introduced the concept of a process/thread default database:

•    The first database opened in a process becomes the process default database.

•    The first database opened in a thread becomes the thread default database.

When a d_Transaction::begin() is called without any arguments, the hidden session begins using the thread default database as the seesion database. If the thread default database is not found, the process default database then is used. Users can also specify a d_Database as the session database by passing its reference to the d_Transaction constructor.

A transaction can associate itself with another database by calling the connect() method and disassociate with it by calling disconnect(). However the default database can never be disconnected because it is the session database.

The following graph shows the physical relationship between d_Database, d_Transaction, and VSession.

 

Derive from d_Object rather than PObject

If you are using ODMG C++, derive your persistent-capable classes from d_Object rather than the VERSANT PObject class.

Although the ODMG class d_Object is aliased to the VERSANT class PVirtual, which is a subclass of PObject, in ODMG C++, you should never directly use class PObject.

 

Database name argument for collection class queries

Queries defined on ODMG collection classes differ from queries defined on corresponding C++/VERSANT collection classes in that they take a pointer to a database object, d_Database*, as their first argument rather than a database name, const char*.

The following methods are affected:

d_Set::select()
d_Bag::select()
d_List::select()
d_Varray::select()

 

Databases are always opened in read/write access mode.

There is no read-only or write-only databases in VERSANT. An "exclusive" database is the same as a VERSANT private database.

 

You cannot commit links to transient objects.

According to ODMG, when a persistent object is committed, the ODBMS "sets its embedded d_Refs to transient objects to the null value." ODMG C++/VERSANT currently doesn't support this. In fact, an application will crash when this happens.

 

Use elemental or link types in your vstrs and collections.

Only certain types can be used in the following parameterized classes:

Vstr<type>
d_Set<type>
d_List<type>
d_VArray<type>
d_Bag<type>

In the above parameterized classes, you can use only the following data types for the type, key, or value substitution parameters:

Elemental Types

char, o_u1b, o_1b, o_u2b, o_2b, o_u4b, o_4b, o_u8b, o_8b, o_float, o_double.

Link Types

d_Ref<type> or d_Ref_Any.

 
This means that collection queries must be over elemental types or links.

 

The back links for the relationship classes must be defined.

For the relationship classes, d_Rel_List<type>, d_Rel_Ref<type>, and d_Rel_Set<type>, you must define the variables used to refer to the other end of the relationship in a .imp schema implementation file. This will let the schema compiler know the values of the two variables while parsing the class definitions.

For example:

a.h

extern const char _ra[], _rb[];
class A : public d_Object { d_Rel_Ref<B,_ra> rb; };
class B : public d_Object { d_Rel_Ref<A,_rb> ra; };

schema.imp

#include "a.h"
const char _ra[] = "ra";
const char _rb[] = "rb";
O_CAPTURE_SCHEMA(A);
O_CAPTURE_SCHEMA(B);
O_CAPTURE_SCHEMA(d_Rel_Ref<A,_rb>);
O_CAPTURE_SCHEMA(d_Rel_Ref<B,_ra>);

 

Some methods in the ODMG link classes are not implemented.

The following friend methods in d_Ref<type> and d_Ref_Any are not implemented, because they conflict with the original C++/VERSANT implementation. For each of these methods, the same functionality is provided in C++/VERSNT.

Not implemented on d_Ref<type>:

boolean predicate to check reference:

operator const void *() const;

comparison methods:

friend int operator == ( const d_Ref<type>& refL,
                         const d_Ref<type>& refR );
friend int operator == ( const d_Ref<type>& refL,
                         const type*        ptrR );
friend int operator == ( const type*        ptrL,
                         const d_Ref<type>& refR );
friend int operator == ( const d_Ref<type>& refL,
                         const d_Ref_Any&   anyR );
friend int operator == ( const d_Ref_Any&   anyL,
                         const d_Ref<type>& refR );
friend int operator != ( const d_Ref<type>& refL,
                         const d_Ref<type>& refR );
friend int operator != ( const d_Ref<type>& refL,
                         const type*        ptrR );
friend int operator != ( const type*        ptrL,
                         const d_Ref<type>& refR );
friend int operator != ( const d_Ref<type>& refL,
                         const d_Ref_Any&   anyR );
friend int operator != ( const d_Ref_Any&   anyL,
                         const d_Ref<type>& refR );

Not implemented on d_Ref_Any:

boolean predicate to check reference:

operator const void *() const;

comparison methods:

friend int operator == ( const d_Ref_Any& anyL,
                         const d_Ref_Any& anyR );
friend int operator == ( const d_Ref_Any& refL,
                         const d_Object*  ptrR );
friend int operator == ( const d_Object*  ptrL,
                         const d_Ref_Any& anyR );
friend int operator != ( const d_Ref_Any& anyL,
                         const d_Ref_Any& anyR );
friend int operator != ( const d_Ref_Any& refL,
                         const d_Object*  ptrR );
friend int operator != ( const d_Object*  ptrL,
                         const d_Ref_Any& anyR );

 

Some methods in ODMG bilink classes have been redefined as private.

Some methods in the bilink classes have been redefined as private to preserve referential integrity.

The following methods in d_Rel_Ref<T, M> are private:

                   d_Rel_Ref   ( d_Rel_Ref<T, M>& );
                   d_Rel_Ref   ( const d_Ref_Any& );
d_Rel_Ref<T, M>&   operator =  ( const d_Rel_Ref<T, M>& );

The following methods in d_Rel_List<T, M> are private:

                   d_Rel_List  ( o_u4b  );
                   d_Rel_List  ( o_vstr );
d_Rel_List<T, M>&  operator  = ( const d_Rel_List<T, M>& list );
d_Rel_List<T, M>&  operator  = (o_vstr);
d_List<d_Ref<T> >& operator += ( const d_List<d_Ref<T> > &ListR)
d_List<d_Ref<T> >& append      ( const d_List<d_Ref<T> >&);
void               set         ( const d_List<d_Ref<T> >&);

The following methods in d_Rel_Set<T, M> are private:

                  d_Rel_Set        ( const d_Rel_Set<T,M>& );
                  d_Rel_Set        ( VstrAny );
d_Rel_Set<T, M>&  operator =       ( VstrAny );
d_Rel_Set<T, M>&  operator =       ( const d_Rel_Set<T, M>& oth );
d_Set<d_Ref<T> >& operator +=      ( const d_Set<d_Ref<T> >& s2);
d_Set<d_Ref<T> >& operator -=      ( const d_Set<d_Ref<T> >& s2);
d_Set<d_Ref<T> >& operator *=      ( const d_Set<d_Ref<T> >& s2);
void              set              ( const d_Set<d_Ref<T> > &);
d_Set<d_Ref<T> >& difference_of    ( const d_Set<d_Ref<T> >& l,
                                     const d_Set<d_Ref<T> >& r);
d_Set<d_Ref<T> >& difference_with  ( const d_Set<d_Ref<T> >& s2);
d_Set<d_Ref<T> >& intersection_of  ( const d_Set<d_Ref<T> >& l,
                                     const d_Set<d_Ref<T> >& r);
d_Set<d_Ref<T> >& intersection_with( const d_Set<d_Ref<T> >& s2);
d_Set<d_Ref<T> >& union_of         ( const d_Set<d_Ref<T> >& l,
                                     const d_Set<d_Ref<T> >& r);
d_Set<d_Ref<T> >& union_with       ( const d_Set<d_Ref<T> >& s2);
 

Optionally implement your own hash and compare methods.

You can implement your own hash() and compare() virtual methods in a persistent class to expedite ODMG collection operations.

Default hash() and compare() methods are defined in d_Object which performs the hash and comparisons based on addresses and cached object descriptor entries. In collection methods which involve searches, you may want to implement your own hash() and compare() methods.

 

For run-time type identification, add a VERSANT macro to your constructors.

As in C++/VERSANT, to use run-time type identification for transient instances of presistent classes deriving from d_Object, you must add the macro VPP_CLASS_CONSTRUCTOR macro to your constructors.

 

Names of objects are not migrated.

If a named object is migrated to another database, the name bound with it will not be migrated.

For example, you can use d_Database::set_object_name() to bind a name to an object. VERSANT will then create a hidden VAssociate object which contains the name and reference to the object. These VAssociate objects are then used when you search for named objects.

When a named object is migrated to another database, the VAssociate objects which refer to it are not migrated.

If you want to migrate the names, you can use the VAssociate::find_link() method to find all VAssociate objects which refer to the migrated object and then migrate them.

VERSANT automatically creates and maintains a unique B-tree index on the VAssociate::name attribute in order to guarantee unique object naming.

Object names are created within the current transaction when you invoke the method d_Database::set_object_name(). Internally, the VAssociate object, which contains the name and reference to the named object, is created within the current transaction. If the current transaction is not connected to the d_Database object where the method set_object_name() is invoked, an exception will be thrown.

 

VQL is supported rather than OQL.

VERSANT implements features similar to those in OQL in an interface called VQL or "VERSANT Query Language."

As in OQL, you can use VQL to construct a d_VQL_Query query object, use parameter substitutions, and then call the function d_oql_execute() to execute the query.

The only major difference between OQL and VQL is that, in VERSANT, the d_oql_execute() function only returns results to types Vstr<type>, LinkVstr<type>, or LinkVstrAny. By comparison, in OQL, d_oql_execute() can return a collection or a d_Ref<type>.

For a reference to VQL, see the chapter "Queries with VERSANT Query Language" in the VERSANT Database Fundamentals Manual.

 

Some VERSANT methods do not make sense in ODMG C++.

Some PDOM methods, such as beginsession(), commit(), and abort(), do not make sense in ODMG, since in the ODMG object model they belong to the d_Database or d_Transaction class. You should be aware of them and use corresponding methods in d_Database or d_Transaction instead.

The following PDOM methods should not be used in ODMG C++ applications.

abort()
beginsession()
commit()
connectdb()
detachsession()
disconnectdb(),
endsession()
joinsession()
rollback()
xact()

 

ODMG C++/VERSANT Compiling and Linking

Compiling and linking are largely the same as in C++/VERSANT.

ODMG header files are in ..h/odmg.

All ODMG C++/VERSANT header files are placed under the directory ..h/odmg. To refer to this directory, you can invoke the oscp utility with the following:

`oscp -p`/h/odmg

Include at least odmg.h.

You generally need to include the header file <odmg/odmg.h> in your application.

Including odmg.h will include all ODMG classes. If you do not want all of them, you can include the header files one by one.

The utilities behave the same.

VERSANT utilities, such as schcomp and sch2db, behave the same as in C++/VERSANT.

 

ODMG C++/VERSANT Sample Code

 

Example 1

Here is a simple ODMG application. We have class Hello which is derived from d_Object. It has a d_String as its data member and access methods. The d_activate() and d_deactivate() methods of Hello are also defined.

File: hello.h

#ifndef HELLO_H
#define HELLO_H
#include <odmg/object.h>
#include <odmg/string.h>
#include <iostream.h>
class Hello : public d_Object {
private:
   PString note;
public:
   // construct given const char pointer
   Hello   (const char*);
   // copy constructor
   Hello   (const Hello& );
   // access functions
   void      setNote(PString );
   PString   getNote();
   void d_activate();
   void d_deactivate();
};
#endif

File: hello.cxx

#include "hello.h"
// constructor, given pointer to const char
Hello::Hello(const char *s) : note(s)
{
    VPP_CLASS_CONSTRUCTOR1(Hello);
}
// copy constructor
Hello::Hello(const Hello& h) : note(h.note)
{
    VPP_CLASS_CONSTRUCTOR1(Hello);
}
// access functions
void Hello::setNote(PString s)
{

dirty();
note = s;
}
PString Hello::getNote()
{
return note;
}
void Hello::d_activate()
{
cout << "Hello::d_activate()" << endl;
}
void Hello::d_deactivate()
{
cout << "Hello::d_deactivate()" << endl;
}

File: runhello.cxx

#include <iostream.h>
#include <odmg/odmg.h>
#include <odmg/set.h>
#include "hello.h"
extern "C" void exit(int);
main(int argc, char** argv)
{
   if ( argc != 2 )
   {
      cout << "Usage: " << argv[0] <<" dbname " << endl;
      exit(1);
   }
   d_Database* database = new d_Database;
   database->open(argv[1]);
   d_Transaction tran;
   tran.begin();
   char* str[6] =
   {
      "Hello", "World", "Versant",
      "Object", "Technology", "Corporation"
   };
   cout << "create Hello object" << endl;
   Hello *h;
   for ( o_4b i = 0; i < 3; i++ )
   {
      h= new(database, "Hello") Hello(str[i]);
   }
   cout << "\nTesting checkpoint..." << endl;
   tran.checkpoint();
   h= new(PClassObject<Hello>::Pointer()) Hello(str[3]);
   for ( i = 4; i < 6; i++ )
   {  // use operator new(size_t, const d_Ref_Any&, const char*)
      h= new(h->cod(), "Hello") Hello(str[i]);
   }
   cout << "\nTesting commit..." << endl;
   tran.commit();
   tran.begin();
   d_Set<d_Ref<Hello> > a =
      (VstrAny)PClassObject(Hello)::Object().select(argv[1],
                                                    FALSE,
                                                    NULL_PREDICATE);
   d_Iterator<d_Ref<Hello> > iter = a.create_iterator();
   d_Ref<Hello> ref;
   for ( ; iter.not_done(); iter.advance() )
   {
      ref = iter.get_element();
      cout << "\ndefrefence an object..." << endl;
      cout << (const char *)ref->getNote() << endl;
   }
   cout << "\nsecond time defefence the same objects" << endl;
   iter.reset();
   for ( ; iter.not_done(); iteradvance )
   {  ref = iter.get_element();
      cout << "\ndefrefence an object..." << endl;
      cout << (const char *)ref->getNote() << endl;
   }
   cout << "\nTesting abort..." << endl;
   tran.abort();
   database->close();
   delete database;
   exit(0);
}

 

Example 2

Following is the example in Chapter 5 of the ODMG-93 document. We have made some modifications to fix errors in the example and use VERSANT VQL instead of OQL.

File: Demo.h

// Schema Definition in C++ ODL
#include <odmg/odmg.h>
class City; // forward declaration
struct Address
{  d_UShort number;
   d_String street;
   d_Ref<City> city;
   Address();
   Address(d_UShort, const char*, const d_Ref<City> &);
};
extern const char _spouse [ ], _parents [ ], _children [ ] ;
class Person : public d_Object
{
public:
   // Attributes (all public, for this example)
   d_String name;
   Address address;
   // Relationships
   d_Rel_Ref<Person, _spouse> spouse;
   d_Rel_List<Person, _parents> children;
   d_Rel_List<Person, _children> parents;
   // Operations
   Person(const char * pname);
   void birth(const d_Ref<Person> &child); // a child is born
   void marriage(const d_Ref<Person> &to_whom);
   d_Ref<d_Set<d_Ref<Person> > > ancestors() const;
                                              // returns ancestors
   void move(const Address &);   // move to a new address
   // Extent
   static  d_Ref<d_Set<d_Ref<Person> > > people;
                                 // a reference to class extent
   static  const char * const extent_name;
};
class City : public d_Object
{
public:
   // Attributes
   d_ULong city_code;
   d_String name;
   //d_Ref<d_Set<d_Ref<Person> > > population;
   d_Set<d_Ref<Person> >  population;
                                 // the people living in this City
   // Operations
   City(d_ULong, const char*);
   // Extension
   static   d_Ref<d_Set<d_Ref<City> > > cities;
                                // a reference to the class extent
   static  const char * const extent_name;
};
File: Demo.cxx

// Classes Implementation in C++

#include "demo.h"
// Address structure:
Address::Address( d_UShort pnum,
                  const char* pstreet,
                  const d_Ref<City> &pcity )
   : number(pnum),
     street(pstreet),
     city(pcity)
{ }
Address::Address()
   : number(0),
     street((char *)0),
     city(0)
{ }
// Person Class:
const char * const Person::extent_name = "people";
d_Ref<d_Set<d_Ref<Person> > > Person::people;
Person::Person(const char * pname)
   : name(pname)
{

people->insert_element(this);

               // Put this Person in the extension
}
void Person::birth(const d_Ref<Person> &child)
{  // Adds a new child to the children list
   children.insert_element_last(child);
   if(spouse)
      spouse->children.insert_element_last(child);
}
void Person::marriage(const d_Ref<Person> &to_whom)
{  // Initializes the spouse relationship
   spouse = to_whom;
      // with->spouse is automatically set to this Person
}
d_Ref<d_Set<d_Ref<Person> > >  Person::ancestors() const
{
   // Constructs the set of all ancestors of this Person
   d_Ref<d_Set<d_Ref<Person> > >
      the_ancestors = new d_Set<d_Ref<Person> >;
   //int i;
   d_Iterator<d_Ref<Person> > i ;
   for( i=parents.begin(); i != parents.end(); i.advance())
        //for( i=parents.begin(); i.not_done(); i.advance())
   {
      // The ancestors = parents union ancestors(parents)
      the_ancestors->insert_element(i.get_element());
      d_Ref<d_Set<d_Ref<Person> > > grand_parents=
         i.get_element()->ancestors();
      //parents[i]->ancestors();
      the_ancestors->union_with(*grand_parents);
      grand_parents.delete_object();
   }
   return the_ancestors;
}
void Person::move(const Address &new_address)
{
   // Updates the address attribute of this Person
   if(address.city)
      address.city->population.remove_element(this);
   new_address.city->population.insert_element(this);
   mark_modified();
   address = new_address;
}
// City class:
const char * const City::extent_name = "cities";
d_Ref<d_Set<d_Ref<City> > > City::cities;
City::City(d_ULong code, const char * cname)
   : city_code(code), name(cname), population()
{
     cities->insert_element(this);
                // Put this City into the extension
}
File: main.cxx

#include <iostream.h>
#include "demo.h"
#include <cxxcls/vql.h>
#include <cxxcls/oqlexec.h>
static d_Database dbobj;
static d_Database * database = &dbobj;
void Load()
{
   // Transaction which populates the database
   d_Transaction load;
   load.begin();
   // Create both Persons and Cities extensions, and name them.
   Person::people = new( database, "d_Set<Link<Person > >")
                         d_Set<d_Ref<Person> >;
   City::cities = new(database, "d_Set<Link<City > >" )
                      d_Set<d_Ref<City> >;
   database->set_object_name(Person::people, Person::extent_name);
   database->set_object_name(City::cities, City::extent_name);
   // Construct 3 persistent objects from class Person.
   d_Ref<Person> God, Adam, Eve;
   God  = new(database, "Person") Person("God");
   Adam = new(database, "Person") Person("Adam");
   Eve  = new(database, "Person") Person("Eve");
   // Construct an Address structure, Paradise,
   // as (7 Apple Street, Garden)
   // and set the address attributes of Adam and Eve.
   Address Paradise( 7,
                     "Apple",
                     new(database, "City") City(0, "Garden"));
   Adam -> move(Paradise);
   Eve  -> move(Paradise);
   // Define the family relationships.
   God->birth(Adam);
   Adam->marriage(Eve);
   Adam->birth(new(database, "Person") Person("Cain"));
   Adam->birth(new(database, "Person") Person("Abel"));
   load.commit();  // Commit transaction, putting objects
                   // into the database.
}
static void print_persons(const d_Collection<d_Ref<Person> >& s)
{
   // A service function to print a collection of Persons
   d_Ref<Person> p;
   d_Iterator<d_Ref<Person> > it = s.create_iterator();
   while( it.next(p) )
   {  cout << "--- " << p->name << " lives in ";
      if (p->address.city)
         cout << p->address.city->name;
      else
         cout << "Unknown";
         cout << endl;
   }
}
void Consult()
{
   // Transaction which consults and updates the database
   d_Transaction consult;
   d_List<d_Ref<Person> > list;
   consult.begin();
   // Static references to objects or collections must be recomputed
   // after a commit
   Person::people = database->lookup_object(Person::extent_name);
   City::cities   = database->lookup_object(City::extent_name);
   // Now begin the transaction
   cout << "All the people ....:" << endl;
   print_persons(*Person::people);
   cout << "All the people name ....:" << endl;
   d_VQL_Query query("select SelfOID from Person");
   LinkVstr<Person> personVstr;
   d_oql_execute(query, personVstr);
   list = (VstrAny) personVstr;
   print_persons(list);
   // Adam and Eve are moving ...
   Address Earth( 13,
                  "Macadam",
                  new(database,
                  "City") City(1,
                  "St-Croix"));
   LinkVstr<Person> Adams; query.set_query_string(
      "select SelfOID from Person where name = \"Adam\"" );
   d_oql_execute(query, Adams);
   d_Ref<Person> Adam = Adams[0];
   Adam->move(Earth);
   Adam->spouse->move(Earth);
   cout << "Cain's ancestors ...:" << endl;
   d_Ref<Person> Cain = Adam->children.retrieve_element_at(0);
   print_persons(*(Cain->ancestors()));
   consult.commit();
}
main()
{
   database->open("family");
   Load();
   Consult();
   database->close();
}
File: schema.imp

// This file is required for VERSANT schema compiler
// to create database schema.

#include "demo.h"

const char _spouse   [ ] = "spouse";
const char _parents  [ ] = "parents";
const char _children [ ] = "children";

O_CAPTURE_SCHEMA(Person);
O_CAPTURE_SCHEMA(Address);
O_CAPTURE_SCHEMA(City);
O_CAPTURE_SCHEMA(d_Collection<d_Ref<Person> >);
O_CAPTURE_SCHEMA(d_Collection<d_Ref<City> >);
O_CAPTURE_SCHEMA(d_List<d_Ref<Person> >);
O_CAPTURE_SCHEMA(d_Rel_Ref<Person, _spouse>);
O_CAPTURE_SCHEMA(d_Rel_List<Person, _parents>);
O_CAPTURE_SCHEMA(d_Rel_List<Person, _children>);
O_CAPTURE_SCHEMA(d_Set<d_Ref<Person> >);
O_CAPTURE_SCHEMA(d_Set<d_Ref<City> >);

 

Example 3

This example basically is the same as the above example, but it demonstrates the usage of database and transaction in a multi-thread situation.

File: demo.h

As above example

File:demo.cxx

As above example

File: schema.imp

As above example

File: main.cxx

#include <stdlib.h>
#include <unistd.h>
#include <iostream.h>
#include "demo.h"
#include <cxxcls/vql.h>
#include <cxxcls/oqlexec.h>
void *thr_main(void *);
void Load();
void print_persons(const d_Collection<d_Ref<Person> >& s);
void Consult();
d_Database *database;
int main(int argc, char** argv)
{

// the first opened database will become the process default
// database
database = new d_Database();
database->open("family");
pthread_t th;
if (pthread_create(&th, NULL, thr_main, NULL))
{
   cerr << "thread creation failed" ;
   throw;
}

if (pthread_join(th, NULL))
{
   cerr << "thread join failed";
   throw;
}
database->close();
delete database;
return 0;

}
void *thr_main(void *arg)
{
// each thread will find the process default database
Load();
Consult();
return NULL;
}
void Load()
{
// Transaction which populates the database
d_Transaction load;
load.begin();
// Create both Persons and Cities extensions, and name them.
Person::people = new(database, "d_Set<Link<Person>>") d_Set<d_Ref<Person> >;
City::cities = new(database, "d_Set<Link<City>>" ) d_Set<d_Ref<City> >;
database->set_object_name(Person::people, Person::extent_name);
database->set_object_name(City::cities, City::extent_name);
// Construct 3 persistent objects from class Person.
d_Ref<Person> God, Adam, Eve;
God    = new(database, "Person") Person("God");
Adam = new(database, "Person") Person("Adam");
Eve    = new(database, "Person") Person("Eve");
// Construct an Address structure, Paradise, as (7 Apple Street, // Garden)
// and set the address attributes of Adam and Eve.
Address Paradise(7, "Apple", new(database, "City") City(0,
                 "Garden"));
Adam->move(Paradise);
Eve->move(Paradise);
// Define the family relationships.
God->birth(Adam);
Adam->marriage(Eve);
Adam->birth(new(database, "Person") Person("Cain"));
Adam->birth(new(database, "Person") Person("Abel"));
load.commit();// Commit transaction, putting objects into the
// database.
}
void print_persons(const d_Collection<d_Ref<Person> >& s)
{
// A service function to print a collection of Persons
d_Ref<Person> p;
d_Iterator<d_Ref<Person> > it = s.create_iterator();
while( it.next(p) )
{
  cout << "--- " << p->name << " lives in ";
  if (p->address.city)
    cout << p->address.city->name;
  else
  cout << "Unknown";
  cout << endl;
}
}

void Consult()
{

// Transaction which consults and updates the database
d_Transaction consult;
d_List<d_Ref<Person> > list;
consult.begin();
// Static references to objects or collections must be recomputed
// after a commit
Person::people = database->lookup_object(Person::extent_name);
City::cities = database->lookup_object(City::extent_name);
// Now begin the transaction
cout << "All the people ....:" << endl;
print_persons(*Person::people);
cout << "All the people name ....:" << endl;
d_VQL_Query query("select SelfOID from Person");
LinkVstr<Person> personVstr;
d_oql_execute(query, personVstr);
list = (VstrAny) personVstr;
print_persons(list);
// Adam and Eve are moving ...
Address Earth(13, "Macadam", new(database, "City") City(1,
              "St- Croix"));
LinkVstr<Person> Adams;
query.set_query_string("select SelfOID from Person where name =
                        \"Adam\"" );
d_oql_execute(query, Adams);
d_Ref<Person> Adam = Adams[0];
Adam->move(Earth);
Adam->spouse->move(Earth);
cout << "Cain's ancestors ...:" << endl;
d_Ref<Person> Cain = Adam->children.retrieve_element_at(0);
print_persons(*(Cain->ancestors()));
consult.commit();
}

 

Example 4

This example demonstrates a multi-database, multi-transaction, and multi-thread situation. First two databases are opened in the main thread. Then three threads are spawned to do database operations. Each thread begin their own transaction and create ten objects, then they join each other and do object selection in the new transaction. Finally they go back to their old transaction and end the transaction there.

A conditional variable, cond, is used to make sure that three transactions successfully begin before threads join each other.

File: main.cxx

#include <stdlib.h>
#include <unistd.h>
#include <iostream.h>
#include <odmg/odmg.h>
#include "number.h"
#define MAX_SESSION3
void *thr_func0(void *);
void *thr_func1(void *);
void *thr_func2(void *);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
d_Transaction *tr[3];
d_Database *db[2];
int session_count=0;
main(int argc, char** argv)
{

if ( argc != 3 )
{
    cout << "Usage: " << argv[0] <<" gdb1 " << " gdb2 " << endl;
    exit(1);
}
db[0]= new d_Database;
db[1]= new d_Database;
db[0]->open(argv[1]);
db[1]->open(argv[2]);
pthread_t th[3];
if (pthread_create(&th[0], NULL, thr_func0, &tr[0]))
{
    cerr << "thread creation failed" ;
    throw;
}

if (pthread_create(&th[1], NULL, thr_func1, &tr[1]))

{
    cerr << "thread creation failed" ;
    throw;
}
if (pthread_create(&th[2], NULL, thr_func2, &tr[2]))
{
    cerr << "thread creation failed" ;
    throw;
}
for (int i=0; i< MAX_SESSION; ++i)
{
    if (pthread_join(th[i], NULL))
    {
      cerr << "thread join failed";
      throw;
    }
}

delete db[0];
delete db[1];
return 0;

}

void *thr_func0(void *arg)
{

const int id=0;
d_Database *mydb=db[0];
*(d_Transaction **)arg = new d_Transaction(*mydb);
d_Transaction *tran=*(d_Transaction **)arg;
tran->begin();
cout << "create Number object" << endl;
d_Ref<Number> h;
for ( o_4b i = 0; i < 10; i++ )
{
    h= new(db[0], "Number") Number(i+id*10);
}
tran->commit();
pthread_mutex_lock(&mutex);
++session_count;
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
if (session_count != MAX_SESSION )
    pthread_cond_wait(&cond, &mutex);
else
    pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
tr[1]->join();
mydb=db[1];
LinkVstr<Number> vector = PClassObject(Number)::Object().select(
mydb->get_this_db_name(),  FALSE, NULL_PREDICATE);
if (vector.size() != 20)
    cout << "vector size is not correct!" << endl;
tr[1]->leave();
pthread_mutex_lock(&mutex);
--session_count;
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
if (session_count != 0)
    pthread_cond_wait(&cond, &mutex);
else
    pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
// join back the old transaction
tran->join();
delete tran;
return NULL;
}

void *thr_func1(void *arg)
{

const int id=1;
d_Database *mydb=db[1];
*(d_Transaction **)arg = new d_Transaction(*mydb);
d_Transaction *tran=*(d_Transaction **)arg;
tran->begin();
cout << "create Number object" << endl;
d_Ref<Number> h;
for ( o_4b i = 0; i < 10; i++ )
{
    h= new(db[1], "Number") Number(i+id*10);
}
tran->commit();
pthread_mutex_lock(&mutex);
++session_count;
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
if (session_count != MAX_SESSION )
    pthread_cond_wait(&cond, &mutex);
else
    pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
tr[0]->join();
mydb=db[0];
LinkVstr<Number> vector = PClassObject(Number)::Object().select(
mydb->get_this_db_name(),  FALSE, NULL_PREDICATE);
if (vector.size() != 10)
    cout << "vector size is not correct!" << endl;
if (d_Transaction::current() == tr[0])
    cout << "current transaction is correct!" << endl;
if (d_Transaction::current()->get_session() ==
    VSession::current())
        cout << "current session is correct!" << endl;
tr[0]->leave();
if (d_Transaction::current() == NULL)
    cout << "current transaction is correct!" << endl;
pthread_mutex_lock(&mutex);
--session_count;
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
if (session_count != 0)
    pthread_cond_wait(&cond, &mutex);
else
    pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
tran->join();
delete tran;
return NULL;
}

void *thr_func2(void *arg)
{

const int id=2;
d_Database *mydb=db[1];
*(d_Transaction **)arg = new d_Transaction(*mydb);
d_Transaction *tran=*(d_Transaction **)arg;
tran->begin();
cout << "create Number object" << endl;
d_Ref<Number> h;
for ( o_4b i = 0; i < 10; i++ )
{
    h= new(db[1], "Number") Number(i+id*10);
}
tran->commit();
pthread_mutex_lock(&mutex);
++session_count;
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
if (session_count != MAX_SESSION )
    pthread_cond_wait(&cond, &mutex);
else
    pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
tr[0]->join();
mydb=db[0];
LinkVstr<Number> vector = PClassObject(Number)::Object().select(
mydb->get_this_db_name(),  FALSE, NULL_PREDICATE);
if (vector.size() != 10)
    cout << "vector size is not correct!" << endl;
tr[0]->leave();
pthread_mutex_lock(&mutex);
--session_count;
pthread_mutex_unlock(&mutex);
pthread_mutex_lock(&mutex);
if (session_count != 0)
    pthread_cond_wait(&cond, &mutex);
else
    pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
tran->join();
delete tran;
return NULL;
}

File: number.h

#include <odmg/object.h>
class Number :public d_Object
{
public:

Number(o_4b id) :_id(id) {}
operator int() const { return _id; }

private:

o_4b _id;
};

File: schema.imp

#include "number.h"

O_CAPTURE_SCHEMA(Number);

 

 

 


This online documentation is confidential and proprietary to Versant Corporation and is licensed to you, or to your organization, pursuant to the terms of an agreement between you and Versant that restricts the use of this documentation. Please refer to the agreement for more information or call Versant at 510-789-1500 with any questions.