history_sql.cxx

Go to the documentation of this file.
00001 /********************************************************************\
00002 
00003   Name:         history_sql.cxx
00004   Created by:   Konstantin Olchanski
00005 
00006   Contents:     Interface class for writing MIDAS history data to SQL databases
00007 
00008   $Id$
00009 
00010 \********************************************************************/
00011 
00012 #include <stdio.h>
00013 #include <stdint.h>
00014 #include <stdlib.h>
00015 #include <string.h>
00016 #include <ctype.h>
00017 #include <assert.h>
00018 
00019 #include <vector>
00020 #include <string>
00021 
00022 ////////////////////////////////////////
00023 //         MIDAS includes             //
00024 ////////////////////////////////////////
00025 
00026 #include "midas.h"
00027 #include "history.h"
00028 
00029 ////////////////////////////////////////
00030 //           helper stuff             //
00031 ////////////////////////////////////////
00032 
00033 #define STRLCPY(dst, src) strlcpy((dst), (src), sizeof(dst))
00034 #define FREE(x) { if (x) free(x); (x) = NULL; }
00035 
00036 ////////////////////////////////////////
00037 // Definitions extracted from midas.c //
00038 ////////////////////////////////////////
00039 
00040 /********************************************************************/
00041 /* data type sizes */
00042 static const int tid_size[] = {
00043    0,                           /* tid == 0 not defined                               */
00044    1,                           /* TID_BYTE      unsigned byte         0       255    */
00045    1,                           /* TID_SBYTE     signed byte         -128      127    */
00046    1,                           /* TID_CHAR      single character      0       255    */
00047    2,                           /* TID_WORD      two bytes             0      65535   */
00048    2,                           /* TID_SHORT     signed word        -32768    32767   */
00049    4,                           /* TID_DWORD     four bytes            0      2^32-1  */
00050    4,                           /* TID_INT       signed dword        -2^31    2^31-1  */
00051    4,                           /* TID_BOOL      four bytes bool       0        1     */
00052    4,                           /* TID_FLOAT     4 Byte float format                  */
00053    8,                           /* TID_DOUBLE    8 Byte float format                  */
00054    1,                           /* TID_BITFIELD  8 Bits Bitfield    00000000 11111111 */
00055    0,                           /* TID_STRING    zero terminated string               */
00056    0,                           /* TID_ARRAY     variable length array of unkown type */
00057    0,                           /* TID_STRUCT    C structure                          */
00058    0,                           /* TID_KEY       key in online database               */
00059    0                            /* TID_LINK      link in online database              */
00060 };
00061 
00062 /* data type names */
00063 static const char *tid_name[] = {
00064    "NULL",
00065    "BYTE",
00066    "SBYTE",
00067    "CHAR",
00068    "WORD",
00069    "SHORT",
00070    "DWORD",
00071    "INT",
00072    "BOOL",
00073    "FLOAT",
00074    "DOUBLE",
00075    "BITFIELD",
00076    "STRING",
00077    "ARRAY",
00078    "STRUCT",
00079    "KEY",
00080    "LINK"
00081 };
00082 
00083 // SQL types
00084 static const char *sql_type_pgsql[] = {
00085    "xxxINVALIDxxxNULL", // TID_NULL
00086    "SMALLINT",  // MYSQL "TINYINT SIGNED", // TID_BYTE
00087    "SMALLINT",  // MYSQL "TINYINT UNSIGNED",  // TID_SBYTE
00088    "CHAR(1)",   // TID_CHAR
00089    "SMALLINT",  // MYSQL "SMALLINT UNSIGNED ", // TID_WORD
00090    "SMALLINT",  // MYSQL "SMALLINT SIGNED ", // TID_SHORT
00091    "INTEGER",   // MYSQL "INT UNSIGNED ", // TID_DWORD
00092    "INTEGER",   // MYSQL "INT SIGNED ", // TID_INT
00093    "BOOL",      // TID_BOOL
00094    "FLOAT(53)", // MYSQL "DOUBLE" TID_FLOAT
00095    "FLOAT(53)", // MYSQL "DOUBLE" TID_DOUBLE
00096    "SMALLINT",  // MYSQL "TINYINT UNSIGNED", // TID_BITFIELD
00097    "VARCHAR",   // TID_STRING
00098    "xxxINVALIDxxxARRAY",
00099    "xxxINVALIDxxxSTRUCT",
00100    "xxxINVALIDxxxKEY",
00101    "xxxINVALIDxxxLINK"
00102 };
00103 
00104 static const char *sql_type_mysql[] = {
00105    "xxxINVALIDxxxNULL", // TID_NULL
00106    "tinyint unsigned",  // TID_BYTE
00107    "tinyint",           // TID_SBYTE
00108    "char",              // TID_CHAR
00109    "smallint unsigned", // TID_WORD
00110    "smallint",          // TID_SHORT
00111    "integer unsigned",  // TID_DWORD
00112    "integer",           // TID_INT
00113    "tinyint",           // TID_BOOL
00114    "float",             // TID_FLOAT
00115    "double",            // TID_DOUBLE
00116    "tinyint unsigned",  // TID_BITFIELD
00117    "VARCHAR",           // TID_STRING
00118    "xxxINVALIDxxxARRAY",
00119    "xxxINVALIDxxxSTRUCT",
00120    "xxxINVALIDxxxKEY",
00121    "xxxINVALIDxxxLINK"
00122 };
00123 
00124 ////////////////////////////////////////
00125 //    Handling of data types          //
00126 ////////////////////////////////////////
00127 
00128 static const char **sql_type = NULL;
00129 
00130 static const char* midasTypeName(int tid)
00131 {
00132    assert(tid>=0);
00133    assert(tid<15);
00134    return tid_name[tid];
00135 }
00136 
00137 static const char* midas2sqlType(int tid)
00138 {
00139    assert(tid>=0);
00140    assert(tid<15);
00141    return sql_type[tid];
00142 }
00143 
00144 static int sql2midasType(const char* name)
00145 {
00146    for (int tid=0; tid<15; tid++)
00147       if (strcasecmp(name, sql_type[tid])==0)
00148          return tid;
00149    printf("sql2midasType: Cannot convert SQL data type \'%s\' to a MIDAS data type!\n", name);
00150    return 0;
00151 }
00152 
00153 static bool isCompatible(int tid, const char* sqlType)
00154 {
00155    if (0)
00156       printf("compare types midas \'%s\'=\'%s\' and sql \'%s\'\n", midasTypeName(tid), midas2sqlType(tid), sqlType);
00157 
00158    if (sql2midasType(sqlType) == tid)
00159       return true;
00160 
00161    if (strcasecmp(midas2sqlType(tid), sqlType) == 0)
00162       return true;
00163 
00164    // permit writing FLOAT into DOUBLE
00165    if (tid==TID_FLOAT && strcmp(sqlType, "double")==0)
00166       return true;
00167 
00168    // T2K quirk!
00169    // permit writing BYTE into signed tinyint
00170    if (tid==TID_BYTE && strcmp(sqlType, "tinyint")==0)
00171       return true;
00172 
00173    // T2K quirk!
00174    // permit writing WORD into signed tinyint
00175    if (tid==TID_WORD && strcmp(sqlType, "tinyint")==0)
00176       return true;
00177 
00178    return false;
00179 }
00180 
00181 /////////////////////////////////////////////////
00182 //    Base class for access to SQL functions   //
00183 /////////////////////////////////////////////////
00184 
00185 class SqlBase
00186 {
00187 public:
00188   virtual int SetDebug(int debug) = 0;
00189   virtual int Connect(const char* dsn = 0) = 0;
00190   virtual int Disconnect() = 0;
00191   virtual bool IsConnected() = 0;
00192   virtual int Exec(const char* sql) = 0;
00193   virtual int GetNumRows() = 0;
00194   virtual int GetNumColumns() = 0;
00195   virtual int Fetch() = 0;
00196   virtual int Done() = 0;
00197   virtual int ListTables(std::vector<std::string> *plist) = 0;
00198   virtual int ListColumns(const char* table, std::vector<std::string> *plist) = 0;
00199   virtual const char* GetColumn(int icol) = 0;
00200   virtual ~SqlBase() { }; // virtual dtor
00201 };
00202 
00203 ////////////////////////////////////////////////////////////////////
00204 //   SqlDebug: for debugging: write all SQL commands to stdout   //
00205 ////////////////////////////////////////////////////////////////////
00206 
00207 class SqlDebug: public SqlBase
00208 {
00209 public:
00210    FILE *fp;
00211    bool fIsConnected;
00212    int fDebug;
00213    
00214 public:
00215    
00216    SqlDebug() // ctor
00217    {
00218       fp = NULL;
00219       fIsConnected = false;
00220    }
00221 
00222    ~SqlDebug() // dtor
00223    {
00224       if (fp)
00225          fclose(fp);
00226       fp = NULL;
00227    }
00228 
00229    int SetDebug(int debug)
00230    {
00231       int old_debug = fDebug;
00232       fDebug = debug;
00233       return old_debug;
00234    }
00235 
00236    int Connect(const char* filename = NULL)
00237    {
00238       if (!filename)
00239          filename = "/dev/fd/1";
00240       fp = fopen(filename, "w");
00241       assert(fp);
00242       sql_type = sql_type_mysql;
00243       fIsConnected = true;
00244       return DB_SUCCESS;
00245    }
00246 
00247    int Exec(const char* sql)
00248    {
00249       fprintf(fp, "%s\n", sql);
00250       return DB_SUCCESS;
00251    }
00252    
00253    int Disconnect()
00254    {
00255       // do nothing
00256       fIsConnected = false;
00257       return DB_SUCCESS;
00258    }
00259    
00260    bool IsConnected()
00261    {
00262       return fIsConnected;
00263    }
00264 
00265    int GetNumRows() { return DB_SUCCESS; }
00266    int GetNumColumns() { return DB_SUCCESS; }
00267    int Fetch() { return DB_NO_MORE_SUBKEYS; }
00268    int Done() { return DB_SUCCESS; }
00269    int ListTables(std::vector<std::string> *plist) { return DB_SUCCESS; };
00270    int ListColumns(const char* table, std::vector<std::string> *plist) { return DB_SUCCESS; };
00271    const char* GetColumn(int icol) { return NULL; };
00272 };
00273 
00274 #ifdef HAVE_ODBC
00275 
00276 ////////////////////////////////////////
00277 //          ODBC includes             //
00278 ////////////////////////////////////////
00279 
00280 // MIDAS defines collide with ODBC
00281 
00282 #define DWORD DWORD_xxx
00283 #define BOOL  BOOL_xxx
00284 
00285 #include <sql.h>
00286 #include <sqlext.h>
00287 #include <sqltypes.h>
00288 
00289 //////////////////////////////////////////
00290 //   SqlODBC: SQL access through ODBC   //
00291 //////////////////////////////////////////
00292 
00293 class SqlODBC: public SqlBase
00294 {
00295 public:
00296    bool fIsConnected;
00297 
00298    std::string fDSN;
00299 
00300    int fDebug;
00301 
00302    SQLHENV  fEnv;
00303    SQLHDBC  fDB;
00304    SQLHSTMT fStmt;
00305 
00306    SqlODBC(); // ctor
00307    ~SqlODBC(); // dtor
00308 
00309    int SetDebug(int debug)
00310    {
00311       int old_debug = fDebug;
00312       fDebug = debug;
00313       return old_debug;
00314    }
00315 
00316    int Connect(const char* dsn);
00317    int Disconnect();
00318    bool IsConnected();
00319 
00320    int ListTables(std::vector<std::string> *plist);
00321    int ListColumns(const char* table_name, std::vector<std::string> *plist);
00322 
00323    int Exec(const char* sql);
00324 
00325    int GetNumRows();
00326    int GetNumColumns();
00327    int Fetch();
00328    const char* GetColumn(int icol);
00329    int Done();
00330 
00331 protected:
00332    void ReportErrors(const char* from, const char* sqlfunc, int status);
00333    int DecodeError();
00334 };
00335 
00336 SqlODBC::SqlODBC() // ctor
00337 {
00338    fIsConnected = false;
00339    fDebug = 0;
00340 }
00341 
00342 SqlODBC::~SqlODBC() // dtor
00343 {
00344    Disconnect();
00345 }
00346 
00347 int SqlODBC::Connect(const char* dsn)
00348 {
00349    if (fIsConnected)
00350       Disconnect();
00351 
00352    fDSN = dsn;
00353 
00354    int status = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &fEnv);
00355 
00356    if (!SQL_SUCCEEDED(status)) {
00357       cm_msg(MERROR, "SqlODBC::Connect", "SQLAllocHandle(SQL_HANDLE_ENV) error %d", status);
00358       return DB_FILE_ERROR;
00359    }
00360 
00361    status = SQLSetEnvAttr(fEnv,
00362                           SQL_ATTR_ODBC_VERSION, 
00363                           (void*)SQL_OV_ODBC2,
00364                           0); 
00365    if (!SQL_SUCCEEDED(status)) {
00366       cm_msg(MERROR, "SqlODBC::Connect", "SQLSetEnvAttr() error %d", status);
00367       SQLFreeHandle(SQL_HANDLE_ENV, fEnv);
00368       return DB_FILE_ERROR;
00369    }
00370 
00371    status = SQLAllocHandle(SQL_HANDLE_DBC, fEnv, &fDB); 
00372    if (!SQL_SUCCEEDED(status)) {
00373       cm_msg(MERROR, "SqlODBC::Connect", "SQLAllocHandle(SQL_HANDLE_DBC) error %d", status);
00374       SQLFreeHandle(SQL_HANDLE_ENV, fEnv);
00375       return DB_FILE_ERROR;
00376    }
00377 
00378    SQLSetConnectAttr(fDB, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
00379 
00380    if (0) {
00381       // connect to PgSQL database
00382       
00383       sql_type = sql_type_pgsql;
00384       status = SQLConnect(fDB, (SQLCHAR*) dsn, SQL_NTS,
00385                           (SQLCHAR*) "xxx", SQL_NTS,
00386                           (SQLCHAR*) "", SQL_NTS);
00387    }
00388    
00389    if (1) {
00390       // connect to MySQL database
00391       
00392       sql_type = sql_type_mysql;
00393       status = SQLConnect(fDB, (SQLCHAR*) dsn, SQL_NTS,
00394                           (SQLCHAR*) NULL, SQL_NTS,
00395                           (SQLCHAR*) NULL, SQL_NTS);
00396    }
00397 
00398    if ((status != SQL_SUCCESS) && (status != SQL_SUCCESS_WITH_INFO)) {
00399       SQLINTEGER    V_OD_err;
00400       SQLSMALLINT   V_OD_mlen;
00401       SQLCHAR       V_OD_stat[10]; // Status SQL
00402       SQLCHAR       V_OD_msg[200];
00403       
00404       SQLGetDiagRec(SQL_HANDLE_DBC, fDB, 1, V_OD_stat, &V_OD_err, V_OD_msg, 100, &V_OD_mlen);
00405       cm_msg(MERROR, "SqlODBC::Connect", "SQLConnect() error %d, %s (%d)", status, V_OD_msg,V_OD_err);
00406       SQLFreeHandle(SQL_HANDLE_ENV, fEnv);
00407       return DB_FILE_ERROR;
00408    }
00409 
00410    SQLAllocHandle(SQL_HANDLE_STMT, fDB, &fStmt);
00411 
00412    if (fDebug)
00413       cm_msg(MINFO, "SqlODBC::Connect", "Connected to ODBC database DSN \'%s\'", dsn);
00414 
00415    fIsConnected = true;
00416 
00417    return DB_SUCCESS;
00418 }
00419 
00420 int SqlODBC::Disconnect()
00421 {
00422    if (!fIsConnected)
00423       return DB_SUCCESS;
00424 
00425    SQLDisconnect(fDB);
00426 
00427    SQLFreeHandle(SQL_HANDLE_DBC,  fDB);
00428    SQLFreeHandle(SQL_HANDLE_STMT, fStmt);
00429    SQLFreeHandle(SQL_HANDLE_ENV,  fEnv);
00430 
00431    fIsConnected = false;
00432 
00433    return DB_SUCCESS;
00434 }
00435 
00436 bool SqlODBC::IsConnected()
00437 {
00438    return fIsConnected;
00439 }
00440 
00441 void SqlODBC::ReportErrors(const char* from, const char* sqlfunc, int status)
00442 {
00443    if (fDebug)
00444       printf("%s: %s error %d\n", from, sqlfunc, status);
00445 
00446    for (int i=1; ; i++) {
00447       SQLCHAR            state[10]; // Status SQL
00448       SQLINTEGER                 error;
00449       SQLCHAR              message[1024];
00450       SQLSMALLINT                mlen;
00451       
00452       status = SQLGetDiagRec(SQL_HANDLE_STMT,
00453                              fStmt,
00454                              i,
00455                              state,
00456                              &error,
00457                              message,
00458                              sizeof(message),
00459                              &mlen);
00460       
00461       if (status == SQL_NO_DATA)
00462          break;
00463 
00464       if (!SQL_SUCCEEDED(status)) {
00465          cm_msg(MERROR, "SqlODBC::ReportErrors", "SQLGetDiagRec() error %d", status);
00466          break;
00467       }
00468       
00469       if (1 || (error != 1060) && (error != 1050)) {
00470          if (fDebug)
00471             printf("%s: %s error: state: \'%s\', message: \'%s\', native error: %d\n", from, sqlfunc, state, message, (int)error);
00472          cm_msg(MERROR, from, "%s error: state: \'%s\', message: \'%s\', native error: %d", sqlfunc, state, message, (int)error);
00473       }
00474    }
00475 }
00476 
00477 int SqlODBC::DecodeError()
00478 {
00479    // returns:
00480    // DB_SUCCESS
00481    // DB_NO_KEY
00482    // DB_KEY_EXIST
00483 
00484    for (int i=1; ; i++) {
00485       SQLCHAR            state[10]; // Status SQL
00486       SQLINTEGER                 error;
00487       SQLCHAR              message[1024];
00488       SQLSMALLINT                mlen;
00489 
00490       error = 0;
00491       
00492       int status = SQLGetDiagRec(SQL_HANDLE_STMT,
00493                                  fStmt,
00494                                  i,
00495                                  state,
00496                                  &error,
00497                                  message,
00498                                  sizeof(message),
00499                                  &mlen);
00500       
00501       if (status == SQL_NO_DATA)
00502          return DB_SUCCESS;
00503 
00504       if (error==1146)
00505          return DB_NO_KEY;
00506 
00507       if (error==1050)
00508          return DB_KEY_EXIST;
00509    }
00510 }
00511 
00512 int SqlODBC::ListTables(std::vector<std::string> *plist)
00513 {
00514    if (!fIsConnected)
00515       return DB_FILE_ERROR;
00516 
00517    for (int i=0; i<2; i++) {
00518       if (fDebug)
00519          printf("SqlODBC::ListTables!\n");
00520 
00521       /* Retrieve a list of tables */
00522       int status = SQLTables(fStmt, NULL, 0, NULL, 0, NULL, 0, (SQLCHAR*)"TABLE", SQL_NTS);
00523 
00524       if (SQL_SUCCEEDED(status))
00525          break;
00526 
00527       if (fDebug)
00528          printf("SqlODBC::ListTables: SQLTables() error %d\n", status);
00529       
00530       ReportErrors("SqlODBC::ListTables", "SQLTables()", status);
00531 
00532       status = DecodeError();
00533 
00534       //if (status == DB_NO_KEY)
00535       //   return status;
00536       //
00537       //if (status == DB_KEY_EXIST)
00538       //  return status;
00539       
00540       cm_msg(MINFO, "SqlODBC::ListTables", "Reconnecting to ODBC database DSN \'%s\'", fDSN.c_str());
00541 
00542       // try to reconnect
00543       std::string dsn = fDSN;
00544       Disconnect();
00545       status = Connect(dsn.c_str());
00546 
00547       if (!fIsConnected) {
00548          cm_msg(MERROR, "SqlODBC::ListTables", "Cannot reconnect to ODBC database DSN \'%s\', status %d. Database is down?", fDSN.c_str(), status);
00549          return DB_FILE_ERROR;
00550       }
00551 
00552       cm_msg(MINFO, "SqlODBC::ListTables", "Reconnected to ODBC database DSN \'%s\'", fDSN.c_str());
00553    }
00554 
00555    int ncols = GetNumColumns();
00556    int nrows = GetNumRows();
00557 
00558    if (ncols <= 0 || nrows <= 0) {
00559       cm_msg(MERROR, "SqlODBC::ListTables", "Error: SQLTables() returned unexpected number of columns %d or number of rows %d", ncols, nrows);
00560    }
00561 
00562    int row = 0;
00563    while (1) {
00564       int status = Fetch();
00565       if (status != DB_SUCCESS)
00566          break;
00567 
00568       if (0) {
00569          printf("row %d: ", row);
00570          for (int i=1; i<=ncols; i++) {
00571             const char* s = GetColumn(i);
00572             printf("[%s]", s);
00573          }
00574          printf("\n");
00575          row++;
00576       }
00577 
00578       plist->push_back(GetColumn(3));
00579    }
00580 
00581    Done();
00582 
00583    return DB_SUCCESS;
00584 }
00585 
00586 int SqlODBC::ListColumns(const char* table, std::vector<std::string> *plist)
00587 {
00588    if (!fIsConnected)
00589       return DB_FILE_ERROR;
00590 
00591    for (int i=0; i<2; i++) {
00592       if (fDebug)
00593          printf("SqlODBC::ListColumns for table \'%s\'\n", table);
00594 
00595       /* Retrieve a list of columns */
00596       int status = SQLColumns(fStmt, NULL, 0, NULL, 0, (SQLCHAR*)table, SQL_NTS, NULL, 0);
00597 
00598       if (SQL_SUCCEEDED(status))
00599          break;
00600 
00601       if (fDebug)
00602          printf("SqlODBC::ListColumns: SQLColumns(%s) error %d\n", table, status);
00603       
00604       ReportErrors("SqlODBC::ListColumns", "SQLColumns()", status);
00605 
00606       status = DecodeError();
00607 
00608       //if (status == DB_NO_KEY)
00609       //   return status;
00610       //
00611       //if (status == DB_KEY_EXIST)
00612       //  return status;
00613       
00614       cm_msg(MINFO, "SqlODBC::ListColumns", "Reconnecting to ODBC database DSN \'%s\'", fDSN.c_str());
00615 
00616       // try to reconnect
00617       std::string dsn = fDSN;
00618       Disconnect();
00619       status = Connect(dsn.c_str());
00620 
00621       if (!fIsConnected) {
00622          cm_msg(MERROR, "SqlODBC::ListColumns", "Cannot reconnect to ODBC database DSN \'%s\', status %d. Database is down?", fDSN.c_str(), status);
00623          return DB_FILE_ERROR;
00624       }
00625 
00626       cm_msg(MINFO, "SqlODBC::ListColumns", "Reconnected to ODBC database DSN \'%s\'", fDSN.c_str());
00627    }
00628 
00629    int ncols = GetNumColumns();
00630    int nrows = GetNumRows(); // nrows seems to be always "-1"
00631 
00632    if (ncols <= 0 /*|| nrows <= 0*/) {
00633       cm_msg(MERROR, "SqlODBC::ListColumns", "Error: SQLColumns(\'%s\') returned unexpected number of columns %d or number of rows %d", table, ncols, nrows);
00634    }
00635 
00636    //printf("get columns [%s]: status %d, ncols %d, nrows %d\n", table, status, ncols, nrows);
00637 
00638    int row = 0;
00639    while (1) {
00640       int status = Fetch();
00641       if (status != DB_SUCCESS)
00642          break;
00643 
00644       if (0) {
00645          printf("row %d: ", row);
00646          for (int i=1; i<=ncols; i++) {
00647             const char* s = GetColumn(i);
00648             printf("[%s]", s);
00649          }
00650          printf("\n");
00651          row++;
00652       }
00653 
00654       plist->push_back(GetColumn(4)); // column name
00655       plist->push_back(GetColumn(6)); // column type
00656    }
00657 
00658    Done();
00659 
00660    return DB_SUCCESS;
00661 }
00662 
00663 int SqlODBC::Exec(const char* sql)
00664 {
00665    // return values:
00666    // DB_SUCCESS
00667    // DB_FILE_ERROR: not connected
00668    // DB_NO_KEY: "table not found"
00669 
00670    if (!fIsConnected)
00671       return DB_FILE_ERROR;
00672     
00673    int status;
00674 
00675    for (int i=0; i<2; i++) {
00676       if (fDebug)
00677          printf("SqlODBC::Exec: %s\n", sql);
00678 
00679       status = SQLExecDirect(fStmt,(SQLCHAR*)sql,SQL_NTS);
00680 
00681       if (SQL_SUCCEEDED(status)) {
00682          return DB_SUCCESS;
00683       }
00684 
00685       if (fDebug)
00686          printf("SqlODBC::Exec: SQLExecDirect() error %d: SQL command: \"%s\"\n", status, sql);
00687       
00688       ReportErrors("SqlODBC::Exec", "SQLExecDirect()", status);
00689 
00690       status = DecodeError();
00691 
00692       if (status == DB_NO_KEY)
00693          return status;
00694       
00695       if (status == DB_KEY_EXIST)
00696          return status;
00697       
00698       cm_msg(MINFO, "SqlODBC::Exec", "Reconnecting to ODBC database DSN \'%s\'", fDSN.c_str());
00699 
00700       // try to reconnect
00701       std::string dsn = fDSN;
00702       Disconnect();
00703       status = Connect(dsn.c_str());
00704 
00705       if (!fIsConnected) {
00706          cm_msg(MERROR, "SqlODBC::Exec", "Cannot reconnect to ODBC database DSN \'%s\', status %d. Database is down?", fDSN.c_str(), status);
00707          return DB_FILE_ERROR;
00708       }
00709 
00710       cm_msg(MINFO, "SqlODBC::Exec", "Reconnected to ODBC database DSN \'%s\'", fDSN.c_str());
00711    }
00712 
00713    return DB_SUCCESS;
00714 }
00715 
00716 int SqlODBC::GetNumRows()
00717 {
00718    SQLLEN nrows = 0;
00719    /* How many rows are there */
00720    int status = SQLRowCount(fStmt, &nrows);
00721    if (!SQL_SUCCEEDED(status)) {
00722       ReportErrors("SqlODBC::GetNumRow", "SQLRowCount()", status);
00723       return -1;
00724    }
00725    return nrows;
00726 }
00727 
00728 int SqlODBC::GetNumColumns()
00729 {
00730    SQLSMALLINT ncols = 0;
00731    /* How many columns are there */
00732    int status = SQLNumResultCols(fStmt, &ncols);
00733    if (!SQL_SUCCEEDED(status)) {
00734       ReportErrors("SqlODBC::GetNumColumns", "SQLNumResultCols()", status);
00735       return -1;
00736    }
00737    return ncols;
00738 }
00739 
00740 int SqlODBC::Fetch()
00741 {
00742    int status = SQLFetch(fStmt);
00743 
00744    if (status == SQL_NO_DATA)
00745       return DB_NO_MORE_SUBKEYS;
00746 
00747    if (!SQL_SUCCEEDED(status)) {
00748       ReportErrors("SqlODBC::Fetch", "SQLFetch()", status);
00749       return DB_FILE_ERROR;
00750    }
00751 
00752    return DB_SUCCESS;
00753 }
00754 
00755 int SqlODBC::Done()
00756 {
00757    int status = SQLCloseCursor(fStmt);
00758    if (!SQL_SUCCEEDED(status)) {
00759       ReportErrors("SqlODBC::Done", "SQLCloseCursor()", status);
00760       return DB_FILE_ERROR;
00761    }
00762    return DB_SUCCESS;
00763 }
00764 
00765 const char* SqlODBC::GetColumn(int icol)
00766 {
00767   static char buf[1024];
00768   SQLLEN indicator;
00769   int status = SQLGetData(fStmt, icol, SQL_C_CHAR, buf, sizeof(buf), &indicator);
00770 
00771   if (!SQL_SUCCEEDED(status)) {
00772     return NULL;
00773   }
00774 
00775   if (indicator == SQL_NULL_DATA)
00776     return NULL;
00777 
00778   return buf;
00779 }
00780 
00781 #endif
00782 
00783 //////////////////////////////////////////
00784 //        Done with SQL stuff           //
00785 //////////////////////////////////////////
00786 
00787 ////////////////////////////////////////////////////////
00788 //  Data structures to keep track of Events and Tags  //
00789 ////////////////////////////////////////////////////////
00790 
00791 struct Tag
00792 {
00793    std::string column_name;
00794    int   offset;
00795    TAG   tag;
00796    bool  create;
00797 };
00798 
00799 struct Event
00800 {
00801    std::string event_name;
00802    std::string table_name;
00803    std::vector<Tag> tags;
00804    bool  active;
00805    
00806    Event() // ctor
00807    {
00808       active = false;
00809    }
00810    
00811    ~Event() // dtor
00812    {
00813       active = false;
00814    }
00815 };
00816 
00817 static void PrintTags(int ntags, const TAG tags[])
00818 {
00819    for (int i=0; i<ntags; i++)
00820       printf("tag %d: %s %s[%d]\n", i, midasTypeName(tags[i].type), tags[i].name, tags[i].n_data);
00821 }
00822 
00823 int WriteEvent(SqlBase* sql, Event *e, time_t t, const char*buf, int size)
00824 {
00825    //printf("event %d, time %s", rec.event_id, ctime(&t));
00826 
00827    int n  = e->tags.size();
00828    
00829    std::string tags;
00830    std::string values;
00831    
00832    //if (n>0)
00833    //  printf(" %s", ctime(&t));
00834    
00835    for (int i=0; i<n; i++) {
00836       const Tag*t = &e->tags[i];
00837       
00838       if (t) {
00839          int offset = t->offset;
00840          void* ptr = (void*)(buf+offset);
00841 
00842          int arraySize = t->tag.n_data;
00843          
00844          for (int j=0; j<arraySize; j++) {
00845             tags   += ", ";
00846             values += ", ";
00847             
00848             if (arraySize <= 1)
00849                tags += t->column_name;
00850             else {
00851                tags += t->column_name;
00852                char s[256];
00853                sprintf(s,"_%d", j);
00854                tags += s;
00855             }
00856                 
00857             char s[1024];
00858             
00859             switch (t->tag.type) {
00860             default:
00861                sprintf(s, "unknownType%d", t->tag.type);
00862                break;
00863             case 1: /* BYTE */
00864                sprintf(s, "%u",((uint8_t*)ptr)[j]);
00865                break;
00866             case 2: /* SBYTE */
00867                sprintf(s, "%d",((int8_t*)ptr)[j]);
00868                break;
00869             case 3: /* CHAR */
00870                sprintf(s, "\'%c\'",((char*)ptr)[j]);
00871                break;
00872             case 4: /* WORD */
00873                sprintf(s, "%u",((uint16_t*)ptr)[j]);
00874                break;
00875             case 5: /* SHORT */
00876                sprintf(s, "%d",((int16_t*)ptr)[j]);
00877                break;
00878             case 6: /* DWORD */
00879                sprintf(s, "%u",((uint32_t*)ptr)[j]);
00880                break;
00881             case 7: /* INT */
00882                sprintf(s, "%d",((int32_t*)ptr)[j]);
00883                break;
00884             case 8: /* BOOL */
00885                sprintf(s, "%u",((uint32_t*)ptr)[j]);
00886                break;
00887             case 9: /* FLOAT */
00888                sprintf(s, "\'%.8g\'",((float*)ptr)[j]);
00889                break;
00890             case 10: /* DOUBLE */
00891                sprintf(s, "\'%.16g\'",((double*)ptr)[j]);
00892                     break;
00893             }
00894             
00895             values += s;
00896          }
00897       }
00898    }
00899 
00900    // 2001-02-16 20:38:40.1
00901    char s[1024];
00902    strftime(s,sizeof(s)-1,"%Y-%m-%d %H:%M:%S.0",localtime(&t));
00903    
00904    char sss[102400];
00905    sprintf(sss, "INSERT INTO %s (_t_time, _i_time%s) VALUES (\'%s\', \'%d\'%s);",
00906            e->table_name.c_str(),
00907            tags.c_str(),
00908            s,
00909            (int)t,
00910            values.c_str());
00911 
00912    int status = sql->Exec(sss);
00913    
00914    if (status != DB_SUCCESS) {
00915       return status;
00916    }
00917 
00918    return HS_SUCCESS;
00919 }
00920 
00921 // convert MIDAS names to SQL names
00922 
00923 static std::string MidasNameToSqlName(const char* s)
00924 {
00925    std::string out;
00926 
00927    for (int i=0; s[i]!=0; i++) {
00928       char c = s[i];
00929       if (isalpha(c) || isdigit(c))
00930          out += tolower(c);
00931       else
00932          out += '_';
00933    }
00934    
00935    return out;
00936 }
00937 
00938 struct IndexEntryTag
00939 {
00940    std::string tag_name;
00941    std::string column_name;
00942    int timestamp;
00943 };
00944 
00945 struct IndexEntry
00946 {
00947    std::string event_name;
00948    std::string table_name;
00949    int timestamp;
00950    std::vector<IndexEntryTag> tags;
00951    std::vector<TAG> tags_cache;
00952 };
00953 
00954 std::vector<IndexEntry*> gHistoryIndex;
00955 
00956 static void PrintIndex()
00957 {
00958    for (unsigned i=0; i<gHistoryIndex.size(); i++) {
00959       IndexEntry *e = gHistoryIndex[i];
00960 
00961       printf("entry %d: [%s] [%s], time %d, tags\n", i, e->event_name.c_str(), e->table_name.c_str(), e->timestamp);
00962 
00963       for (unsigned j=0; j<e->tags.size(); j++)
00964          printf("  tag %d: [%s] [%s], time %d\n", j, e->tags[j].tag_name.c_str(), e->tags[j].column_name.c_str(), e->tags[j].timestamp);
00965    }
00966 }
00967 
00968 static IndexEntry* FindIndexByTableName(const char* table_name)
00969 {
00970    for (unsigned i=0; i<gHistoryIndex.size(); i++)
00971       if (equal_ustring(gHistoryIndex[i]->table_name.c_str(), table_name)) {
00972          return gHistoryIndex[i];
00973       }
00974    return NULL;
00975 }
00976 
00977 static IndexEntry* FindIndexByEventName(const char* event_name)
00978 {
00979    for (unsigned i=0; i<gHistoryIndex.size(); i++)
00980       if (equal_ustring(gHistoryIndex[i]->event_name.c_str(), event_name)) {
00981          return gHistoryIndex[i];
00982       }
00983    return NULL;
00984 }
00985 
00986 static IndexEntryTag* FindIndexByTagName(IndexEntry* ie, const char* tag_name)
00987 {
00988    for (unsigned i=0; i<ie->tags.size(); i++)
00989       if (equal_ustring(ie->tags[i].tag_name.c_str(), tag_name)) {
00990          return &ie->tags[i];
00991       }
00992    return NULL;
00993 }
00994 
00995 static IndexEntryTag* FindIndexByColumnName(IndexEntry* ie, const char* column_name)
00996 {
00997    for (unsigned i=0; i<ie->tags.size(); i++)
00998       if (equal_ustring(ie->tags[i].column_name.c_str(), column_name)) {
00999          return &ie->tags[i];
01000       }
01001    return NULL;
01002 }
01003 
01004 static int gHaveIndex = true;
01005 static int gHaveIndexAll = false;
01006 
01007 static int gTrace = 0;
01008 
01009 static int ReadIndex(SqlBase* sql, const char* event_name)
01010 {
01011    if (gTrace)
01012       printf("ReadIndex [%s]\n", event_name);
01013 
01014    if (!gHaveIndex)
01015       return HS_FILE_ERROR;
01016 
01017    if (gHaveIndexAll)
01018       return HS_SUCCESS;
01019 
01020    if (gTrace)
01021       printf("ReadIndex: reading index for event [%s]\n", event_name);
01022 
01023    //event_name = NULL;
01024 
01025    char cmd[256];
01026 
01027    if (event_name)
01028       sprintf(cmd, "SELECT event_name, table_name, tag_name, column_name, itimestamp FROM _history_index where event_name=\'%s\';", event_name);
01029    else
01030       sprintf(cmd, "SELECT event_name, table_name, tag_name, column_name, itimestamp FROM _history_index;");
01031    
01032    int status = sql->Exec(cmd);
01033 
01034    if (status == DB_NO_KEY) {
01035       gHaveIndex = false;
01036       return HS_FILE_ERROR;
01037    }
01038    
01039    if (gTrace) {
01040       printf("ReadIndex: event %s, Read status %d, nrows: %d\n",
01041              event_name,
01042              status,
01043              sql->GetNumRows());
01044    }
01045    
01046    if (status != SUCCESS)
01047       return HS_FILE_ERROR;
01048    
01049    if (sql->GetNumRows() == 0) {
01050       sql->Done();
01051       return HS_FILE_ERROR;
01052    }
01053    
01054    int nrows = sql->GetNumRows();
01055    int ncols = sql->GetNumColumns();
01056    
01057    if (nrows == 0)
01058       return HS_SUCCESS;
01059    
01060    if (gTrace)
01061       printf("ReadIndex: event %s, nrows: %d, ncols: %d\n",
01062              event_name,
01063              nrows, ncols);
01064    
01065    if (nrows < 0)
01066       return HS_FILE_ERROR;
01067    
01068    if (ncols < 1)
01069       return HS_FILE_ERROR;
01070    
01071    /* Loop through the rows in the result-set */
01072    while (1) {
01073       status = sql->Fetch();
01074       if (status != DB_SUCCESS)
01075          break;
01076 
01077       std::string xevent_name  = sql->GetColumn(1);
01078 
01079       const char* p = sql->GetColumn(2);
01080       if (p) { // table declaration
01081          std::string xtable_name  = p;
01082          std::string xtimestamp   = sql->GetColumn(5);
01083          int timestamp = atoi(xtimestamp.c_str());
01084 
01085          IndexEntry* ie = FindIndexByEventName(xevent_name.c_str());
01086          if (!ie) {
01087             ie = new IndexEntry;
01088             gHistoryIndex.push_back(ie);
01089             ie->timestamp = timestamp - 1; // make sure we update this entry
01090          }
01091 
01092          if (timestamp > ie->timestamp) {
01093             ie->event_name = xevent_name;
01094             ie->table_name = xtable_name;
01095             ie->timestamp  = timestamp;
01096          }
01097 
01098          //printf("%s %s %s %s %s [%s]\n", xevent_name.c_str(), xtable_name.c_str(), "???", "???", xtimestamp.c_str(), p);
01099          continue;
01100       }
01101          
01102       p = sql->GetColumn(3);
01103       if (p) { // tag declaration
01104          std::string xtag_name    = p;
01105          std::string xcolumn_name = sql->GetColumn(4);
01106          std::string xtimestamp   = sql->GetColumn(5);
01107          int timestamp = atoi(xtimestamp.c_str());
01108 
01109          IndexEntry* ie = FindIndexByEventName(xevent_name.c_str());
01110          if (!ie) {
01111             ie = new IndexEntry;
01112             gHistoryIndex.push_back(ie);
01113             ie->timestamp = 0;
01114             ie->event_name = xevent_name;
01115          }
01116 
01117          bool found = false;
01118          for (unsigned j=0; j<ie->tags.size(); j++)
01119             if (ie->tags[j].tag_name == xtag_name) {
01120                if (timestamp > ie->tags[j].timestamp) {
01121                   ie->tags[j].timestamp = timestamp;
01122                   ie->tags[j].column_name = xcolumn_name;
01123                }
01124                found = true;
01125                break;
01126             }
01127 
01128          if (!found) {
01129             IndexEntryTag it;
01130             it.tag_name = xtag_name;
01131             it.column_name = xcolumn_name;
01132             it.timestamp  = timestamp;
01133             ie->tags.push_back(it);
01134          }
01135 
01136          //printf("%s %s %s %s %s\n", xevent_name.c_str(), "???", xtag_name.c_str(), xcolumn_name.c_str(), xtimestamp.c_str());
01137          continue;
01138       }
01139 
01140    }
01141 
01142    sql->Done();
01143 
01144    gHaveIndex = true;
01145 
01146    if (event_name == NULL)
01147       gHaveIndexAll = true;
01148 
01149    //PrintIndex();
01150 
01151    return HS_SUCCESS;
01152 }
01153 
01154 ////////////////////////////////////////////////////////
01155 //    Implementation of the MidasHistoryInterface     //
01156 ////////////////////////////////////////////////////////
01157 
01158 class SqlHistory: public MidasHistoryInterface
01159 {
01160 public:
01161    SqlBase *fSql;
01162    int fDebug;
01163    std::string fConnectString;
01164    int fConnectRetry;
01165    int fNextConnect;
01166    std::vector<Event*> fEvents;
01167    std::vector<std::string> fIndexEvents;
01168    bool fHaveIndex;
01169    bool fHaveXIndex;
01170 
01171    SqlHistory(SqlBase* b)
01172    {
01173       fDebug = 0;
01174       fConnectRetry = 0;
01175       fNextConnect  = 0;
01176       fSql = b;
01177       fHaveIndex = false;
01178       fHaveXIndex = false;
01179    }
01180 
01181    ~SqlHistory()
01182    {
01183       hs_disconnect();
01184       delete fSql;
01185       fSql = NULL;
01186    }
01187 
01188    int hs_set_debug(int debug)
01189    {
01190       int old = fDebug;
01191       fDebug = debug;
01192       gTrace = debug;
01193       fSql->SetDebug(debug);
01194       return old;
01195    }
01196    
01197    int hs_connect(const char* connect_string)
01198    {
01199       if (fDebug)
01200          printf("hs_connect %s!\n", connect_string);
01201 
01202       assert(fSql);
01203 
01204       if (fSql->IsConnected())
01205          if (strcmp(fConnectString.c_str(), connect_string) == 0)
01206             return HS_SUCCESS;
01207     
01208       hs_disconnect();
01209     
01210       fConnectString = connect_string;
01211     
01212       if (fDebug)
01213          printf("hs_connect: connecting to SQL database \'%s\'\n", fConnectString.c_str());
01214     
01215       int status = fSql->Connect(fConnectString.c_str());
01216       if (status != DB_SUCCESS)
01217          return status;
01218 
01219       std::vector<std::string> tables;
01220 
01221       status = fSql->ListTables(&tables);
01222       if (status != DB_SUCCESS)
01223          return status;
01224 
01225       for (unsigned i=0; i<tables.size(); i++) {
01226          if (tables[i] == "_history_index") {
01227             fHaveIndex = true;
01228             break;
01229          }
01230       }
01231 
01232       return HS_SUCCESS;
01233    }
01234 
01235    int hs_disconnect()
01236    {
01237       if (fDebug)
01238          printf("hs_disconnect!\n");
01239       
01240       fSql->Disconnect();
01241 
01242       hs_clear_cache();
01243       
01244       return HS_SUCCESS;
01245    }
01246 
01247    int Reconnect()
01248    {
01249       if (fDebug)
01250          printf("Reconnect to SQL database!\n");
01251       
01252       fSql->Disconnect();
01253       fSql->Connect(fConnectString.c_str());
01254       if (!fSql->IsConnected()) {
01255          return HS_FILE_ERROR;
01256       }
01257       
01258       return HS_SUCCESS;
01259    }
01260 
01261    ////////////////////////////////////////////////////////
01262    //             Internal data caches                   //
01263    ////////////////////////////////////////////////////////
01264 
01265    int hs_clear_cache()
01266    {
01267       if (fDebug)
01268          printf("hs_clear_cache!\n");
01269 
01270       gHaveIndex = true;
01271       gHaveIndexAll = false;
01272       fHaveXIndex = false;
01273 
01274       for (unsigned i=0; i<gHistoryIndex.size(); i++) {
01275          IndexEntry* ie = gHistoryIndex[i];
01276          delete ie;
01277       }
01278       gHistoryIndex.clear();
01279 
01280       fIndexEvents.clear();
01281 
01282       return HS_SUCCESS;
01283    }
01284 
01285    int XReadIndex()
01286    {
01287       if (fHaveXIndex)
01288          return HS_SUCCESS;
01289 
01290       if (fDebug)
01291          printf("XReadIndex!\n");
01292 
01293       std::vector<std::string> tables;
01294 
01295       int status = fSql->ListTables(&tables);
01296       if (status != DB_SUCCESS)
01297          return status;
01298       
01299       for (unsigned i=0; i<tables.size(); i++) {
01300          if (tables[i] == "_history_index")
01301             continue;
01302 
01303          IndexEntry* ie = NULL; //FindEventName(tables[i].c_str());
01304 
01305          if (!ie) {
01306             ie = new IndexEntry;
01307 
01308             ie->table_name = tables[i];
01309             ie->event_name = ie->table_name;
01310 
01311             gHistoryIndex.push_back(ie);
01312          }
01313 
01314          std::vector<std::string> columns;
01315 
01316          status = fSql->ListColumns(ie->table_name.c_str(), &columns);
01317          if (status != DB_SUCCESS)
01318             return status;
01319 
01320          for (unsigned int j=0; j<columns.size(); j+=2) {
01321             if (columns[j] == "_t_time")
01322                continue;
01323             if (columns[j] == "_i_time")
01324                continue;
01325 
01326             IndexEntryTag t;
01327             t.column_name = columns[j];
01328             t.tag_name = t.column_name;
01329             t.timestamp = 0;
01330 
01331             ie->tags.push_back(t);
01332          }
01333       }
01334 
01335       fHaveXIndex = true;
01336 
01337       //PrintIndex();
01338 
01339       return HS_SUCCESS;
01340    }
01341 
01342    ////////////////////////////////////////////////////////
01343    //             Functions used by mlogger              //
01344    ////////////////////////////////////////////////////////
01345 
01346    int hs_define_event(const char* event_name, int ntags, const TAG tags[])
01347    {
01348       int status;
01349 
01350       if (fDebug) {
01351          printf("define event [%s] with %d tags:\n", event_name, ntags);
01352          PrintTags(ntags, tags);
01353       }
01354 
01355       // delete all events with the same name
01356       for (unsigned int i=0; i<fEvents.size(); i++)
01357          if (fEvents[i])
01358             if (fEvents[i]->event_name == event_name) {
01359                if (fDebug)
01360                   printf("deleting exising event %s\n", event_name);
01361                delete fEvents[i];
01362                fEvents[i] = NULL;
01363             }
01364 
01365       Event* e = new Event();
01366 
01367       e->event_name = event_name;
01368 
01369       if (!fHaveIndex) {
01370          char buf[1024];
01371          sprintf(buf, "CREATE TABLE _history_index (event_name VARCHAR(256) NOT NULL, table_name VARCHAR(256), tag_name VARCHAR(256), column_name VARCHAR(256), itimestamp INTEGER NOT NULL);");
01372          int status = fSql->Exec(buf);
01373          if (status == DB_KEY_EXIST)
01374             /* do nothing */ ;
01375          else if (status != DB_SUCCESS)
01376             return status;
01377          fHaveIndex = true;
01378       }
01379 
01380       IndexEntry* ie = FindIndexByEventName(event_name);
01381 
01382       if (!ie) {
01383          ReadIndex(fSql, event_name);
01384          ie = FindIndexByEventName(event_name);
01385       }
01386 
01387       if (!ie) {
01388          std::string table_name = MidasNameToSqlName(event_name);
01389 
01390          double now = time(NULL);
01391          
01392          char sss[102400];
01393          sprintf(sss, "INSERT INTO _history_index (event_name, table_name, itimestamp) VALUES (\'%s\', \'%s\', \'%.0f\');",
01394                  event_name,
01395                  table_name.c_str(),
01396                  now);
01397          
01398          int status = fSql->Exec(sss);
01399          if (status != DB_SUCCESS)
01400             return HS_FILE_ERROR;
01401 
01402          ReadIndex(fSql, event_name);
01403          ie = FindIndexByEventName(event_name);
01404       }
01405 
01406       if (!ie) {
01407          cm_msg(MERROR, "hs_define_event", "could not add event name to SQL history index table, see messages");
01408          return HS_FILE_ERROR;
01409       }
01410 
01411       e->table_name = ie->table_name;
01412       e->active = true;
01413 
01414       bool create_event = false;
01415 
01416       int offset = 0;
01417       for (int i=0; i<ntags; i++) {
01418          for (unsigned int j=0; j<tags[i].n_data; j++) {
01419             std::string tagname = tags[i].name;
01420             std::string colname = MidasNameToSqlName(tags[i].name);
01421 
01422             if (tags[i].n_data > 1) {
01423                char s[256];
01424                sprintf(s, "[%d]", j);
01425                tagname += s;
01426                
01427                sprintf(s, "_%d", j);
01428                colname += s;
01429             }
01430 
01431             IndexEntryTag *it = FindIndexByTagName(ie, tagname.c_str());
01432             if (!it) {
01433                // check for duplicate names
01434 
01435                while (1) {
01436                   bool dupe = false;
01437 
01438                   for (unsigned i=0; i<e->tags.size(); i++) {
01439                      if (colname == e->tags[i].column_name) {
01440                         //printf("duplicate name %s\n", colname.c_str());
01441                         dupe = true;
01442                         break;
01443                      }
01444                   }
01445 
01446                   if (!dupe)
01447                      break;
01448 
01449                   char s[256];
01450                   sprintf(s, "_%d", rand());
01451                   colname += s;
01452                }
01453 
01454                // add tag name to column name translation to the history index
01455 
01456                double now = time(NULL);
01457       
01458                char sss[102400];
01459                sprintf(sss, "INSERT INTO _history_index (event_name, tag_name, column_name, itimestamp) VALUES (\'%s\', \'%s\', \'%s\', \'%.0f\');",
01460                        event_name,
01461                        tagname.c_str(),
01462                        colname.c_str(),
01463                        now);
01464                
01465                int status = fSql->Exec(sss);
01466                if (status != DB_SUCCESS)
01467                   return HS_FILE_ERROR;
01468 
01469                // reload the history index
01470                
01471                ReadIndex(fSql, event_name);
01472                ie = FindIndexByEventName(event_name);
01473                assert(ie);
01474                it = FindIndexByTagName(ie, tagname.c_str());
01475             }
01476 
01477             if (!it) {
01478                cm_msg(MERROR, "hs_define_event", "could not add event tags to SQL history index table, see messages");
01479                return HS_FILE_ERROR;
01480             }
01481 
01482             Tag t;
01483             t.column_name = it->column_name;
01484             t.create = false;
01485             t.offset = offset;
01486             t.tag = tags[i];
01487             t.tag.n_data = 1;
01488             e->tags.push_back(t);
01489             int size = tid_size[tags[i].type];
01490             offset += size;
01491          }
01492       }
01493 
01494       std::vector<std::string> columns;
01495 
01496       status = fSql->ListColumns(e->table_name.c_str(), &columns);
01497       if (status != DB_SUCCESS)
01498          return status;
01499 
01500       if (columns.size() <= 0)
01501          create_event = true;
01502 
01503       for (size_t i=0; i<e->tags.size(); i++) {
01504          // check for duplicate column names
01505          for (size_t j=i+1; j<e->tags.size(); j++)
01506             if (e->tags[i].column_name == e->tags[j].column_name) {
01507                cm_msg(MERROR, "hs_define_event", "Error: History event \'%s\': Duplicated column name \'%s\' from tags %d \'%s\' and %d \'%s\'", event_name, e->tags[i].column_name.c_str(), i, e->tags[i].tag.name, j, e->tags[j].tag.name);
01508                e->active = false;
01509                break;
01510             }
01511 
01512          // check if new column needs to be created
01513          bool found = false;
01514          for (size_t j=0; j<columns.size(); j+=2) {
01515             if (e->tags[i].column_name == columns[j]) {
01516                // column exists, check data type
01517                //printf("column \'%s\', data type %s\n", e->tags[i].column_name.c_str(), columns[j+1].c_str());
01518 
01519                if (!isCompatible(e->tags[i].tag.type, columns[j+1].c_str())) {
01520                   cm_msg(MERROR, "hs_define_event", "Error: History event \'%s\': Incompatible data type for tag \'%s\' type \'%s\', SQL column \'%s\' type \'%s\'", event_name, e->tags[i].tag.name, midasTypeName(e->tags[i].tag.type), columns[j].c_str(), columns[j+1].c_str());
01521                   e->active = false;
01522                }
01523 
01524                found = true;
01525                break;
01526             }
01527          }
01528 
01529          if (!found) {
01530             // create it
01531             //printf("column \'%s\', data type %s  --- create!\n", e->tags[i].column_name.c_str(), midasTypeName(e->tags[i].tag.type));
01532             e->tags[i].create = true;
01533          }
01534       }
01535 
01536       if (create_event) {
01537          char buf[1024];
01538          sprintf(buf, "CREATE TABLE %s (_t_time TIMESTAMP NOT NULL, _i_time INTEGER NOT NULL, INDEX (_i_time), INDEX (_t_time));", e->table_name.c_str());
01539          status = fSql->Exec(buf);
01540          if (status != DB_SUCCESS) {
01541             e->active = false;
01542             return HS_FILE_ERROR;
01543          }
01544       }
01545    
01546       for (size_t i=0; i<e->tags.size(); i++)
01547          if (e->tags[i].create) {
01548             char buf[1024];
01549             
01550             sprintf(buf, "ALTER TABLE %s ADD COLUMN %s %s;",
01551                     e->table_name.c_str(),
01552                     e->tags[i].column_name.c_str(),
01553                     midas2sqlType(e->tags[i].tag.type));
01554             
01555             status = fSql->Exec(buf);
01556             
01557             if (status != DB_SUCCESS) {
01558                e->active = false;
01559                return HS_FILE_ERROR;
01560             }
01561          }
01562 
01563       // find empty slot in events list
01564       for (unsigned int i=0; i<fEvents.size(); i++)
01565          if (!fEvents[i]) {
01566             fEvents[i] = e;
01567             e = NULL;
01568             break;
01569          }
01570 
01571       // if no empty slots, add at the end
01572       if (e)
01573          fEvents.push_back(e);
01574 
01575       return HS_SUCCESS;
01576    }
01577 
01578    int hs_write_event(const char* event_name, time_t timestamp, int buffer_size, const char* buffer)
01579    {
01580       if (fDebug)
01581          printf("hs_write_event: write event \'%s\', time %d, size %d\n", event_name, (int)timestamp, buffer_size);
01582 
01583       // if disconnected, try to reconnect
01584 
01585       if (!fSql->IsConnected()) {
01586          time_t now = time(NULL);
01587 
01588          // too early to try reconnecting?
01589          if (fConnectRetry !=0 && now < fNextConnect) {
01590             return HS_FILE_ERROR;
01591          }
01592 
01593          cm_msg(MINFO, "hs_write_event", "Trying to reconnect to SQL database \'%s\'", fConnectString.c_str());
01594 
01595          int status = fSql->Connect(fConnectString.c_str());
01596 
01597          if (status != DB_SUCCESS) {
01598 
01599             // first retry in 5 seconds
01600             if (fConnectRetry == 0)
01601                fConnectRetry = 5;
01602 
01603             fNextConnect = now + fConnectRetry;
01604 
01605             // exponential backoff
01606             fConnectRetry *= 2;
01607 
01608             // but no more than every 10 minutes
01609             if (fConnectRetry > 10*60)
01610                fConnectRetry = 10*60;
01611 
01612             return HS_FILE_ERROR;
01613          }
01614 
01615          cm_msg(MINFO, "hs_write_event", "Reconnected to SQL database \'%s\'", fConnectString.c_str());
01616       }
01617 
01618       fNextConnect = 0;
01619       fConnectRetry = 0;
01620 
01621       Event *e = NULL;
01622 
01623       // find this event
01624       for (size_t i=0; i<fEvents.size(); i++)
01625          if (fEvents[i]->event_name == event_name) {
01626             e = fEvents[i];
01627             break;
01628          }
01629 
01630       // not found
01631       if (!e)
01632          return HS_UNDEFINED_EVENT;
01633 
01634       // deactivated because of error?
01635       if (!e->active)
01636          return HS_FILE_ERROR;
01637 
01638       int status = WriteEvent(fSql, e, timestamp, buffer, buffer_size);
01639 
01640       // if could not write to SQL?
01641       if (status != HS_SUCCESS) {
01642 
01643          // if lost SQL connection, try again later
01644 
01645          if (!fSql->IsConnected()) {
01646             return HS_FILE_ERROR;
01647          }
01648 
01649          // otherwise, deactivate this event
01650 
01651          e->active = false;
01652 
01653          cm_msg(MERROR, "hs_write_event", "Event \'%s\' disabled after write error %d into SQL database \'%s\'", event_name, status, fConnectString.c_str());
01654 
01655          return HS_FILE_ERROR;
01656       }
01657 
01658       return HS_SUCCESS;
01659    }
01660 
01661    ////////////////////////////////////////////////////////
01662    //             Functions used by mhttpd               //
01663    ////////////////////////////////////////////////////////
01664 
01665    int hs_get_events(std::vector<std::string> *pevents)
01666    {
01667       if (fDebug)
01668          printf("hs_get_events!\n");
01669 
01670       if (fIndexEvents.size() == 0) {
01671 
01672          if (fDebug)
01673             printf("hs_get_events: reading event names!\n");
01674 
01675          ReadIndex(fSql, NULL);
01676 
01677          std::vector<std::string> tables;
01678          int status = fSql->ListTables(&tables);
01679          if (status != DB_SUCCESS)
01680             return status;
01681  
01682          for (unsigned i=0; i<tables.size(); i++) {
01683             if (tables[i] == "_history_index")
01684                continue;
01685 
01686             IndexEntry* ie = FindIndexByTableName(tables[i].c_str());
01687             if (!ie) {
01688                ReadIndex(fSql, NULL);
01689                ie = FindIndexByTableName(tables[i].c_str());
01690             }
01691 
01692             if (ie)
01693                fIndexEvents.push_back(ie->event_name);
01694             else
01695                fIndexEvents.push_back(tables[i]);
01696          }
01697       }
01698 
01699       assert(pevents);
01700       *pevents = fIndexEvents;
01701       
01702       return HS_SUCCESS;
01703    }
01704       
01705    int hs_get_tags(const char* event_name, std::vector<TAG> *ptags)
01706    {
01707       if (fDebug)
01708          printf("hs_get_tags for [%s]\n", event_name);
01709 
01710       assert(ptags);
01711 
01712       IndexEntry* ie = FindIndexByEventName(event_name);
01713 
01714       if (!ie) {
01715          ReadIndex(fSql, event_name);
01716          ie = FindIndexByEventName(event_name);
01717       }
01718 
01719       if (!ie) {
01720          XReadIndex();
01721          ie = FindIndexByEventName(event_name);
01722       }
01723 
01724       if (!ie)
01725          return HS_UNDEFINED_EVENT;
01726 
01727       if (ie->tags_cache.size() == 0) {
01728          if (fDebug)
01729             printf("hs_get_tags reading tags for [%s]\n", event_name);
01730 
01731          std::string tname = ie->table_name;
01732 
01733          std::vector<std::string> columns;
01734 
01735          int status = fSql->ListColumns(tname.c_str(), &columns);
01736          if (status != DB_SUCCESS)
01737             return status;
01738 
01739          if (columns.size() < 1) {
01740             cm_msg(MERROR, "hs_get_tags", "Cannot get columns for table \'%s\', try to reconnect to the database", tname.c_str());
01741 
01742             int status = Reconnect();
01743             if (status != HS_SUCCESS)
01744                return status;
01745 
01746             columns.clear();
01747             status = fSql->ListColumns(tname.c_str(), &columns);      
01748             if (status != DB_SUCCESS)
01749                return status;
01750          }
01751 
01752          TAG* t = (TAG*)malloc(sizeof(TAG)*columns.size());
01753          assert(t);
01754 
01755          for (unsigned int j=0; j<columns.size(); j+=2) {
01756             if (columns[j] == "_t_time")
01757                continue;
01758             if (columns[j] == "_i_time")
01759                continue;
01760 
01761             IndexEntryTag* it = FindIndexByColumnName(ie, columns[j].c_str());
01762 
01763             TAG t;
01764             if (it)
01765                STRLCPY(t.name, it->tag_name.c_str());
01766             else
01767                STRLCPY(t.name, columns[j].c_str());
01768             t.type = sql2midasType(columns[j+1].c_str());
01769             t.n_data = 1;
01770 
01771             ie->tags_cache.push_back(t);
01772          }
01773       }
01774       
01775       for (unsigned i=0; i<ie->tags_cache.size(); i++)
01776          ptags->push_back(ie->tags_cache[i]);
01777 
01778       return HS_SUCCESS;
01779    }
01780 
01781    int hs_read_old_style(double start_time, double end_time, double interval,
01782                          const char* event_name, const char* tag_name, int var_index,
01783                          int *num_entries,
01784                          time_t** time_buffer, double**data_buffer)
01785    {
01786       if (fDebug) {
01787          printf("hs_read_old_style: event \"%s\", tag \"%s\"\n", event_name, tag_name);
01788       }
01789 
01790       ReadIndex(fSql, NULL);
01791 
01792       for (unsigned e=0; e<gHistoryIndex.size(); e++) {
01793 
01794          const char* s = gHistoryIndex[e]->event_name.c_str();
01795 
01796          bool match = false;
01797          for (int j=0; s[j]; j++) {
01798 
01799             if ((event_name[j]==0) && (s[j]=='/')) {
01800                match = true;
01801                break;
01802             }
01803 
01804             if ((event_name[j]==0) && (s[j]=='_')) {
01805                match = true;
01806                break;
01807             }
01808 
01809             if (event_name[j]==0) {
01810                match = false;
01811                break;
01812             }
01813 
01814             if (tolower(event_name[j]) != tolower(s[j])) {
01815                match = false;
01816                break;
01817             }
01818          }
01819 
01820          //printf("try %s, match %d\n", s, match);
01821 
01822          if (match) {
01823             bool found_tag = false;
01824             IndexEntry *ie = gHistoryIndex[e];
01825             for (unsigned v=0; v<ie->tags.size(); v++) {
01826                //printf("try tag [%s] looking for [%s]\n", ie->tags[v].tag_name.c_str(), tag_name);
01827                if (equal_ustring(tag_name, ie->tags[v].tag_name.c_str())) {
01828                   found_tag = true;
01829                   break;
01830                }
01831             }
01832 
01833             if (!found_tag)
01834                match = false;
01835          }
01836 
01837          if (match) {
01838             if (fDebug)
01839                printf("hs_read_old_style: event \"%s\", tag \"%s\", try matching event \'%s\'\n", event_name, tag_name, s);
01840 
01841             int status = hs_read(start_time, end_time, interval,
01842                                  s, tag_name, var_index,
01843                                  num_entries,
01844                                  time_buffer, data_buffer);
01845 
01846             if (status==HS_SUCCESS && *num_entries>0)
01847                return HS_SUCCESS;
01848          }
01849       }
01850 
01851       return HS_UNDEFINED_VAR;
01852    }
01853 
01854    int hs_read(double start_time, double end_time, double interval,
01855                const char* event_name, const char* tag_name, int tag_index,
01856                int *num_entries,
01857                time_t** time_buffer, double**data_buffer)
01858    {
01859       *num_entries = 0;
01860       *time_buffer = NULL;
01861       *data_buffer = NULL;
01862 
01863       if (fDebug)
01864          printf("hs_read: event [%s], tag [%s], index %d, start %f, end %f, dt %f, interval %f, max points %f\n",
01865                 event_name, tag_name, tag_index,
01866                 start_time, end_time, end_time-start_time, interval, (end_time-start_time)/interval);
01867 
01868       if (event_name==NULL)
01869          return HS_SUCCESS;
01870 
01871       IndexEntry*ie = FindIndexByEventName(event_name);
01872 
01873       if (!ie) {
01874          ReadIndex(fSql, event_name);
01875          ie = FindIndexByEventName(event_name);
01876       }
01877 
01878       if (!ie) {
01879          XReadIndex();
01880          ie = FindIndexByEventName(event_name);
01881       }
01882 
01883       IndexEntryTag *it = NULL;
01884 
01885       if (ie)
01886          it = FindIndexByTagName(ie, tag_name);
01887 
01888       if (ie && !it) { // maybe this is an array without "Names"?
01889          char xxx[256];
01890          sprintf(xxx, "%s[%d]", tag_name, tag_index);
01891          it = FindIndexByTagName(ie, xxx);
01892       }
01893          
01894       // new-style event name: "equipment_name/variable_name:tag_name"
01895       // old style event name: "equipment_name:tag_name" ("variable_name" is missing)
01896       bool oldStyleEventName = (strchr(event_name, '/')==NULL);
01897 
01898       if (oldStyleEventName)
01899          if (!ie || !it) {
01900             return hs_read_old_style(start_time, end_time, interval,
01901                                      event_name, tag_name, tag_index,
01902                                      num_entries,
01903                                      time_buffer, data_buffer);
01904          }
01905 
01906       if (!it)
01907          return HS_UNDEFINED_VAR;
01908 
01909       assert(ie);
01910       assert(it);
01911 
01912       std::string tname = ie->table_name;
01913       std::string cname = it->column_name;
01914 
01915       char cmd[256];
01916       sprintf(cmd, "SELECT _i_time, %s FROM %s WHERE _i_time>=%.0f and _i_time<=%.0f ORDER BY _i_time;",
01917               cname.c_str(), tname.c_str(),
01918               start_time, end_time);
01919            
01920       int status = fSql->Exec(cmd);
01921 
01922       if (fDebug) {
01923          printf("hs_read: event \"%s\", tag \"%s\", index %d: Read table \"%s\" column \"%s\": status %d, nrows: %d, ncolumns: %d\n",
01924                 event_name, tag_name, tag_index,
01925                 tname.c_str(),
01926                 cname.c_str(),
01927                 status,
01928                 fSql->GetNumRows(),
01929                 fSql->GetNumColumns()
01930                 );
01931       }
01932 
01933       if (status != SUCCESS) {
01934          return HS_FILE_ERROR;
01935       }
01936 
01937       if (fSql->GetNumRows() == 0) {
01938          fSql->Done();
01939 
01940          if (oldStyleEventName) {
01941             return hs_read_old_style(start_time, end_time, interval,
01942                                      event_name, tag_name, tag_index,
01943                                      num_entries,
01944                                      time_buffer, data_buffer);
01945          }
01946 
01947          return HS_SUCCESS;
01948       }
01949 
01950       int nrows = fSql->GetNumRows();
01951       int ncols = fSql->GetNumColumns();
01952 
01953       if (nrows < 0)
01954          return HS_FILE_ERROR;
01955 
01956       if (ncols < 1)
01957          return HS_FILE_ERROR;
01958 
01959       *num_entries = 0;
01960       *time_buffer = (time_t*)malloc(nrows * sizeof(time_t));
01961       *data_buffer = (double*)malloc(nrows * sizeof(double));
01962   
01963       /* Loop through the rows in the result-set */
01964       int row = 0;
01965       time_t tt = 0;
01966       int ann = 0;
01967       double att = 0;
01968       double avv = 0;
01969       while (1) {
01970          status = fSql->Fetch();
01971          if (status != DB_SUCCESS)
01972             break;
01973 
01974          time_t t = 0;
01975          double v = 0;
01976      
01977          const char* timedata = fSql->GetColumn(1);
01978          if (timedata)
01979             t = atoi(timedata);
01980      
01981          const char* valuedata = fSql->GetColumn(2);
01982          if (valuedata)
01983             v = atof(valuedata);
01984      
01985          if (t < start_time || t > end_time)
01986             continue;
01987      
01988          //printf("Row %d, time %d, value %f\n", row, t, v);
01989          //printf("tt: %d, ann: %d\n", tt, ann);
01990      
01991          if (tt == 0 || t >= tt + interval) {
01992         
01993             if (ann > 0) {
01994                assert(row < nrows);
01995            
01996                (*time_buffer)[row] = (time_t)(att/ann);
01997                (*data_buffer)[row] = avv/ann;
01998            
01999                row++;
02000                (*num_entries) = row;
02001             }
02002 
02003             ann = 0;
02004             att = 0;
02005             avv = 0;
02006             tt  = t;
02007          
02008          }
02009 
02010          ann++;
02011          att += t;
02012          avv += v;
02013       }
02014 
02015       if (ann > 0) {
02016          assert(row < nrows);
02017 
02018          (*time_buffer)[row] = (time_t)(att/ann);
02019          (*data_buffer)[row] = avv/ann;
02020      
02021          row++;
02022          (*num_entries) = row;
02023       }
02024 
02025       fSql->Done();
02026 
02027       if (fDebug)
02028          printf("hs_read: return %d entries\n", *num_entries);
02029 
02030       return HS_SUCCESS;
02031    }
02032 
02033    int hs_read(time_t start_time, time_t end_time, time_t interval,
02034                int num_var,
02035                const char* event_name[], const char* tag_name[], const int tag_index[],
02036                int num_entries[],
02037                time_t* time_buffer[], double* data_buffer[],
02038                int st[])
02039    {
02040       if (fDebug)
02041          printf("hs_read: %d variables\n", num_var);
02042 
02043       if (!fSql->IsConnected())
02044          return HS_FILE_ERROR;
02045 
02046       for (int i=0; i<num_var; i++) {
02047 
02048          if (event_name[i]==NULL) {
02049             st[i] = HS_UNDEFINED_EVENT;
02050             num_entries[i] = 0;
02051             continue;
02052          }
02053 
02054          st[i] = hs_read(start_time, end_time, interval,
02055                          event_name[i], tag_name[i], tag_index[i],
02056                          &num_entries[i],
02057                          &time_buffer[i], &data_buffer[i]);
02058       }
02059       
02060       return HS_SUCCESS;
02061    }
02062 };
02063 
02064 ////////////////////////////////////////////////////////
02065 //               Factory constructors                 //
02066 ////////////////////////////////////////////////////////
02067 
02068 #ifdef HAVE_ODBC
02069 MidasHistoryInterface* MakeMidasHistoryODBC()
02070 {
02071    return new SqlHistory(new SqlODBC());
02072 }
02073 #endif
02074 
02075 MidasHistoryInterface* MakeMidasHistorySqlDebug()
02076 {
02077    return new SqlHistory(new SqlDebug());
02078 }
02079 
02080 // end

Midas DOC Version 3.0.0 ---- PSI Stefan Ritt ----
Contributions: Pierre-Andre Amaudruz - Sergio Ballestrero - Suzannah Daviel - Doxygen - Peter Green - Qing Gu - Greg Hackman - Gertjan Hofman - Paul Knowles - Exaos Lee - Rudi Meier - Glenn Moloney - Dave Morris - John M O'Donnell - Konstantin Olchanski - Renee Poutissou - Tamsen Schurman - Andreas Suter - Jan M.Wouters - Piotr Adam Zolnierczuk