////////////////////////////////////////////////////////////////////////// //////////////////////////// ROOT API //////////////////////////// ////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include "TList.h" #include "TString.h" #include "TSystem.h" #include "IDbi.hxx" #include "IDbiAsciiDbImporter.hxx" #include "IDbiConnection.hxx" #include "IDbiExceptionLog.hxx" #include "IDbiServices.hxx" #include #include using std::endl; #include ClassImp(COMET::IDbiConnection) // Definition of static data members // ********************************* // Definition of all member functions (static or otherwise) // ******************************************************* // // - ordered: ctors, dtor, operators then in alphabetical order. //..................................................................... // Throws COMET::EBadConnection() if can not make connection COMET::IDbiConnection::IDbiConnection(const std::string& url /* = "" */, const std::string& user /* = "" */, const std::string& password /* = "" */, int maxConnects) : fUrl(url.c_str()), fUser(user), fPassword(password), fUrlValidated(false), fNumConnectedStatements(0), fIsTemporary(true), fServer(0) { // // // Purpose: Default constructor fMaxConnectionAttempts = maxConnects; COMETTrace( "Creating COMET::IDbiConnection" << " "); if ( this->Open() ) { COMETInfo( "Successfully opened connection to: " << this->GetUrl() << " "); fUrlValidated = true; // Initialise the list existing supported tables. this->SetTableExists(); // If URL looks O.K., check that both client and server support prepared statements. if ( fUrlValidated ) { #if ROOT_VERSION_CODE >= ROOT_VERSION(5,15,9) if ( ! fServer->HasStatement() ) { #else if ( ! fServer->IsSupportStatement() ) { #endif COMETSevere( " This client does not support prepared statements." << " "); fUrlValidated = false; #if ROOT_VERSION_CODE >= ROOT_VERSION(5,15,9) } #else } #endif std::string serverInfo(fServer->ServerInfo()); if ( serverInfo < "4.1" ) { COMETSevere( "This MySQL server (" << serverInfo << ") does NOT support prepared statements." << " "); fUrlValidated = false; } if ( fUrlValidated ) { COMETInfo( "This client, and MySQL server (" << serverInfo << ") supports prepared statements." << " "); } else { COMETSevere( "\n" << "This version of MySQL does not support prepared statements.\n" << "\n" << "Please upgrade to MySQL (client and server) version 4.1 or greater \n" << "\n" << " "); } } } if ( ! fUrlValidated ) { COMETSevere( "FATAL: " << "Aborting due to above errors" << " "); throw COMET::EBadConnection(); } fDbName = fUrl.GetFile(); } //..................................................................... COMET::IDbiConnection::~IDbiConnection() { // // // Purpose: Destructor COMETTrace( "Destroying COMET::IDbiConnection" << " "); this->Close(true); } //..................................................................... ///\verbatim /// /// Purpose: Close server connection unless active (or always if forced) . /// /// Return: true if connection now closed. ///\endverbatim Bool_t COMET::IDbiConnection::Close(Bool_t force /* = false */ ) { this->ClearExceptionLog(); if ( this->IsClosed() ) return true; if ( fNumConnectedStatements ) { if ( ! force ) { COMETInfo( "Unable to close connection: " << this->GetUrl() << "; it still has " << fNumConnectedStatements << "active statements. " << " "); return false; } COMETInfo( "Closing connection: " << this->GetUrl() << "; even though it still has " << fNumConnectedStatements << " active statements. " << " "); } delete fServer; fServer = 0; COMETDebug( "Closed connection: " << this->GetUrl() << " "); return true; } //..................................................................... /// /// /// Purpose: Close idle connection. Idle means there are no active connections to this database. void COMET::IDbiConnection::CloseIdleConnection() { if ( fIsTemporary && fNumConnectedStatements == 0 ) this->Close(); } //..................................................................... ///\verbatim /// /// Purpose: Open if necessary and get a prepared statment. /// /// Return: Statement - Caller must take ownership. /// will be 0 if failure. ///\endverbatim TSQLStatement* COMET::IDbiConnection::CreatePreparedStatement(const std::string& sql) { TSQLStatement* stmt = 0; if ( ! this->Open() ) return stmt; stmt = fServer->Statement(sql.c_str()); if ( ! stmt ) { fExceptionLog.AddEntry(*fServer); } else stmt->EnableErrorOutput(false); return stmt; } //..................................................................... ///\verbatim /// /// Purpose: Open if necessary and get a TSQLServer. /// /// Return: Server ( = 0 if connection not open). /// /// WARNING: The server returned remains is being borrowed from the /// COMET::IDbiConnection and remains under its ownership and must /// not be deleted. However the caller must invoke the /// Connect() method on this COMET::IDbiConnection before borrowing /// it and must invoke the DisConnect() when it has finished /// using it to ensure the COMET::IDbiConnection does not close it /// prematurely i.e.:- /// /// void Demo(COMET::IDbiConnection* con) { /// con->Connect(); /// TSQLServer* server = con->GetServer(); /// // Do stuff /// con->DisConnect(); /// } ///\endverbatim TSQLServer* COMET::IDbiConnection::GetServer() { if ( ! this->Open() ) return 0; return fServer; } //..................................................................... ///\verbatim /// Don't ask me why TUrl::GetUrl() is non-const, just accept that it is! /// /// Note: This function returns a reference to a shared string; use the /// value or make a copy of it before any subsequent call to this /// function. ///\endverbatim const std::string& COMET::IDbiConnection::GetUrl() const { static std::string url; url = const_cast(this)->fUrl.GetUrl(); return url; } //..................................................................... /// /// Purpose: Open connection if necessary. /// Bool_t COMET::IDbiConnection::Open() { this->ClearExceptionLog(); if ( ! this->IsClosed() ) return true; if ( ! fUrl.IsValid() ) { ostringstream oss; oss << "Unable to open connection: URL '" << fUrl.GetUrl() << "' is invalid"; COMETSevere( oss.str() << " "); fExceptionLog.AddEntry(oss.str()); return false; } // Make several attempts (or more if URL is known to be O.K.) to open connection. int maxAttempt = fUrlValidated ? 100: fMaxConnectionAttempts ; for (int attempt = 1; attempt <= maxAttempt; attempt++) { fServer = TSQLServer::Connect(fUrl.GetUrl(),fUser.c_str(),fPassword.c_str()); if ( ! fServer ) { ostringstream oss; oss << "Failing to open: " << fUrl.GetUrl() << " for user " << fUser << " and password " << fPassword << " (attempt " << attempt << ")"; fExceptionLog.AddEntry(oss.str()); if ( fMaxConnectionAttempts > attempt ){ if ( attempt == 1 ) { COMETSevere( " retrying ... " << " "); } COMETLog(" Waiting "<Sleep(attempt*1000); } } else { fServer->EnableErrorOutput(false); if ( attempt > 1 ) COMETWarn( "... Connection opened on attempt " << attempt << " "); COMETDebug( "Successfully opened connection to: " << fUrl.GetUrl() << " "); // If this is an ASCII database, populate it and make the connection permanent // unless even ASCII DB connections are temporary. TString ascii_file = fUrl.GetAnchor(); if ( ascii_file.IsNull() ) return true; gSystem->Setenv("DBI_CATALOGUE_PATH",gSystem->DirName(fUrl.GetAnchor())); COMET::IDbiAsciiDbImporter importer(ascii_file,fServer); const COMET::IDbiExceptionLog& el(importer.GetExceptionLog()); if ( ! el.IsEmpty() ) { COMETSevere( "Failed to populate ASCII database from " << fUrl.GetUrl() << "\n" << el << " "); delete fServer; fServer = 0; return false; } fIsTemporary = COMET::IDbiServices::AsciiDBConectionsTemporary(); // Add imported tables names. const std::list tableNames(importer.GetImportedTableNames()); std::list::const_iterator itr(tableNames.begin()), itrEnd(tableNames.end()); while ( itr != itrEnd ) { this->SetTableExists(*itr); ++itr; } return true; } } COMETSevere( "... Failed to open a connection to: " << fUrl.GetUrl() << " for user " << fUser << " and pwd " << fPassword << " "); return false; }// // Purpose: Record an exception that has occurred while a client was using its TSQLServer. //..................................................................... ///\verbatim /// /// Purpose: Print all warning at supplied Msg log level. /// /// Return: kTRUE if warnings have occurred ///\endverbatim Bool_t COMET::IDbiConnection::PrintExceptionLog(Int_t level) const { switch(level) { case ICOMETLog::QuietLevel: break; case ICOMETLog::LogLevel: COMETInfo(fExceptionLog); break; case ICOMETLog::InfoLevel: COMETInfo(fExceptionLog); break; case ICOMETLog::VerboseLevel: COMETVerbose(fExceptionLog); break; } return fExceptionLog.Size() != 0; } //..................................................................... /// /// Purpose: Record an exception that has occurred while a client was using its TSQLServer. void COMET::IDbiConnection::RecordException() { fExceptionLog.AddEntry(*fServer); } //..................................................................... ///\verbatim /// Purpose: Add name to list of existing tables (necessary when creating tables) /// /// Note: If tableName is null refresh list from the database. ///\enbdverbatim void COMET::IDbiConnection::SetTableExists(const std::string& tableName) { if ( tableName == "" ) { TSQLStatement* stmt = CreatePreparedStatement("show tables"); if ( stmt ) { if (stmt->Process()) { stmt->StoreResult(); while (stmt->NextResultRow()) { std::string tn(stmt->GetString(0)); this->SetTableExists(tn); } } delete stmt; stmt = 0; } } else { if ( ! this->TableExists(tableName) ) { fExistingTableList += ",'"; fExistingTableList += tableName; fExistingTableList += "'"; } } } //..................................................................... /// Purpose: Check to see table exists in connected database. Bool_t COMET::IDbiConnection::TableExists(const std::string& tableName) const { // std::string test("'"); test += tableName; test += "'"; return fExistingTableList.find(test) != std::string::npos; }