// @(#)root/sql:$Name: $:$Id: TSQLFile.cxx,v 1.7 2006/02/01 18:57:41 pcanal Exp $
// Author: Sergey Linev 20/11/2005
/*************************************************************************
* Copyright (C) 1995-2005, Rene Brun and Fons Rademakers. *
* All rights reserved. *
* *
* For the licensing terms see $ROOTSYS/LICENSE. *
* For the list of contributors see $ROOTSYS/README/CREDITS. *
*************************************************************************/
//________________________________________________________________________
//
// The main motivation for the TSQLFile development is to have
// "transparent" access to SQL data base via standard TFile interface.
//
// The main approach that each class (but not each object) has one or two tables
// with names like $(CLASSNAME)_ver$(VERSION) and $(CLASSNAME)_streamer_ver$(VERSION)
// For example: TAxis_ver8 or TList_streamer_ver5
// Second kind of tables appears, when some of class members can not be converted to
// normalized form or when class has custom streamer.
// For instance, for TH1 class two tables are required: TH1_ver4 and TH1_streamer_ver4
// Most of memebers are stored in TH1_ver4 table columnwise, and only memeber:
//
// Double_t* fBuffer; //[fBufferSize]
//
// can not be represented as column while size of array is not known apriory.
// Therefore, fBuffer will be written as list of values in TH1_streamer_ver4 table.
//
// All objects, stored in the DB, will be registered in table "ObjectsTable".
// In this there are following columns:
// "key:id" - key identifier to which belong object
// "obj:id" - object identifier
// "Class" - object class name
// "Version" - object class version
// Data in each "ObjectsTable" row uniqly identify, in which table
// and which column object is stored.
//
// In normal situation all class data should be sorted columnwise.
// Up to now following member are supported:
// 1) Basic data types
// Here is everything clear. Column SQL type will be as much as possible
// close to the original type of value.
// 2) Fixed array of basic data types
// In this case n columns like fArr[0], fArr[1] and so on will be created.
// If there is multidimensional array, names will be fArr2[1][2][1] and so on
// 3) Parent class
// In this case version of parent class is stored and
// data of parent class will be stored with the same obj:id in corrspondent table.
// There is a special case, when parent store nothing (this is for instance TQObject).
// In that case just -1 is written to avoid any extra checks if table exist or not.
// 4) Object as data member.
// In that case object is saved in normal way to data base and column
// will contain id of this object.
// 5) Pointer on object
// Same as before. In case if object was already stored, just its id
// will be placed in the column. For NULL pointer 0 is used.
// 6) TString
// Now column with limited width like VARCAHR(255) in MySQL is used.
// Later this will be improved to support maximum possible strings
// 7) Anything else.
// Data will be converted to raw format and saved in _streamer_ table.
// Each row supplied with obj:id and row:id, where row:id indicates
// data, corresponding to this particular data member, and column
// will contain this raw:id
//
// All conversion to SQL statements are done with help of TSQLStructure class.
// This is special hierarchical structure wich internally is very similar
// to XML structures. TBufferSQL2 creates these structures, when object
// data is streamed by ROOT and only afterwards all SQL statements will be produced
// and applied all together.
//
// When data is reading, TBufferSQL2 will produce requests to database
// during unstreaming of object data.
//
// Optionally (default this options on) name of column includes
// suffix which indicates type of column. For instance:
// *:parent - parent class, column contain class version
// *:object - other object, column contain object id
// *:rawdata - raw data, column contains id of raw data from _streamer_ table
// *:Int_t - column with integer value
// Use TSQLFile::SetUseSuffixes(kFALSE) to disable suffixes usage.
//
// This and several other options can be changed only when
// TSQLFile created with options "CREATE" or "RECREATE" and only before
// first write operation. These options are:
// SetUseSuffixes() - suffix usage in column names (default - on)
// SetArrayLimit() - defines maximum array size, which can
// has column for each element (default 20)
// SetTablesType() - table type name in MySQL database (default "InnoDB")
// SetUseIndexes() - usage of indexes in database (default kIndexesBasic)
// Normally these functions should be called immidiately after TSQLFile constructor.
//
// When objects data written to database, by default START TRANSACTION/COMMIT
// SQL commands are used before and after data storage. If TSQLFile detects
// any problems, ROLLBACK command will be used to restore
// previous state of data base. If transactions not supported by SQL server,
// they can be disabled by SetUseTransactions(kTransactionsOff). Or user
// can take responsibility to use transactions function to hime
//
// By default only indexes for basic tables are created.
// In most cases usage of indexes increase perfomance to data reading,
// but it also can increase time of writing data to database.
// There are several modes of index usage available in SetUseIndexes() method
//
// There is MakeSelectQuery(TClass*) method, which
// produces SELECT statement to get objects data of specified class.
// Difference from simple statement like:
// mysql> SELECT * FROM TH1I_ver1
// that not only data for that class, but also data from parent classes
// will be extracted from other tables and combined in single result table.
// Such select query can be usufull for external access to objects data.
//
// Up to now MySQL 4.1 and Oracle 9i were tested.
// Some extra work is required for other SQL databases.
// Hopefully, this should be straigthforward.
//
// Known problems and open questions.
// 1) TTree is not supported by TSQLFile. There is independent development
// of TTreeSQL class, which allows to store trees directly in SQL database
// 2) TClonesArray is store objects in raw format,
// which can not be accessed outside ROOT.
// This will be changed later.
// 3) TDirectory cannot work. Hopefully, will (changes in ROOT basic I/O is required)
// 4) Streamer infos are not written to file, therefore schema evolution
// is not yet supported. All eforts are done to enable this feature in
// the near future
//
// Example how TSQLFile can be used:
//
// example of a session saving data to a SQL data base
// =====================================================
//
// const char* dbname = "mysql://host.domain:3306/dbname";
// const char* username = "username";
// const char* userpass = "userpass";
//
// // Clean data base and create primary tables
// TSQLFile* f = new TSQLFile(dbname, "recreate", username, userpass);
// // Write with standard I/O functions
// arr->Write("arr", TObject::kSingleKey);
// h1->Write("histo");
// // Close connection to DB
// delete f;
//
// example of a session read data from SQL data base
// =====================================================
//
// // Open database again in read-only mode
// TSQLFile* f = new TSQLFile(dbname, "open", username, userpass);
// // Show list of keys
// f->ls();
// // Read stored object, again standard ROOT I/O
// TH1* h1 = (TH1*) f->Get("histo");
// if (h1!=0) { h1->SetDirectory(0); h1->Draw(); }
// TObject* obj = f->Get("arr");
// if (obj!=0) obj->Print("*");
// // close connection to DB
// delete f;
//
// The "SQL I/O" package is currently under development.
// Any bug reports and suggestions are welcome.
// Author: S.Linev, GSI Darmstadt, S.Linev@gsi.de
//
//______________________________________________________________________________
#include "TSQLFile.h"
#include "TROOT.h"
#include "TSystem.h"
#include "TList.h"
#include "TBrowser.h"
#include "TObjArray.h"
#include "TObjString.h"
#include "TArrayC.h"
#include "TStreamerInfo.h"
#include "TStreamerElement.h"
#include "TProcessID.h"
#include "TError.h"
#include "TSQLServer.h"
#include "TSQLResult.h"
#include "TSQLRow.h"
#include "TBufferSQL2.h"
#include "TSQLStructure.h"
#include "TKeySQL.h"
#include "TSQLClassInfo.h"
#include "TSQLObjectData.h"
#include "Riostream.h"
ClassImp(TSQLFile);
const char* mysql_BasicTypes[20] = {
"VARCHAR(255)", // kBase = 0, used for text
"TINYINT UNSIGNED", // kChar = 1,
"SMALLINT", // kShort = 2,
"INT", // kInt = 3,
"BIGINT", // kLong = 4,
"FLOAT", // kFloat = 5,
"INT", // kCounter = 6,
"VARCHAR(255)", // kCharStar = 7,
"DOUBLE", // kDouble = 8,
"DOUBLE", // kDouble32= 9,
"", // nothing
"TINYINT UNSIGNED", // kUChar = 11,
"SMALLINT UNSIGNED",// kUShort = 12,
"INT UNSIGNED", // kUInt = 13,
"BIGINT UNSIGNED", // kULong = 14,
"INT UNSIGNED", // kBits = 15,
"BIGINT", // kLong64 = 16,
"BIGINT UNSIGNED", // kULong64 = 17,
"BOOL", // kBool = 18,
""
};
const char* mysql_OtherTypes[13] = {
"VARCHAR(255)", // smallest text
"255", // maximum length of small text
"TEXT", // biggest size text
"DATETIME", // date & time
"`", // quote for identifier like table name or column name
"dir:id", // dir id column
"key:id", // key id column
"obj:id", // object id column
"raw:id", // raw data id column
"str:id", // string id column
":", // name separator between name and type like TObject:Parent
"\"", // quote for string values in MySQL
"InnoDB" // default tables types, used only for MySQL tables
};
const char* oracle_BasicTypes[20] = {
"VARCHAR(255)", // kBase = 0, used for text
"INT", // kChar = 1,
"INT", // kShort = 2,
"INT", // kInt = 3,
"INT", // kLong = 4,
"FLOAT", // kFloat = 5,
"INT", // kCounter = 6,
"VARCHAR(255)", // kCharStar = 7,
"DOUBLE PRECISION", // kDouble = 8,
"DOUBLE PRECISION", // kDouble32= 9,
"", // nothing
"INT", // kUChar = 11,
"INT", // kUShort = 12,
"INT", // kUInt = 13,
"INT", // kULong = 14,
"INT", // kBits = 15,
"INT", // kLong64 = 16,
"INT", // kULong64 = 17,
"INT", // kBool = 18,
""
};
const char* oracle_OtherTypes[13] = {
"VARCHAR(1000)", // smallest text
"4095", // maximum size of smallest text
"VARCHAR(4000)", // biggest size text, CLOB is not yet supported by TOracleRow
"VARCHAR(50)", // date & time
"\"", // quote for identifier like table name or column name
"dir:id", // dir id column
"key:id", // key id column
"obj:id", // object id column
"raw:id", // raw data id column
"str:id", // string id column
":", // name separator between name and type like TObject:parent
"'", // quote for string values in Oracle
"" // default tables types, used only for MySQL tables
};
// ******************************************************************
// these are two class to produce deep copy of sql result tables
// introduced to overcome Oracle problem
class TSQLRowCopy : public TSQLRow {
protected:
TObjArray fFields;
public:
TSQLRowCopy(TSQLRow* res, Int_t nfields) :
TSQLRow(),
fFields()
{
for(Int_t n=0;n<nfields;n++) {
const char* value = res->GetField(n);
fFields.Add(new TObjString(value));
}
}
virtual ~TSQLRowCopy()
{
}
virtual void Close(Option_t *option="")
{
fFields.Delete(option);
}
virtual ULong_t GetFieldLength(Int_t field)
{
const char * value = GetField(field);
if ((value==0) || (*value==0)) return 0;
return strlen(value);
}
virtual const char *GetField(Int_t field)
{
if ((field<0) || (field>fFields.GetLast())) return 0;
return fFields[field]->GetName();
}
};
class TSQLResultCopy : public TSQLResult {
protected:
TObjArray fRows;
TObjArray fNames;
Int_t fCounter;
public:
TSQLResultCopy(TSQLResult* res) :
TSQLResult(),
fRows(),
fNames(),
fCounter(0)
{
Int_t nfields = res->GetFieldCount();
for (Int_t n=0;n<nfields;n++) {
const char* name = res->GetFieldName(n);
fNames.Add(new TObjString(name));
}
fRowCount = 0;
TSQLRow* row = res->Next();
while (row!=0) {
fRowCount++;
fRows.Add(new TSQLRowCopy(row, nfields));
delete row;
row = res->Next();
}
delete res;
}
virtual ~TSQLResultCopy()
{
}
virtual void Close(Option_t* option="")
{
fRows.Delete(option);
fNames.Delete(option);
}
virtual Int_t GetFieldCount()
{
return fNames.GetLast() + 1;
}
virtual const char *GetFieldName(Int_t field)
{
if ((field<0) || (field>fNames.GetLast())) return 0;
return fNames[field]->GetName();
}
virtual TSQLRow* Next()
{
if (fCounter>fRows.GetLast()) return 0;
TSQLRow* curr = (TSQLRow*) fRows.At(fCounter++);
fRows.Remove(curr); // not make a copy, just remove from buffer, cannot use again
return curr;
}
};
//______________________________________________________________________________
TSQLFile::TSQLFile() :
TFile(),
fSQL(0),
fSQLClassInfos(0),
fUseSuffixes(kTRUE),
fSQLIOversion(1),
fArrayLimit(20),
fCanChangeConfig(kFALSE),
fTablesType(),
fUseTransactions(0),
fUseIndexes(0),
fModifyCounter(0),
fQuerisCounter(0),
fBasicTypes(0),
fOtherTypes(0),
fUserName(),
fLogFile(0)
{
// default TSQLFile constructor
}
//______________________________________________________________________________
TSQLFile::TSQLFile(const char* dbname, Option_t* option, const char* user, const char* pass) :
TFile(),
fSQL(0),
fSQLClassInfos(0),
fUseSuffixes(kTRUE),
fSQLIOversion(1),
fArrayLimit(20),
fCanChangeConfig(kFALSE),
fTablesType(),
fUseTransactions(0),
fUseIndexes(0),
fModifyCounter(0),
fQuerisCounter(0),
fBasicTypes(mysql_BasicTypes),
fOtherTypes(mysql_OtherTypes),
fUserName(user),
fLogFile(0)
{
// Connects to SQL server with provided arguments.
// If the constructor fails in any way IsZombie() will
// return true. Use IsOpen() to check if the file is (still) open.
//
// If option = NEW or CREATE create a ROOT tables in database
// if the tables already exists connection is
// not opened.
// = RECREATE create completely new tables. Any existing tables
// will be deleted
// = UPDATE open an existing database for writing.
// If data base open by other TSQLFile instance for writing,
// write access will be rejected
// = BREAKLOCK Special case when lock was not correctly released
// by TSQLFile instance. This may happen if program crashed when
// TSQLFile was open with write access mode.
// = READ or OPEN open an existing data base for reading.
//
// For more details see comments for TFile::TFile() constructor
//
// For a moment TSQLFile does not support TTree objects and subdirectories
if (!gROOT)
::Fatal("TFile::TFile", "ROOT system not initialized");
gDirectory = 0;
SetName(dbname);
SetTitle("TFile interface to SQL DB");
TDirectory::Build();
fFile = this;
if (strstr(dbname,"oracle://")!=0) {
fBasicTypes = oracle_BasicTypes;
fOtherTypes = oracle_OtherTypes;
}
fArrayLimit = 20;
fTablesType = SQLDefaultTableType();
fUseIndexes = 1;
fUseTransactions = kTransactionsAuto;
fD = -1;
fFile = this;
fFree = 0;
fVersion = gROOT->GetVersionInt(); //ROOT version in integer format
fUnits = 4;
fOption = option;
SetCompressionLevel(5);
fWritten = 0;
fSumBuffer = 0;
fSum2Buffer = 0;
fBytesRead = 0;
fBytesWrite = 0;
fClassIndex = 0;
fSeekInfo = 0;
fNbytesInfo = 0;
fCache = 0;
fProcessIDs = 0;
fNProcessIDs= 0;
fSeekDir = sqlio::Ids_RootDir;
fOption = option;
fOption.ToUpper();
if (fOption == "NEW") fOption = "CREATE";
Bool_t breaklock = kFALSE;
if (fOption == "BREAKLOCK") { breaklock = kTRUE; fOption = "UPDATE"; }
Bool_t create = (fOption == "CREATE") ? kTRUE : kFALSE;
Bool_t recreate = (fOption == "RECREATE") ? kTRUE : kFALSE;
Bool_t update = (fOption == "UPDATE") ? kTRUE : kFALSE;
Bool_t read = (fOption == "READ") ? kTRUE : kFALSE;
if (!create && !recreate && !update && !read) {
read = kTRUE;
fOption = "READ";
}
if (!dbname || !strlen(dbname)) {
Error("TSQLFile", "Database not specified");
goto zombie;
}
gROOT->cd();
fSQL = TSQLServer::Connect(dbname, user, pass);
if (fSQL==0) {
Error("TSQLFile", "Cannot connect to DB %s", dbname);
goto zombie;
}
if (recreate) {
if (IsTablesExists())
if (!IsWriteAccess()) {
Error("TSQLFile", "no write permission, DB %s locked", dbname);
goto zombie;
}
SQLDeleteAllTables();
recreate = kFALSE;
create = kTRUE;
fOption = "CREATE";
}
if (create && IsTablesExists()) {
Error("TSQLFile", "DB tables already exists");
goto zombie;
}
if (update) {
if (!IsTablesExists()) {
update = kFALSE;
create = kTRUE;
}
if (update && !breaklock && !IsWriteAccess()) {
Error("TSQLFile", "no write permission, DB %s locked", dbname);
goto zombie;
}
}
if (read) {
if (!IsTablesExists()) {
Error("TSQLFile", "DB %s tables not exist", dbname);
goto zombie;
}
if (!IsReadAccess()) {
Error("TSQLFile", "no read permission for DB %s tables", dbname);
goto zombie;
}
}
fRealName = dbname;
if (create || update) {
SetWritable(kTRUE);
if (update) SetLocking(kLockBusy);
} else
SetWritable(kFALSE);
// user can change configurations only when create (recreate) options
// was specified. When first object will be saved, configurations will
// be frozen.
fCanChangeConfig = create;
InitSqlDatabase(create);
return;
zombie:
delete fSQL;
fSQL = 0;
MakeZombie();
gDirectory = gROOT;
}
//______________________________________________________________________________
void TSQLFile::StartLogFile(const char* fname)
{
// start logging of all SQL statements in specified file
StopLogFile();
fLogFile = new std::ofstream(fname);
}
//______________________________________________________________________________
void TSQLFile::StopLogFile()
{
// close logging file
if (fLogFile!=0) {
delete fLogFile;
fLogFile = 0;
}
}
//______________________________________________________________________________
Bool_t TSQLFile::IsMySQL() const
{
// checks, if MySQL database
if (fSQL==0) return kFALSE;
return strcmp(fSQL->ClassName(),"TMySQLServer")==0;
}
//______________________________________________________________________________
Bool_t TSQLFile::IsOracle() const
{
// checks, if Oracle database
if (fSQL==0) return kFALSE;
return strcmp(fSQL->ClassName(),"TOracleServer")==0;
}
//______________________________________________________________________________
void TSQLFile::SetUseSuffixes(Bool_t on)
{
// enable/disable uasge of suffixes in columns names
// can be changed before first object is saved into file
if (!fCanChangeConfig)
Error("SetUseSuffixes", "Configurations already cannot be changed");
else
fUseSuffixes = on;
}
//______________________________________________________________________________
void TSQLFile::SetArrayLimit(Int_t limit)
{
// Defines maximum number of columns for array representation
// If array size bigger than limit, array data will be converted to raw format
// This is usefull to prevent tables with very big number of columns
// If limit==0, all arrays will be stored in raw format
// If limit<0, all array values will be stored in column form
// Default value is 20
if (!fCanChangeConfig)
Error("SetArrayLimit", "Configurations already cannot be changed");
else
fArrayLimit = limit;
}
//______________________________________________________________________________
void TSQLFile::SetTablesType(const char* tables_type)
{
// Defines tables type, which is used in CREATE TABLE statements
// Now is only used for MySQL database, where following types are supported:
// "BDB", "HEAP", "ISAM", "InnoDB", "MERGE", "MRG_MYISAM", "MYISAM"
// Default for TSQLFile is "InnoDB". For more detailes see MySQL docs.
if (!fCanChangeConfig)
Error("SetTablesType", "Configurations already cannot be changed");
else
fTablesType = tables_type;
}
//______________________________________________________________________________
void TSQLFile::SetUseTransactions(Int_t mode)
{
// Defines usage of transactions statements for writing objects data to database.
// kTransactionsOff=0 - no transaction operation are allowed
// kTransactionsAuto=1 - automatic mode. Each write operation,
// produced by TSQLFile, will be supplied by START TRANSACTION and COMMIT calls.
// If any error happen, ROLLBACK will returns database to previous state
// kTransactionsUser=2 - transactions are delegated to user. Methods
// StartTransaction(), Commit() and Rollback() should be called by user.
// Default UseTransactions option is kTransactionsAuto
fUseTransactions = mode;
}
//______________________________________________________________________________
Bool_t TSQLFile::StartTransaction()
{
// Start user transaction.
// This can be usesfull, when big number of objects should be stored in
// data base and commitment required only if all operations were succesfull.
// In that case in the end of all operations method Commit() should be
// called. If operation on user-level is looks like not successfull,
// method Rollback() will return database data and TSQLFile instance to
// previous state.
// In MySQL not all tables types support transaction mode of operation.
// See SetTablesType() method for details .
if (GetUseTransactions()!=kTransactionsUser) {
Error("SQLStartTransaction","Only allowed when SetUseTransactions(kUserTransactions) was configured");
return kFALSE;
}
return SQLStartTransaction();
}
//______________________________________________________________________________
Bool_t TSQLFile::Commit()
{
// Commit transaction, started by StartTransaction() call.
// Only after that call data will be written and visible on database side.
if (GetUseTransactions()!=kTransactionsUser) {
Error("SQLCommit","Only allowed when SetUseTransactions(kUserTransactions) was configured");
return kFALSE;
}
return SQLCommit();
}
//______________________________________________________________________________
Bool_t TSQLFile::Rollback()
{
// Rollback all operations, done after StartTransaction() call.
// Database should return to initial state.
if (GetUseTransactions()!=kTransactionsUser) {
Error("SQLRollback","Only allowed when SetUseTransactions(kUserTransactions) was configured");
return kFALSE;
}
return SQLRollback();
}
//______________________________________________________________________________
void TSQLFile::SetUseIndexes(Int_t use_type)
{
// Specify usage of indexes for data tables
// kIndexesNone = 0 - no indexes are used
// kIndexesBasic = 1 - indexes used only for keys list and
// objects list tables (default)
// kIndexesClass = 2 - index also created for every normal class table
// kIndexesAll = 3 - index created for every table, including _streamer_ tables
// Indexes in general should increase speed of access to objects data,
// but they required more operations and more disk space on server side
if (!fCanChangeConfig)
Error("SetUseIndexes", "Configurations already cannot be changed");
else
fUseIndexes = use_type;
}
//______________________________________________________________________________
const char* TSQLFile::GetDataBaseName() const
{
// Return name of data base on the host
// For Oracle always return 0
if (IsOracle()) return 0;
const char* name = strrchr(GetName(),'/');
if (name==0) return 0;
return name + 1;
}
//______________________________________________________________________________
void TSQLFile::Close(Option_t *option)
{
// Close a SQL file
// For more comments see TFile::Close() function
if (!IsOpen()) return;
TString opt = option;
if (opt.Length()>0)
opt.ToLower();
if (IsWritable()) {
SaveToDatabase();
SetLocking(kLockFree);
}
fWritable = kFALSE;
if (fClassIndex) {
delete fClassIndex;
fClassIndex = 0;
}
TDirectory *cursav = gDirectory;
cd();
if (cursav == this || cursav->GetFile() == this) {
cursav = 0;
}
// Delete all supported directories structures from memory
TDirectory::Close();
cd(); // Close() sets gFile = 0
if (cursav)
cursav->cd();
else {
gFile = 0;
gDirectory = gROOT;
}
//delete the TProcessIDs
TList pidDeleted;
TIter next(fProcessIDs);
TProcessID *pid;
while ((pid = (TProcessID*)next())) {
if (!pid->DecrementCount()) {
if (pid != TProcessID::GetSessionProcessID()) pidDeleted.Add(pid);
} else if(opt.Contains("r")) {
pid->Clear();
}
}
pidDeleted.Delete();
gROOT->GetListOfFiles()->Remove(this);
}
//______________________________________________________________________________
TSQLFile::~TSQLFile()
{
// destructor of TSQLFile object
Close();
if (fSQLClassInfos!=0) {
fSQLClassInfos->Delete();
delete fSQLClassInfos;
}
StopLogFile();
if (fSQL!=0) {
delete fSQL;
fSQL = 0;
}
}
//______________________________________________________________________________
void TSQLFile::operator=(const TSQLFile &)
{
// make private to exclude copy operator
}
//______________________________________________________________________________
Bool_t TSQLFile::IsOpen() const
{
// return kTRUE if file is opened and can be accessed
return fSQL != 0;
}
//______________________________________________________________________________
Int_t TSQLFile::ReOpen(Option_t* mode)
{
// Reopen a file with a different access mode, like from READ to
// See TFile::Open() for details
cd();
TString opt = mode;
opt.ToUpper();
if (opt != "READ" && opt != "UPDATE") {
Error("ReOpen", "mode must be either READ or UPDATE, not %s", opt.Data());
return 1;
}
if (opt == fOption || (opt == "UPDATE" && fOption == "CREATE"))
return 1;
if (opt == "READ") {
// switch to READ mode
if (IsOpen() && IsWritable()) {
SaveToDatabase();
SetLocking(kLockFree);
}
fOption = opt;
SetWritable(kFALSE);
} else {
// switch to UPDATE mode
if (!IsWriteAccess()) {
Error("ReOpen","Tables are locked, no write access");
return 1;
}
fOption = opt;
SetWritable(kTRUE);
SetLocking(kLockBusy);
}
return 0;
}
//______________________________________________________________________________
TKey* TSQLFile::CreateKey(TDirectory* mother, const TObject* obj, const char* name, Int_t )
{
// create SQL key, which will store object in data base
return new TKeySQL(mother, obj, name);
}
//______________________________________________________________________________
TKey* TSQLFile::CreateKey(TDirectory* mother, const void* obj, const TClass* cl, const char* name, Int_t )
{
// create SQL key, which will store object in data base
return new TKeySQL(mother, obj, cl, name);
}
//______________________________________________________________________________
void TSQLFile::WriteHeader()
{
// Write file info like configurations, title, UUID and other
// WriteSpecialObject(sqlio::Ids_TSQLFile, this, GetName(), GetTitle());
}
//______________________________________________________________________________
void TSQLFile::WriteStreamerInfo()
{
// Store all TStreamerInfo, used in file, in sql database
// For the moment function is disabled while no proper reader is
// existing
return;
// do not write anything when no basic tables was created
if (!IsTablesExists()) return;
if (gDebug>1)
Info("WriteStreamerInfo","Saving streamer infos to database");
TList list;
TIter iter(gROOT->GetListOfStreamerInfo());
TStreamerInfo* info = 0;
while ((info = (TStreamerInfo*) iter()) !=0 ) {
Int_t uid = info->GetNumber();
if (fClassIndex->fArray[uid]) {
if (gDebug>1) Info("WriteStreamerInfo","Add %s",info->GetName());
list.Add(info);
}
}
if (list.GetSize()==0) return;
fClassIndex->fArray[0] = 2; //to prevent adding classes in TStreamerInfo::TagFile
WriteSpecialObject(sqlio::Ids_StreamerInfos, &list, "StreamerInfo", "StreamerInfos of this file");
fClassIndex->fArray[0] = 0; //to prevent adding classes in TStreamerInfo::TagFile
}
//______________________________________________________________________________
Bool_t TSQLFile::WriteSpecialObject(Long64_t keyid, TObject* obj, const char* name, const char* title)
{
// write special kind of object like streamer infos or file itself
// keys for that objects should exist in tables but not indicated in list of keys,
// therefore users can not get them with TDirectory::Get() method
DeleteKeyFromDB(keyid);
if (obj==0) return kTRUE;
Long64_t objid = StoreObjectInTables(keyid, obj, obj->IsA());
if (objid>0) {
TDatime now;
TKeySQL* key = new TKeySQL(this, keyid, objid,
name, title,
now.AsSQLString(), 1, obj->ClassName());
WriteKeyData(key);
delete key;
}
return (objid>0);
}
//______________________________________________________________________________
TObject* TSQLFile::ReadSpecialObject(Long64_t keyid, TObject* obj)
{
// Read data of special kind of objects
TKeySQL* key = 0;
StreamKeysForDirectory(this, kFALSE, keyid, &key);
if (key==0) return obj;
TBufferSQL2 buffer(TBuffer::kRead, this);
TClass* cl = 0;
void* res = buffer.SqlReadAny(key->GetDBKeyId(), key->GetDBObjId(), &cl, obj);
if ((cl==TSQLFile::Class()) && (res!=0) && (obj==this)) {
// name should not be preserved while name of database may be was changed
SetTitle(key->GetTitle());
}
delete key;
return (TObject*) res;
}
//______________________________________________________________________________
TList* TSQLFile::GetStreamerInfoList()
{
// Read back streamer infos from database
// List of streamer infos is always stored with key:id 0,
// which is not shown in normal keys list
// Method is not active while TStreamerElement and TStreamerBase has custom
// streamers, which can not be handled by TBufferSQL2.
// Hopefully, problem will be solved soon
return new TList;
if (gDebug>1)
Info("GetStreamerInfoList","Start reading of streamer infos");
TObject* obj = ReadSpecialObject(sqlio::Ids_StreamerInfos);
TList* list = dynamic_cast<TList*> (obj);
if (list==0) { delete obj; list = new TList; }
return list;
}
//______________________________________________________________________________
void TSQLFile::SaveToDatabase()
{
// save data which is not yet in Database
// Typically this is streamerinfos structures or
if (fSQL==0) return;
WriteStreamerInfo();
WriteHeader();
}
//______________________________________________________________________________
Int_t TSQLFile::StreamKeysForDirectory(TDirectory* dir, Bool_t doupdate, Long64_t specialkeyid, TKeySQL** specialkey)
{
// read keys for specified directory (when update == kFALSE)
// or update value for modified keys when update == kTRUE
// Returns number of succesfully read keys or -1 if error
if (dir==0) return -1;
const char* quote = SQLIdentifierQuote();
Long64_t dirid = dir->GetSeekDir();
TString sqlcmd;
sqlcmd.Form("SELECT * FROM %s%s%s WHERE %s%s%s=%lld",
quote, sqlio::KeysTable, quote,
quote, SQLDirIdColumn(), quote, dirid);
if (specialkeyid>=0) {
TString buf;
buf.Form(" AND %s%s%s=%lld", quote, SQLKeyIdColumn(), quote, specialkeyid);
sqlcmd += buf;
}
TSQLResult* res = SQLQuery(sqlcmd.Data(), 2);
if (res==0) return -1;
Int_t nkeys = res->GetRowCount();
for(Int_t nrow=0;nrow<nkeys;nrow++) {
TSQLRow* row = res->Next();
Long64_t keyid = sqlio::atol64((*row)[0]);
// Int_t dirid = atoi((*row)[1]);
Long64_t objid = sqlio::atol64((*row)[2]);
const char* keyname = (*row)[3];
const char* keytitle = (*row)[4];
const char* keydatime = (*row)[5];
Int_t cycle = atoi((*row)[6]);
const char* classname = (*row)[7];
if (gDebug>4)
cout << " Reading keyid = " << keyid << " name = " << keyname << endl;
if ((keyid>=sqlio::Ids_FirstKey) || (keyid==specialkeyid))
if (doupdate) {
TKeySQL* key = FindSQLKey(dir, keyid);
if (key==0) {
Error("StreamKeysForDirectory","Key with id %d not exist in list", keyid);
nkeys = -1; // this will finish execution
} else
if (key->IsKeyModified(keyname, keytitle, keydatime, cycle, classname))
UpdateKeyData(key);
} else {
TKeySQL* key = new TKeySQL(dir, keyid, objid,
keyname, keytitle,
keydatime, cycle, classname);
if (specialkey!=0)
{ *specialkey = key; nkeys = 1; }
else
dir->GetListOfKeys()->Add(key);
}
delete row;
}
delete res;
if (gDebug>4) {
Info("StreamKeysForDirectory","dir = %s numread = %d",dir->GetName(), nkeys);
dir->GetListOfKeys()->Print("*");
}
return nkeys;
}
//______________________________________________________________________________
void TSQLFile::InitSqlDatabase(Bool_t create)
{
// initialize sql database and correspondent structures
// identical to TFile::Init() function
Int_t len = gROOT->GetListOfStreamerInfo()->GetSize()+1;
if (len<5000) len = 5000;
fClassIndex = new TArrayC(len);
fClassIndex->Reset(0);
if (!create) {
Bool_t ok = ReadConfigurations();
// read data corresponding to TSQLFile
if (ok) {
ReadStreamerInfo();
// ok = (ReadSpecialObject(sqlio::Ids_TSQLFile, this) != 0);
}
// read list of keys
if (ok)
ok = StreamKeysForDirectory(this, kFALSE)>=0;
if (!ok) {
Error("InitSqlDatabase", "Cannot detect proper tabled in database. Close.");
Close();
delete fSQL;
fSQL = 0;
MakeZombie();
gDirectory = gROOT;
return;
}
}
gROOT->GetListOfFiles()->Add(this);
cd();
fNProcessIDs = 0;
TKey* key = 0;
TIter iter(fKeys);
while ((key = (TKey*)iter())!=0) {
if (!strcmp(key->GetClassName(),"TProcessID")) fNProcessIDs++;
}
fProcessIDs = new TObjArray(fNProcessIDs+1);
}
//______________________________________________________________________________
Bool_t TSQLFile::ReadConfigurations()
{
// read table configurations as special table
const char* quote = SQLIdentifierQuote();
TString sqlcmd;
sqlcmd.Form("SELECT * FROM %s%s%s",
quote, sqlio::ConfigTable, quote);
TSQLResult* res = SQLQuery(sqlcmd.Data(), 2);
if (res==0) return kFALSE;
// should be found, otherwise will be error
fSQLIOversion = 0;
Int_t lock = 0;
#define ReadIntCfg(name, target) \
if ((field.CompareTo(name, TString::kIgnoreCase)==0)) \
target = value.Atoi(); else
#define ReadBoolCfg(name, target) \
if ((field.CompareTo(name, TString::kIgnoreCase)==0)) \
target = value.CompareTo(sqlio::True, TString::kIgnoreCase)==0; else
#define ReadStrCfg(name, target) \
if ((field.CompareTo(name, TString::kIgnoreCase)==0)) \
target = value; else
for(Int_t nrow=0;nrow<res->GetRowCount();nrow++) {
TSQLRow* row = res->Next();
TString field = row->GetField(0);
TString value = row->GetField(1);
delete row;
ReadIntCfg(sqlio::cfg_Version, fSQLIOversion)
ReadBoolCfg(sqlio::cfg_UseSufixes, fUseSuffixes)
ReadIntCfg(sqlio::cfg_ArrayLimit, fArrayLimit)
ReadStrCfg(sqlio::cfg_TablesType, fTablesType)
ReadIntCfg(sqlio::cfg_UseTransactions, fUseTransactions)
ReadIntCfg(sqlio::cfg_UseIndexes, fUseIndexes)
ReadIntCfg(sqlio::cfg_ModifyCounter, fModifyCounter)
ReadIntCfg(sqlio::cfg_LockingMode, lock)
{
Error("ReadConfigurations","Invalid configuration field %s", field.Data());
fSQLIOversion = 0;
break;
}
}
delete res;
return (fSQLIOversion>0);
}
//______________________________________________________________________________
void TSQLFile::CreateBasicTables()
{
// Creates initial tables in database
// This is table with configurations and table with keys
// Function called once when first object is stored to the file.
TString sqlcmd;
const char* quote = SQLIdentifierQuote();
const char* vquote = SQLValueQuote();
if (SQLTestTable(sqlio::ConfigTable)) {
sqlcmd.Form("DROP TABLE %s%s%s", quote, sqlio::ConfigTable, quote);
SQLQuery(sqlcmd.Data());
}
sqlcmd.Form("CREATE TABLE %s%s%s (%s%s%s %s, %s%s%s %s)",
quote, sqlio::ConfigTable, quote,
quote, sqlio::CT_Field, quote, SQLSmallTextType(),
quote, sqlio::CT_Value, quote, SQLSmallTextType());
if ((fTablesType.Length()>0) && IsMySQL()) {
sqlcmd +=" TYPE=";
sqlcmd += fTablesType;
}
SQLQuery(sqlcmd.Data());
#define WrintCfg(name, type, value) \
{ \
sqlcmd.Form("INSERT INTO %s%s%s VALUES (%s%s%s, %s"type"%s)", \
quote, sqlio::ConfigTable, quote, \
vquote, name, vquote, \
vquote, value, vquote); \
SQLQuery(sqlcmd.Data()); \
}
WrintCfg(sqlio::cfg_Version, "%d", fSQLIOversion);
WrintCfg(sqlio::cfg_UseSufixes, "%s", fUseSuffixes ? sqlio::True : sqlio::False);
WrintCfg(sqlio::cfg_ArrayLimit, "%d", fArrayLimit);
WrintCfg(sqlio::cfg_TablesType, "%s", fTablesType.Data());
WrintCfg(sqlio::cfg_UseTransactions, "%d", fUseTransactions);
WrintCfg(sqlio::cfg_UseIndexes, "%d", fUseIndexes);
WrintCfg(sqlio::cfg_ModifyCounter, "%d", fModifyCounter);
WrintCfg(sqlio::cfg_LockingMode, "%d", kLockBusy);
// from this moment on user cannot change configurations
fCanChangeConfig = kFALSE;
if (SQLTestTable(sqlio::KeysTable)) {
sqlcmd.Form("DROP TABLE %s%s%s", quote, sqlio::KeysTable, quote);
SQLQuery(sqlcmd.Data());
}
sqlcmd.Form("CREATE TABLE %s%s%s (%s%s%s %s, %s%s%s %s, %s%s%s %s, %s%s%s %s, %s%s%s %s, %s%s%s %s, %s%s%s %s, %s%s%s %s)",
quote, sqlio::KeysTable, quote,
quote, SQLKeyIdColumn(), quote, SQLIntType(),
quote, SQLDirIdColumn(), quote, SQLIntType(),
quote, SQLObjectIdColumn(), quote, SQLIntType(),
quote, sqlio::KT_Name, quote, SQLSmallTextType(),
quote, sqlio::KT_Title, quote, SQLSmallTextType(),
quote, sqlio::KT_Datetime, quote, SQLDatetimeType(),
quote, sqlio::KT_Cycle, quote, SQLIntType(),
quote, sqlio::KT_Class, quote, SQLSmallTextType());
if ((fTablesType.Length()>0) && IsMySQL()) {
sqlcmd +=" TYPE=";
sqlcmd += fTablesType;
}
SQLQuery(sqlcmd.Data());
if (GetUseIndexes()>kIndexesNone) {
sqlcmd.Form("CREATE UNIQUE INDEX %s%s%s ON %s%s%s (%s%s%s)",
quote, sqlio::KeysTableIndex, quote,
quote, sqlio::KeysTable, quote,
quote, SQLKeyIdColumn(), quote);
SQLQuery(sqlcmd.Data());
}
}
//______________________________________________________________________________
void TSQLFile::IncrementModifyCounter()
{
if (!IsWritable()) {
Error("IncrementModifyCounter","Cannot update tables without write accsess");
return;
}
TString sqlcmd;
const char* quote = SQLIdentifierQuote();
const char* vquote = SQLValueQuote();
sqlcmd.Form("UPDATE %s%s%s SET %s%s%s=%d WHERE %s%s%s=%s%s%s",
quote, sqlio::ConfigTable, quote,
quote, sqlio::CT_Value, quote, ++fModifyCounter,
quote, sqlio::CT_Field, quote,
vquote, sqlio::cfg_ModifyCounter, vquote);
SQLQuery(sqlcmd.Data());
}
//______________________________________________________________________________
TString TSQLFile::MakeSelectQuery(TClass* cl)
{
// Produce SELECT statement which can be used to get all data
// of class cl in one SELECT statement
// This statement also can be used to create VIEW by command like
// mysql> CREATE VIEW TH1I_view AS $CLASSSELECT$
// Where $CLASSSELECT$ argument should be produced by call
// f->MakeSelectQuery(TH1I::Class());
// VIEWs supported by latest MySQL 5 and Oracle
TString res = "";
TSQLClassInfo* sqlinfo = RequestSQLClassInfo(cl);
if (sqlinfo==0) return res;
TString columns, tables;
Int_t tablecnt = 0;
if (!ProduceClassSelectQuery(cl->GetStreamerInfo(), sqlinfo, columns, tables, tablecnt))
return res;
res.Form("SELECT %s FROM %s", columns.Data(), tables.Data());
return res;
}
//______________________________________________________________________________
Bool_t TSQLFile::ProduceClassSelectQuery(TStreamerInfo* info,
TSQLClassInfo* sqlinfo,
TString& columns,
TString& tables,
Int_t& tablecnt)
{
// used by MakeClassSelectQuery method to add columns from table of
// class, specified by TStreamerInfo structure
if ((info==0) || (sqlinfo==0)) return kFALSE;
if (!sqlinfo->IsClassTableExist()) return kFALSE;
const char* quote = SQLIdentifierQuote();
TString table_syn;
table_syn.Form("t%d", ++tablecnt);
Bool_t start = tables.Length()==0;
TString buf;
if (start)
buf.Form("%s AS %s", sqlinfo->GetClassTableName(), table_syn.Data());
else
buf.Form(" LEFT JOIN %s AS %s USING(%s%s%s)",
sqlinfo->GetClassTableName(), table_syn.Data(),
quote, SQLObjectIdColumn(), quote);
tables += buf;
if (start)
columns.Form("%s.%s%s%s",table_syn.Data(), quote, SQLObjectIdColumn(), quote);
if (info->GetClass()==TObject::Class()) {
buf.Form(", %s.%s",table_syn.Data(), sqlio::TObjectUniqueId);
columns+=buf;
buf.Form(", %s.%s",table_syn.Data(), sqlio::TObjectBits);
columns+=buf;
buf.Form(", %s.%s",table_syn.Data(), sqlio::TObjectProcessId);
columns+=buf;
return kTRUE;
}
TIter iter(info->GetElements());
TStreamerElement* elem = 0;
while ((elem = (TStreamerElement*) iter()) != 0) {
Int_t coltype = TSQLStructure::DefineElementColumnType(elem, this);
TString colname = TSQLStructure::DefineElementColumnName(elem, this);
buf = "";
switch (coltype) {
case TSQLStructure::kColObject:
case TSQLStructure::kColObjectPtr:
case TSQLStructure::kColTString:
case TSQLStructure::kColSimple: {
buf.Form(", %s.%s%s%s",table_syn.Data(), quote, colname.Data(), quote);
columns+=buf;
break;
}
case TSQLStructure::kColParent: {
TClass* parentcl = elem->GetClassPointer();
ProduceClassSelectQuery(parentcl->GetStreamerInfo(),
RequestSQLClassInfo(parentcl),
columns, tables, tablecnt);
break;
}
case TSQLStructure::kColSimpleArray: {
for(Int_t n=0;n<elem->GetArrayLength();n++) {
colname = TSQLStructure::DefineElementColumnName(elem, this, n);
buf.Form(", %s.%s%s%s",table_syn.Data(), quote, colname.Data(), quote);
columns+=buf;
}
break;
}
} // switch
}
return (columns.Length()>0) && (tables.Length()>0);
}
//______________________________________________________________________________
Bool_t TSQLFile::IsTablesExists()
{
// Checks if main keys table is existing
return SQLTestTable(sqlio::KeysTable) && SQLTestTable(sqlio::ConfigTable);
}
//______________________________________________________________________________
Bool_t TSQLFile::IsWriteAccess()
{
// Checkis, if lock is free in configuration tables
return GetLocking()==kLockFree;
}
//______________________________________________________________________________
void TSQLFile::SetLocking(Int_t mode)
{
// Set locking mode for current database
TString sqlcmd;
const char* quote = SQLIdentifierQuote();
const char* vquote = SQLValueQuote();
sqlcmd.Form("UPDATE %s%s%s SET %s%s%s=%d WHERE %s%s%s=%s%s%s",
quote, sqlio::ConfigTable, quote,
quote, sqlio::CT_Value, quote, mode,
quote, sqlio::CT_Field, quote,
vquote, sqlio::cfg_LockingMode, vquote);
SQLQuery(sqlcmd.Data());
}
//______________________________________________________________________________
Int_t TSQLFile::GetLocking()
{
// Return current locking mode for that file
const char* quote = SQLIdentifierQuote();
const char* vquote = SQLValueQuote();
TString sqlcmd;
sqlcmd.Form("SELECT %s%s%s FROM %s%s%s WHERE %s%s%s=%s%s%s",
quote, sqlio::CT_Value, quote,
quote, sqlio::ConfigTable, quote,
quote, sqlio::CT_Field, quote,
vquote, sqlio::cfg_LockingMode, vquote);
TSQLResult* res = SQLQuery(sqlcmd.Data(), 1);
TSQLRow* row = (res==0) ? 0 : res->Next();
TString field = (row==0) ? "" : row->GetField(0);
delete row;
delete res;
if (field.Length()==0) return kLockFree;
return field.Atoi();
}
//______________________________________________________________________________
Bool_t TSQLFile::IsReadAccess()
{
// dummy, in future should check about read access to database
return kTRUE;
}
//______________________________________________________________________________
TSQLResult* TSQLFile::SQLQuery(const char* cmd, Int_t flag, Bool_t* ok)
{
// submits query to SQL server
// if flag==0, result is not interesting and will be deleted
// if flag==1, return result of submitted query
// if flag==2, results is may be necessary for long time
// Oracle plugin do not support working with several TSQLResult
// objects, therefore explicit deep copy will be produced
// If ok!=0, it will contains kTRUE is Query was successfull, otherwise kFALSE
if (fLogFile!=0)
*fLogFile << cmd << endl;
if (ok!=0) *ok = kFALSE;
if (fSQL==0) return 0;
if (gDebug>2) Info("SQLQuery",cmd);
fQuerisCounter++;
TSQLResult* res = fSQL->Query(cmd);
if (ok!=0) *ok = res!=0;
if (res==0) return 0;
if (flag==0) {
delete res;
return 0;
}
if ((flag==2) && IsOracle())
res = new TSQLResultCopy(res);
return res;
}
//______________________________________________________________________________
Bool_t TSQLFile::SQLApplyCommands(TObjArray* cmds)
{
// supplies set of commands to server
// Commands is stored as array of TObjString
if ((cmds==0) || (fSQL==0)) return kFALSE;
Bool_t ok = kTRUE;
TIter iter(cmds);
TObject* cmd= 0;
while ((cmd=iter())!=0) {
SQLQuery(cmd->GetName(),0,&ok);
if(!ok) break;
}
return ok;
}
//______________________________________________________________________________
TObjArray* TSQLFile::SQLTablesList(const char* searchtable)
{
// Produces list of tables, presented in database
// if searchtable!=0, looks only for this specific table
// list should be deleted by user afterwards
// P.S. Unfortunately, the TSQLServer::GetTables function is not
// correctly implemented for all cases,
// therefore special function is required.
if (fSQL==0) return 0;
TObjArray* res = 0;
if (IsOracle()) {
TString sqlcmd;
TString user = fUserName;
user.ToUpper();
sqlcmd.Form("SELECT object_name FROM ALL_OBJECTS WHERE object_type='TABLE' and owner='%s'",user.Data());
if (searchtable!=0) {
TString table = searchtable;
sqlcmd += Form(" and object_name='%s'",table.Data());
}
TSQLResult* tables = SQLQuery(sqlcmd.Data(), 1);
if (tables==0) return 0;
TSQLRow* row = tables->Next();
while (row!=0) {
const char* tablename = row->GetField(0);
if (strpbrk(tablename,"$=")==0) {
if (res==0) res = new TObjArray;
res->Add(new TObjString(tablename));
}
delete row;
row = tables->Next();
}
delete tables;
} else {
TSQLResult* tables = fSQL->GetTables(GetDataBaseName(), searchtable);
if (tables==0) return 0;
TSQLRow* row = tables->Next();
while (row!=0) {
if (res==0) res = new TObjArray;
res->Add(new TObjString(row->GetField(0)));
delete row;
row = tables->Next();
}
delete tables;
}
if (res!=0) res->SetOwner(kTRUE);
return res;
}
//______________________________________________________________________________
TObjArray* TSQLFile::SQLTableColumns(const char* tablename)
{
// produces list of columns for specified table
// list consist of TNamed objects with name and type for each column
// list should be deleted aftrewards
// P.S. Oracle plug-in do not provides types
if (fSQL==0) return 0;
TObjArray* res = 0;
if (IsOracle()) {
// TSQLResult* cols = fSQL->GetColumns(0, tablename, "");
// if (cols==0) return 0;
// for (Int_t n=0;n<cols->GetFieldCount();n++) {
// TNamed* col = new TNamed(cols->GetFieldName(n), "TYPE?");
// if (res==0) res = new TObjArray;
// res->Add(col);
// }
// delete cols;
TString sqlcmd;
const char* quote = SQLIdentifierQuote();
sqlcmd.Form("SELECT * FROM %s%s%s WHERE ROWNUM<2",
quote, tablename, quote);
TSQLResult* cols = SQLQuery(sqlcmd.Data(), 1);
if (cols==0) return 0;
for (Int_t n=0;n<cols->GetFieldCount();n++) {
TNamed* col = new TNamed(cols->GetFieldName(n), "TYPE?");
if (res==0) res = new TObjArray;
res->Add(col);
}
delete cols;
} else {
TSQLResult* cols = fSQL->GetColumns(GetDataBaseName(), tablename, "");
if (cols==0) return 0;
TSQLRow* row = cols->Next();
while (row!=0) {
TNamed* col = new TNamed(row->GetField(0), row->GetField(1));
if (res==0) res = new TObjArray;
res->Add(col);
delete row;
row = cols->Next();
}
delete cols;
}
return res;
}
//______________________________________________________________________________
Bool_t TSQLFile::SQLTestTable(const char* tablename)
{
// Test, if table of specified name exists
TObjArray* list = SQLTablesList(tablename);
Bool_t res = (list!=0);
delete list;
return res;
}
//______________________________________________________________________________
Long64_t TSQLFile::SQLMaximumValue(const char* tablename, const char* columnname)
{
// Returns maximum value, found in specified columnname of table tablename
// Column type should be numeric
if (fSQL==0) return -1;
if (fSQL==0) return -1;
if (gDebug>2)
Info("SQLMaximumValue","Requests for %s column %s", tablename, columnname);
const char* quote = SQLIdentifierQuote();
TString query;
query.Form("SELECT MAX(%s%s%s) FROM %s%s%s",
quote, columnname, quote,
quote, tablename, quote);
TSQLResult* res = SQLQuery(query.Data(), 1);
if (res==0) return -1;
TSQLRow* row = res->Next();
Long64_t maxid = -1;
if (row!=0)
if (row->GetField(0)!=0)
maxid = sqlio::atol64(row->GetField(0));
delete row;
delete res;
if (gDebug>2)
Info("SQLMaximumValue","Result = %lld",maxid);;
return maxid;
}
//______________________________________________________________________________
void TSQLFile::SQLDeleteAllTables()
{
// Delete all tables in database
TObjArray* tables = SQLTablesList();
if (tables==0) return;
TString sqlcmd;
const char* quote = SQLIdentifierQuote();
TIter iter(tables);
TObject* obj = 0;
while ((obj=iter())!=0) {
sqlcmd.Form("DROP TABLE %s%s%s", quote, obj->GetName(), quote);
SQLQuery(sqlcmd.Data());
}
delete tables;
}
//______________________________________________________________________________
Bool_t TSQLFile::SQLStartTransaction()
{
// Start SQL transaction.
Bool_t ok;
SQLQuery("START TRANSACTION", 0, &ok);
return ok;
}
//______________________________________________________________________________
Bool_t TSQLFile::SQLCommit()
{
// Commit SQL transaction
Bool_t ok;
SQLQuery("COMMIT", 0, &ok);
return ok;
}
//______________________________________________________________________________
Bool_t TSQLFile::SQLRollback()
{
// Rollback all SQL operations, done after start transaction
Bool_t ok;
SQLQuery("ROLLBACK", 0, &ok);
return ok;
}
//______________________________________________________________________________
void TSQLFile::DeleteKeyFromDB(Long64_t keyid)
{
// remove key with specified id from keys table
// also removes all objects data, related to this table
if (!IsWritable() || (keyid<0) || (fSQL==0)) return;
TString sqlcmd;
const char* quote = SQLIdentifierQuote();
sqlcmd.Form("SELECT MIN(%s%s%s), MAX(%s%s%s) FROM %s%s%s WHERE %s%s%s=%lld",
quote, SQLObjectIdColumn(), quote,
quote, SQLObjectIdColumn(), quote,
quote, sqlio::ObjectsTable, quote,
quote, SQLKeyIdColumn(), quote, keyid);
TSQLResult* res = SQLQuery(sqlcmd.Data(), 2);
TSQLRow* row = res==0 ? 0 : res->Next();
Long64_t minid(1), maxid(0);
if ((row!=0) && (row->GetField(0)!=0) && (row->GetField(1)!=0)) {
minid = sqlio::atol64(row->GetField(0));
maxid = sqlio::atol64(row->GetField(1));
}
delete row;
delete res;
// can be that object tables does not include any entry this that keyid
if (minid<=maxid) {
TObjArray* tables = SQLTablesList();
TIter iter(tables);
TObject* obj = 0;
while ((obj=iter())!=0) {
TString tablename = obj->GetName();
if ((tablename.CompareTo(sqlio::KeysTable,TString::kIgnoreCase)==0) ||
(tablename.CompareTo(sqlio::ObjectsTable,TString::kIgnoreCase)==0) ||
(tablename.CompareTo(sqlio::ConfigTable,TString::kIgnoreCase)==0)) continue;
TString query;
query.Form("DELETE FROM %s%s%s WHERE %s%s%s BETWEEN %lld AND %lld",
quote, tablename.Data(), quote,
quote, SQLObjectIdColumn(), quote,
minid, maxid);
SQLQuery(query.Data());
}
delete tables;
}
sqlcmd.Form("DELETE FROM %s%s%s WHERE %s%s%s=%lld", quote, sqlio::ObjectsTable, quote, quote, SQLKeyIdColumn(), quote, keyid);
SQLQuery(sqlcmd.Data());
sqlcmd.Form("DELETE FROM %s%s%s WHERE %s%s%s=%lld", quote, sqlio::KeysTable, quote, quote, SQLKeyIdColumn(), quote, keyid);
SQLQuery(sqlcmd.Data());
IncrementModifyCounter();
}
//______________________________________________________________________________
TKeySQL* TSQLFile::FindSQLKey(TDirectory* dir, Long64_t keyid)
{
// Search for TKeySQL object with specified keyid
if (dir==0) return 0;
TIter next(dir->GetListOfKeys());
TObject* obj = 0;
while ((obj = next())!=0) {
TKeySQL* key = dynamic_cast<TKeySQL*> (obj);
if (key!=0)
if (key->GetDBKeyId()==keyid) return key;
}
return 0;
}
//______________________________________________________________________________
Bool_t TSQLFile::WriteKeyData(TKeySQL* key)
{
// add entry into keys table
if ((fSQL==0) || (key==0)) return kFALSE;
if (!IsTablesExists()) CreateBasicTables();
TString sqlcmd;
const char* valuequote = SQLValueQuote();
const char* quote = SQLIdentifierQuote();
sqlcmd.Form("INSERT INTO %s%s%s VALUES (%lld, %lld, %lld, %s%s%s, %s%s%s, %s%s%s, %d, %s%s%s)",
quote, sqlio::KeysTable, quote,
key->GetDBKeyId(), key->GetDBDirId(), key->GetDBObjId(),
valuequote, key->GetName(), valuequote,
valuequote, key->GetTitle(), valuequote,
valuequote, key->GetDatime().AsSQLString(), valuequote,
key->GetCycle(),
valuequote, key->GetClassName(), valuequote);
Bool_t ok = kTRUE;
SQLQuery(sqlcmd.Data(), 0, &ok);
if (ok) IncrementModifyCounter();
return ok;
}
//______________________________________________________________________________
Bool_t TSQLFile::UpdateKeyData(TKeySQL* key)
{
if ((fSQL==0) || (key==0)) return kFALSE;
TString sqlcmd;
const char* valuequote = SQLValueQuote();
const char* quote = SQLIdentifierQuote();
sqlcmd.Form("UPDATE %s%s%s SET %s%s%s=%s, %s%s%s=%s, %s%s%s=%s, %s=%s WHERE %s%s%s=%lld",
quote, sqlio::KeysTable, quote,
sqlio::KT_Name, valuequote, key->GetName(), valuequote,
sqlio::KT_Title, valuequote, key->GetTitle(), valuequote,
sqlio::KT_Datetime, valuequote, key->GetDatime().AsSQLString(), valuequote,
sqlio::KT_Cycle, key->GetCycle(),
quote, SQLKeyIdColumn(), quote, key->GetDBKeyId());
Bool_t ok = kTRUE;
SQLQuery(sqlcmd.Data(), 0, &ok);
if (ok) IncrementModifyCounter();
return ok;
}
//______________________________________________________________________________
Long64_t TSQLFile::DefineNextKeyId()
{
// Returns next possible key identifier
Long64_t max = -1;
if (SQLTestTable(sqlio::KeysTable))
max = SQLMaximumValue(sqlio::KeysTable, SQLKeyIdColumn());
if (max<0) return sqlio::Ids_FirstKey;
return max+1;
}
//______________________________________________________________________________
TSQLClassInfo* TSQLFile::FindSQLClassInfo(const char* clname, Int_t version)
{
// return (if exists) TSQLClassInfo for specified class and version
if (fSQLClassInfos==0) return 0;
TIter iter(fSQLClassInfos);
TSQLClassInfo* info = 0;
while ((info = (TSQLClassInfo*) iter()) !=0 ) {
if (strcmp(info->GetName(), clname)==0)
if (info->GetClassVarsion()==version) return info;
}
return 0;
}
//______________________________________________________________________________
TSQLClassInfo* TSQLFile::RequestSQLClassInfo(const char* clname, Int_t version, Bool_t force)
{
// search in database tables for specified class and return TSQLClassInfo object
TSQLClassInfo* info = FindSQLClassInfo(clname, version);
if (!force && (info!=0)) return info;
if (fSQL==0) return 0;
if (info==0) info = new TSQLClassInfo(clname, version);
TObjArray* columns = 0;
// first check if class table is exist
if (SQLTestTable(info->GetClassTableName()))
columns = SQLTableColumns(info->GetClassTableName());
Bool_t israwtable = SQLTestTable(info->GetRawTableName());
info->SetTableStatus(columns, israwtable);
if (fSQLClassInfos==0) fSQLClassInfos = new TList;
fSQLClassInfos->Add(info);
return info;
}
//______________________________________________________________________________
TSQLClassInfo* TSQLFile::RequestSQLClassInfo(const TClass* cl, Bool_t force)
{
// search in database tables for specified class and return TSQLClassInfo object
return RequestSQLClassInfo(cl->GetName(), cl->GetClassVersion(), force);
}
//______________________________________________________________________________
Bool_t TSQLFile::SyncSQLClassInfo(TSQLClassInfo* sqlinfo, TObjArray* columns, Bool_t hasrawdata)
{
// Synchronise TSQLClassInfo structure with specified columns list and
// create/delete appropriate tables in database
if (sqlinfo==0) return kFALSE;
if (gDebug>2)
Info("SyncSQLClassInfo", sqlinfo->GetName());
const char* quote = SQLIdentifierQuote();
if (sqlinfo->IsClassTableExist() && (columns==0)) {
TString sqlcmd;
sqlcmd.Form("DROP TABLE %s%s%s", quote, sqlinfo->GetClassTableName(), quote);
SQLQuery(sqlcmd.Data());
sqlinfo->SetColumns(0);
}
if (!sqlinfo->IsClassTableExist() && (columns!=0)) {
TString sqlcmd;
sqlcmd.Form("CREATE TABLE %s%s%s (",
quote, sqlinfo->GetClassTableName(), quote);
TObjArray* newcolumns = new TObjArray();
TIter iter(columns);
TSQLColumnData* col;
Bool_t first = kTRUE;
Bool_t forcequote = IsOracle();
while ((col=(TSQLColumnData*)iter())!=0) {
if (!first) sqlcmd+=", "; else first = false;
const char* colname = col->GetName();
if ((strpbrk(colname,"[:.]<>")!=0) || forcequote) {
sqlcmd += quote;
sqlcmd += colname;
sqlcmd += quote;
sqlcmd += " ";
} else {
sqlcmd += colname,
sqlcmd += " ";
}
sqlcmd += col->GetType();
newcolumns->Add(new TNamed(col->GetName(), col->GetType()));
}
sqlcmd += ")";
if ((fTablesType.Length()>0) && IsMySQL()) {
sqlcmd +=" TYPE=";
sqlcmd += fTablesType;
}
SQLQuery(sqlcmd.Data());
sqlinfo->SetColumns(newcolumns);
if (GetUseIndexes()>kIndexesBasic) {
sqlcmd.Form("CREATE UNIQUE INDEX %s%s_Index%s ON %s%s%s (%s%s%s)",
quote, sqlinfo->GetClassTableName(), quote,
quote, sqlinfo->GetClassTableName(), quote,
quote, SQLObjectIdColumn(), quote);
SQLQuery(sqlcmd.Data());
}
}
if (hasrawdata && !sqlinfo->IsRawTableExist()) {
TString sqlcmd;
sqlcmd.Form("CREATE TABLE %s%s%s (%s%s%s %s, %s%s%s %s, %s %s, %s %s)",
quote, sqlinfo->GetRawTableName(), quote,
quote, SQLObjectIdColumn(), quote, SQLIntType(),
quote, SQLRawIdColumn(), quote, SQLIntType(),
sqlio::BT_Field, SQLSmallTextType(),
sqlio::BT_Value, SQLSmallTextType());
if ((fTablesType.Length()>0) && IsMySQL()) {
sqlcmd +=" TYPE=";
sqlcmd += fTablesType;
}
SQLQuery(sqlcmd.Data());
sqlinfo->SetRawExist(kTRUE);
if (GetUseIndexes()>kIndexesClass) {
sqlcmd.Form("CREATE UNIQUE INDEX %s%s_Index%s ON %s%s%s (%s%s%s, %s%s%s)",
quote, sqlinfo->GetRawTableName(), quote,
quote, sqlinfo->GetRawTableName(), quote,
quote, SQLObjectIdColumn(), quote,
quote, SQLRawIdColumn(), quote);
SQLQuery(sqlcmd.Data());
}
}
return kTRUE;
}
//______________________________________________________________________________
Bool_t TSQLFile::VerifyLongStringTable()
{
// Checks that table for big strings is exists
// If not, will be created
if (fSQL==0) return kFALSE;
if (SQLTestTable(sqlio::StringsTable)) return kTRUE;
const char* quote = SQLIdentifierQuote();
TString sqlcmd;
sqlcmd.Form("CREATE TABLE %s (%s%s%s %s, %s%s%s %s, %s %s)",
sqlio::StringsTable,
quote, SQLObjectIdColumn(), quote, SQLIntType(),
quote, SQLStrIdColumn(), quote, SQLIntType(),
sqlio::ST_Value, SQLBigTextType());
if (fTablesType.Length()>0) {
sqlcmd +=" TYPE=";
sqlcmd += fTablesType;
}
SQLQuery(sqlcmd.Data());
return kTRUE;
}
//______________________________________________________________________________
TString TSQLFile::CodeLongString(Long64_t objid, Int_t strid)
{
// produces id which will be placed in column instead of string itself
TString res;
res.Form("%s %lld %s %d %s", sqlio::LongStrPrefix, objid, sqlio::LongStrPrefix, strid, sqlio::LongStrPrefix);
return res;
}
//______________________________________________________________________________
Int_t TSQLFile::IsLongStringCode(Long64_t objid, const char* value)
{
// checks if this is long string code
// returns 0, if not or string id
if (value==0) return 0;
if (strlen(value)<strlen(sqlio::LongStrPrefix)*3+6) return 0;
if (strstr(value, sqlio::LongStrPrefix)!=value) return 0;
value+=strlen(sqlio::LongStrPrefix);
if (*value++!=' ') return 0;
TString s_strid, s_objid;
if ((*value<'1') || (*value>'9')) return 0;
do {
s_objid.Append(*value++);
} while ((*value!=0) && (*value>='0') && (*value<='9'));
if (*value++ != ' ') return 0;
if ((*value==0) || (strstr(value, sqlio::LongStrPrefix)!=value)) return 0;
value+=strlen(sqlio::LongStrPrefix);
if (*value++!=' ') return 0;
if ((*value<'1') || (*value>'9')) return 0;
do {
s_strid.Append(*value++);
} while ((*value!=0) && (*value>='0') && (*value<='9'));
if (*value++!=' ') return 0;
if ((*value==0) || (strcmp(value, sqlio::LongStrPrefix)!=0)) return 0;
Long64_t objid2 = sqlio::atol64(s_objid.Data());
if (objid2!=objid) return 0;
return atoi(s_strid.Data());
}
//______________________________________________________________________________
Bool_t TSQLFile::GetLongString(Long64_t objid, Int_t strid, TString& value)
{
// returns value of string, extracted from special table,
// where long strings are stored
if (!SQLTestTable(sqlio::StringsTable)) return kFALSE;
TString cmd;
const char* quote = SQLIdentifierQuote();
cmd.Form("SELECT %s FROM %s%s%s WHERE %s%s%s=%lld AND %s%s%s=%d",
sqlio::ST_Value,
quote, sqlio::StringsTable, quote,
quote, SQLObjectIdColumn(), quote, objid,
quote, SQLStrIdColumn(), quote, strid);
TSQLResult* res = SQLQuery(cmd.Data(), 1);
if (res==0) return kFALSE;
TSQLRow* row = res->Next();
if (row==0) { delete res; return kFALSE; }
value = row->GetField(0);
delete row;
delete res;
return kTRUE;
}
//______________________________________________________________________________
Long64_t TSQLFile::VerifyObjectTable()
{
// Checks that objects table is exists
// If not, table will be created
// Returns maximum value for existing objects id
if (fSQL==0) return -1;
Long64_t maxid = -1;
if (gDebug>2)
Info("VerifyObjectTable", "Checks if object table is there");
if (SQLTestTable(sqlio::ObjectsTable))
maxid = SQLMaximumValue(sqlio::ObjectsTable, SQLObjectIdColumn());
else {
TString sqlcmd;
const char* quote = SQLIdentifierQuote();
sqlcmd.Form("CREATE TABLE %s%s%s (%s%s%s %s, %s%s%s %s, %s%s%s %s, %s%s%s %s)",
quote, sqlio::ObjectsTable, quote,
quote, SQLKeyIdColumn(), quote, SQLIntType(),
quote, SQLObjectIdColumn(), quote, SQLIntType(),
quote, sqlio::OT_Class, quote, SQLSmallTextType(),
quote, sqlio::OT_Version, quote, SQLIntType());
if ((fTablesType.Length()>0) && IsMySQL()) {
sqlcmd +=" TYPE=";
sqlcmd += fTablesType;
}
SQLQuery(sqlcmd.Data());
if (GetUseIndexes()>kIndexesNone) {
sqlcmd.Form("CREATE UNIQUE INDEX %s%s%s ON %s%s%s (%s%s%s)",
quote, sqlio::ObjectsTableIndex, quote,
quote, sqlio::ObjectsTable, quote,
quote, SQLObjectIdColumn(), quote);
SQLQuery(sqlcmd.Data());
}
}
return maxid;
}
//______________________________________________________________________________
Bool_t TSQLFile::SQLObjectInfo(Long64_t objid, TString& clname, Version_t &version)
{
// Read from objects table data for specified objectid
if (fSQL==0) return kFALSE;
TString sqlcmd;
const char* quote = SQLIdentifierQuote();
sqlcmd.Form("SELECT %s%s%s, %s%s%s FROM %s%s%s WHERE %s%s%s=%lld",
quote, sqlio::OT_Class, quote,
quote, sqlio::OT_Version, quote,
quote, sqlio::ObjectsTable, quote,
quote, SQLObjectIdColumn(), quote, objid);
TSQLResult* res = SQLQuery(sqlcmd.Data(), 1);
if (res==0) return kFALSE;
TSQLRow* row = res->Next();
if (row!=0) {
clname = row->GetField(0);
version = atoi(row->GetField(1));
}
delete row;
delete res;
return row!=0;
}
//______________________________________________________________________________
TObjArray* TSQLFile::SQLObjectsInfo(Long64_t keyid)
{
// Produce array of TSQLObjectInfo objects for all objects, belong to that key
// Array should be deleted by calling function afterwards
if (fSQL==0) return 0;
TString sqlcmd;
const char* quote = SQLIdentifierQuote();
sqlcmd.Form("SELECT %s%s%s, %s%s%s, %s%s%s FROM %s%s%s WHERE %s%s%s=%lld ORDER BY %s%s%s",
quote, SQLObjectIdColumn(), quote,
quote, sqlio::OT_Class, quote,
quote, sqlio::OT_Version, quote,
quote, sqlio::ObjectsTable, quote,
quote, SQLKeyIdColumn(), quote, keyid,
quote, SQLObjectIdColumn(), quote);
TSQLResult* res = SQLQuery(sqlcmd.Data(), 1);
if (res==0) return 0;
TObjArray* arr = 0;
TSQLRow* row = 0;
while ((row = res->Next()) != 0) {
Long64_t objid = atoi(row->GetField(0));
const char* clname = row->GetField(1);
Int_t version = atoi(row->GetField(2));
TSQLObjectInfo* info = new TSQLObjectInfo(objid, clname, version);
if (arr==0) arr = new TObjArray();
arr->Add(info);
delete row;
}
delete res;
return arr;
}
//______________________________________________________________________________
TSQLResult* TSQLFile::GetNormalClassData(Long64_t objid, TSQLClassInfo* sqlinfo)
{
// Method return request result for specified objid from normal classtable
if (!sqlinfo->IsClassTableExist()) return 0;
TString sqlcmd;
const char* quote = SQLIdentifierQuote();
sqlcmd.Form("SELECT * FROM %s%s%s WHERE %s%s%s=%lld",
quote, sqlinfo->GetClassTableName(), quote,
quote, SQLObjectIdColumn(), quote, objid);
return SQLQuery(sqlcmd.Data(), 2);
}
//______________________________________________________________________________
TSQLResult* TSQLFile::GetNormalClassDataAll(Long64_t minobjid, Long64_t maxobjid, TSQLClassInfo* sqlinfo)
{
if (!sqlinfo->IsClassTableExist()) return 0;
TString sqlcmd;
const char* quote = SQLIdentifierQuote();
sqlcmd.Form("SELECT * FROM %s%s%s WHERE %s%s%s BETWEEN %lld AND %lld ORDER BY %s%s%s",
quote, sqlinfo->GetClassTableName(), quote,
quote, SQLObjectIdColumn(), quote, minobjid, maxobjid,
quote, SQLObjectIdColumn(), quote);
return SQLQuery(sqlcmd.Data(), 2);
}
//______________________________________________________________________________
TSQLResult* TSQLFile::GetBlobClassData(Long64_t objid, TSQLClassInfo* sqlinfo)
{
// Method return request results for specified objid from _streamer_ classtable
if (sqlinfo->IsRawTableExist()) {
TString sqlcmd;
const char* quote = SQLIdentifierQuote();
sqlcmd.Form("SELECT %s, %s FROM %s%s%s WHERE %s%s%s=%lld ORDER BY %s%s%s",
sqlio::BT_Field, sqlio::BT_Value,
quote, sqlinfo->GetRawTableName(), quote,
quote, SQLObjectIdColumn(), quote, objid,
quote, SQLRawIdColumn(), quote);
return SQLQuery(sqlcmd.Data(), 2);
}
return 0;
}
//______________________________________________________________________________
TSQLObjectData* TSQLFile::GetObjectClassData(Long64_t objid, TSQLClassInfo* sqlinfo)
{
// Get data for specified object from particular class table
// Returns TSQLObjectData object, which contains one row from
// normal class table and data from _streamer_ table.
// TSQLObjectData object used later in TBufferSQL2 to unstream object
if ((fSQL==0) || (objid<0) || (sqlinfo==0)) return 0;
if (gDebug>1)
Info("GetObjectClassData","Request for %s id = %lld", sqlinfo->GetName(), objid);
TSQLResult *classdata = GetNormalClassData(objid, sqlinfo);
TSQLResult *blobdata = GetBlobClassData(objid, sqlinfo);
if (gDebug>3)
Info("GetObjectClassData","normal = %x blobdata = %x", classdata, blobdata);
return new TSQLObjectData(sqlinfo, objid, classdata, 0, blobdata);
}
//______________________________________________________________________________
Long64_t TSQLFile::StoreObjectInTables(Long64_t keyid, const void* obj, const TClass* cl)
{
// Store object in database. Return stored object id or -1 if error
if (fSQL==0) return -1;
Long64_t objid = VerifyObjectTable();
if (objid<=0) objid = 1; else objid++;
TBufferSQL2 buffer(TBuffer::kWrite, this);
TSQLStructure* s = buffer.SqlWriteAny(obj, cl, objid);
if ((buffer.GetErrorFlag()>0) && (s!=0)) {
Error("StoreObjectInTables","Cannot convert object data to TSQLStructure");
objid = -1;
} else {
TObjArray cmds;
// here tables may be already created, therefore
// it should be protected by transactions operations
if (!s->ConvertToTables(this, keyid, &cmds)) {
Error("StoreObjectInTables","Cannot convert to SQL statements");
objid = -1;
} else {
Bool_t needcommit = kFALSE;
if (GetUseTransactions()==kTransactionsAuto) {
SQLStartTransaction();
needcommit = kTRUE;
}
if (!SQLApplyCommands(&cmds)) {
Error("StoreObject","Cannot correctly store object data in database");
objid = -1;
if (needcommit) SQLRollback();
} else {
if (needcommit) SQLCommit();
}
}
cmds.Delete();
}
return objid;
}
//______________________________________________________________________________
const char* TSQLFile::SQLCompatibleType(Int_t typ) const
{
// returns sql type name which is most closer to ROOT basic type
// typ should be from TStreamerInfo::constansts like TStreamerInfo::kInt
return (typ<0) || (typ>18) ? 0 : fBasicTypes[typ];
}
//______________________________________________________________________________
const char* TSQLFile::SQLIntType() const
{
// return SQL integer type
return SQLCompatibleType(TStreamerInfo::kInt);
}
ROOT page - Class index - Class Hierarchy - Top of the page
This page has been automatically generated. If you have any comments or suggestions about the page layout send a mail to ROOT support, or contact the developers with any questions or problems regarding ROOT.