history_odbc.cxx

Go to the documentation of this file.
00001 // history_odb.cxx
00002 
00003 #include <stdio.h>
00004 #include <stdint.h>
00005 #include <stdlib.h>
00006 #include <string.h>
00007 #include <ctype.h>
00008 #include <assert.h>
00009 
00010 #include <vector>
00011 #include <string>
00012 
00013 ////////////////////////////////////////
00014 //         MIDAS includes             //
00015 ////////////////////////////////////////
00016 
00017 #include "midas.h"
00018 #include "history_odbc.h"
00019 
00020 ////////////////////////////////////////
00021 //          ODBC includes             //
00022 ////////////////////////////////////////
00023 
00024 // MIDAS defines collide with ODBC
00025 
00026 #define DWORD DWORD_xxx
00027 #define BOOL  BOOL_xxx
00028 
00029 #include <sql.h>
00030 #include <sqlext.h>
00031 #include <sqltypes.h>
00032 
00033 ////////////////////////////////////////
00034 //           helper stuff             //
00035 ////////////////////////////////////////
00036 
00037 #define STRLCPY(dst, src) strlcpy(dst, src, sizeof(dst))
00038 #define FREE(x) { if (x) free(x); x = NULL; }
00039 
00040 ////////////////////////////////////////
00041 //        global variables            //
00042 ////////////////////////////////////////
00043 
00044 static int         gTrace = 0;
00045 static std::string gAlarmName;
00046 static std::string gOdbcDsn;
00047 static time_t      gNextConnect = 0;
00048 static int         gConnectRetry = 0;
00049 
00050 ////////////////////////////////////////
00051 // Definitions extracted from midas.c //
00052 ////////////////////////////////////////
00053 
00054 /********************************************************************/
00055 /* data type sizes */
00056 static int tid_size[] = {
00057    0,                           /* tid == 0 not defined                               */
00058    1,                           /* TID_BYTE      unsigned byte         0       255    */
00059    1,                           /* TID_SBYTE     signed byte         -128      127    */
00060    1,                           /* TID_CHAR      single character      0       255    */
00061    2,                           /* TID_WORD      two bytes             0      65535   */
00062    2,                           /* TID_SHORT     signed word        -32768    32767   */
00063    4,                           /* TID_DWORD     four bytes            0      2^32-1  */
00064    4,                           /* TID_INT       signed dword        -2^31    2^31-1  */
00065    4,                           /* TID_BOOL      four bytes bool       0        1     */
00066    4,                           /* TID_FLOAT     4 Byte float format                  */
00067    8,                           /* TID_DOUBLE    8 Byte float format                  */
00068    1,                           /* TID_BITFIELD  8 Bits Bitfield    00000000 11111111 */
00069    0,                           /* TID_STRING    zero terminated string               */
00070    0,                           /* TID_ARRAY     variable length array of unkown type */
00071    0,                           /* TID_STRUCT    C structure                          */
00072    0,                           /* TID_KEY       key in online database               */
00073    0                            /* TID_LINK      link in online database              */
00074 };
00075 
00076 /* data type names */
00077 static char *tid_name[] = {
00078    "NULL",
00079    "BYTE",
00080    "SBYTE",
00081    "CHAR",
00082    "WORD",
00083    "SHORT",
00084    "DWORD",
00085    "INT",
00086    "BOOL",
00087    "FLOAT",
00088    "DOUBLE",
00089    "BITFIELD",
00090    "STRING",
00091    "ARRAY",
00092    "STRUCT",
00093    "KEY",
00094    "LINK"
00095 };
00096 
00097 // SQL types
00098 static char *sql_type_pgsql[] = {
00099    "xxxINVALIDxxxNULL", // TID_NULL
00100    "SMALLINT",  // MYSQL "TINYINT SIGNED", // TID_BYTE
00101    "SMALLINT",  // MYSQL "TINYINT UNSIGNED",  // TID_SBYTE
00102    "CHAR(1)",   // TID_CHAR
00103    "SMALLINT",  // MYSQL "SMALLINT UNSIGNED ", // TID_WORD
00104    "SMALLINT",  // MYSQL "SMALLINT SIGNED ", // TID_SHORT
00105    "INTEGER",   // MYSQL "INT UNSIGNED ", // TID_DWORD
00106    "INTEGER",   // MYSQL "INT SIGNED ", // TID_INT
00107    "BOOL",      // TID_BOOL
00108    "FLOAT(53)", // MYSQL "DOUBLE" TID_FLOAT
00109    "FLOAT(53)", // MYSQL "DOUBLE" TID_DOUBLE
00110    "SMALLINT",  // MYSQL "TINYINT UNSIGNED", // TID_BITFIELD
00111    "VARCHAR",   // TID_STRING
00112    "xxxINVALIDxxxARRAY",
00113    "xxxINVALIDxxxSTRUCT",
00114    "xxxINVALIDxxxKEY",
00115    "xxxINVALIDxxxLINK"
00116 };
00117 
00118 static char *sql_type_mysql[] = {
00119    "xxxINVALIDxxxNULL", // TID_NULL
00120    "tinyint unsigned",  // TID_BYTE
00121    "tinyint",           // TID_SBYTE
00122    "char",              // TID_CHAR
00123    "smallint unsigned", // TID_WORD
00124    "smallint",          // TID_SHORT
00125    "integer unsigned",  // TID_DWORD
00126    "integer",           // TID_INT
00127    "tinyint",           // TID_BOOL
00128    "float",             // TID_FLOAT
00129    "double",            // TID_DOUBLE
00130    "tinyint unsigned",  // TID_BITFIELD
00131    "VARCHAR",           // TID_STRING
00132    "xxxINVALIDxxxARRAY",
00133    "xxxINVALIDxxxSTRUCT",
00134    "xxxINVALIDxxxKEY",
00135    "xxxINVALIDxxxLINK"
00136 };
00137 
00138 ////////////////////////////////////////
00139 //    Handling of data types          //
00140 ////////////////////////////////////////
00141 
00142 static char **sql_type = NULL;
00143 
00144 static const char* midasTypeName(int tid)
00145 {
00146    assert(tid>=0);
00147    assert(tid<15);
00148    return tid_name[tid];
00149 }
00150 
00151 static const char* midas2sqlType(int tid)
00152 {
00153    assert(tid>=0);
00154    assert(tid<15);
00155    return sql_type[tid];
00156 }
00157 
00158 static int sql2midasType(const char* name)
00159 {
00160    for (int tid=0; tid<15; tid++)
00161       if (strcasecmp(name, sql_type[tid])==0)
00162          return tid;
00163    printf("sql2midasType: Cannot convert SQL data type \'%s\' to a MIDAS data type!\n", name);
00164    return 0;
00165 }
00166 
00167 static bool isCompatible(int tid, const char* sqlType)
00168 {
00169    if (0 && gTrace)
00170       printf("compare types midas \'%s\'=\'%s\' and sql \'%s\'\n", midasTypeName(tid), midas2sqlType(tid), sqlType);
00171 
00172    if (sql2midasType(sqlType) == tid)
00173       return true;
00174 
00175    if (strcasecmp(midas2sqlType(tid), sqlType) == 0)
00176       return true;
00177 
00178    // permit writing FLOAT into DOUBLE
00179    if (tid==TID_FLOAT && strcmp(sqlType, "double")==0)
00180       return true;
00181 
00182    // T2K quirk!
00183    // permit writing BYTE into signed tinyint
00184    if (tid==TID_BYTE && strcmp(sqlType, "tinyint")==0)
00185       return true;
00186 
00187    // T2K quirk!
00188    // permit writing WORD into signed tinyint
00189    if (tid==TID_WORD && strcmp(sqlType, "tinyint")==0)
00190       return true;
00191 
00192    return false;
00193 }
00194 
00195 /////////////////////////////////////////////////
00196 //    Base class for access to SQL functions   //
00197 /////////////////////////////////////////////////
00198 
00199 class SqlBase
00200 {
00201 public:
00202   virtual int Connect(const char* dsn = 0) = 0;
00203   virtual int Disconnect() = 0;
00204   virtual bool IsConnected() = 0;
00205   virtual int Exec(const char* sql) = 0;
00206   virtual int GetNumRows() = 0;
00207   virtual int GetNumColumns() = 0;
00208   virtual int Fetch() = 0;
00209   virtual int Done() = 0;
00210   virtual std::vector<std::string> ListTables() = 0;
00211   virtual std::vector<std::string> ListColumns(const char* table) = 0;
00212   virtual const char* GetColumn(int icol) = 0;
00213   virtual ~SqlBase() { }; // virtual dtor
00214 };
00215 
00216 ////////////////////////////////////////////////////////////////////
00217 //   SqlStdout: for debugging: write all SQL commands to stdout   //
00218 ////////////////////////////////////////////////////////////////////
00219 
00220 class SqlStdout: public SqlBase
00221 {
00222 public:
00223   FILE *fp;
00224   bool fIsConnected;
00225 
00226 public:
00227   int Connect(const char* filename = NULL)
00228   {
00229     if (!filename)
00230       filename = "/dev/fd/1";
00231     fp = fopen(filename, "w");
00232     assert(fp);
00233     sql_type = sql_type_mysql;
00234     fIsConnected = true;
00235     return 0;
00236   }
00237 
00238   int Exec(const char* sql)
00239   {
00240     fprintf(fp, "%s\n", sql);
00241     return 0;
00242   }
00243 
00244   int Disconnect()
00245   {
00246     // do nothing
00247     fIsConnected = false;
00248     return 0;
00249   }
00250   
00251   bool IsConnected()
00252   {
00253     return fIsConnected;
00254   }
00255 
00256   SqlStdout() // ctor
00257   {
00258     fp = NULL;
00259     fIsConnected = false;
00260   }
00261 
00262   ~SqlStdout() // dtor
00263   {
00264     if (fp)
00265       fclose(fp);
00266     fp = NULL;
00267   }
00268 
00269   int GetNumRows() { return 0; }
00270   int GetNumColumns() { return 0; }
00271   int Fetch() { return 0; }
00272   int Done() { return 0; }
00273   std::vector<std::string> ListTables() { std::vector<std::string> list; return list; };
00274   std::vector<std::string> ListColumns(const char* table) { std::vector<std::string> list; return list; };
00275   const char* GetColumn(int icol) { return NULL; };
00276 };
00277 
00278 //////////////////////////////////////////
00279 //   SqlODBC: SQL access through ODBC   //
00280 //////////////////////////////////////////
00281 
00282 class SqlODBC: public SqlBase
00283 {
00284 public:
00285    bool fIsConnected;
00286 
00287    std::string fDSN;
00288 
00289    SQLHENV  fEnv;
00290    SQLHDBC  fDB;
00291    SQLHSTMT fStmt;
00292 
00293    SqlODBC(); // ctor
00294    ~SqlODBC(); // dtor
00295 
00296    int Connect(const char* dsn);
00297    int Disconnect();
00298    bool IsConnected();
00299 
00300    std::vector<std::string> ListTables();
00301    std::vector<std::string> ListColumns(const char* table_name);
00302 
00303    int Exec(const char* sql);
00304 
00305    int GetNumRows();
00306    int GetNumColumns();
00307    int Fetch();
00308    const char* GetColumn(int icol);
00309    int Done();
00310 
00311 protected:
00312    void ReportErrors(const char* from, const char* sqlfunc, int status);
00313 };
00314 
00315 SqlODBC::SqlODBC() // ctor
00316 {
00317    fIsConnected = false;
00318 }
00319 
00320 SqlODBC::~SqlODBC() // dtor
00321 {
00322    Disconnect();
00323 }
00324 
00325 int SqlODBC::Connect(const char* dsn)
00326 {
00327    if (fIsConnected)
00328       Disconnect();
00329 
00330    fDSN = dsn;
00331 
00332    int status = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &fEnv);
00333 
00334    if (!SQL_SUCCEEDED(status)) {
00335       cm_msg(MERROR, "SqlODBC::Connect", "SQLAllocHandle(SQL_HANDLE_ENV) error %d", status);
00336       return -1;
00337    }
00338 
00339    status = SQLSetEnvAttr(fEnv,
00340                           SQL_ATTR_ODBC_VERSION, 
00341                           (void*)SQL_OV_ODBC2,
00342                           0); 
00343    if (!SQL_SUCCEEDED(status)) {
00344       cm_msg(MERROR, "SqlODBC::Connect", "SQLSetEnvAttr() error %d", status);
00345       SQLFreeHandle(SQL_HANDLE_ENV, fEnv);
00346       return -1;
00347    }
00348 
00349    status = SQLAllocHandle(SQL_HANDLE_DBC, fEnv, &fDB); 
00350    if (!SQL_SUCCEEDED(status)) {
00351       cm_msg(MERROR, "SqlODBC::Connect", "SQLAllocHandle(SQL_HANDLE_DBC) error %d", status);
00352       SQLFreeHandle(SQL_HANDLE_ENV, fEnv);
00353       exit(0);
00354    }
00355 
00356    SQLSetConnectAttr(fDB, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
00357 
00358    if (0) {
00359       // connect to PgSQL database
00360       
00361       sql_type = sql_type_pgsql;
00362       status = SQLConnect(fDB, (SQLCHAR*) dsn, SQL_NTS,
00363                           (SQLCHAR*) "xxx", SQL_NTS,
00364                           (SQLCHAR*) "", SQL_NTS);
00365    }
00366    
00367    if (1) {
00368       // connect to MySQL database
00369       
00370       sql_type = sql_type_mysql;
00371       status = SQLConnect(fDB, (SQLCHAR*) dsn, SQL_NTS,
00372                           (SQLCHAR*) NULL, SQL_NTS,
00373                           (SQLCHAR*) NULL, SQL_NTS);
00374    }
00375 
00376    if ((status != SQL_SUCCESS) && (status != SQL_SUCCESS_WITH_INFO)) {
00377       SQLINTEGER    V_OD_err;
00378       SQLSMALLINT   V_OD_mlen;
00379       SQLCHAR       V_OD_stat[10]; // Status SQL
00380       SQLCHAR       V_OD_msg[200];
00381       
00382       SQLGetDiagRec(SQL_HANDLE_DBC, fDB, 1, V_OD_stat, &V_OD_err, V_OD_msg, 100, &V_OD_mlen);
00383       cm_msg(MERROR, "SqlODBC::Connect", "SQLConnect() error %d, %s (%d)", status, V_OD_msg,V_OD_err);
00384       SQLFreeHandle(SQL_HANDLE_ENV, fEnv);
00385       return -1;
00386    }
00387 
00388    SQLAllocHandle(SQL_HANDLE_STMT, fDB, &fStmt);
00389 
00390    cm_msg(MINFO, "SqlODBC::Connect", "history_odbc: Connected to %s", dsn);
00391 
00392    if (gAlarmName.length()>0)
00393       al_reset_alarm(gAlarmName.c_str());
00394 
00395    fIsConnected = true;
00396 
00397    return 0;
00398 }
00399 
00400 int SqlODBC::Disconnect()
00401 {
00402    if (!fIsConnected)
00403       return 0;
00404 
00405    SQLDisconnect(fDB);
00406 
00407    SQLFreeHandle(SQL_HANDLE_DBC,  fDB);
00408    SQLFreeHandle(SQL_HANDLE_STMT, fStmt);
00409    SQLFreeHandle(SQL_HANDLE_ENV,  fEnv);
00410 
00411    fIsConnected = false;
00412 
00413    if (gAlarmName.length() > 0) {
00414       char buf[256];
00415       sprintf(buf, "%s lost connection to the history database", gAlarmName.c_str());
00416       al_trigger_alarm(gAlarmName.c_str(), buf, "Alarm", "", AT_INTERNAL);
00417    }
00418 
00419    return 0;
00420 }
00421 
00422 bool SqlODBC::IsConnected()
00423 {
00424    return fIsConnected;
00425 }
00426 
00427 void SqlODBC::ReportErrors(const char* from, const char* sqlfunc, int status)
00428 {
00429    if (gTrace)
00430       printf("%s: %s error %d\n", from, sqlfunc, status);
00431 
00432    for (int i=1; ; i++) {
00433       SQLCHAR            state[10]; // Status SQL
00434       SQLINTEGER                 error;
00435       SQLCHAR              message[1024];
00436       SQLSMALLINT                mlen;
00437       
00438       status = SQLGetDiagRec(SQL_HANDLE_STMT,
00439                              fStmt,
00440                              i,
00441                              state,
00442                              &error,
00443                              message,
00444                              sizeof(message),
00445                              &mlen);
00446       
00447       if (status == SQL_NO_DATA)
00448          break;
00449 
00450       if (!SQL_SUCCEEDED(status)) {
00451          cm_msg(MERROR, "SqlODBC::ReportErrors", "SQLGetDiagRec() error %d", status);
00452          break;
00453       }
00454       
00455       if (1 || (error != 1060) && (error != 1050)) {
00456          if (gTrace)
00457             printf("%s: %s error: state: \'%s\', message: \'%s\', native error: %d\n", from, sqlfunc, state, message, error);
00458          cm_msg(MERROR, from, "%s error: state: \'%s\', message: \'%s\', native error: %d", sqlfunc, state, message, error);
00459       }
00460    }
00461 }
00462 
00463 std::vector<std::string> SqlODBC::ListTables()
00464 {
00465    std::vector<std::string> list;
00466 
00467    if (!fIsConnected)
00468       return list;
00469 
00470    /* Retrieve a list of tables */
00471    int status = SQLTables(fStmt, NULL, 0, NULL, 0, NULL, 0, (SQLCHAR*)"TABLE", SQL_NTS);
00472    if (!SQL_SUCCEEDED(status)) {
00473       ReportErrors("SqlODBC::ListTables", "SQLTables()", status);
00474       return list;
00475    }
00476 
00477    int ncols = GetNumColumns();
00478    int nrows = GetNumRows();
00479 
00480    if (ncols <= 0 || nrows <= 0) {
00481       cm_msg(MERROR, "SqlODBC::ListTables", "Error: SQLTables() returned unexpected number of columns %d or number of rows %d, status %d", ncols, nrows, status);
00482    }
00483 
00484    int row = 0;
00485    while (Fetch()) {
00486       if (0) {
00487          printf("row %d: ", row);
00488          for (int i=1; i<=ncols; i++) {
00489             const char* s = GetColumn(i);
00490             printf("[%s]", s);
00491          }
00492          printf("\n");
00493          row++;
00494       }
00495 
00496       list.push_back(GetColumn(3));
00497    }
00498 
00499    Done();
00500 
00501    return list;
00502 }
00503 
00504 std::vector<std::string> SqlODBC::ListColumns(const char* table)
00505 {
00506    std::vector<std::string> list;
00507 
00508    if (!fIsConnected)
00509       return list;
00510 
00511    /* Retrieve a list of tables */
00512    int status = SQLColumns(fStmt, NULL, 0, NULL, 0, (SQLCHAR*)table, SQL_NTS, NULL, 0);
00513    if (!SQL_SUCCEEDED(status)) {
00514       ReportErrors("SqlODBC::ListColumns", "SQLColumns()", status);
00515       return list;
00516    }
00517 
00518    int ncols = GetNumColumns();
00519    int nrows = GetNumRows(); // nrows seems to be always "-1"
00520 
00521    if (ncols <= 0 /*|| nrows <= 0*/) {
00522       cm_msg(MERROR, "SqlODBC::ListColumns", "Error: SQLColumns(\'%s\') returned unexpected number of columns %d or number of rows %d, status %d", table, ncols, nrows, status);
00523    }
00524 
00525    //printf("get columns [%s]: status %d, ncols %d, nrows %d\n", table, status, ncols, nrows);
00526 
00527    int row = 0;
00528    while (Fetch()) {
00529       if (0) {
00530          printf("row %d: ", row);
00531          for (int i=1; i<=ncols; i++) {
00532             const char* s = GetColumn(i);
00533             printf("[%s]", s);
00534          }
00535          printf("\n");
00536          row++;
00537       }
00538 
00539       list.push_back(GetColumn(4)); // column name
00540       list.push_back(GetColumn(6)); // column type
00541    }
00542 
00543    Done();
00544 
00545    return list;
00546 }
00547 
00548 int SqlODBC::Exec(const char* sql)
00549 {
00550    if (!fIsConnected)
00551       return -1;
00552     
00553    int status;
00554 
00555    for (int i=0; i<2; i++) {
00556       if (gTrace)
00557          printf("SqlODBC::Exec: %s\n", sql);
00558 
00559       status = SQLExecDirect(fStmt,(SQLCHAR*)sql,SQL_NTS);
00560 
00561       if (SQL_SUCCEEDED(status)) {
00562          return 0;
00563       }
00564 
00565       if (gTrace)
00566          printf("SqlODBC::Exec: SQLExecDirect() error %d: SQL command: \"%s\"\n", status, sql);
00567       
00568       ReportErrors("SqlODBC::Exec", "SQLExecDirect()", status);
00569       
00570       cm_msg(MINFO, "SqlODBC::Exec", "history_odbc: Trying to reconnect to %s", fDSN.c_str());
00571 
00572       // try to reconnect
00573       std::string dsn = fDSN;
00574       Disconnect();
00575       Connect(dsn.c_str());
00576 
00577       if (!fIsConnected) {
00578          cm_msg(MERROR, "SqlODBC::Exec", "history_odbc: Reconnect to %s failed. Database is down?", fDSN.c_str());
00579       }
00580    }
00581 
00582    return 0;
00583 }
00584 
00585 int SqlODBC::GetNumRows()
00586 {
00587    SQLINTEGER nrows = 0;
00588    /* How many rows are there */
00589    int status = SQLRowCount(fStmt, &nrows);
00590    if (!SQL_SUCCEEDED(status)) {
00591       ReportErrors("SqlODBC::GetNumRow", "SQLRowCount()", status);
00592       return -1;
00593    }
00594    return nrows;
00595 }
00596 
00597 int SqlODBC::GetNumColumns()
00598 {
00599    SQLSMALLINT ncols = 0;
00600    /* How many columns are there */
00601    int status = SQLNumResultCols(fStmt, &ncols);
00602    if (!SQL_SUCCEEDED(status)) {
00603       ReportErrors("SqlODBC::GetNumColumns", "SQLNumResultCols()", status);
00604       return -1;
00605    }
00606    return ncols;
00607 }
00608 
00609 int SqlODBC::Fetch()
00610 {
00611    int status = SQLFetch(fStmt);
00612 
00613    if (status == SQL_NO_DATA)
00614       return 0;
00615 
00616    if (!SQL_SUCCEEDED(status)) {
00617       ReportErrors("SqlODBC::Fetch", "SQLFetch()", status);
00618       return -1;
00619    }
00620 
00621    return 1;
00622 }
00623 
00624 int SqlODBC::Done()
00625 {
00626    int status = SQLCloseCursor(fStmt);
00627    if (!SQL_SUCCEEDED(status)) {
00628       ReportErrors("SqlODBC::Done", "SQLCloseCursor()", status);
00629       return -1;
00630    }
00631    return 0;
00632 }
00633 
00634 const char* SqlODBC::GetColumn(int icol)
00635 {
00636   static char buf[1024];
00637   SQLINTEGER indicator;
00638   int status = SQLGetData(fStmt, icol, SQL_C_CHAR, buf, sizeof(buf), &indicator);
00639 
00640   if (!SQL_SUCCEEDED(status)) {
00641     return NULL;
00642   }
00643 
00644   if (indicator == SQL_NULL_DATA)
00645     return NULL;
00646 
00647   return buf;
00648 }
00649 
00650 //////////////////////////////////////////
00651 //        Done with SQL stuff           //
00652 //////////////////////////////////////////
00653 
00654 ////////////////////////////////////////////////////////
00655 //  Data structures to keep track of Events and Tags  //
00656 ////////////////////////////////////////////////////////
00657 
00658 struct Tag
00659 {
00660    std::string column_name;
00661    int   offset;
00662    TAG   tag;
00663    bool  create;
00664 };
00665 
00666 struct Event
00667 {
00668    std::string event_name;
00669    std::string table_name;
00670    std::vector<Tag> tags;
00671    bool  active;
00672    bool  create;
00673    
00674    Event() // ctor
00675    {
00676       active = false;
00677       create = false;
00678    }
00679    
00680    ~Event() // dtor
00681    {
00682       create = false;
00683       active = false;
00684    }
00685 };
00686 
00687 static void PrintTags(int ntags, const TAG tags[])
00688 {
00689    for (int i=0; i<ntags; i++)
00690       printf("tag %d: %s %s[%d]\n", i, midasTypeName(tags[i].type), tags[i].name, tags[i].n_data);
00691 }
00692 
00693 int CreateEvent(SqlBase* sql, Event* e)
00694 {
00695    int status;
00696    
00697    if (e->create) {
00698       char buf[1024];
00699       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());
00700       status = sql->Exec(buf);
00701       if (status != 0) {
00702          e->active = false;
00703          return -1;
00704       }
00705    }
00706    
00707    for (size_t i=0; i<e->tags.size(); i++)
00708       if (e->tags[i].create) {
00709          char buf[1024];
00710 
00711          sprintf(buf, "ALTER TABLE %s ADD COLUMN %s %s;",
00712                  e->table_name.c_str(),
00713                  e->tags[i].column_name.c_str(),
00714                  midas2sqlType(e->tags[i].tag.type));
00715 
00716          status = sql->Exec(buf);
00717 
00718          if (status != 0) {
00719             e->active = false;
00720             return -1;
00721          }
00722       }
00723 
00724    return 0;
00725 }
00726 
00727 int WriteEvent(SqlBase* sql, Event *e, time_t t, const char*buf, int size)
00728 {
00729    //printf("event %d, time %s", rec.event_id, ctime(&t));
00730 
00731    int n  = e->tags.size();
00732    
00733    std::string tags;
00734    std::string values;
00735    
00736    //if (n>0)
00737    //  printf(" %s", ctime(&t));
00738    
00739    for (int i=0; i<n; i++) {
00740       const Tag*t = &e->tags[i];
00741       
00742       if (t) {
00743          int offset = t->offset;
00744          void* ptr = (void*)(buf+offset);
00745 
00746          int arraySize = t->tag.n_data;
00747          
00748          for (int j=0; j<arraySize; j++) {
00749             tags   += ", ";
00750             values += ", ";
00751             
00752             if (arraySize <= 1)
00753                tags += t->column_name;
00754             else {
00755                tags += t->column_name;
00756                char s[256];
00757                sprintf(s,"_%d", j);
00758                tags += s;
00759             }
00760                 
00761             char s[1024];
00762             
00763             switch (t->tag.type) {
00764             default:
00765                sprintf(s, "unknownType%d", t->tag.type);
00766                break;
00767             case 1: /* BYTE */
00768                sprintf(s, "%u",((uint8_t*)ptr)[j]);
00769                break;
00770             case 2: /* SBYTE */
00771                sprintf(s, "%d",((int8_t*)ptr)[j]);
00772                break;
00773             case 3: /* CHAR */
00774                sprintf(s, "\'%c\'",((char*)ptr)[j]);
00775                break;
00776             case 4: /* WORD */
00777                sprintf(s, "%u",((uint16_t*)ptr)[j]);
00778                break;
00779             case 5: /* SHORT */
00780                sprintf(s, "%d",((int16_t*)ptr)[j]);
00781                break;
00782             case 6: /* DWORD */
00783                sprintf(s, "%u",((uint32_t*)ptr)[j]);
00784                break;
00785             case 7: /* INT */
00786                sprintf(s, "%d",((int32_t*)ptr)[j]);
00787                break;
00788             case 8: /* BOOL */
00789                sprintf(s, "%u",((uint32_t*)ptr)[j]);
00790                break;
00791             case 9: /* FLOAT */
00792                sprintf(s, "\'%.8g\'",((float*)ptr)[j]);
00793                break;
00794             case 10: /* DOUBLE */
00795                sprintf(s, "\'%.16g\'",((double*)ptr)[j]);
00796                     break;
00797             }
00798             
00799             values += s;
00800          }
00801       }
00802    }
00803 
00804    // 2001-02-16 20:38:40.1
00805    char s[1024];
00806    strftime(s,sizeof(s)-1,"%Y-%m-%d %H:%M:%S.0",localtime(&t));
00807    
00808    char sss[102400];
00809    sprintf(sss, "INSERT INTO %s (_t_time, _i_time%s) VALUES (\'%s\', \'%d\'%s);",
00810            e->table_name.c_str(),
00811            tags.c_str(),
00812            s,
00813            (int)t,
00814            values.c_str());
00815 
00816    int status = sql->Exec(sss);
00817    
00818    if (status != 0) {
00819       e->active = false;
00820       return -1;
00821    }
00822 
00823    return 0;
00824 }
00825 
00826 ////////////////////////////////////////////////////////
00827 //    Implementations of public hs_xxx() functions    //
00828 ////////////////////////////////////////////////////////
00829 
00830 static SqlBase *gSql = NULL;
00831 
00832 int hs_debug_odbc(int debug)
00833 {
00834    int old = gTrace;
00835    gTrace = debug;
00836    return old;
00837 }
00838 
00839 int hs_set_alarm_odbc(const char* alarm_name)
00840 {
00841    if (alarm_name)
00842       gAlarmName = alarm_name;
00843    else
00844       gAlarmName = "";
00845    return HS_SUCCESS;
00846 }
00847 
00848 int hs_connect_odbc(const char* odbc_dsn)
00849 {
00850    int status;
00851 
00852    if (gSql && gSql->IsConnected())
00853       if (strcmp(gOdbcDsn.c_str(), odbc_dsn) == 0)
00854          return HS_SUCCESS;
00855    
00856    if (gSql)
00857       hs_disconnect_odbc();
00858    
00859    assert(!gSql);
00860 
00861    gOdbcDsn = odbc_dsn;
00862    
00863    if (gTrace)
00864       printf("hs_connect_odbc: set DSN to \'%s\'\n", gOdbcDsn.c_str());
00865 
00866    if (gOdbcDsn[0] == '/')
00867       gSql = new SqlStdout();
00868    else
00869       gSql = new SqlODBC();
00870    
00871    status = gSql->Connect(gOdbcDsn.c_str());
00872    if (status != 0)
00873       return HS_FILE_ERROR;
00874    
00875    return HS_SUCCESS;
00876 }
00877 
00878 int hs_disconnect_odbc()
00879 {
00880    if (gTrace)
00881       printf("hs_disconnect_odbc!\n");
00882 
00883    if (gSql) {
00884       gSql->Disconnect();
00885       delete gSql;
00886       gSql = NULL;
00887    }
00888 
00889    return HS_SUCCESS;
00890 }
00891 
00892 // convert MIDAS names to SQL names
00893 
00894 static std::string tagName2columnName(const char* s)
00895 {
00896   char out[1024];
00897   int i;
00898   for (i=0; s[i]!=0; i++)
00899     {
00900       char c = s[i];
00901       if (isalpha(c) || isdigit(c))
00902         out[i] = tolower(c);
00903       else
00904         out[i] = '_';
00905     }
00906 
00907   out[i] = 0;
00908   return out;
00909 }
00910 
00911 static std::string eventName2tableName(const char* s)
00912 {
00913    return tagName2columnName(s);
00914 }
00915 
00916 ////////////////////////////////////////////////////////
00917 //             Functions used by mlogger              //
00918 ////////////////////////////////////////////////////////
00919 
00920 static std::vector<Event*> gEvents;
00921 
00922 int hs_define_event_odbc(const char* event_name, const TAG tags[], int tags_size)
00923 {
00924    assert(gSql);
00925 
00926    int ntags = tags_size/sizeof(TAG);
00927 
00928    if (gTrace) {
00929       printf("define event [%s] with %d tags:\n", event_name, ntags);
00930       PrintTags(ntags, tags);
00931    }
00932 
00933    // delete all events with the same name
00934    for (unsigned int i=0; i<gEvents.size(); i++)
00935       if (gEvents[i])
00936          if (gEvents[i]->event_name == event_name) {
00937             printf("deleting exising event %s\n", event_name);
00938             delete gEvents[i];
00939             gEvents[i] = NULL;
00940          }
00941 
00942    Event* e = new Event();
00943 
00944    e->event_name = event_name;
00945    e->table_name = eventName2tableName(event_name);
00946    e->active = true;
00947    e->create = false;
00948 
00949    int offset = 0;
00950    for (int i=0; i<ntags; i++) {
00951       for (unsigned int j=0; j<tags[i].n_data; j++) {
00952          Tag t;
00953          t.create = false;
00954          if (tags[i].n_data == 1)
00955             t.column_name = tagName2columnName(tags[i].name);
00956          else {
00957             char s[256];
00958             sprintf(s, "_%d", j);
00959             t.column_name = tagName2columnName(tags[i].name) + s;
00960          }
00961          t.offset = offset;
00962          t.tag = tags[i];
00963          t.tag.n_data = 1;
00964          e->tags.push_back(t);
00965          int size = tid_size[tags[i].type];
00966          offset += size;
00967       }
00968    }
00969 
00970    std::vector<std::string> columns = gSql->ListColumns(e->table_name.c_str());
00971 
00972    if (columns.size() <= 0)
00973       e->create = true;
00974 
00975    for (size_t i=0; i<e->tags.size(); i++) {
00976       // check for duplicate column names
00977       for (size_t j=i+1; j<e->tags.size(); j++)
00978          if (e->tags[i].column_name == e->tags[j].column_name) {
00979             cm_msg(MERROR, "hs_define_event_odbc", "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);
00980             e->active = false;
00981             break;
00982          }
00983 
00984       // check if new column needs to be created
00985       bool found = false;
00986       for (size_t j=0; j<columns.size(); j+=2) {
00987          if (e->tags[i].column_name == columns[j]) {
00988             // column exists, check data type
00989             //printf("column \'%s\', data type %s\n", e->tags[i].column_name.c_str(), columns[j+1].c_str());
00990 
00991             if (!isCompatible(e->tags[i].tag.type, columns[j+1].c_str())) {
00992                cm_msg(MERROR, "hs_define_event_odbc", "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());
00993                e->active = false;
00994             }
00995 
00996             found = true;
00997             break;
00998          }
00999       }
01000 
01001       if (!found) {
01002          // create it
01003          //printf("column \'%s\', data type %s  --- create!\n", e->tags[i].column_name.c_str(), midasTypeName(e->tags[i].tag.type));
01004          e->tags[i].create = true;
01005       }
01006    }
01007 
01008    int status = CreateEvent(gSql, e);
01009 
01010    if (status != 0) {
01011       // if cannot create event in SQL database, disable this event and carry on.
01012 
01013       e->active = false;
01014 
01015       if (gAlarmName.length() > 0) {
01016          char buf[256];
01017          sprintf(buf, "%s cannot define history event \'%s\', see messages", gAlarmName.c_str(), e->event_name.c_str());
01018          al_trigger_alarm(gAlarmName.c_str(), buf, "Alarm", "", AT_INTERNAL);
01019       }
01020    }
01021 
01022    // find empty slot in events list
01023    for (unsigned int i=0; i<gEvents.size(); i++)
01024       if (!gEvents[i]) {
01025          gEvents[i] = e;
01026          e = NULL;
01027          break;
01028       }
01029 
01030    // if no empty slots, add at the end
01031    if (e)
01032       gEvents.push_back(e);
01033 
01034    return HS_SUCCESS;
01035 }
01036 
01037 int hs_write_event_odbc(const char* event_name, time_t timestamp, const char* buffer, int buffer_size)
01038 {
01039    if (gTrace)
01040       printf("write event [%s] size %d\n", event_name, buffer_size);
01041 
01042    assert(gSql);
01043 
01044    // if disconnected, try to reconnect
01045 
01046    if (!gSql->IsConnected()) {
01047       time_t now = time(NULL);
01048 
01049       // too early to try reconnecting?
01050       if (gConnectRetry!=0 && now < gNextConnect) {
01051          return HS_FILE_ERROR;
01052       }
01053 
01054       int status = gSql->Connect(gOdbcDsn.c_str());
01055 
01056       if (status != 0) {
01057 
01058          // first retry in 5 seconds
01059          if (gConnectRetry == 0)
01060             gConnectRetry = 5;
01061 
01062          gNextConnect = now + gConnectRetry;
01063 
01064          // exponential backoff
01065          gConnectRetry *= 2;
01066 
01067          // but no more than every 10 minutes
01068          if (gConnectRetry > 10*60)
01069             gConnectRetry = 10*60;
01070 
01071          return HS_FILE_ERROR;
01072       }
01073    }
01074 
01075    gNextConnect = 0;
01076    gConnectRetry = 0;
01077 
01078    Event *e = NULL;
01079 
01080    // find this event
01081    for (size_t i=0; i<gEvents.size(); i++)
01082       if (gEvents[i]->event_name == event_name) {
01083          e = gEvents[i];
01084          break;
01085       }
01086 
01087    // not found
01088    if (!e)
01089       return HS_FILE_ERROR;
01090 
01091    // deactivated because of error?
01092    if (!e->active)
01093       return HS_FILE_ERROR;
01094 
01095    int status = WriteEvent(gSql, e, timestamp, buffer, buffer_size);
01096 
01097    // if could not write to SQL?
01098    if (status != 0) {
01099 
01100       // if lost SQL connection, try again later
01101       if (!gSql->IsConnected()) {
01102          return HS_FILE_ERROR;
01103       }
01104 
01105       // otherwise, deactivate this event, raise alarm
01106 
01107       e->active = 0;
01108 
01109       if (gAlarmName.length() > 0) {
01110          char buf[256];
01111          sprintf(buf, "%s cannot write history event \'%s\', see messages", gAlarmName.c_str(), e->event_name.c_str());
01112          al_trigger_alarm(gAlarmName.c_str(), buf, "Alarm", "", AT_INTERNAL);
01113       }
01114 
01115       return HS_FILE_ERROR;
01116    }
01117 
01118    return HS_SUCCESS;
01119 }
01120 
01121 ////////////////////////////////////////////////////////
01122 //             Functions used by mhttpd               //
01123 ////////////////////////////////////////////////////////
01124 
01125 int hs_get_tags_odbc(const char* event_name, int *n_tags, TAG **tags)
01126 {
01127    assert(gSql);
01128 
01129    *n_tags = 0;
01130    *tags = NULL;
01131 
01132    std::string tname = eventName2tableName(event_name);
01133 
01134    std::vector<std::string> columns = gSql->ListColumns(tname.c_str());
01135 
01136    if (columns.size() < 1) {
01137       cm_msg(MERROR, "hs_get_tags_odbc", "Cannot get columns for table \'%s\', try to reconnect to the database", tname.c_str());
01138 
01139       gSql->Disconnect();
01140       gSql->Connect(gOdbcDsn.c_str());
01141       if (!gSql->IsConnected()) {
01142 
01143          if (gAlarmName.length() > 0) {
01144             char buf[256];
01145             sprintf(buf, "%s lost connection to the history database", gAlarmName.c_str());
01146             al_trigger_alarm(gAlarmName.c_str(), buf, "Alarm", "", AT_INTERNAL);
01147          }
01148 
01149          return HS_FILE_ERROR;
01150       }
01151 
01152       columns = gSql->ListColumns(tname.c_str());      
01153    }
01154 
01155    TAG* t = (TAG*)malloc(sizeof(TAG)*columns.size());
01156    assert(t);
01157 
01158    int n=0;
01159    for (unsigned int j=0; j<columns.size(); j+=2) {
01160       if (columns[j] == "_t_time")
01161          continue;
01162       if (columns[j] == "_i_time")
01163          continue;
01164       STRLCPY(t[n].name, columns[j].c_str());
01165       t[n].type = sql2midasType(columns[j+1].c_str());
01166       t[n].n_data = 1;
01167       n++;
01168    }
01169 
01170    if (0) {
01171       printf("event [%s] table [%s], tags: %d\n", event_name, tname.c_str(), n);
01172       PrintTags(n, t);
01173    }
01174 
01175    *n_tags = n;
01176    *tags = t;
01177 
01178    return HS_SUCCESS;
01179 }
01180 
01181 int hs_read_odbc(time_t start_time, time_t end_time, time_t interval,
01182                  const char* event_name, const char* tag_name, int var_index,
01183                  int *num_entries,
01184                  time_t** time_buffer, double**data_buffer)
01185 {
01186    assert(gSql);
01187 
01188    *num_entries = 0;
01189    *time_buffer = NULL;
01190    *data_buffer = NULL;
01191 
01192    //printf("start %d, end %d, dt %d, interval %d, max points %d\n", start_time, end_time, end_time-start_time, interval, (end_time-start_time)/interval);
01193 
01194    std::string ename = eventName2tableName(event_name);
01195    std::string tname = tagName2columnName(tag_name);
01196 
01197    int status = 1;
01198 
01199    std::vector<std::string> tables = gSql->ListTables();
01200 
01201    if (tables.size() <= 1) {
01202         cm_msg(MERROR, "hs_read_odbc", "ListTables() returned nothing, trying to reconnect to the database");
01203 
01204         gSql->Disconnect();
01205         gSql->Connect(gOdbcDsn.c_str());
01206         if (!gSql->IsConnected()) {
01207 
01208            if (gAlarmName.length() > 0) {
01209               char buf[256];
01210               sprintf(buf, "%s lost connection to the history database", gAlarmName.c_str());
01211               al_trigger_alarm(gAlarmName.c_str(), buf, "Alarm", "", AT_INTERNAL);
01212            }
01213 
01214            return HS_FILE_ERROR;
01215         }
01216 
01217         tables = gSql->ListTables();
01218    }
01219 
01220    int len = strlen(event_name);
01221    for (unsigned int i=0; i<tables.size(); i++) {
01222       //printf("table %s\n", tables[i].c_str());
01223 
01224       const char* t = tables[i].c_str();
01225       const char* s = strstr(t, ename.c_str());
01226 
01227       if (s==t && (t[len]=='_'||t[len]==0)) {
01228 
01229          bool found = false;
01230          std::vector<std::string> columns = gSql->ListColumns(tables[i].c_str());
01231          for (unsigned int j=0; j<columns.size(); j+=2) {
01232             //printf("column %s\n", columns[j].c_str());
01233             if (columns[j] == tname) {
01234                found = true;
01235                break;
01236             }
01237          }
01238 
01239          if (found) {
01240             char cmd[256];
01241             sprintf(cmd, "SELECT _i_time, %s FROM %s where _i_time>=%d and _i_time<=%d;",
01242                     tname.c_str(), tables[i].c_str(),
01243                     (int)start_time, (int)end_time);
01244            
01245             status = gSql->Exec(cmd);
01246 
01247             if (gTrace) {
01248                printf("hs_read_odbc: event %s, name %s, index %d: Read table %s: status %d, nrows: %d\n",
01249                       event_name, tag_name, var_index,
01250                       tables[i].c_str(),
01251                       status,
01252                       gSql->GetNumRows());
01253             }
01254 
01255             if (status)
01256                continue;
01257 
01258             if (gSql->GetNumRows() == 0) {
01259                gSql->Done();
01260                status = SQL_NO_DATA;
01261                continue;
01262             }
01263 
01264             break;
01265          }
01266       }
01267    }
01268 
01269    if (status == SQL_NO_DATA)
01270       return HS_SUCCESS;
01271 
01272    if (status) {
01273       if (gTrace)
01274          printf("hs_read_odbc: event %s, name %s, index %d, could not find the right table? status %d\n",
01275                 event_name, tag_name, var_index,
01276                 status);
01277       
01278       return HS_FILE_ERROR;
01279    }
01280 
01281    int nrows = gSql->GetNumRows();
01282    int ncols = gSql->GetNumColumns();
01283 
01284    if (nrows == 0)
01285       return HS_SUCCESS;
01286 
01287    if (gTrace)
01288       printf("hs_read_odbc: event %s, name %s, index %d, nrows: %d, ncols: %d\n",
01289              event_name, tag_name, var_index,
01290              nrows, ncols);
01291 
01292    if (nrows < 0)
01293       return HS_FILE_ERROR;
01294 
01295    if (ncols < 1)
01296       return HS_FILE_ERROR;
01297 
01298    *num_entries = 0;
01299    *time_buffer = (time_t*)malloc(nrows * sizeof(time_t));
01300    *data_buffer = (double*)malloc(nrows * sizeof(double));
01301   
01302    /* Loop through the rows in the result-set */
01303    int row = 0;
01304    time_t tt = 0;
01305    int ann = 0;
01306    double att = 0;
01307    double avv = 0;
01308    while (gSql->Fetch()) {
01309       time_t t = 0;
01310       double v = 0;
01311      
01312       const char* timedata = gSql->GetColumn(1);
01313       if (timedata)
01314          t = atoi(timedata);
01315      
01316       const char* valuedata = gSql->GetColumn(2);
01317       if (valuedata)
01318          v = atof(valuedata);
01319      
01320       if (t < start_time || t > end_time)
01321          continue;
01322      
01323       //printf("Row %d, time %d, value %f\n", row, t, v);
01324       //printf("tt: %d, ann: %d\n", tt, ann);
01325      
01326       if (tt == 0 || t >= tt + interval) {
01327         
01328          if (ann > 0) {
01329             assert(row < nrows);
01330            
01331             (*time_buffer)[row] = (time_t)(att/ann);
01332             (*data_buffer)[row] = avv/ann;
01333            
01334             row++;
01335             (*num_entries) = row;
01336          }
01337 
01338          ann = 0;
01339          att = 0;
01340          avv = 0;
01341          tt  = t;
01342          
01343       }
01344 
01345       ann++;
01346       att += t;
01347       avv += v;
01348    }
01349 
01350    if (ann > 0) {
01351       assert(row < nrows);
01352 
01353       (*time_buffer)[row] = (time_t)(att/ann);
01354       (*data_buffer)[row] = avv/ann;
01355      
01356       row++;
01357       (*num_entries) = row;
01358    }
01359 
01360    gSql->Done();
01361 
01362    if (gTrace)
01363       printf("hs_read_odbc: return %d events\n", *num_entries);
01364 
01365    return HS_SUCCESS;
01366 }
01367 
01368 // 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