STAR   Computing Tutorials main page
DataBase Data Access & Use
Offline computing tutorial Maintained by Jeff Porter
Last modified Fri Jul 03 00:19:00 1998

CAUTION: These pages need rework


Use Case Boundary Conditions

The boundary conditions for setting up the database structures consist of the following;
  1. persistent structures repesent logically connected groups of data elements
  2. transient objects at user codes can be a more complex grouping of data elements
    1. User Codes may need multiple pieces of persistent data
    2. Specific transient tables now in use should be supported

Specifically, in traditional uses of database data there is typically a call to the database (or framework) for some set of data "arrays". These arrays are generally the persistent structures (item 1) that are then used in the analysis/reconstruction codes. While the DB-data are these "arrays", the correct manipulation of these arrays (item 2) are relegated to either the user codes or, to limit duplication of code, a set of global functions. We can, however, now make use of C++ to systematize the data access and manipulation within "objects" (item 2a). Then the "array" layout can hidden from (is irrelevant to) the user codes with explicit accessors and the generic data manipulations (set of routines) are put into the objects' functionality. Benifits of this approach are,
  • objects can be complicated to perform complete tasks
  • routines are provided by objects' functions
    • prototypes are defined in the class definition
    • a set of routines are introduced (passed) with the data into codes
    • routines can be over-ridden (polymorphism) without change to user codes
While such objects are being developed and deployed, the currently used table definitions need to be supported ...i.e. MyDbObject needs a method to return my-idl-defined table (item 2b).

Example: gain corrections of adc values may typically done via,

index = getIndex(irow,ipad);
fadc = adc_val*gains[index];
In our C++ world, this can become
StTpcGains *mgains .... obtained somehow;
...
fadc = adc_val*mgains->getGain(irow,ipad);
In the former case, the index evaluated from "geometry data" is needed to access the gain data; in the latter the index "comes" with the data. This getIndex(irow,ipad) - a global function - performs the functions that are really part of the gain data accessor. Of course on could use gains[getIndex(irow,ipad)] or hide it all within another global function that has direct access to the DB-data, getGain(irow,ipad), & achieve a similar result. But C++ provides a clean way to organize these functions within the object definition.

In order to provide support of current codes that use the original method, one should implement the direct accessor for the gain table

gains = mgains->getGainTable();
and pass this table to the codes which use them.

Note, if we also want to provide gains calculated from system parameters, we can add this functionality to the object without the gymnastics of new global functions or imbedding it in specific user codes. e.g.

fadc = adc_val*mgains->getCalcGain(irow,ipad);
In general, modifications to the underlying data-structure or data-manipulations can be hidden in the object & re-introduced via the "obtained somehow" part of the puzzle. This is the puzzle part that needs to be addressed to push the DB data definitions forward.


Accessing Database Data

The StRoot framework is setup to get database data from a single source with a single accessor. The database-data is then distributed to the rest of the "chain" via standard StRoot object passing. This is illustrated in the following diagram:

A Maker requests data (tree of data) from the infrastructure & this request is forwarded to the St_db_Maker. The St_db_Maker then loads the corresponding (tree of) data from the database and passes the pointer to the tree (in the form of a St_DataSet) back to the Maker making the request. The St_db_Maker knows nothing about the objects except that,
  • they are named and in a named tree-catalog (now a unix file system)
  • they are returned as St_DataSet objects (or can be objects put into St_DataSet)
The data objects are known only to the user codes & the database. The storage of the objects is known only to the StDbRealFactory. To insulate the St_db_Maker from the details of the store (e.g. objectivity, MySQL, Root-Macros) the St_db_Maker only knows about the interface to the store.

The persistent data structure in the database can simply be defined as "tables" of data elements. However, the user codes (St_A_Maker) may wish to have the more complex object made available. That is, it never wants the gain table without the padplane geometry that allows one to navigate within the gain table. The question becomes, "where are the complex objects created & filled?".


Setting Up Real DB-Utility Objects

Since idl-defined tables will serve as the definition language for database object, the creation of the more complex user objects is better (& more realizable in the short term) localized in non-Database code. To achieve this, each detector/sub-system domain will need to supply and maintain the code used to build such objects. Then the more general description for accessing the database data is shown in a modification of the previous diagram:

In this diagram, the St[sys]DbUtilMaker requests the simple data structures from the database (via St_db_Maker) and builds the usefull db objects. These are then made available to the rest of the chain (e.g. St_A_Maker) though named-object request in the normal StRoot way. With this technique, only the St[sys]DbUtilMaker makes requests that are filled St_db_Maker. This is not a requirement but a feature of this approach. As these utility objects stabelize, it may be appropriate to move the creation of such objects behind the database interface.. e.g. so that one calls the database for such an object as a named object "in " the database.

Example: The gain & time-offset objects defined in the database contain the arrays:

tpc/SectorXX/gainFactors = innerSectorGainFactors & outerSectorGainFactors
tpc/SectorXX/timeOffsets = innerSectorTimeOffsets & outerSectorTimeOffsets
While the padplane layout is defined in the object,
tpc/PadPlanes
According to the access model of the previous section, the St_db_Maker only knows that named objects exist in & can be extracted from the database. With these objects available, we can write an StTpcUtilMaker that builds the usefull object out of the one dimensional DB objects.

Consider such a usefull DB object, TpcCalibPixels.

class TpcCalibPixels : public TpcCalibPixelsI{

// the class TpcCalibPixelsI defines the accessor object (interface) that is exposed
// to the user codes. This "real" TpcCalibPixels object is a specific impementation that could evolve without
// affecting any user codes as long as the interface is supported.

private:

int mcurrentSector; // Current Sector
int mnumSectorsLoaded; // Sanity check
vector<SectorGainFactors> msectorGains; // from Calib DB
vector<SectorTimeOffsets> msectorTimeOffs; // from Calib DB
PadPlanes mpadPlanes; // from Geometry DB
TpcConditions mtpcConditions; // Place holder for Conditions information from which gains, Toffs, & such could be calculated

public:

TpcCalibPixels(){};
TpcCalibPixels(St_DataSet *tpcgeo, St_DataSet *tpccal); // create with geometry & gains & Toffs.
virtual ~TpcCalibPixels();

void addSectorInfo(St_DataSet *tpccal); // load gains & toffs
void addPadGeometry(St_DataSet *tpcgeo); // load geometry
void addConditions(St_DataSet *tpccond); // load conditions

// accessors supporting The Interface TpcCalibPixelsI

virtual void setSector(int isec);
virtual float getGain(int ipad, int irow, int isec=0) ;
virtual float getTimeOff(int ipad, int irow, int isec=0) ;
virtual float getCalcGain(int ipad, int irow, int isec=0);
virtual float getCalcTimeOff(int ipad, int irow, int isec=0);
.... other accessors ....
// Interface accessors supporting Legacy Codes

virtual gain_table* getGainTable(int isec=0);
virtual toffset_table* getTimeOffTable(int isec=0);

}


Evolution of Codes Making Use of DB-Utility Objects

The initial phase of developing and deploying the DB-Utility objects will include the following steps,
  1. Current Makers will access DB-objects as provided by St_db_Maker's retrieval from the database
  2. New Utility Makers will be developed at least for each detector system (with perhaps an additional 'global' utility) to build the usefull db-objects
  3. Current Makers will move to accessing the usefull db-objects in order to extract their idl-defined tables
  4. User codes will evolve to using the C++ versions of the db-objects directly
In some more detail:

Step 1: A Maker has & fills its data member as follows,

St_mytable* m_mytable; // data member in St_A_Maker.h

// in St_A_Maker::Init()
St_DataSet *mdetector = GetDataBase("Dbdir/mydetector");
St_DataSetIter local(mdetector);
m_mytable = (St_mytable *)local("mytable" );

// in St_A_Maker::Make()
Int_t Result = mypam(m_mytable); // call the pam

Step 2: A Utility Maker has & fills utility Objects,
St_ObjectSet* m_DbUtil; // data member in St_AUtilMaker.h

// in St_AUtilMaker::Init()
St_DataSet *mdetector = GetDataBase("Dbdir/mydetector"); // database access now done here
St_DataSetIter local(mdetector);
St_mytable* m_mytable = (St_mytable *)local("mytable");

m_DbUtil = new St_MyUtilyObject(m_mytable->GetTable()); // create with table or perhaps with St_DataSet * mdetector ...
m_DataSet->Add(m_DbUtil); // make available to other Makers

Step 3: Step 1 is modified slightly here,
St_mytable* m_mytable; // data member in St_A_Maker.h

// in St_A_Maker::Init()
St_ObjectSet *mDbUtil = GetData("DbUtilName");
St_DataSetIter local(mdetector);
m_mytable = new St_mytable("mytable",mDbUtil->getMyTable); // my table now obtained from mDbUtil object

// in St_A_Maker::Make()
Int_t Result = mypam(m_mytable); // call the pam

Step 4: Step 3 is modified only where pam evolves into C++ package,
StUserObjectI* m_dbObjectInterface; // data member in St_A_Maker.h

// in St_A_Maker::Init()
St_ObjectSet *mDbUtil = GetData("DbUtilName");
m_dbObjectInterface = dynamic_cast<StUserObjectI*> mDbUtil;

// in St_A_Maker::Make()
AnalysisMod mAnalysisObj(m_dbObjectInterface,...); // call the real user code that uses the Interface Object
mAnalysisObj.analyze(); // do analysis