Migration to the new Condition/DB

Igor A.Gaponenko, David N.Brown

June 5, 2002

Document update history

Changes since June 3, 2002

The most recent tags link compatible with 11.12.1 and 12.0.0 releases:

The following tag is not link compatible with 11.12.1 release but will be a part of the 12.0.0 release:

Changes since May 24, 2002

The most recent tags link compatible with 11.12.1 and 12.0.0 releases:

Table of Contents

  1  Introduction

      1.1  Whom to contact in case of problems, questions, etc.

      1.2  Where to get more information on the Condition/DB?

  2  What needs to be migrated

  3  Conditions names in the new database API

  4  Migrating the "read-only" clients

      4.1  How do I know if my code needs to be "migrated"?

             4.1.1  Which solution is better?

      4.2  Using a proxy base class (generic solution)

             4.2.1  How a header file of a concrete proxy should look alike

             4.2.2  How an implementation file of a concrete proxy should look alike

             4.2.3  More about the interface of the proxy base class

      4.3  Using proxy template class when a trivial persistent-to-transient conversion is available

      4.4  Existing clients of the CalDatabase package

      4.5  What if my code does not fit into above described models?

      4.6  Existing clients of the BdbIntervalItr class

  5  Migrating the condition objects loaders

      5.1  How do I know if my loader needs to be "migrated"?

      5.2  Using a list loader class (loading calibrations constants from flat text file)

      5.3  Existing clients of CalDatabase package

      5.4  Straightforward migration

             5.4.1  Simplified solution in case of trivial transient-to-persistent conversion

             5.4.2  Migrating clients using storeAndTruncate operation

  6  Migrating the "rolling calibration" objects loader ("finalizer")

  7  Transaction management in the new Condition/DB API

  8  Migrating the Framework modules and sequences

  9  Migrating the GenEnv class

10  Converting existing databases into new format

1  Introduction

This document is aiming at those BaBar code developers who for one or another reason is going to be involved into an on-going migration towards the brand new Condition/DB. The document describes which parts of the BaBar software are affected by the migration, and how exactly the affected code has to be modified in order to be compatible with the new Condition/DB API.

There was a number of quite serious reasons forced us to design and implement what we call CDB (stands for Condition/DB. We use this abbreviation in the rest of this document to distinguish the new Condition/DB from the old one.). Among these reasons were (to count just a few):

So it should not be surprising that given the above presented list this "new Condition/DB" not only means just another implementation of the old good ideas, but it also means new data model, new API and different organization of data in the database (we call it "persistent schema").

The important thing however (and it will be discussed in more details at the rest of the current document) is that the new API can also provide access to the existing database. This lets us to implement smooth migration path from the current database to the new one. The migration process can be done in (at least) two steps:

One of the extra benefits of this approach is that old and new code can coexist in the same software release as long as it needed for the migration purposes. And it also means that both new and old data formats can be available for testing purposes in a course of the migration process.

1.1 Whom to contact in case of problems, questions, etc.

Send your questions to Igor Gaponenko (gapon@slac.stanford.edu) or Dave Brown (Dave_Brown@lbl.gov).

1.2 Where to get more information on the Condition/DB?

The detailed explanation of the Proxy Dictionary and proxies can be found at "The ProxyDict Programmers Guide".

The old Condition/DB documentation can be found here.

2  What needs to be migrated

Here is a list of tasks to be done to accomplish the full migration:

The rest of the document will be dealing with technical description of each of these areas.

3  Conditions names in the new database API

The new model of the Condition/DB introduces a hierarchical namespace for condition names, in which conditions can be grouped into folders much like regular files are grouped into catalogs in a UNIX file system. So condition names may appear at any folder of the Condition/DB "file system". Each condition has full path in the namespace, whose components (folders and conditions names) are separated by mean of a separator, which is currently a slash symbol '/'. here is an example:

"/Allignments/dch/SomeAlignment"
"/Electro-Magnetic-Calorimeter/RollingCalibrations/EmcCalibrP"

In reality this raises one more migration problem not mentioned at the previous section - mapping the names from the old format into the new one. In order to simplify the initial migration process and to make new condition names (to some extent) backward compatible with the old one we put the following restriction onto the mapping:

If we apply these rules then the following condition of the current database:

DETECTOR:  "pep"
CONDITION: "PepBeamsP"

will look like:

"/pep/PepBeamsP"

in the new one. Later on as we gain more experience with the new condition database we may want to start using the full set of features brought us with the hierarchical namespace.

4  Migrating the "read-only" clients

This section describes how to accomplish the migration of code, which is only meant to fetch the information from the Condition/DB. Since the major group of these applications includes so called Condition/DB "proxies" (see the following document for detailed explanation of this term) then most of the information presented hereafter will be related to these proxies. However the same recipes especially when it comes to the one-to-one migration from the old API to the new one (see the section 4.5 below), can equally be applied to other kinds of the Condition/DB read-only clients.

4.1 How do I know if my code needs to be "migrated"?

The answer is simple - if the code uses any of the following classes:

BdbCond/BdbCondProxyBase
BdbCond/BdbEnvProxy
BdbCond/BdbDatabase

or if it implements the following method:

IfdDataProxyTemplate::faultHandler(...)

in the context of the Condition/DB then the code needs to be migrated.

4.1.1 Which solution is better?

The subsequent material proposes three methods ranging from a very simple one (and the most preferable) to the straightforward one-to-one migration:

 The final choice depends on the complexity of the migrated code. Still it's highly recommended to pick either first or the second method and to look into redesigning a proxy to follow these recipes.

4.2 Using a proxy base class (generic solution)

The solution we're proposing here goes far beyond a trivial migration to the new Condition/DB API. In fact we're also suggesting to simplify the way the proxies are written from a user's point of view. The key decision is to handle most of the routine operations with the persistent store (including communication with the new Condition/DB API) through a special proxy base class:

CdbBdb/CdbBdbProxyBase

In a new scheme every single Condition/DB proxy class is supposed to derive from this base class. The responsibilities of the proxy base class are:

What is left to the actual (user defined) proxy is:

In the current model of proxies everything from both lists is a proxy developer's responsibility. As a result we have lots of unnecessary code duplication. In addition users' code does lots of unspecific things, which can be (and should be) done somewhere else.

The rest of this section represents a step-by-step guide of what exactly has to be done in order to implement proxies based on the suggested  design.

4.2.1 How a header file of a concrete proxy should look alike?

So, once again, every proxy has to derive from a special base class. This is a template class parameterized by a type of proxy's transient product. The constructor of this class has the only optional parameter - a pointer onto a "strategy":

// File: CdbBdb/CdbBdbProxyBase.hh
 
class BdbCondDefStrategy;
 
template< class T >
class CdbBdbProxyBase ... {
 
public:
    CdbBdbProxyBase( BdbCondDefStrategy* theStrategy = 0 );
};

We leave the discussion on what comes with the concept of the strategy out of the scope of the current document and will only focus at the technical aspects of using these strategies to construct user defined proxies.

It's important to note, that the strategy pointer is actually a pointer onto an interface, which is a base class for every single concrete strategy. If a null pointer is passed to the constructor then some default strategy will be instantiated by the constructor itself. Remember, that constructor always takes over the ownership of the passed pointer. The interface itself is defined at:

BdbCond/BdbCondDefStrategy.hh

At the time this document is being written (release 11.12.1) we have the following concrete strategies:

BdbCond/BdbCondDefBkgFirst
BdbCond/BdbCondDefEventKey

The next important thing to know about the public interface of the proxy base class is that it defines a pure virtual method redefinedFaultHandler (remember that the current model of proxies requires a concrete proxy developer to implement the faultHandler method insteed) to be implemented by its subclasses. Now the proxy base class looks like:

// File: CdbBdb/CdbBdbProxyBase .hh
 
class CdbDefProxyStrategy;
#include "CdbBdb/CdbbdbProxyElement.hh"
 
template< class T >
class CdbBdbProxyBase ... {
 
public:
    CdbBdbProxyBase( CdbDefProxyStrategy* theStrategy = 0 );
protected:
    virtual T* redefinedFaultHandler( const std::vector<CdbBdbProxyElement>& listOfElements ) = 0;
};

So if we had a simple hypothetical proxy named MyProxy responsible for creating a transient product of MyProxyProduct class than its definition would look like:

// File: MyPackage/MyProxy.hh
 
#include "CdbBdb/CdbBdbProxyBase.hh"
 
class MyProxy : public CdbBdbProxyBase< MyProxyProduct > {
public:
    MyProxy( );
protected:
    virtual MyProxyProduct* redefinedFaultHandler( const std::vector<CdbBdbProxyElement>& listOfElements );
};

Remember that the user implemented redefinedFaultHandler is called when a previously generated (if any) instance of a transient proxy product (whose class is defined through the only template parameter of a proxy base) needs to be updated.

That the bare minimum to know in order to define a simple proxy. The next subsection explains how to implement the constructor of a concrete proxy and the redefinedFaultHandler method in order to produce a transient product of a concrete proxy.

4.2.2 How an implementation file of a concrete proxy should look alike?

Let's keep using the above defined concrete proxy class MyProxy and its transient product of MyProxyProduct class as an example. Let's also supposed that the transient product of our test proxy requires to load the information from a condition, whose name is:

"/xyz/MyDataP"

The constructor of our sample proxy has to tell its proxy base class that it (MyProxy ) is interested in persistent handles fetched from this condition in order to construct its transient product. Here is how it should be done:

// File: MyPackage/MyProxy.cc
 
#include "MyPackage/MyProxy.hh"
 
MyProxy::MyProxy( ) :
    CdbBdbProxyBase< MyProxyProduct > { )  // We may also want to pass a strategy object
{
    subscribeCondition( "/xyz/MyDataP" );  // Tell the proxy base class to deliver persistent
                                           // objects from this condition.
}
...

Note, that unlike in current proxies we don't need to defined into a concrete proxy class any data members supporting the transaction management, or transient validity interval of the proxy's product. All of this is handled through the proxy base class.

The second thing, which needs to be done at an implementation file by a concrete proxy is to implement the body of the redefinedFaultHandler method. Here is how it could look in case of our sample proxy class:

...
MyProxyProduct*
MyProxy::redefinedFaultHandler( const std::vector<CdbBdbProxyElement>& listOfElements  )
{
    std::vector<CdbBdbProxyElement>::const_iterator itr;
    for( itr = listOfElements.begin( ); itr < listOfElements.end( ); ++itr ) {
 
        CdbBdbProxyElement e = (*itr);
        if( e.name( ) == "/xyz/MyDataP" ) {
 
          // Now we're locked at the desired element. Let's narrow a persistent reference
          // delivered with the element down to an appropriate persistent type. It's our
          // responsibility to do this.
          //
          // NOTE: The delivered reference is guaranteed to be non-zero and point onto
          //       a valid object.
 
              BdbHandle(MyDataP) handle;
              handle = (const BdbRef(MyDataP)&)(e.objectRef( ));
 
          // Do your favorite persistent-to-transient conversion, for example like this.
 
              return handle->toTransient( );  // Assuming this method creates an instance of an
                                              // appropriate transient class and return its ownership.
        }
    }
 
  // Error - no object found
 
    return 0;
}

The element class used above is defined here:

CdbBdb/CdbBdbProxyElement.hh

The public contract of this class has a few more methods not used by the example above. This includes:

class CdbBdbProxyElement {
public:
 
    std::string            name( ) const;    // Full condition path, including folders.
    std::string       shortName( ) const;    // No folders. The condition name alone.
 
    BdbIntervalBase    validity( ) const;    // The validity interval of the object below
    BdbRef(BdbObject) objectRef( ) const;    // The persistent object
    bool                updated( ) const;    // Is "true" if the object above just has been reloaded
};

The updated method of the element class does only make sense if a proxy subscribes for more that one persistent condition. If this is the case then its (concrete proxy's) redefinedFaultHandler method will be called when any (or both) of the subscribed condition objects managed by the proxy base class gets expired. Since the vector of elements passed to the redefinedFaultHandler  method will always have two elements then this proxy developer may want to know which of these condition objects was reloaded. An excellent example of using this technique can be found at the implementation of the following proxy (use the HEAD of CVS since this proxy may not in an official release yet):

SvtGeomP/SvtDetectorProxy.hh

4.2.3 More about the interface of the proxy base class

In addition to above described functionality the API of the proxy base class provides the following two operations, which might be useful in some scenarious:

The transient object can be obtained by calling the following method of the proxy base class:

T* transinetCache( );

which returns a non-const pointer onto the current product. When the redefinedFaultHandler is invoked for the first time this pointer is guaranteed to be 0. When the user-implemented redefinedFaultHandler creates a new instance of the object and returns it back to the caller (as it's required by the above described model of the proxy base class) then the proxy base class will destroy the old instance of the product and will replace it with the newly created one. However it's quite legal to return the same pointer, which is delivered through the call to the transientCache method. In this case nothing is going to happen to the old pointer. Remember, that the non-const pointer also lets a developer to modify the value (not a pointer) of the object. Therefore it's forbidden (by the current policy) to delete the current instance of the product obtained through the above mentioned call to transientCache method!!!

The current fault time of the proxy can be accessed by calling the following method defined by the proxy base class:

BdbTime faultTime( ) const;

Both of the above mentioned methods are defined as "protected" ones by the proxy base class.

4.3 Using proxy template class when a trivial persistent-to-transient conversion is available

This is a further simplification of the above (section 4.2) presented solution, which is meant to replace any uses of the following class:

BdbCond/BdbEnvProxy

with its functional equivalent:

CdbBdb/CdbBdbEnvProxy

Since the new class provides the same interface (including its constructor) then the code migration can be done just mechanically for the relevant proxies. It's also worth to consider this option for other proxies whose persistent-to-transient conversion fits into this model. Remember, that this approach is only applicable when objects of a persistent class can be trivially converted into the corresponding transient counterparts through a single call to a factory method returning a pointer onto a transient object. Here is a minimal interface of a persistent class required to satisfy this kind of proxies:

// File: MyPersClass.ddl
//
// This is a definition of a persistent class whose objects can be trivially
// converted into the transient ones by calling a single method (see below).
 
class MyTransientClass;
 
class MyPersClass : public BdbObject {
public:
 
  // Creates a transient object out of the persistent one.
 
    MyTransientClass* transient( ) const;
};

This is how the corresponding proxy can be constructed:

#include "CdbBdb/CdbBdbEnvProxy.hh"
 
#include "MyPackage/MyPersClass.hh"
#include "MyPackage/MyTransientClass.hh"
 
CdbBdbEnvProxy< MyTransientClass, MyPersClass >* myProxyPtr
    = new CdbBdbEnvProxy< MyTransientClass, MyPersClass >( "emc", " MyProxyName" );

That's it. Look for more details in the code of the proxy, which is available in the nightly builds (NOTE: This class was not yet available at 11.12.1 release).

4.4 Existing clients of CalDatabase package

The internal implementation of this package is going to be migrated to CDB API very soon. None of the clients of this package are going to be affected by this migration since the the CalDatabase classes are not exposing the persistency.

4.5 What if my code does not fit into above described models?

In those cases when the code (proxy, etc.) can't be implemented by none of the above presented methods then it's time to deal directly with the CDB API. This in general is a fairly straightforward process. To understand how it works let's consider a typical code pattern of an existing proxy finding a persistent object of the MyPersClass in the existing Condition/DB:

#include "BdbTime/BdbTime.hh"
#include "BdbTime/BdbIntervalBase.hh"
#include "BdbCond/BdbDatabase.hh"
 
#include "MyPackage/MyPersClass.hh"
 
  // Find a persistent object in the database
  //
  // NOTES: (1) We assume that our condition belongs to "emc" subsystem
  //        (2) The condition name is the same as the corresponding persistent
  //            class name ("MyPersClass"), which in reality may not be quite true.
  //
 
    BdbDatabase            database( "emc" );
    BdbHandle(MyPersClass) handle;
    BdbIntervalBase        validityInterval;
    BdbTime                fetchTime( "01/01/2002 10:00:01" );
 
    BdbStatus s = database.fetch( handle,
                                  validityInterval,
                                  "MyPersClass",
                                  fetchTime );
    if(( BdbcSuccess != s ) || BdbIsNull(handle)) {
        cerr << "Tough luck buddy!" << endl;
        ...
    }
    cout << "begin of the object's validity interval : " << validityInterval.begin( ) << endl
         << "end   of the object's validity interval : " << validityInterval.end( ) << endl;
 
  // Now produce the transient class out of the persistent one
 
    ...

An equivalent functional replacement of this code expressed in the new API would be:

#include "BdbTime/BdbTime.hh"
 
#include "CdbBase/CdbCondition.hh"
#include "CdbBase/CdbObject.hh"
 
#include "CdbBdb/CdbBdbObjectConvertor.hh"
 
#include "MyPackage/MyPersClass.hh"
 
  // Step A : Locate a condition.
  //
  // This operation if successful will setup a smart pointer onto an object
  // of the CdbCondition class providing operations with specified condition.

    CdbConditionPtr cPtr;

    if( CdbStatus::Success != CdbCondition::instance( cPtr, "/emc/MyPersClass" ) {
        cerr << "Failed to find or open the condition." << endl;
        ...
    }
 
  // Step B : Locate an object.
  //
  // This step if successful will set up a smart pointer onto an object
  // of the CdbObject class. Objects of this class provide access to the
  // metadata information of the corresponding objects. This includes
  //
  //     - validity interval of an object
  //     - the insertion time of the object
  //     - etc.
  //
  // Objects of this class also "hide" the information about actual
  // persistent objects, which will be extracted on the next step.
 
    BdbTime      fetchTime( "01/01/2002 10:00:01" );
    CdbObjectPtr oPtr;
    if( CdbStatus::Success != cPtr->findObject( oPtr, fetchTime )) {
        cerr << "Failed to find or open the object." << endl;
        ...
    }
    cout << "begin of the object's validity interval : " << oPtr->begin( ) << endl
         << "end   of the object's validity interval : " << oPtr->end( ) << endl
         << "the object was created on : " << oPtr->created( ) << endl;
 
  // Step C : Extract the corresponding persistent handle out of the above found
  //          generic object.
  // This step is needed since steps A and B above do not explicitly expose
  // any persistent technology or concrete CDB API implementation. What
  // they (steps A & B) essentially do is that they find metadata information.
  // Now it's time to "jump' from metadata into a real persistent "world".
  //
  // To extract the handle we need to apply a special Bdb specific conversion
  // facility. This will also verify if the above found generic object
  // hides the Objectivity/Db handle.
 
    BdbHandle(BdbObject) handle;

    if( CdbStatus::Success != CdbBdbObjectConvertor::narrow( handle, oPtr )) {
    }
 
    BdbHandle(MyPersClass) myHandle;
    myHandle = (const BdbHandle(MyPersClass)&) handle;

  
  // Now produce the transient class out of the persistent one
 
    ...

A few additional comments to this code sample:

4.6 Existing clients of the BdbIntervalItr class

The class mentioned in the title of this subsection is defined at:

BdbCond/BdbIntervalItr.hh

It's mostly used to obtain a list of persistent intervals and/or the corresponding condition objects in the validity time dimension. The original model of the class provides an option to choose among three different collections of intervals/objects:

BdbIntervalItr::setTopmost(...)
BdbIntervalItr::setBaseline(...)
BdbIntervalItr::setRevision(...)

These collections do not have direct (one-to-one) functional replacements in the CDB API. The reason of this is that according to the conceptual model of the new Condition/DB the only collection available to an application is the one defined through the current configuration of a job. De-facto it's also true for the old Condition/DB, although its API still have these tree choices. In reality all of existing clients of the current (old) Condition/DB are dealing with the "topmost" layer of conditions, which is defined by the current job configuration. Therefore any clients of the old e BdbIntervalItr class should be migrated to use the following method:

CdbCondition::objectIterator(CdbObjectItr& itr,
                             const BdbTime& beginValidity = BdbTime::minusInfinity,
                             const BdbTime& endValidity   = BdbTime::minusInfinity );

A simplified example of how it can be done is shown below. Let's suppose we have the following code:

#include "BdbCond/BdbIntervalItr.hh"
 
  // Step A : Setup an iterator to browse the TOPMOST layer of the specified detector/condition
  //          The iteration will begin at specified time.
 
    BdbTime        beginTime( "01/01/2002 10:00:01" );
    BdbIntervalItr itr;
    if( ! itr.setTopmost( "emc", "MyPersClass", beginTime )) {
        cerr << "Failed to set up the iterator." << endl;
        ...
    }
 
  // Step B : Browse the condition till "the end of the time" (+Infinity)
  //          and print the validity interval of each found object.
 
    while( itr.next( )) {
 
        BdbHandle(BdbIntervalR) intervalH;
        intervalH = itr;
 
        cout << "BEGIN TIME: " << intervalH->beginTime( ) << endl
             << "END   TIME: " << intervalH->endTime( )   << endl;
    }
    ...

The functional replacement for this code expressed in the new API would be:

#include "CdbBase/CdbCondition.hh"
#include "CdbBase/CdbObjectItr.hh"
#include "CdbBase/CdbObject.hh"
 
  // Step A : Locate a condition.
  //
  // This operation if successful will setup a smart pointer onto an object
  // of the CdbCondition class providing operations with specified condition.

    CdbConditionPtr cPtr;
    if( CdbStatus::Success != CdbCondition::instance( cPtr, "/emc/MyPersClass" ) {
        cerr << "Failed to find or open the condition." << endl;
        ...
    }
 
  // Step B : Initialize an iterator of objects.
 
    BdbTime      beginTime( "01/01/2002 10:00:01" );
    CdbObjectItr itr;
    if( CdbStatus::Success != cPtr->objectIterator( itr, beginTime )) {
        cerr << "Failed to set up the iterator." << endl;
        ...
    }
 
  // Step C : Browse the condition till "the end of the time" (+Infinity)
  //          and print the validity interval of each found object.
 
    while( itr.next( )) {
 
        CdbObjectPtr oPtr = itr.value( );
 
        cout << "BEGIN TIME: " << oPtr->begin( ) << endl
             << "END   TIME: " << oPtr->end( )   << endl;
    }
    ...

A complete example of a simple applications using this CDB iterator class can be found at the following location:

A few additional comments to this code sample:

CdbBdbTests/CdbSimplestObjectIteratorTest.cc

etc.

5  Migrating the condition objects loaders

5.1 How do I know if my loader needs to be "migrated"?

As in case of proxies the answer is simple - if a loader uses any of the following classes:

BdbCond/BdbCondLoadList
BdbCond/BdbDatabase

then your code needs to be migrated.

5.2 Using a list loader class (loading calibration constants from flat text file)

In case if your code uses the following class:

BdbCond/BdbCondLoadList

then it can simply be migrated by replacing any uses of this class with the following one:

CdbBdb/CdbBdbLoadList

No other changes are required.

5.3 Existing clients of CalDatabase package

The internal implementation of this package is going to be migrated to Cdb API very soon. None of the clients of this package are going to be affected by this migration.

5.4 Straightforward migration

The migration in this direction is a bit more complicated than in case of proxies. The main reason of this is that we're changing the "philosophy" of the users objects clustering. Now instead of the old "user-driven" approach we're introducing the "API-driven" one. The difference can be seen on a single example. Here is an illustration of how the "user-driven" technology works in the old code:

#include "BdbTime/BdbTime.hh"
#include "BdbCond/BdbDatabase.hh"
#include "BdbClustering/BdbCondClusteringHint.hh"
 
#include "MyPackage/MyPersClass.hh"
 
 
  // A user creates a persistent object at a collection of objects
  // of the "emc" subsystem.
 
    BdbCondClusteringHint  hint( "emc" );
    BdbHandle(MyPersClass) handle;
 
    handle = new( hint.updatedHint( )) MyPersClass (...);
 
  // A user tells the Condition/DB API to register a newly created handle
  // with specified key (condition name) in the collection of objects
  // of the "emc" subsystem.
 
    BdbTime beginStoreTime( "01/01/2002 10:00:01" );
    BdbTime endStoreTime( BdbTime::plusInfinity );
 
    BdbDatabase database( "emc" );
 
    BdbStatus s = database.store( handle,
                                  "MyPersClass",
                                  beginStoreTime,
                                  endStoreTime );
    if( BdbcSuccess != s ) {
        cerr << "Tough luck buddy!" << endl;
        ...
    }

Needless to say how insecure is this way of separated creation and registration of user defined persistent objects. Its second main problem is that it does not let the Condition/DB API to control the data placement granularity when it's needed. For example we can't put the "MyPersClass" only conditions into a separate persistent collection. The new API fixes both of these problems by introducing the "API-driven" approach. It's basic concept can be expressed as simple as:

"For a persistent class P a developer provides a function, which creates a persistent object of the class P at a location suggested by the CDB API. The CDB API calls this function when it's time to create a new object of type P and it also supplies this function (through one of its parameters) with a value of the clustering hint to tell the function where the new object has to be created."

Technically it boils down to the following design:

Here is an example how the original code can be re-implemented in CDB:

// File: Mypackage/MyLoader.cc
 
#include "BdbTime/BdbTime.hh"
 
#include "CdbBase/CdbCondition.hh"
#include "CdbBdb/CdbBdbObjectFactory.hh"
 
#include "MyPackage/MyPersClass.hh"
 
// Put class definition into the anonymous namespace of the loader's CC file.
// It can also live at as a separate class in its own files
 
namespace {
 
  // Step A : Define a specialized factory for the MyPersClass class.
  //
  // In this oversimplified example we're assuming that it makes sense
  // to use the default constructor of the MyPersClass class.
 
    class MyFactory : public CdbBdbObjectFactory {
    protected:
 
      // Implement the method defined in the base class.
 
        virtual CdbStatus doCreate( BdbHandle(BdbObject)& theProduct,
                                    const BdbRefAny&      theHint )
        {
            theProduct = new( theHint ) MyPersClass( );
            return CdbStatus::Success;
        }  
    };
};
 
...<the rest of the loader's code...>
 
  // Step B : Locate a condition.
  //
  // This operation if successful will setup a smart pointer onto an object
  // of the CdbCondition class providing operations with specified condition.

    CdbConditionPtr cPtr;

    if( CdbStatus::Success != CdbCondition::instance( cPtr, "/emc/MyPersClass" ) {
        cerr << "Failed to find or open the condition." << endl;
        ...
    }
 
  // Step C : Store an object.
  //
  // The CDB API will trigger the object creation by calling the user defined doCreate( )
  // method of the factory.
 
    MyFactory factory;
 
    BdbTime beginStoreTime( "01/01/2002 10:00:01" );
    BdbTime endStoreTime( BdbTime::plusInfinity );
 
    if( CdbStatus::Success != cPtr->storeObject( factory, beginStoreTime, endStoreTime )) {
        cerr << "Failed to create/store the object." << endl;
        ...
    }

The _implementation_ of the following class is a good illustration of this approach. Just look inside.

CdbBdb/CdbBdbLoadList.cc

5.4.1 Simplified solution in case of trivial transient-to-persistent conversion

In those cases when a constructor of persistent object has just one parameter of some transient type then there is no need for a developer to create a special factory class. The CDB API already has a template solution to this problem. It comes with the following class:

CdbBdb/CdbBdbTObjectFactory

Here is a modified version of  the above given example using this class:

#include "BdbTime/BdbTime.hh"
 
#include "CdbBase/CdbCondition.hh"
#include "CdbBdb/CdbBdbTObjectFactory.hh"
 
#include "MyPackage/MyPersClass.hh"
#include "MyPackage/MyClass.hh"
 
  // Step A : Instantiate a specialized factory for the MyPersClass class.
  //
  // Here we're assuming that objects of this persistent class can be
  // constructed out of the value of teh transient class MyClass.
 
    MyClass value( ... );
 
    CdbBdbTObjectFactory< MyClass, MyPersClass > factory( value );
 
  // Step B : Locate a condition.
  //
  // This operation if successful will setup a smart pointer onto an object
  // of the CdbCondition class providing operations with specified condition.

    CdbConditionPtr cPtr;

    if( CdbStatus::Success != CdbCondition::instance( cPtr, "/emc/MyPersClass" ) {
        cerr << "Failed to find or open the condition." << endl;
        ...
    }
 
  // Step C : Store an object.
  //
  // The CDB API will trigger the object creation by calling the user defined doCreate( )
  // method of the above instantiated (Step A) factory.
 
    BdbTime beginStoreTime( "01/01/2002 10:00:01" );
    BdbTime endStoreTime( BdbTime::plusInfinity );
 
    if( CdbStatus::Success != cPtr->storeObject( factory, beginStoreTime, endStoreTime )) {
        cerr << "Failed to create/store the object." << endl;
        ...
    }

See more details in the header file of this factory class.

5.4.2 Migrating clients who use storeAndTruncate operation

This section is only relevant to a subset clients of section 5.4, who are using the following method in the old Condition/DB API:

BdbDatabase::storeAndTruncate(..)

The corresponding operation in the new API is called:

CdbCondition::storeAndTruncateObject(..)

Here is a modified version of the loader's example using this method:

...
 
  // Step C : Store an object using "storeAndTruncate" operation
  //
  // The CDB API will trigger the object creation by calling the user defined doCreate( )
  // method of the above instantiated (Step A) factory.
 
    BdbTime storeTime( "01/01/2002 10:00:01" );
    BdbTime truncateTime( storeTime + 10 );      // Truncate 10 seconds after begin
 
    if( CdbStatus::Success != cPtr->storeAndTruncateObject( factory, storeTime, truncateTime )) {
        cerr << "Failed to create/store the object." << endl;
        ...
    }
...

This feature is not in the 11.12.1 test release. In order to use it the following (or newer) tags are required:

CdbBase       V00-01-06
CdbBdbWrapper V00-01-04

6  Migrating the "rolling calibration" objects loader ("finalizer")

This has to be done exactly as it's done in case of the regular condition objects loaders using the storeAndTruncateObject method. See section 5.4.2 for details.

7  Transaction management in the new Condition/DB API

In general the transaction management in Condition/DB applications stays the same as it was imposed by the old API. The main idea is that applications (through the private or protected inheritance) should use the following class:

BdbCond/BdbCondProxyBase

Since this is a very simple (and handy) class then we don't have a special documentation for this class except this short memo. This solution, however suffers from three major problems:

The "salvation" comes with the resource-acquisition-is-initialization paradigm. To implement this idea the new Condition/DB API introduces the following class:

CdbBdb/CdbBdbTransaction

This class will implicitly start transaction upon its instantiation (when its only constructor is called) and will commit it when its destructor is executed. The constructor has an optional parameter allowing to specify the mode of the transaction. By default it's the READ-ONLY transaction. That's all about the public contract and behavior of the class. In addition to the regular start/commit operation, the transaction management class can also be used to invoke the "commit-and-hold" operation (see the corresponding method in the example below), which in particular is useful to flush the results (modified/created persistent object) out of a process's cache (Objectivity/DB client's cache to be more specific).

Here is how this class can be and should be used:

#include "CdbBdb/CdbBdbTransaction.hh"
...
{
  // Here my block of code begins. It may also be a body of a method or function,
  // or a loop.
  //
  // Let's begin an UPDATE mode transaction. REMEMBER: the transaction
  // mode is an optional parameter of the constructor.
 
    CdbBdbTransaction transaction( BdbcUpdate );
 
    ...<do whatever you want with the persistent store>
    ...<leave the current block at any time>...
    ...<don't worry about finishing transaction anymore>...
 
    transaction.commitAndHold( );    // Flush modifications back to the persistent store
 
    ..<keep modifying the database>...
 
}  // At this point the previously started transaction is guaranteed to be
   // automatically committed. This happens because when a control flow leaves
   // a block then destructors for every single variable allocated
   // within a block (except the ones assigned to local plain C pointers) will
   // be executed.

Unfortunately this class was not available at the time when 11.12.1 release was being "cooked". So you should use the most recent tag of the CdbBdb package in the nightly. This will be fixed with the next test or production release.

Enjoy!

8 Migrating the Framework modules and sequences

Any Framework application dealing with the Condition/DB through the new CDB API has to include the following module into its Framework sequence to make sure that a proper CDB API implementation "plug-in" is properly installed and the right Condition/DB is connected:

BdbModules/CdbBdbInit

This module is available as of 11.12.1 release.

The "big" Framework applications, such as Elf, Bear, Beta should already have this module in their sequences. However, of you have your own private application, which is usually built from a special file found in your package:

<YourPackage>/AppUserBuild.cc

then it's your responsibility to provide that above mentioned module were linked into your application and put into a sequence somewhere. Here is an example showing how this could be done (the inserted code is shown in bold font):

// File: '<YourPackage>/AppUserBuild.cc'
...
#include "BdbModules/CdbBdbInit.hh"
...
//----------------
// Constructors --
//----------------

AppUserBuild::AppUserBuild( AppFramework* theFramework )
    : AppBuild( theFramework )
{
    add(new GenBuildEnv( "GenBuildEnv", "Build General Environment" ));
    add(new PdtInit("PdtInit", "Initialize PDT"));
    add(new CdbBdbInit("CdbBdbInit", "Initialize CDB" ));
    ...

For other non-Framework applications the following code has to be executed before any operations with the CDB:

#include "CdbBdbWrapper/CdbBdbWrapper.hh"
 
main( ... )
{
  // Instantiate the plug-in for the "Wrapper" implementation of the CDB API.
  // remember that this particular implementation of the API will forward
  // any operations to the existing ("old") Condition/DB.
 
    CdbBdbWrapper::forceLoad( );
    ...
 
  // Now CDB API is avaialble.
 
    ...
}

In fact the mentioned above CdbBdbInit module does exactly the same ting. Note, that similar actions will have to be done for other implementations of the CDB API when they will become available.

9  Migrating the GenEnv class

This class has to be extended to provide a pointer onto an instance of the following class:

CdbBase/CdbStateId

Two extra methods should be added to set and to get the value of this pointer.

Here is how the proposed change may look like in the public contract of this class:

// File: GenEnv/GenEnv.hh
 
class GenEnv ... {
public:
 
    const CdbStateId* condStateId( ) const;
 
    void setStateId( const CdbStateId* newid );
};

 must be the corresponding methods to  implemented.

10  Converting existing databases into new format

This section is to be implemented.