MIDAS
Loading...
Searching...
No Matches
history_schema.cxx
Go to the documentation of this file.
1/********************************************************************\
2
3 Name: history_schema.cxx
4 Created by: Konstantin Olchanski
5
6 Contents: Schema based MIDAS history. Available drivers:
7 FileHistory: storage of data in binary files (replacement for the traditional MIDAS history)
8 MysqlHistory: storage of data in MySQL database (replacement for the ODBC based SQL history)
9 PgsqlHistory: storage of data in PostgreSQL database
10 SqliteHistory: storage of data in SQLITE3 database (not suitable for production use)
11
12\********************************************************************/
13
14#undef NDEBUG // midas required assert() to be always enabled
15
16#include "midas.h"
17#include "msystem.h"
18#include "mstrlcpy.h"
19
20#include <math.h>
21
22#include <vector>
23#include <list>
24#include <string>
25#include <map>
26#include <unordered_map>
27#include <algorithm>
28
29// make mysql/my_global.h happy - it redefines closesocket()
30#undef closesocket
31
32//
33// benchmarks
34//
35// /usr/bin/time ./linux/bin/mh2sql . /ladd/iris_data2/alpha/alphacpc09-elog-history/history/121019.hst
36// -rw-r--r-- 1 alpha users 161028048 Oct 19 2012 /ladd/iris_data2/alpha/alphacpc09-elog-history/history/121019.hst
37// flush 10000, sync=OFF -> 51.51user 1.51system 0:53.76elapsed 98%CPU
38// flush 1000000, sync=NORMAL -> 51.83user 2.09system 1:08.37elapsed 78%CPU (flush never activated)
39// flush 100000, sync=NORMAL -> 51.38user 1.94system 1:06.94elapsed 79%CPU
40// flush 10000, sync=NORMAL -> 51.37user 2.03system 1:31.63elapsed 58%CPU
41// flush 1000, sync=NORMAL -> 52.16user 2.70system 4:38.58elapsed 19%CPU
42
44// MIDAS includes //
46
47#include "midas.h"
48#include "history.h"
49
51// helper stuff //
53
54#define FREE(x) { if (x) free(x); (x) = NULL; }
55
56static char* skip_spaces(char* s)
57{
58 while (*s) {
59 if (!isspace(*s))
60 break;
61 s++;
62 }
63 return s;
64}
65
66static std::string TimeToString(time_t t)
67{
68 const char* sign = "";
69
70 if (t == 0)
71 return "0";
72
73 time_t tt = t;
74
75 if (t < 0) {
76 sign = "-";
77 tt = -t;
78 }
79
80 assert(tt > 0);
81
82 std::string v;
83 while (tt) {
84 char c = '0' + (char)(tt%10);
85 tt /= 10;
86 v = c + v;
87 }
88
89 v = sign + v;
90
91 //printf("time %.0f -> %s\n", (double)t, v.c_str());
92
93 return v;
94}
95
96static std::string SmallIntToString(int i)
97{
98 //int ii = i;
99
100 if (i == 0)
101 return "0";
102
103 assert(i > 0);
104
105 std::string v;
106 while (i) {
107 char c = '0' + (char)(i%10);
108 i /= 10;
109 v = c + v;
110 }
111
112 //printf("SmallIntToString: %d -> %s\n", ii, v.c_str());
113
114 return v;
115}
116
117static bool MatchEventName(const char* event_name, const char* var_event_name)
118{
119 // new-style event name: "equipment_name/variable_name:tag_name"
120 // old-style event name: "equipment_name:tag_name" ("variable_name" is missing)
121 bool newStyleEventName = (strchr(var_event_name, '/')!=NULL);
122
123 //printf("looking for event_name [%s], try table [%s] event name [%s], new style [%d]\n", var_event_name, table_name, event_name, newStyleEventName);
124
125 if (strcasecmp(event_name, var_event_name) == 0) {
126 return true;
127 } else if (newStyleEventName) {
128 return false;
129 } else { // for old style names, need more parsing
130 bool match = false;
131
132 const char* s = event_name;
133 for (int j=0; s[j]; j++) {
134
135 if ((var_event_name[j]==0) && (s[j]=='/')) {
136 match = true;
137 break;
138 }
139
140 if ((var_event_name[j]==0) && (s[j]=='_')) {
141 match = true;
142 break;
143 }
144
145 if (var_event_name[j]==0) {
146 match = false;
147 break;
148 }
149
150 if (tolower(var_event_name[j]) != tolower(s[j])) { // does not work for UTF-8 Unicode
151 match = false;
152 break;
153 }
154 }
155
156 return match;
157 }
158}
159
160static bool MatchTagName(const char* tag_name, int n_data, const char* var_tag_name, const int var_tag_index)
161{
162 char alt_tag_name[1024]; // maybe this is an array without "Names"?
163 sprintf(alt_tag_name, "%s[%d]", var_tag_name, var_tag_index);
164
165 //printf(" looking for tag [%s] alt [%s], try column name [%s]\n", var_tag_name, alt_tag_name, tag_name);
166
167 if (strcasecmp(tag_name, var_tag_name) == 0)
168 if (var_tag_index >= 0 && var_tag_index < n_data)
169 return true;
170
171 if (strcasecmp(tag_name, alt_tag_name) == 0)
172 return true;
173
174 return false;
175}
176
177static void PrintTags(int ntags, const TAG tags[])
178{
179 for (int i=0; i<ntags; i++)
180 printf("tag %d: %s %s[%d]\n", i, rpc_tid_name(tags[i].type), tags[i].name, tags[i].n_data);
181}
182
183// convert MIDAS event name to something acceptable as an SQL identifier - table name, column name, etc
184
185static std::string MidasNameToSqlName(const char* s)
186{
187 std::string out;
188
189 for (int i=0; s[i]!=0; i++) {
190 char c = s[i];
191 if (isalpha(c) || isdigit(c))
192 out += tolower(c); // does not work for UTF-8 Unicode
193 else
194 out += '_';
195 }
196
197 return out;
198}
199
200// convert MIDAS event name to something acceptable as a file name
201
202static std::string MidasNameToFileName(const char* s)
203{
204 std::string out;
205
206 for (int i=0; s[i]!=0; i++) {
207 char c = s[i];
208 if (isalpha(c) || isdigit(c))
209 out += tolower(c); // does not work for UTF-8 Unicode
210 else
211 out += '_';
212 }
213
214 return out;
215}
216
217// compare event names
218
219static int event_name_cmp(const std::string& e1, const char* e2)
220{
221 return strcasecmp(e1.c_str(), e2);
222}
223
224// compare variable names
225
226static int var_name_cmp(const std::string& v1, const char* v2)
227{
228 return strcasecmp(v1.c_str(), v2);
229}
230
232// SQL data types //
234
235#ifdef HAVE_SQLITE
236static const char *sql_type_sqlite[TID_LAST] = {
237 "xxxINVALIDxxxNULL", // TID_NULL
238 "INTEGER", // TID_UINT8
239 "INTEGER", // TID_INT8
240 "TEXT", // TID_CHAR
241 "INTEGER", // TID_UINT16
242 "INTEGER", // TID_INT16
243 "INTEGER", // TID_UINT32
244 "INTEGER", // TID_INT32
245 "INTEGER", // TID_BOOL
246 "REAL", // TID_FLOAT
247 "REAL", // TID_DOUBLE
248 "INTEGER", // TID_BITFIELD
249 "TEXT", // TID_STRING
250 "xxxINVALIDxxxARRAY",
251 "xxxINVALIDxxxSTRUCT",
252 "xxxINVALIDxxxKEY",
253 "xxxINVALIDxxxLINK"
254};
255#endif
256
257#ifdef HAVE_PGSQL
258static const char *sql_type_pgsql[TID_LAST] = {
259 "xxxINVALIDxxxNULL", // TID_NULL
260 "smallint", // TID_BYTE
261 "smallint", // TID_SBYTE
262 "char(1)", // TID_CHAR
263 "integer", // TID_WORD
264 "smallint", // TID_SHORT
265 "bigint", // TID_DWORD
266 "integer", // TID_INT
267 "smallint", // TID_BOOL
268 "real", // TID_FLOAT
269 "double precision", // TID_DOUBLE
270 "bigint", // TID_BITFIELD
271 "text", // TID_STRING
272 "xxxINVALIDxxxARRAY",
273 "xxxINVALIDxxxSTRUCT",
274 "xxxINVALIDxxxKEY",
275 "xxxINVALIDxxxLINK"
276};
277#endif
278
279#ifdef HAVE_MYSQL
280static const char *sql_type_mysql[TID_LAST] = {
281 "xxxINVALIDxxxNULL", // TID_NULL
282 "tinyint unsigned", // TID_BYTE
283 "tinyint", // TID_SBYTE
284 "char", // TID_CHAR
285 "smallint unsigned", // TID_WORD
286 "smallint", // TID_SHORT
287 "integer unsigned", // TID_DWORD
288 "integer", // TID_INT
289 "tinyint", // TID_BOOL
290 "float", // TID_FLOAT
291 "double", // TID_DOUBLE
292 "integer unsigned", // TID_BITFIELD
293 "VARCHAR", // TID_STRING
294 "xxxINVALIDxxxARRAY",
295 "xxxINVALIDxxxSTRUCT",
296 "xxxINVALIDxxxKEY",
297 "xxxINVALIDxxxLINK"
298};
299#endif
300
301void DoctorPgsqlColumnType(std::string* col_type, const char* index_type)
302{
303 if (*col_type == index_type)
304 return;
305
306 if (*col_type == "bigint" && strcmp(index_type, "int8")==0) {
307 *col_type = index_type;
308 return;
309 }
310
311 if (*col_type == "integer" && strcmp(index_type, "int4")==0) {
312 *col_type = index_type;
313 return;
314 }
315
316 if (*col_type == "smallint" && strcmp(index_type, "int2")==0) {
317 *col_type = index_type;
318 return;
319 }
320
321 cm_msg(MERROR, "SqlHistory", "Cannot use this SQL database, incompatible column names: created column type [%s] is reported with column type [%s]", index_type, col_type->c_str());
323 abort();
324}
325
326void DoctorSqlColumnType(std::string* col_type, const char* index_type)
327{
328 if (*col_type == index_type)
329 return;
330
331 if (*col_type == "int(10) unsigned" && strcmp(index_type, "integer unsigned")==0) {
332 *col_type = index_type;
333 return;
334 }
335
336 if (*col_type == "int(11)" && strcmp(index_type, "integer")==0) {
337 *col_type = index_type;
338 return;
339 }
340
341 if (*col_type == "integer" && strcmp(index_type, "int(11)")==0) {
342 *col_type = index_type;
343 return;
344 }
345
346 // MYSQL 8.0.23
347
348 if (*col_type == "int" && strcmp(index_type, "integer")==0) {
349 *col_type = index_type;
350 return;
351 }
352
353 if (*col_type == "int unsigned" && strcmp(index_type, "integer unsigned")==0) {
354 *col_type = index_type;
355 return;
356 }
357
358 cm_msg(MERROR, "SqlHistory", "Cannot use this SQL database, incompatible column names: created column type [%s] is reported with column type [%s]", index_type, col_type->c_str());
360 abort();
361}
362
363#if 0
364static int sql2midasType_mysql(const char* name)
365{
366 for (int tid=0; tid<TID_LAST; tid++)
367 if (strcasecmp(name, sql_type_mysql[tid])==0)
368 return tid;
369 // FIXME!
370 printf("sql2midasType: Cannot convert SQL data type \'%s\' to a MIDAS data type!\n", name);
371 return 0;
372}
373#endif
374
375#if 0
376static int sql2midasType_sqlite(const char* name)
377{
378 if (strcmp(name, "INTEGER") == 0)
379 return TID_INT;
380 if (strcmp(name, "REAL") == 0)
381 return TID_DOUBLE;
382 if (strcmp(name, "TEXT") == 0)
383 return TID_STRING;
384 // FIXME!
385 printf("sql2midasType: Cannot convert SQL data type \'%s\' to a MIDAS data type!\n", name);
386 return 0;
387}
388#endif
389
391// Schema base classes //
393
395 std::string tag_name; // tag name from MIDAS
396 std::string tag_type; // tag type from MIDAS
397 std::string name; // entry name, same as tag_name except when read from SQL history when it could be the SQL column name
398 int type = 0; // MIDAS data type TID_xxx
399 int n_data = 0; // MIDAS array size
400 int n_bytes = 0; // n_data * size of MIDAS data type (only used by HsFileSchema?)
401};
402
404{
405public:
406
407 // event schema definitions
408 std::string fEventName;
409 time_t fTimeFrom = 0;
410 time_t fTimeTo = 0;
411 std::vector<HsSchemaEntry> fVariables;
412 std::vector<int> fOffsets;
413 size_t fNumBytes = 0;
414
415 // run time data used by hs_write_event()
418 size_t fWriteMaxSize = 0;
419 size_t fWriteMinSize = 0;
420
421 // schema disabled by write error
422 bool fDisabled = true;
423
424public:
425
426 HsSchema() // ctor
427 {
428 // empty
429 }
430
431 virtual void remove_inactive_columns() = 0; // used by SQL schemas
432 virtual void print(bool print_tags = true) const;
433 virtual ~HsSchema(); // dtor
434 virtual int flush_buffers() = 0;
435 virtual int close() = 0;
436 virtual int write_event(const time_t t, const char* data, const size_t data_size) = 0;
437 virtual int match_event_var(const char* event_name, const char* var_name, const int var_index);
438 virtual int read_last_written(const time_t timestamp,
439 const int debug,
440 time_t* last_written) = 0;
441 virtual int read_data(const time_t start_time,
442 const time_t end_time,
443 const int num_var, const std::vector<int>& var_schema_index, const int var_index[],
444 const int debug,
445 std::vector<time_t>& last_time,
446 MidasHistoryBufferInterface* buffer[]) = 0;
447};
448
450{
451protected:
452 std::vector<HsSchema*> fData;
453
454public:
455 ~HsSchemaVector() { // dtor
456 clear();
457 }
458
460 return fData[index];
461 }
462
463 size_t size() const {
464 return fData.size();
465 }
466
467 void add(HsSchema* s);
468
469 void clear() {
470 for (size_t i=0; i<fData.size(); i++)
471 if (fData[i]) {
472 delete fData[i];
473 fData[i] = NULL;
474 }
475 fData.clear();
476 }
477
478 void print(bool print_tags = true) const {
479 for (size_t i=0; i<fData.size(); i++)
480 fData[i]->print(print_tags);
481 }
482
483 HsSchema* find_event(const char* event_name, const time_t timestamp, int debug = 0);
484};
485
487// Base class functions //
489
491{
492 //printf("HsSchema::dtor %p!\n", this);
493 // only report if undersize/oversize happens more than once -
494 // the first occurence is already reported by hs_write_event()
495 if (fCountWriteUndersize > 1) {
496 cm_msg(MERROR, "hs_write_event", "Event \'%s\' data size mismatch count: %d, expected %zu bytes, hs_write_event() called with as few as %zu bytes", fEventName.c_str(), fCountWriteUndersize, fNumBytes, fWriteMinSize);
497 }
498
499 if (fCountWriteOversize > 1) {
500 cm_msg(MERROR, "hs_write_event", "Event \'%s\' data size mismatch count: %d, expected %zu bytes, hs_write_event() called with as many as %zu bytes", fEventName.c_str(), fCountWriteOversize, fNumBytes, fWriteMaxSize);
501 }
502};
503
505{
506 // schema list "data" is sorted by decreasing "fTimeFrom", newest schema first
507
508 //printf("add: %s..%s %s\n", TimeToString(s->fTimeFrom).c_str(), TimeToString(s->fTimeTo).c_str(), s->fEventName.c_str());
509
510 bool added = false;
511
512 for (auto it = fData.begin(); it != fData.end(); it++) {
513 if (event_name_cmp((*it)->fEventName, s->fEventName.c_str())==0) {
514 if (s->fTimeFrom == (*it)->fTimeFrom) {
515 // duplicate schema, keep the last one added (for file schema it is the newer file)
516 s->fTimeTo = (*it)->fTimeTo;
517 delete (*it);
518 (*it) = s;
519 return;
520 }
521 }
522
523 if (s->fTimeFrom > (*it)->fTimeFrom) {
524 fData.insert(it, s);
525 added = true;
526 break;
527 }
528 }
529
530 if (!added) {
531 fData.push_back(s);
532 }
533
534 //time_t oldest_time_from = fData.back()->fTimeFrom;
535
536 time_t time_to = 0;
537
538 for (auto it = fData.begin(); it != fData.end(); it++) {
539 if (event_name_cmp((*it)->fEventName, s->fEventName.c_str())==0) {
540 (*it)->fTimeTo = time_to;
541 time_to = (*it)->fTimeFrom;
542
543 //printf("vvv: %s..%s %s\n", TimeToString((*it)->fTimeFrom-oldest_time_from).c_str(), TimeToString((*it)->fTimeTo-oldest_time_from).c_str(), (*it)->fEventName.c_str());
544 }
545 }
546}
547
548HsSchema* HsSchemaVector::find_event(const char* event_name, time_t t, int debug)
549{
550 HsSchema* ss = NULL;
551
552 if (debug) {
553 printf("find_event: All schema for event %s: (total %zu)\n", event_name, fData.size());
554 int found = 0;
555 for (size_t i=0; i<fData.size(); i++) {
556 HsSchema* s = fData[i];
557 printf("find_event: schema %zu name [%s]\n", i, s->fEventName.c_str());
558 if (event_name)
559 if (event_name_cmp(s->fEventName, event_name)!=0)
560 continue;
561 s->print();
562 found++;
563 }
564 printf("find_event: Found %d schemas for event %s\n", found, event_name);
565
566 //if (found == 0)
567 // abort();
568 }
569
570 for (size_t i=0; i<fData.size(); i++) {
571 HsSchema* s = fData[i];
572
573 // wrong event
574 if (event_name)
575 if (event_name_cmp(s->fEventName, event_name)!=0)
576 continue;
577
578 // schema is from after the time we are looking for
579 if (s->fTimeFrom > t)
580 continue;
581
582 if (!ss)
583 ss = s;
584
585 // remember the newest schema
586 if (s->fTimeFrom > ss->fTimeFrom)
587 ss = s;
588 }
589
590 // try to find
591 for (size_t i=0; i<fData.size(); i++) {
592 HsSchema* s = fData[i];
593
594 // wrong event
595 if (event_name)
596 if (event_name_cmp(s->fEventName, event_name)!=0)
597 continue;
598
599 // schema is from after the time we are looking for
600 if (s->fTimeFrom > t)
601 continue;
602
603 if (!ss)
604 ss = s;
605
606 // remember the newest schema
607 if (s->fTimeFrom > ss->fTimeFrom)
608 ss = s;
609 }
610
611 if (debug) {
612 if (ss) {
613 printf("find_event: for time %s, returning:\n", TimeToString(t).c_str());
614 ss->print();
615 } else {
616 printf("find_event: for time %s, nothing found:\n", TimeToString(t).c_str());
617 }
618 }
619
620 return ss;
621}
622
624// Sql interface class //
626
627class SqlBase
628{
629public:
630 int fDebug = 0;
631 bool fIsConnected = false;
633
634 SqlBase() { // ctor
635 };
636
637 virtual ~SqlBase() { // dtor
638 // confirm that the destructor of the concrete class
639 // disconnected the database
640 assert(!fIsConnected);
641 fDebug = 0;
642 fIsConnected = false;
643 }
644
645 virtual int Connect(const char* path) = 0;
646 virtual int Disconnect() = 0;
647 virtual bool IsConnected() = 0;
648
649 virtual int ListTables(std::vector<std::string> *plist) = 0;
650 virtual int ListColumns(const char* table_name, std::vector<std::string> *plist) = 0;
651
652 // sql commands
653 virtual int Exec(const char* table_name, const char* sql) = 0;
654 virtual int ExecDisconnected(const char* table_name, const char* sql) = 0;
655
656 // queries
657 virtual int Prepare(const char* table_name, const char* sql) = 0;
658 virtual int Step() = 0;
659 virtual const char* GetText(int column) = 0;
660 virtual time_t GetTime(int column) = 0;
661 virtual double GetDouble(int column) = 0;
662 virtual int Finalize() = 0;
663
664 // transactions
665 virtual int OpenTransaction(const char* table_name) = 0;
666 virtual int CommitTransaction(const char* table_name) = 0;
667 virtual int RollbackTransaction(const char* table_name) = 0;
668
669 // data types
670 virtual const char* ColumnType(int midas_tid) = 0;
671 virtual bool TypesCompatible(int midas_tid, const char* sql_type) = 0;
672
673 // string quoting
674 virtual std::string QuoteString(const char* s) = 0; // quote text string
675 virtual std::string QuoteId(const char* s) = 0; // quote identifier, such as table or column name
676};
677
679// Schema concrete classes //
681
682class HsSqlSchema : public HsSchema
683{
684public:
685
686 SqlBase* fSql = NULL;
687 std::string fTableName;
688 std::vector<std::string> fColumnNames;
689 std::vector<std::string> fColumnTypes;
690 std::vector<bool> fColumnInactive;
691
692 // Cache: SQL column name -> index into fColumnNames/fColumnTypes/fColumnInactive/fVariables/fOffsets.
693 // Rebuilt lazily when it is out of sync with fColumnNames (see find_column_index()).
694 std::unordered_map<std::string, size_t> fColumnIndexCache;
695
696public:
697
698 HsSqlSchema() // ctor
699 {
700 // empty
701 }
702
703 ~HsSqlSchema() // dtor
704 {
705 assert(get_transaction_count() == 0);
706 }
707
709 int find_column_index(const std::string& column_name);
710 void print(bool print_tags = true) const;
714 int close_transaction();
716 int close() { return close_transaction(); }
717 int write_event(const time_t t, const char* data, const size_t data_size);
718 int match_event_var(const char* event_name, const char* var_name, const int var_index);
719 int read_last_written(const time_t timestamp,
720 const int debug,
721 time_t* last_written);
722 int read_data(const time_t start_time,
723 const time_t end_time,
724 const int num_var, const std::vector<int>& var_schema_index, const int var_index[],
725 const int debug,
726 std::vector<time_t>& last_time,
728
729private:
730 // Sqlite uses a transaction per table; MySQL uses a single transaction for all tables.
731 // But to support future "single transaction" DBs more easily (e.g. if user wants to
732 // log to both Postgres and MySQL in future), we keep track of the transaction count
733 // per SQL engine.
735 static std::map<SqlBase*, int> gfTransactionCount;
736};
737
738std::map<SqlBase*, int> HsSqlSchema::gfTransactionCount;
739
740int HsSqlSchema::find_column_index(const std::string& column_name)
741{
742 // Rebuild the cache if it is out of sync with fColumnNames. Columns are only
743 // ever appended (read_column_names) or removed in bulk with a resize
744 // (remove_inactive_columns), so a size mismatch reliably detects staleness.
745 if (fColumnIndexCache.size() != fColumnNames.size()) {
746 fColumnIndexCache.clear();
747 for (size_t i=0; i<fColumnNames.size(); i++)
749 }
750
751 auto it = fColumnIndexCache.find(column_name);
752 if (it == fColumnIndexCache.end())
753 return -1;
754 return (int)it->second;
755}
756
757class HsFileSchema : public HsSchema
758{
759public:
760
761 std::string fFileName;
762 size_t fRecordSize = 0;
763 off64_t fDataOffset = 0;
764 size_t fLastSize = 0;
765 int fWriterFd = -1;
767 char* fRecordBuffer = NULL;
768
769public:
770 off64_t fFileSizeInitial = 0; // initial file size
771 off64_t fFileSize = 0; // file size including any new data we wrote
772
773public:
774
775 HsFileSchema() // ctor
776 {
777 // empty
778 }
779
781 {
782 //printf("HsFileSchema::dtor %p!\n", this);
783 close();
784 fRecordSize = 0;
785 fDataOffset = 0;
786 fLastSize = 0;
787 fWriterFd = -1;
788 if (fRecordBuffer) {
789 free(fRecordBuffer);
790 fRecordBuffer = NULL;
791 }
793 }
794
795 void remove_inactive_columns() { /* empty */ };
796 void print(bool print_tags = true) const;
797 int flush_buffers() { return HS_SUCCESS; };
798 int close();
799 int write_event(const time_t t, const char* data, const size_t data_size);
800 int read_last_written(const time_t timestamp,
801 const int debug,
802 time_t* last_written);
803 int read_data(const time_t start_time,
804 const time_t end_time,
805 const int num_var, const std::vector<int>& var_schema_index, const int var_index[],
806 const int debug,
807 std::vector<time_t>& last_time,
809};
810
812// Print functions //
814
815void HsSchema::print(bool print_tags) const
816{
817 size_t nv = this->fVariables.size();
818 printf("event [%s], time %s..%s, %zu variables, %zu bytes\n", this->fEventName.c_str(), TimeToString(this->fTimeFrom).c_str(), TimeToString(this->fTimeTo).c_str(), nv, fNumBytes);
819 if (print_tags)
820 for (size_t j=0; j<nv; j++)
821 printf(" %zu: name [%s], type [%s] tid %d, n_data %d, n_bytes %d, offset %d\n", j, this->fVariables[j].name.c_str(), rpc_tid_name(this->fVariables[j].type), this->fVariables[j].type, this->fVariables[j].n_data, this->fVariables[j].n_bytes, this->fOffsets[j]);
822};
823
824void HsSqlSchema::print(bool print_tags) const
825{
826 size_t nv = this->fVariables.size();
827 printf("event [%s], sql_table [%s], time %s..%s, %zu variables, %zu bytes\n", this->fEventName.c_str(), this->fTableName.c_str(), TimeToString(this->fTimeFrom).c_str(), TimeToString(this->fTimeTo).c_str(), nv, fNumBytes);
828 if (print_tags) {
829 for (size_t j=0; j<nv; j++) {
830 printf(" %zu: name [%s], type [%s] tid %d, n_data %d, n_bytes %d", j, this->fVariables[j].name.c_str(), rpc_tid_name(this->fVariables[j].type), this->fVariables[j].type, this->fVariables[j].n_data, this->fVariables[j].n_bytes);
831 printf(", sql_column [%s], sql_type [%s], offset %d", this->fColumnNames[j].c_str(), this->fColumnTypes[j].c_str(), this->fOffsets[j]);
832 printf(", inactive %d", (int)this->fColumnInactive[j]);
833 printf("\n");
834 }
835 }
836}
837
838void HsFileSchema::print(bool print_tags) const
839{
840 size_t nv = this->fVariables.size();
841 printf("event [%s], file_name [%s], time %s..%s, %zu variables, %zu bytes, dat_offset %jd, record_size %zu\n", this->fEventName.c_str(), this->fFileName.c_str(), TimeToString(this->fTimeFrom).c_str(), TimeToString(this->fTimeTo).c_str(), nv, fNumBytes, (intmax_t)fDataOffset, fRecordSize);
842 if (print_tags) {
843 for (size_t j=0; j<nv; j++)
844 printf(" %zu: name [%s], type [%s] tid %d, n_data %d, n_bytes %d, offset %d\n", j, this->fVariables[j].name.c_str(), rpc_tid_name(this->fVariables[j].type), this->fVariables[j].type, this->fVariables[j].n_data, this->fVariables[j].n_bytes, this->fOffsets[j]);
845 }
846}
847
849// File functions //
851
852#ifdef HAVE_MYSQL
853
855// MYSQL/MariaDB database access //
857
858//#warning !!!HAVE_MYSQL!!!
859
860//#include <my_global.h> // my_global.h removed MySQL 8.0, MariaDB 10.2. K.O.
861#include <mysql.h>
862
863class Mysql: public SqlBase
864{
865public:
866 std::string fConnectString;
867 MYSQL* fMysql = NULL;
868
869 // query results
870 MYSQL_RES* fResult = NULL;
871 MYSQL_ROW fRow = NULL;
872 int fNumFields = 0;
873
874 // disconnected operation
875 size_t fMaxDisconnected = 0;
876 std::list<std::string> fDisconnectedBuffer;
877 time_t fNextReconnect = 0;
878 int fNextReconnectDelaySec = 0;
879 int fDisconnectedLost = 0;
880
881 Mysql(); // ctor
882 ~Mysql(); // dtor
883
884 int Connect(const char* path);
885 int Disconnect();
886 bool IsConnected();
887
888 int ConnectTable(const char* table_name);
889
890 int ListTables(std::vector<std::string> *plist);
891 int ListColumns(const char* table_name, std::vector<std::string> *plist);
892
893 int Exec(const char* table_name, const char* sql);
894 int ExecDisconnected(const char* table_name, const char* sql);
895
896 int Prepare(const char* table_name, const char* sql);
897 int Step();
898 const char* GetText(int column);
899 time_t GetTime(int column);
900 double GetDouble(int column);
901 int Finalize();
902
903 int OpenTransaction(const char* table_name);
904 int CommitTransaction(const char* table_name);
905 int RollbackTransaction(const char* table_name);
906
907 const char* ColumnType(int midas_tid);
908 bool TypesCompatible(int midas_tid, const char* sql_type);
909
910 std::string QuoteId(const char* s);
911 std::string QuoteString(const char* s);
912};
913
914Mysql::Mysql() // ctor
915{
916 fMysql = NULL;
917 fResult = NULL;
918 fRow = NULL;
919 fNumFields = 0;
920 fMaxDisconnected = 1000;
921 fNextReconnect = 0;
922 fNextReconnectDelaySec = 0;
923 fDisconnectedLost = 0;
924 fTransactionPerTable = false;
925}
926
927Mysql::~Mysql() // dtor
928{
929 Disconnect();
930 fMysql = NULL;
931 fResult = NULL;
932 fRow = NULL;
933 fNumFields = 0;
934 if (fDisconnectedBuffer.size() > 0) {
935 cm_msg(MINFO, "Mysql::~Mysql", "Lost %zu history entries accumulated while disconnected from the database", fDisconnectedBuffer.size());
937 }
938}
939
940int Mysql::Connect(const char* connect_string)
941{
942 if (fIsConnected)
943 Disconnect();
944
945 fConnectString = connect_string;
946
947 if (fDebug) {
948 cm_msg(MINFO, "Mysql::Connect", "Connecting to Mysql database specified by \'%s\'", connect_string);
950 }
951
952 std::string host_name;
953 std::string user_name;
954 std::string user_password;
955 std::string db_name;
956 int tcp_port = 0;
957 std::string unix_socket;
958 std::string buffer;
959
960 FILE* fp = fopen(connect_string, "r");
961 if (!fp) {
962 cm_msg(MERROR, "Mysql::Connect", "Cannot read MYSQL connection parameters from \'%s\', fopen() error %d (%s)", connect_string, errno, strerror(errno));
963 return DB_FILE_ERROR;
964 }
965
966 while (1) {
967 char buf[256];
968 char* s = fgets(buf, sizeof(buf), fp);
969 if (!s)
970 break; // EOF
971
972 char*ss;
973 // kill trailing \n and \r
974 ss = strchr(s, '\n');
975 if (ss) *ss = 0;
976 ss = strchr(s, '\r');
977 if (ss) *ss = 0;
978
979 //printf("line [%s]\n", s);
980
981 if (strncasecmp(s, "server=", 7)==0)
982 host_name = skip_spaces(s + 7);
983 if (strncasecmp(s, "port=", 5)==0)
984 tcp_port = atoi(skip_spaces(s + 5));
985 if (strncasecmp(s, "database=", 9)==0)
986 db_name = skip_spaces(s + 9);
987 if (strncasecmp(s, "socket=", 7)==0)
988 unix_socket = skip_spaces(s + 7);
989 if (strncasecmp(s, "user=", 5)==0)
990 user_name = skip_spaces(s + 5);
991 if (strncasecmp(s, "password=", 9)==0)
992 user_password = skip_spaces(s + 9);
993 if (strncasecmp(s, "buffer=", 7)==0)
994 buffer = skip_spaces(s + 7);
995 }
996
997 fclose(fp);
998
999 int buffer_int = atoi(buffer.c_str());
1000
1001 if (buffer_int > 0 && buffer_int < 1000000)
1002 fMaxDisconnected = buffer_int;
1003
1004 if (fDebug)
1005 printf("Mysql::Connect: connecting to server [%s] port %d, unix socket [%s], database [%s], user [%s], password [%s], buffer [%zu]\n", host_name.c_str(), tcp_port, unix_socket.c_str(), db_name.c_str(), user_name.c_str(), user_password.c_str(), fMaxDisconnected);
1006
1007 if (!fMysql) {
1008 fMysql = mysql_init(NULL);
1009 if (!fMysql) {
1010 return DB_FILE_ERROR;
1011 }
1012 }
1013
1014 int client_flag = 0|CLIENT_IGNORE_SIGPIPE;
1015
1016 if (mysql_real_connect(fMysql, host_name.c_str(), user_name.c_str(), user_password.c_str(), db_name.c_str(), tcp_port, unix_socket.c_str(), client_flag) == NULL) {
1017 cm_msg(MERROR, "Mysql::Connect", "mysql_real_connect() to host [%s], port %d, unix socket [%s], database [%s], user [%s], password [%s]: error %d (%s)", host_name.c_str(), tcp_port, unix_socket.c_str(), db_name.c_str(), user_name.c_str(), "xxx", mysql_errno(fMysql), mysql_error(fMysql));
1018 Disconnect();
1019 return DB_FILE_ERROR;
1020 }
1021
1022 int status;
1023
1024 // FIXME:
1025 //my_bool reconnect = 0;
1026 //mysql_options(&mysql, MYSQL_OPT_RECONNECT, &reconnect);
1027
1028 status = Exec("(notable)", "SET SESSION sql_mode='ANSI'");
1029 if (status != DB_SUCCESS) {
1030 cm_msg(MERROR, "Mysql::Connect", "Cannot set ANSI mode, nothing will work");
1031 Disconnect();
1032 return DB_FILE_ERROR;
1033 }
1034
1035 if (fDebug) {
1036 cm_msg(MINFO, "Mysql::Connect", "Connected to a MySQL database on host [%s], port %d, unix socket [%s], database [%s], user [%s], password [%s], buffer %zu", host_name.c_str(), tcp_port, unix_socket.c_str(), db_name.c_str(), user_name.c_str(), "xxx", fMaxDisconnected);
1038 }
1039
1040 fIsConnected = true;
1041
1042 int count = 0;
1043 while (fDisconnectedBuffer.size() > 0) {
1044 status = Exec("(flush)", fDisconnectedBuffer.front().c_str());
1045 if (status != DB_SUCCESS) {
1046 return status;
1047 }
1048 fDisconnectedBuffer.pop_front();
1049 count++;
1050 }
1051
1052 if (count > 0) {
1053 cm_msg(MINFO, "Mysql::Connect", "Saved %d, lost %d history events accumulated while disconnected from the database", count, fDisconnectedLost);
1055 }
1056
1057 assert(fDisconnectedBuffer.size() == 0);
1058 fDisconnectedLost = 0;
1059
1060 return DB_SUCCESS;
1061}
1062
1063int Mysql::Disconnect()
1064{
1065 if (fRow) {
1066 // FIXME: mysql_free_result(fResult);
1067 }
1068
1069 if (fResult)
1070 mysql_free_result(fResult);
1071
1072 if (fMysql)
1073 mysql_close(fMysql);
1074
1075 fMysql = NULL;
1076 fResult = NULL;
1077 fRow = NULL;
1078
1079 fIsConnected = false;
1080 return DB_SUCCESS;
1081}
1082
1083bool Mysql::IsConnected()
1084{
1085 return fIsConnected;
1086}
1087
1088int Mysql::OpenTransaction(const char* table_name)
1089{
1090 return Exec(table_name, "START TRANSACTION");
1091 return DB_SUCCESS;
1092}
1093
1094int Mysql::CommitTransaction(const char* table_name)
1095{
1096 Exec(table_name, "COMMIT");
1097 return DB_SUCCESS;
1098}
1099
1100int Mysql::RollbackTransaction(const char* table_name)
1101{
1102 Exec(table_name, "ROLLBACK");
1103 return DB_SUCCESS;
1104}
1105
1106int Mysql::ListTables(std::vector<std::string> *plist)
1107{
1108 if (!fIsConnected)
1109 return DB_FILE_ERROR;
1110
1111 if (fDebug)
1112 printf("Mysql::ListTables!\n");
1113
1114 int status;
1115
1116 fResult = mysql_list_tables(fMysql, NULL);
1117
1118 if (fResult == NULL) {
1119 cm_msg(MERROR, "Mysql::ListTables", "mysql_list_tables() error %d (%s)", mysql_errno(fMysql), mysql_error(fMysql));
1120 return DB_FILE_ERROR;
1121 }
1122
1123 fNumFields = mysql_num_fields(fResult);
1124
1125 while (1) {
1126 status = Step();
1127 if (status != DB_SUCCESS)
1128 break;
1129 std::string tn = GetText(0);
1130 plist->push_back(tn);
1131 };
1132
1133 status = Finalize();
1134
1135 return DB_SUCCESS;
1136}
1137
1138int Mysql::ListColumns(const char* table_name, std::vector<std::string> *plist)
1139{
1140 if (!fIsConnected)
1141 return DB_FILE_ERROR;
1142
1143 if (fDebug)
1144 printf("Mysql::ListColumns for table \'%s\'\n", table_name);
1145
1146 int status;
1147
1148 std::string cmd;
1149 cmd += "SHOW COLUMNS FROM ";
1150 cmd += QuoteId(table_name);
1151 cmd += ";";
1152
1153 status = Prepare(table_name, cmd.c_str());
1154 if (status != DB_SUCCESS)
1155 return status;
1156
1157 fNumFields = mysql_num_fields(fResult);
1158
1159 while (1) {
1160 status = Step();
1161 if (status != DB_SUCCESS)
1162 break;
1163 std::string cn = GetText(0);
1164 std::string ct = GetText(1);
1165 plist->push_back(cn);
1166 plist->push_back(ct);
1167 //printf("cn [%s]\n", cn.c_str());
1168 //for (int i=0; i<fNumFields; i++)
1169 //printf(" field[%d]: [%s]\n", i, GetText(i));
1170 };
1171
1172 status = Finalize();
1173
1174 return DB_SUCCESS;
1175}
1176
1177int Mysql::Exec(const char* table_name, const char* sql)
1178{
1179 if (fDebug)
1180 printf("Mysql::Exec(%s, %s)\n", table_name, sql);
1181
1182 // FIXME: match Sqlite::Exec() return values:
1183 // return values:
1184 // DB_SUCCESS
1185 // DB_FILE_ERROR: not connected
1186 // DB_KEY_EXIST: "table already exists"
1187
1188 if (!fMysql)
1189 return DB_FILE_ERROR;
1190
1191 assert(fMysql);
1192 assert(fResult == NULL); // there should be no unfinalized queries
1193 assert(fRow == NULL);
1194
1195 if (mysql_query(fMysql, sql)) {
1196 if (mysql_errno(fMysql) == 1050) { // "Table already exists"
1197 return DB_KEY_EXIST;
1198 }
1199 if (mysql_errno(fMysql) == 1146) { // "Table does not exist"
1200 return DB_FILE_ERROR;
1201 }
1202 cm_msg(MERROR, "Mysql::Exec", "mysql_query(%s) error %d (%s)", sql, mysql_errno(fMysql), mysql_error(fMysql));
1203 if (mysql_errno(fMysql) == 1060) // "Duplicate column name"
1204 return DB_KEY_EXIST;
1205 if (mysql_errno(fMysql) == 2006) { // "MySQL server has gone away"
1206 Disconnect();
1207 return ExecDisconnected(table_name, sql);
1208 }
1209 return DB_FILE_ERROR;
1210 }
1211
1212 return DB_SUCCESS;
1213}
1214
1215int Mysql::ExecDisconnected(const char* table_name, const char* sql)
1216{
1217 if (fDebug)
1218 printf("Mysql::ExecDisconnected(%s, %s)\n", table_name, sql);
1219
1220 if (fDisconnectedBuffer.size() < fMaxDisconnected) {
1221 fDisconnectedBuffer.push_back(sql);
1222 if (fDisconnectedBuffer.size() >= fMaxDisconnected) {
1223 cm_msg(MERROR, "Mysql::ExecDisconnected", "Error: Disconnected database buffer overflow, size %zu, subsequent events are lost", fDisconnectedBuffer.size());
1224 }
1225 } else {
1226 fDisconnectedLost++;
1227 }
1228
1229 time_t now = time(NULL);
1230
1231 if (fNextReconnect == 0 || now >= fNextReconnect) {
1232 int status = Connect(fConnectString.c_str());
1233 if (status == DB_SUCCESS) {
1234 fNextReconnect = 0;
1235 fNextReconnectDelaySec = 0;
1236 } else {
1237 if (fNextReconnectDelaySec == 0) {
1238 fNextReconnectDelaySec = 5;
1239 } else if (fNextReconnectDelaySec < 10*60) {
1240 fNextReconnectDelaySec *= 2;
1241 }
1242 if (fDebug) {
1243 cm_msg(MINFO, "Mysql::ExecDisconnected", "Next reconnect attempt in %d sec, history events buffered %zu, lost %d", fNextReconnectDelaySec, fDisconnectedBuffer.size(), fDisconnectedLost);
1245 }
1246 fNextReconnect = now + fNextReconnectDelaySec;
1247 }
1248 }
1249
1250 return DB_SUCCESS;
1251}
1252
1253int Mysql::Prepare(const char* table_name, const char* sql)
1254{
1255 if (fDebug)
1256 printf("Mysql::Prepare(%s, %s)\n", table_name, sql);
1257
1258 if (!fMysql)
1259 return DB_FILE_ERROR;
1260
1261 assert(fMysql);
1262 assert(fResult == NULL); // there should be no unfinalized queries
1263 assert(fRow == NULL);
1264
1265 // if (mysql_query(fMysql, sql)) {
1266 // cm_msg(MERROR, "Mysql::Prepare", "mysql_query(%s) error %d (%s)", sql, mysql_errno(fMysql), mysql_error(fMysql));
1267 // return DB_FILE_ERROR;
1268 //}
1269
1270 // Check if the connection to MySQL timed out; fix from B. Smith
1271 int status = mysql_query(fMysql, sql);
1272 if (status) {
1273 if (mysql_errno(fMysql) == 2006 || mysql_errno(fMysql) == 2013) {
1274 // "MySQL server has gone away" or "Lost connection to MySQL server during query"
1275 status = Connect(fConnectString.c_str());
1276 if (status == DB_SUCCESS) {
1277 // Retry after reconnecting
1278 status = mysql_query(fMysql, sql);
1279 } else {
1280 cm_msg(MERROR, "Mysql::Prepare", "mysql_query(%s) - MySQL server has gone away, and couldn't reconnect - %d", sql, status);
1281 return DB_FILE_ERROR;
1282 }
1283 }
1284 if (status) {
1285 cm_msg(MERROR, "Mysql::Prepare", "mysql_query(%s) error %d (%s)", sql, mysql_errno(fMysql), mysql_error(fMysql));
1286 return DB_FILE_ERROR;
1287 }
1288 cm_msg(MINFO, "Mysql::Prepare", "Reconnected to MySQL after long inactivity.");
1289 }
1290
1291 fResult = mysql_store_result(fMysql);
1292 //fResult = mysql_use_result(fMysql); // cannot use this because it blocks writing into table
1293
1294 if (!fResult) {
1295 cm_msg(MERROR, "Mysql::Prepare", "mysql_store_result(%s) returned NULL, error %d (%s)", sql, mysql_errno(fMysql), mysql_error(fMysql));
1296 return DB_FILE_ERROR;
1297 }
1298
1299 fNumFields = mysql_num_fields(fResult);
1300
1301 //printf("num fields %d\n", fNumFields);
1302
1303 return DB_SUCCESS;
1304}
1305
1306int Mysql::Step()
1307{
1308 if (/* DISABLES CODE */ (0) && fDebug)
1309 printf("Mysql::Step()\n");
1310
1311 assert(fMysql);
1312 assert(fResult);
1313
1314 fRow = mysql_fetch_row(fResult);
1315
1316 if (fRow)
1317 return DB_SUCCESS;
1318
1319 if (mysql_errno(fMysql) == 0)
1320 return DB_NO_MORE_SUBKEYS;
1321
1322 cm_msg(MERROR, "Mysql::Step", "mysql_fetch_row() error %d (%s)", mysql_errno(fMysql), mysql_error(fMysql));
1323
1324 return DB_FILE_ERROR;
1325}
1326
1327const char* Mysql::GetText(int column)
1328{
1329 assert(fMysql);
1330 assert(fResult);
1331 assert(fRow);
1332 assert(fNumFields > 0);
1333 assert(column >= 0);
1334 assert(column < fNumFields);
1335 if (fRow[column] == NULL)
1336 return "";
1337 return fRow[column];
1338}
1339
1340double Mysql::GetDouble(int column)
1341{
1342 return atof(GetText(column));
1343}
1344
1345time_t Mysql::GetTime(int column)
1346{
1347 return strtoul(GetText(column), NULL, 0);
1348}
1349
1350int Mysql::Finalize()
1351{
1352 assert(fMysql);
1353 assert(fResult);
1354
1355 mysql_free_result(fResult);
1356 fResult = NULL;
1357 fRow = NULL;
1358 fNumFields = 0;
1359
1360 return DB_SUCCESS;
1361}
1362
1363const char* Mysql::ColumnType(int midas_tid)
1364{
1365 assert(midas_tid>=0);
1366 assert(midas_tid<TID_LAST);
1367 return sql_type_mysql[midas_tid];
1368}
1369
1370bool Mysql::TypesCompatible(int midas_tid, const char* sql_type)
1371{
1372 if (/* DISABLES CODE */ (0))
1373 printf("compare types midas \'%s\'=\'%s\' and sql \'%s\'\n", rpc_tid_name(midas_tid), ColumnType(midas_tid), sql_type);
1374
1375 //if (sql2midasType_mysql(sql_type) == midas_tid)
1376 // return true;
1377
1378 if (strcasecmp(ColumnType(midas_tid), sql_type) == 0)
1379 return true;
1380
1381 // permit writing FLOAT into DOUBLE
1382 if (midas_tid==TID_FLOAT && strcmp(sql_type, "double")==0)
1383 return true;
1384
1385 // T2K quirk!
1386 // permit writing BYTE into signed tinyint
1387 if (midas_tid==TID_BYTE && strcmp(sql_type, "tinyint")==0)
1388 return true;
1389
1390 // T2K quirk!
1391 // permit writing WORD into signed tinyint
1392 if (midas_tid==TID_WORD && strcmp(sql_type, "tinyint")==0)
1393 return true;
1394
1395 // mysql quirk!
1396 //if (midas_tid==TID_DWORD && strcmp(sql_type, "int(10) unsigned")==0)
1397 // return true;
1398
1399 if (/* DISABLES CODE */ (0))
1400 printf("type mismatch!\n");
1401
1402 return false;
1403}
1404
1405std::string Mysql::QuoteId(const char* s)
1406{
1407 std::string q;
1408 q += "`";
1409 q += s;
1410 q += "`";
1411 return q;
1412}
1413
1414std::string Mysql::QuoteString(const char* s)
1415{
1416 std::string q;
1417 q += "\'";
1418 q += s;
1419#if 0
1420 while (int c = *s++) {
1421 if (c == '\'') {
1422 q += "\\'";
1423 } if (c == '"') {
1424 q += "\\\"";
1425 } else if (isprint(c)) {
1426 q += c;
1427 } else {
1428 char buf[256];
1429 sprintf(buf, "\\\\x%02x", c&0xFF);
1430 q += buf;
1431 }
1432 }
1433#endif
1434 q += "\'";
1435 return q;
1436}
1437
1438#endif // HAVE_MYSQL
1439
1440#ifdef HAVE_PGSQL
1441
1443// PostgreSQL database access //
1445
1446//#warning !!!HAVE_PGSQL!!!
1447
1448#include <libpq-fe.h>
1449
1450class Pgsql: public SqlBase
1451{
1452public:
1453 std::string fConnectString;
1454 int fDownsample = 0;
1455 PGconn* fPgsql = NULL;
1456
1457 // query results
1458 PGresult *fResult = NULL;
1459 int fNumFields = 0;
1460 int fRow = 0;
1461
1462 // disconnected operation
1463 size_t fMaxDisconnected = 0;
1464 std::list<std::string> fDisconnectedBuffer;
1465 time_t fNextReconnect = 0;
1466 int fNextReconnectDelaySec = 0;
1467 int fDisconnectedLost = 0;
1468
1469 Pgsql(); // ctor
1470 ~Pgsql(); // dtor
1471
1472 int Connect(const char* path);
1473 int Disconnect();
1474 bool IsConnected();
1475
1476 int ConnectTable(const char* table_name);
1477
1478 int ListTables(std::vector<std::string> *plist);
1479 int ListColumns(const char* table_name, std::vector<std::string> *plist);
1480
1481 int Exec(const char* table_name, const char* sql);
1482 int ExecDisconnected(const char* table_name, const char* sql);
1483
1484 int Prepare(const char* table_name, const char* sql);
1485 std::string BuildDownsampleQuery(const time_t start_time, const time_t end_time, const int npoints, const char* table_name, const char* column_name);
1486 int Step();
1487 const char* GetText(int column);
1488 time_t GetTime(int column);
1489 double GetDouble(int column);
1490 int Finalize();
1491
1492 int OpenTransaction(const char* table_name);
1493 int CommitTransaction(const char* table_name);
1494 int RollbackTransaction(const char* table_name);
1495
1496 const char* ColumnType(int midas_tid);
1497 bool TypesCompatible(int midas_tid, const char* sql_type);
1498
1499 std::string QuoteId(const char* s);
1500 std::string QuoteString(const char* s);
1501};
1502
1503Pgsql::Pgsql() // ctor
1504{
1505 fPgsql = NULL;
1506 fDownsample = 0;
1507 fResult = NULL;
1508 fRow = -1;
1509 fNumFields = 0;
1510 fMaxDisconnected = 1000;
1511 fNextReconnect = 0;
1512 fNextReconnectDelaySec = 0;
1513 fDisconnectedLost = 0;
1514 fTransactionPerTable = false;
1515}
1516
1517Pgsql::~Pgsql() // dtor
1518{
1519 Disconnect();
1520 if(fResult)
1521 PQclear(fResult);
1522 fRow = -1;
1523 fNumFields = 0;
1524 if (fDisconnectedBuffer.size() > 0) {
1525 cm_msg(MINFO, "Pgsql::~Pgsql", "Lost %zu history entries accumulated while disconnected from the database", fDisconnectedBuffer.size());
1527 }
1528}
1529
1530int Pgsql::Connect(const char* connect_string)
1531{
1532 if (fIsConnected)
1533 Disconnect();
1534
1535 fConnectString = connect_string;
1536
1537 if (fDebug) {
1538 cm_msg(MINFO, "Pgsql::Connect", "Connecting to PostgreSQL database specified by \'%s\'", connect_string);
1540 }
1541
1542 std::string host_name;
1543 std::string user_name;
1544 std::string user_password;
1545 std::string db_name;
1546 std::string tcp_port;
1547 std::string unix_socket;
1548 std::string buffer;
1549
1550 FILE* fp = fopen(connect_string, "r");
1551 if (!fp) {
1552 cm_msg(MERROR, "Pgsql::Connect", "Cannot read PostgreSQL connection parameters from \'%s\', fopen() error %d (%s)", connect_string, errno, strerror(errno));
1553 return DB_FILE_ERROR;
1554 }
1555
1556 while (1) {
1557 char buf[256];
1558 char* s = fgets(buf, sizeof(buf), fp);
1559 if (!s)
1560 break; // EOF
1561
1562 char*ss;
1563 // kill trailing \n and \r
1564 ss = strchr(s, '\n');
1565 if (ss) *ss = 0;
1566 ss = strchr(s, '\r');
1567 if (ss) *ss = 0;
1568
1569 //printf("line [%s]\n", s);
1570
1571 if (strncasecmp(s, "server=", 7)==0)
1572 host_name = skip_spaces(s + 7);
1573 if (strncasecmp(s, "port=", 5)==0)
1574 tcp_port = skip_spaces(s + 5);
1575 if (strncasecmp(s, "database=", 9)==0)
1576 db_name = skip_spaces(s + 9);
1577 if (strncasecmp(s, "socket=", 7)==0)
1578 unix_socket = skip_spaces(s + 7);
1579 if (strncasecmp(s, "user=", 5)==0)
1580 user_name = skip_spaces(s + 5);
1581 if (strncasecmp(s, "password=", 9)==0)
1582 user_password = skip_spaces(s + 9);
1583 if (strncasecmp(s, "buffer=", 7)==0)
1584 buffer = skip_spaces(s + 7);
1585 }
1586
1587 fclose(fp);
1588
1589 int buffer_int = atoi(buffer.c_str());
1590
1591 if (buffer_int > 0 && buffer_int < 1000000)
1592 fMaxDisconnected = buffer_int;
1593
1594 if (fDebug)
1595 printf("Pgsql::Connect: connecting to server [%s] port %s, unix socket [%s], database [%s], user [%s], password [%s], buffer [%zu]\n", host_name.c_str(), tcp_port.c_str(), unix_socket.c_str(), db_name.c_str(), user_name.c_str(), user_password.c_str(), fMaxDisconnected);
1596
1597 fPgsql = PQsetdbLogin(host_name.c_str(), tcp_port.c_str(), NULL, NULL, db_name.c_str(), user_name.c_str(), user_password.c_str());
1598 if (PQstatus(fPgsql) != CONNECTION_OK) {
1599 std::string msg(PQerrorMessage(fPgsql));
1600 msg.erase(std::remove(msg.begin(), msg.end(), '\n'), msg.end());
1601 cm_msg(MERROR, "Pgsql::Connect", "PQsetdbLogin() to host [%s], port %s, unix socket [%s], database [%s], user [%s], password [%s]: error (%s)", host_name.c_str(), tcp_port.c_str(), unix_socket.c_str(), db_name.c_str(), user_name.c_str(), "xxx", msg.c_str());
1602 Disconnect();
1603 return DB_FILE_ERROR;
1604 }
1605
1606 int status;
1607
1608 if (fDebug) {
1609 cm_msg(MINFO, "Pgsql::Connect", "Connected to a PostgreSQL database on host [%s], port %s, unix socket [%s], database [%s], user [%s], password [%s], buffer %zu", host_name.c_str(), tcp_port.c_str(), unix_socket.c_str(), db_name.c_str(), user_name.c_str(), "xxx", fMaxDisconnected);
1611 }
1612
1613 fIsConnected = true;
1614
1615 int count = 0;
1616 while (fDisconnectedBuffer.size() > 0) {
1617 status = Exec("(flush)", fDisconnectedBuffer.front().c_str());
1618 if (status != DB_SUCCESS) {
1619 return status;
1620 }
1621 fDisconnectedBuffer.pop_front();
1622 count++;
1623 }
1624
1625 if (count > 0) {
1626 cm_msg(MINFO, "Pgsql::Connect", "Saved %d, lost %d history events accumulated while disconnected from the database", count, fDisconnectedLost);
1628 }
1629
1630 assert(fDisconnectedBuffer.size() == 0);
1631 fDisconnectedLost = 0;
1632
1633 if (fDownsample) {
1634 status = Prepare("pg_extensions", "select extname from pg_extension where extname = 'timescaledb';");
1635
1636 if (status != DB_SUCCESS || PQntuples(fResult) == 0) {
1637 cm_msg(MERROR, "Pgsql::Connect", "TimescaleDB extension not installed");
1638 return DB_FILE_ERROR;
1639 }
1640 Finalize();
1641
1642 status = Prepare("pg_extensions", "select extname from pg_extension where extname = 'timescaledb_toolkit';");
1643
1644 if (status != DB_SUCCESS || PQntuples(fResult) == 0) {
1645 cm_msg(MERROR, "Pgsql::Connect", "TimescaleDB_toolkit extension not installed");
1646 return DB_FILE_ERROR;
1647 }
1648 Finalize();
1649
1650 cm_msg(MINFO, "Pgsql::Connect", "TimescaleDB extensions found - downsampling enabled");
1651 }
1652
1653 return DB_SUCCESS;
1654}
1655
1656int Pgsql::Disconnect()
1657{
1658 if (fPgsql)
1659 PQfinish(fPgsql);
1660
1661 fPgsql = NULL;
1662 fRow = -1;
1663
1664 fIsConnected = false;
1665 return DB_SUCCESS;
1666}
1667
1668bool Pgsql::IsConnected()
1669{
1670 return fIsConnected;
1671}
1672
1673int Pgsql::OpenTransaction(const char* table_name)
1674{
1675 return Exec(table_name, "BEGIN TRANSACTION;");
1676}
1677
1678int Pgsql::CommitTransaction(const char* table_name)
1679{
1680 return Exec(table_name, "COMMIT;");
1681}
1682
1683int Pgsql::RollbackTransaction(const char* table_name)
1684{
1685 return Exec(table_name, "ROLLBACK;");
1686}
1687
1688int Pgsql::ListTables(std::vector<std::string> *plist)
1689{
1690 if (!fIsConnected)
1691 return DB_FILE_ERROR;
1692
1693 if (fDebug)
1694 printf("Pgsql::ListTables!\n");
1695
1696 int status = Prepare("pg_tables", "select tablename from pg_tables where schemaname = 'public';");
1697
1698 if (status != DB_SUCCESS) {
1699 cm_msg(MERROR, "Pgsql::ListTables", "error %s (%s)", PQresStatus(PQresultStatus(fResult)), PQresultErrorMessage(fResult));
1700 return DB_FILE_ERROR;
1701 }
1702
1703 while (1) {
1704 if (Step() != DB_SUCCESS)
1705 break;
1706 std::string tn = GetText(0);
1707 plist->push_back(tn);
1708 };
1709
1710 Finalize();
1711
1712 return DB_SUCCESS;
1713}
1714
1715int Pgsql::ListColumns(const char* table_name, std::vector<std::string> *plist)
1716{
1717 if (!fIsConnected)
1718 return DB_FILE_ERROR;
1719
1720 if (fDebug)
1721 printf("Pgsql::ListColumns for table \'%s\'\n", table_name);
1722
1723 std::string cmd;
1724 cmd += "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = ";
1725 cmd += QuoteString(table_name);
1726 cmd += ";";
1727
1728 int status = Prepare(table_name, cmd.c_str());
1729 if (status != DB_SUCCESS)
1730 return status;
1731
1732 fNumFields = PQnfields(fResult);
1733
1734 while (1) {
1735 if (Step() != DB_SUCCESS)
1736 break;
1737 std::string cn = GetText(0);
1738 std::string ct = GetText(1);
1739 plist->push_back(cn);
1740 plist->push_back(ct);
1741 };
1742
1743 Finalize();
1744
1745 return DB_SUCCESS;
1746}
1747
1748int Pgsql::Exec(const char* table_name, const char* sql)
1749{
1750 if (fDebug)
1751 printf("Pgsql::Exec(%s, %s)\n", table_name, sql);
1752
1753 if (!fPgsql)
1754 return DB_FILE_ERROR;
1755
1756 assert(fPgsql);
1757 assert(fRow == -1);
1758
1759 fResult = PQexec(fPgsql, sql);
1760 ExecStatusType err = PQresultStatus(fResult);
1761 if(err != PGRES_TUPLES_OK) {
1762 if(err == PGRES_FATAL_ERROR) {
1763 // handle fatal error
1764 if(strstr(PQresultErrorMessage(fResult), "already exists"))
1765 return DB_KEY_EXIST;
1766 else return DB_FILE_ERROR;
1767 }
1768
1769 if(PQstatus(fPgsql) == CONNECTION_BAD) {
1770 Disconnect();
1771 return ExecDisconnected(table_name, sql);
1772 }
1773 }
1774
1775 return DB_SUCCESS;
1776}
1777
1778int Pgsql::ExecDisconnected(const char* table_name, const char* sql)
1779{
1780 if (fDebug)
1781 printf("Pgsql::ExecDisconnected(%s, %s)\n", table_name, sql);
1782
1783 if (fDisconnectedBuffer.size() < fMaxDisconnected) {
1784 fDisconnectedBuffer.push_back(sql);
1785 if (fDisconnectedBuffer.size() >= fMaxDisconnected) {
1786 cm_msg(MERROR, "Pgsql::ExecDisconnected", "Error: Disconnected database buffer overflow, size %zu, subsequent events are lost", fDisconnectedBuffer.size());
1787 }
1788 } else {
1789 fDisconnectedLost++;
1790 }
1791
1792 time_t now = time(NULL);
1793
1794 if (fNextReconnect == 0 || now >= fNextReconnect) {
1795 int status = Connect(fConnectString.c_str());
1796 if (status == DB_SUCCESS) {
1797 fNextReconnect = 0;
1798 fNextReconnectDelaySec = 0;
1799 } else {
1800 if (fNextReconnectDelaySec == 0) {
1801 fNextReconnectDelaySec = 5;
1802 } else if (fNextReconnectDelaySec < 10*60) {
1803 fNextReconnectDelaySec *= 2;
1804 }
1805 if (fDebug) {
1806 cm_msg(MINFO, "Pgsql::ExecDisconnected", "Next reconnect attempt in %d sec, history events buffered %zu, lost %d", fNextReconnectDelaySec, fDisconnectedBuffer.size(), fDisconnectedLost);
1808 }
1809 fNextReconnect = now + fNextReconnectDelaySec;
1810 }
1811 }
1812
1813 return DB_SUCCESS;
1814}
1815
1816int Pgsql::Prepare(const char* table_name, const char* sql)
1817{
1818 if (fDebug)
1819 printf("Pgsql::Prepare(%s, %s)\n", table_name, sql);
1820
1821 if (!fPgsql)
1822 return DB_FILE_ERROR;
1823
1824 assert(fPgsql);
1825 //assert(fResult==NULL);
1826 assert(fRow == -1);
1827
1828 fResult = PQexec(fPgsql, sql);
1829 if (PQstatus(fPgsql) == CONNECTION_BAD) {
1830 // lost connection to server
1831 int status = Connect(fConnectString.c_str());
1832 if (status == DB_SUCCESS) {
1833 // Retry after reconnecting
1834 fResult = PQexec(fPgsql, sql);
1835 } else {
1836 cm_msg(MERROR, "Pgsql::Prepare", "PQexec(%s) PostgreSQL server has gone away, and couldn't reconnect - %d", sql, status);
1837 return DB_FILE_ERROR;
1838 }
1839 if (status) {
1840 cm_msg(MERROR, "Pgsql::Prepare", "PQexec(%s) error %s", sql, PQresStatus(PQresultStatus(fResult)));
1841 return DB_FILE_ERROR;
1842 }
1843 cm_msg(MINFO, "Pgsql::Prepare", "Reconnected to PostgreSQL after long inactivity.");
1844 }
1845
1846 fNumFields = PQnfields(fResult);
1847
1848 return DB_SUCCESS;
1849}
1850
1851std::string Pgsql::BuildDownsampleQuery(const time_t start_time, const time_t end_time, const int npoints,
1852 const char* table_name, const char* column_name)
1853{
1854 std::string cmd;
1855 cmd += "SELECT extract(epoch from time::TIMESTAMPTZ) as _i_time, value ";
1856
1857 cmd += " FROM unnest(( SELECT lttb";
1858 cmd += "(_t_time, ";
1859 cmd += column_name;
1860 cmd += ", ";
1861 cmd += std::to_string(npoints);
1862 cmd += ") ";
1863 cmd += "FROM ";
1864 cmd += QuoteId(table_name);
1865 cmd += " WHERE _t_time BETWEEN ";
1866 cmd += "to_timestamp(";
1867 cmd += TimeToString(start_time);
1868 cmd += ") AND to_timestamp(";
1869 cmd += TimeToString(end_time);
1870 cmd += ") )) ORDER BY time;";
1871
1872 return cmd;
1873}
1874
1875int Pgsql::Step()
1876{
1877 assert(fPgsql);
1878 assert(fResult);
1879
1880 fRow++;
1881
1882 if (fRow == PQntuples(fResult))
1883 return DB_NO_MORE_SUBKEYS;
1884
1885 return DB_SUCCESS;
1886}
1887
1888const char* Pgsql::GetText(int column)
1889{
1890 assert(fPgsql);
1891 assert(fResult);
1892 assert(fNumFields > 0);
1893 assert(column >= 0);
1894 assert(column < fNumFields);
1895
1896 return PQgetvalue(fResult, fRow, column);
1897}
1898
1899double Pgsql::GetDouble(int column)
1900{
1901 return atof(GetText(column));
1902}
1903
1904time_t Pgsql::GetTime(int column)
1905{
1906 return strtoul(GetText(column), NULL, 0);
1907}
1908
1909int Pgsql::Finalize()
1910{
1911 assert(fPgsql);
1912 assert(fResult);
1913
1914 fRow = -1;
1915 fNumFields = 0;
1916
1917 return DB_SUCCESS;
1918}
1919
1920const char* Pgsql::ColumnType(int midas_tid)
1921{
1922 assert(midas_tid>=0);
1923 assert(midas_tid<TID_LAST);
1924 return sql_type_pgsql[midas_tid];
1925}
1926
1927bool Pgsql::TypesCompatible(int midas_tid, const char* sql_type)
1928{
1929 if (/* DISABLES CODE */ (0))
1930 printf("compare types midas \'%s\'=\'%s\' and sql \'%s\'\n", rpc_tid_name(midas_tid), ColumnType(midas_tid), sql_type);
1931
1932 //if (sql2midasType_mysql(sql_type) == midas_tid)
1933 // return true;
1934
1935 if (strcasecmp(ColumnType(midas_tid), sql_type) == 0)
1936 return true;
1937
1938 // permit writing FLOAT into DOUBLE
1939 if (midas_tid==TID_FLOAT && strcmp(sql_type, "double precision")==0)
1940 return true;
1941
1942 // T2K quirk!
1943 // permit writing BYTE into signed tinyint
1944 if (midas_tid==TID_BYTE && strcmp(sql_type, "integer")==0)
1945 return true;
1946
1947 // T2K quirk!
1948 // permit writing WORD into signed tinyint
1949 if (midas_tid==TID_WORD && strcmp(sql_type, "integer")==0)
1950 return true;
1951
1952 if (/* DISABLES CODE */ (0))
1953 printf("type mismatch!\n");
1954
1955 return false;
1956}
1957
1958std::string Pgsql::QuoteId(const char* s)
1959{
1960 std::string q;
1961 q += '"';
1962 q += s;
1963 q += '"';
1964 return q;
1965}
1966
1967std::string Pgsql::QuoteString(const char* s)
1968{
1969 std::string q;
1970 q += '\'';
1971 q += s;
1972 q += '\'';
1973 return q;
1974}
1975
1976#endif // HAVE_PGSQL
1977
1978#ifdef HAVE_SQLITE
1979
1981// SQLITE database access //
1983
1984#include <sqlite3.h>
1985
1986typedef std::map<std::string, sqlite3*> DbMap;
1987
1988class Sqlite: public SqlBase
1989{
1990public:
1991 std::string fPath;
1992
1993 DbMap fMap;
1994
1995 // temporary storage of query data
1996 sqlite3* fTempDB;
1997 sqlite3_stmt* fTempStmt;
1998
1999 Sqlite(); // ctor
2000 ~Sqlite(); // dtor
2001
2002 int Connect(const char* path);
2003 int Disconnect();
2004 bool IsConnected();
2005
2006 int ConnectTable(const char* table_name);
2007 sqlite3* GetTable(const char* table_name);
2008
2009 int ListTables(std::vector<std::string> *plist);
2010 int ListColumns(const char* table_name, std::vector<std::string> *plist);
2011
2012 int Exec(const char* table_name, const char* sql);
2013 int ExecDisconnected(const char* table_name, const char* sql);
2014
2015 int Prepare(const char* table_name, const char* sql);
2016 int Step();
2017 const char* GetText(int column);
2018 time_t GetTime(int column);
2019 double GetDouble(int column);
2020 int Finalize();
2021
2022 int OpenTransaction(const char* table_name);
2023 int CommitTransaction(const char* table_name);
2024 int RollbackTransaction(const char* table_name);
2025
2026 const char* ColumnType(int midas_tid);
2027 bool TypesCompatible(int midas_tid, const char* sql_type);
2028
2029 std::string QuoteId(const char* s);
2030 std::string QuoteString(const char* s);
2031};
2032
2033std::string Sqlite::QuoteId(const char* s)
2034{
2035 std::string q;
2036 q += "\"";
2037 q += s;
2038 q += "\"";
2039 return q;
2040}
2041
2042std::string Sqlite::QuoteString(const char* s)
2043{
2044 std::string q;
2045 q += "\'";
2046 q += s;
2047 q += "\'";
2048 return q;
2049}
2050
2051const char* Sqlite::ColumnType(int midas_tid)
2052{
2053 assert(midas_tid>=0);
2054 assert(midas_tid<TID_LAST);
2055 return sql_type_sqlite[midas_tid];
2056}
2057
2058bool Sqlite::TypesCompatible(int midas_tid, const char* sql_type)
2059{
2060 if (0)
2061 printf("compare types midas \'%s\'=\'%s\' and sql \'%s\'\n", rpc_tid_name(midas_tid), ColumnType(midas_tid), sql_type);
2062
2063 //if (sql2midasType_sqlite(sql_type) == midas_tid)
2064 // return true;
2065
2066 if (strcasecmp(ColumnType(midas_tid), sql_type) == 0)
2067 return true;
2068
2069 // permit writing FLOAT into DOUBLE
2070 if (midas_tid==TID_FLOAT && strcasecmp(sql_type, "double")==0)
2071 return true;
2072
2073 return false;
2074}
2075
2076const char* Sqlite::GetText(int column)
2077{
2078 return (const char*)sqlite3_column_text(fTempStmt, column);
2079}
2080
2081time_t Sqlite::GetTime(int column)
2082{
2083 return sqlite3_column_int64(fTempStmt, column);
2084}
2085
2086double Sqlite::GetDouble(int column)
2087{
2088 return sqlite3_column_double(fTempStmt, column);
2089}
2090
2091Sqlite::Sqlite() // ctor
2092{
2093 fIsConnected = false;
2094 fTempDB = NULL;
2095 fTempStmt = NULL;
2096 fDebug = 0;
2097}
2098
2099Sqlite::~Sqlite() // dtor
2100{
2101 Disconnect();
2102}
2103
2104const char* xsqlite3_errstr(sqlite3* db, int errcode)
2105{
2106 //return sqlite3_errstr(errcode);
2107 return sqlite3_errmsg(db);
2108}
2109
2110int Sqlite::ConnectTable(const char* table_name)
2111{
2112 std::string fname = fPath + "mh_" + table_name + ".sqlite3";
2113
2114 sqlite3* db = NULL;
2115
2116 int status = sqlite3_open(fname.c_str(), &db);
2117
2118 if (status != SQLITE_OK) {
2119 cm_msg(MERROR, "Sqlite::Connect", "Table %s: sqlite3_open(%s) error %d (%s)", table_name, fname.c_str(), status, xsqlite3_errstr(db, status));
2120 sqlite3_close(db);
2121 db = NULL;
2122 return DB_FILE_ERROR;
2123 }
2124
2125#if SQLITE_VERSION_NUMBER >= 3006020
2126 status = sqlite3_extended_result_codes(db, 1);
2127 if (status != SQLITE_OK) {
2128 cm_msg(MERROR, "Sqlite::Connect", "Table %s: sqlite3_extended_result_codes(1) error %d (%s)", table_name, status, xsqlite3_errstr(db, status));
2129 }
2130#else
2131#warning Missing sqlite3_extended_result_codes()!
2132#endif
2133
2134 fMap[table_name] = db;
2135
2136 Exec(table_name, "PRAGMA journal_mode=persist;");
2137 Exec(table_name, "PRAGMA synchronous=normal;");
2138 //Exec(table_name, "PRAGMA synchronous=off;");
2139 Exec(table_name, "PRAGMA journal_size_limit=-1;");
2140
2141 if (0) {
2142 Exec(table_name, "PRAGMA legacy_file_format;");
2143 Exec(table_name, "PRAGMA synchronous;");
2144 Exec(table_name, "PRAGMA journal_mode;");
2145 Exec(table_name, "PRAGMA journal_size_limit;");
2146 }
2147
2148#ifdef SQLITE_LIMIT_COLUMN
2149 if (0) {
2150 int max_columns = sqlite3_limit(db, SQLITE_LIMIT_COLUMN, -1);
2151 printf("Sqlite::Connect: SQLITE_LIMIT_COLUMN=%d\n", max_columns);
2152 }
2153#endif
2154
2155 if (fDebug)
2156 cm_msg(MINFO, "Sqlite::Connect", "Table %s: connected to Sqlite file \'%s\'", table_name, fname.c_str());
2157
2158 return DB_SUCCESS;
2159}
2160
2161sqlite3* Sqlite::GetTable(const char* table_name)
2162{
2163 sqlite3* db = fMap[table_name];
2164
2165 if (db)
2166 return db;
2167
2168 int status = ConnectTable(table_name);
2169 if (status != DB_SUCCESS)
2170 return NULL;
2171
2172 return fMap[table_name];
2173}
2174
2175int Sqlite::Connect(const char* path)
2176{
2177 if (fIsConnected)
2178 Disconnect();
2179
2180 fPath = path;
2181
2182 // add trailing '/'
2183 if (fPath.length() > 0) {
2184 if (fPath[fPath.length()-1] != DIR_SEPARATOR)
2185 fPath += DIR_SEPARATOR_STR;
2186 }
2187
2188 if (fDebug)
2189 cm_msg(MINFO, "Sqlite::Connect", "Connected to Sqlite database in \'%s\'", fPath.c_str());
2190
2191 fIsConnected = true;
2192
2193 return DB_SUCCESS;
2194}
2195
2196int Sqlite::Disconnect()
2197{
2198 if (!fIsConnected)
2199 return DB_SUCCESS;
2200
2201 for (DbMap::iterator iter = fMap.begin(); iter != fMap.end(); ++iter) {
2202 const char* table_name = iter->first.c_str();
2203 sqlite3* db = iter->second;
2204 int status = sqlite3_close(db);
2205 if (status != SQLITE_OK) {
2206 cm_msg(MERROR, "Sqlite::Disconnect", "sqlite3_close(%s) error %d (%s)", table_name, status, xsqlite3_errstr(db, status));
2207 }
2208 }
2209
2210 fMap.clear();
2211
2212 fIsConnected = false;
2213
2214 return DB_SUCCESS;
2215}
2216
2217bool Sqlite::IsConnected()
2218{
2219 return fIsConnected;
2220}
2221
2222int Sqlite::OpenTransaction(const char* table_name)
2223{
2224 int status = Exec(table_name, "BEGIN TRANSACTION");
2225 return status;
2226}
2227
2228int Sqlite::CommitTransaction(const char* table_name)
2229{
2230 int status = Exec(table_name, "COMMIT TRANSACTION");
2231 return status;
2232}
2233
2234int Sqlite::RollbackTransaction(const char* table_name)
2235{
2236 int status = Exec(table_name, "ROLLBACK TRANSACTION");
2237 return status;
2238}
2239
2240int Sqlite::Prepare(const char* table_name, const char* sql)
2241{
2242 sqlite3* db = GetTable(table_name);
2243 if (!db)
2244 return DB_FILE_ERROR;
2245
2246 if (fDebug)
2247 printf("Sqlite::Prepare(%s, %s)\n", table_name, sql);
2248
2249 assert(fTempDB==NULL);
2250 fTempDB = db;
2251
2252#if SQLITE_VERSION_NUMBER >= 3006020
2253 int status = sqlite3_prepare_v2(db, sql, strlen(sql), &fTempStmt, NULL);
2254#else
2255#warning Missing sqlite3_prepare_v2()!
2256 int status = sqlite3_prepare(db, sql, strlen(sql), &fTempStmt, NULL);
2257#endif
2258
2259 if (status == SQLITE_OK)
2260 return DB_SUCCESS;
2261
2262 std::string sqlstring = sql;
2263 cm_msg(MERROR, "Sqlite::Prepare", "Table %s: sqlite3_prepare_v2(%s...) error %d (%s)", table_name, sqlstring.substr(0,60).c_str(), status, xsqlite3_errstr(db, status));
2264
2265 fTempDB = NULL;
2266
2267 return DB_FILE_ERROR;
2268}
2269
2270int Sqlite::Step()
2271{
2272 if (0 && fDebug)
2273 printf("Sqlite::Step()\n");
2274
2275 assert(fTempDB);
2276 assert(fTempStmt);
2277
2278 int status = sqlite3_step(fTempStmt);
2279
2280 if (status == SQLITE_DONE)
2281 return DB_NO_MORE_SUBKEYS;
2282
2283 if (status == SQLITE_ROW)
2284 return DB_SUCCESS;
2285
2286 cm_msg(MERROR, "Sqlite::Step", "sqlite3_step() error %d (%s)", status, xsqlite3_errstr(fTempDB, status));
2287
2288 return DB_FILE_ERROR;
2289}
2290
2291int Sqlite::Finalize()
2292{
2293 if (0 && fDebug)
2294 printf("Sqlite::Finalize()\n");
2295
2296 assert(fTempDB);
2297 assert(fTempStmt);
2298
2299 int status = sqlite3_finalize(fTempStmt);
2300
2301 if (status != SQLITE_OK) {
2302 cm_msg(MERROR, "Sqlite::Finalize", "sqlite3_finalize() error %d (%s)", status, xsqlite3_errstr(fTempDB, status));
2303
2304 fTempDB = NULL;
2305 fTempStmt = NULL; // FIXME: maybe a memory leak?
2306 return DB_FILE_ERROR;
2307 }
2308
2309 fTempDB = NULL;
2310 fTempStmt = NULL;
2311
2312 return DB_SUCCESS;
2313}
2314
2315int Sqlite::ListTables(std::vector<std::string> *plist)
2316{
2317 if (!fIsConnected)
2318 return DB_FILE_ERROR;
2319
2320 if (fDebug)
2321 printf("Sqlite::ListTables at path [%s]\n", fPath.c_str());
2322
2323 int status;
2324
2325 const char* cmd = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;";
2326
2327 DIR *dir = opendir(fPath.c_str());
2328 if (!dir) {
2329 cm_msg(MERROR, "Sqlite::ListTables", "Cannot opendir(%s), errno %d (%s)", fPath.c_str(), errno, strerror(errno));
2330 return HS_FILE_ERROR;
2331 }
2332
2333 while (1) {
2334 const struct dirent* de = readdir(dir);
2335 if (!de)
2336 break;
2337
2338 const char* dn = de->d_name;
2339
2340 //if (dn[0]!='m' || dn[1]!='h')
2341 //continue;
2342
2343 const char* s;
2344
2345 s = strstr(dn, "mh_");
2346 if (!s || s!=dn)
2347 continue;
2348
2349 s = strstr(dn, ".sqlite3");
2350 if (!s || s[8]!=0)
2351 continue;
2352
2353 char table_name[256];
2354 mstrlcpy(table_name, dn+3, sizeof(table_name));
2355 // FIXME: skip names like "xxx.sqlite3~" and "xxx.sqlite3-deleted"
2356 char* ss = strstr(table_name, ".sqlite3");
2357 if (!ss)
2358 continue;
2359 *ss = 0;
2360
2361 //printf("dn [%s] tn [%s]\n", dn, table_name);
2362
2363 status = Prepare(table_name, cmd);
2364 if (status != DB_SUCCESS)
2365 continue;
2366
2367 while (1) {
2368 status = Step();
2369 if (status != DB_SUCCESS)
2370 break;
2371
2372 const char* tn = GetText(0);
2373 //printf("table [%s]\n", tn);
2374 plist->push_back(tn);
2375 }
2376
2377 status = Finalize();
2378 }
2379
2380 closedir(dir);
2381 dir = NULL;
2382
2383 return DB_SUCCESS;
2384}
2385
2386int Sqlite::ListColumns(const char* table, std::vector<std::string> *plist)
2387{
2388 if (!fIsConnected)
2389 return DB_FILE_ERROR;
2390
2391 if (fDebug)
2392 printf("Sqlite::ListColumns for table \'%s\'\n", table);
2393
2394 std::string cmd;
2395 cmd = "PRAGMA table_info(";
2396 cmd += table;
2397 cmd += ");";
2398
2399 int status;
2400
2401 status = Prepare(table, cmd.c_str());
2402 if (status != DB_SUCCESS)
2403 return status;
2404
2405 while (1) {
2406 status = Step();
2407 if (status != DB_SUCCESS)
2408 break;
2409
2410 const char* colname = GetText(1);
2411 const char* coltype = GetText(2);
2412 //printf("column [%s] [%s]\n", colname, coltype);
2413 plist->push_back(colname); // column name
2414 plist->push_back(coltype); // column type
2415 }
2416
2417 status = Finalize();
2418
2419 return DB_SUCCESS;
2420}
2421
2422static int callback_debug = 0;
2423
2424static int callback(void *NotUsed, int argc, char **argv, char **azColName){
2425 if (callback_debug) {
2426 printf("history_sqlite::callback---->\n");
2427 for (int i=0; i<argc; i++){
2428 printf("history_sqlite::callback[%d] %s = %s\n", i, azColName[i], argv[i] ? argv[i] : "NULL");
2429 }
2430 }
2431 return 0;
2432}
2433
2434int Sqlite::Exec(const char* table_name, const char* sql)
2435{
2436 // return values:
2437 // DB_SUCCESS
2438 // DB_FILE_ERROR: not connected
2439 // DB_KEY_EXIST: "table already exists"
2440
2441 if (!fIsConnected)
2442 return DB_FILE_ERROR;
2443
2444 sqlite3* db = GetTable(table_name);
2445 if (!db)
2446 return DB_FILE_ERROR;
2447
2448 if (fDebug)
2449 printf("Sqlite::Exec(%s, %s)\n", table_name, sql);
2450
2451 int status;
2452
2453 callback_debug = fDebug;
2454 char* errmsg = NULL;
2455
2456 status = sqlite3_exec(db, sql, callback, 0, &errmsg);
2457 if (status != SQLITE_OK) {
2458 if (status == SQLITE_ERROR && strstr(errmsg, "duplicate column name"))
2459 return DB_KEY_EXIST;
2460 if (status == SQLITE_ERROR && strstr(errmsg, "already exists"))
2461 return DB_KEY_EXIST;
2462 std::string sqlstring = sql;
2463 cm_msg(MERROR, "Sqlite::Exec", "Table %s: sqlite3_exec(%s...) error %d (%s)", table_name, sqlstring.substr(0,60).c_str(), status, errmsg);
2464 sqlite3_free(errmsg);
2465 return DB_FILE_ERROR;
2466 }
2467
2468 return DB_SUCCESS;
2469}
2470
2471int Sqlite::ExecDisconnected(const char* table_name, const char* sql)
2472{
2473 cm_msg(MERROR, "Sqlite::Exec", "sqlite driver does not support disconnected operations");
2474 return DB_FILE_ERROR;
2475}
2476
2477#endif // HAVE_SQLITE
2478
2480// Methods of HsFileSchema //
2482
2483int HsFileSchema::write_event(const time_t t, const char* data, const size_t data_size)
2484{
2485 HsFileSchema* s = this;
2486
2487 assert(s->fVariables.size() == s->fOffsets.size());
2488
2489 if (s->fWriterFd < 0) {
2490 s->fWriterFd = open(s->fFileName.c_str(), O_RDWR);
2491 if (s->fWriterFd < 0) {
2492 cm_msg(MERROR, "FileHistory::write_event", "Cannot write to \'%s\', open() errno %d (%s)", s->fFileName.c_str(), errno, strerror(errno));
2493 return HS_FILE_ERROR;
2494 }
2495
2496 //static_assert((sizeof(int)==3),"here!");
2497 //static_assert((sizeof(off64_t)==7),"here!");
2498 //static_assert((sizeof(size_t)==3),"here!");
2499 //static_assert((sizeof(intmax_t)==3),"here!");
2500
2501 //off64_t zzz = 1;
2502 //off64_t xxx = zzz<<60;
2503 //int yyy = xxx;
2504 //printf("%d", yyy);
2505
2506 off64_t file_size = ::lseek64(s->fWriterFd, 0, SEEK_END);
2507
2508 if (file_size < 0) {
2509 cm_msg(MERROR, "FileHistory::write_event", "Cannot read file size of \'%s\', lseek64(SEEK_END) errno %d (%s)", s->fFileName.c_str(), errno, strerror(errno));
2510 return HS_FILE_ERROR;
2511 }
2512
2513 off64_t nrec = 0;
2514
2515 if (file_size >= s->fDataOffset) {
2516 nrec = (file_size - s->fDataOffset)/s->fRecordSize;
2517 }
2518
2519 if (nrec < 0)
2520 nrec = 0;
2521
2522 off64_t data_end = s->fDataOffset + nrec*s->fRecordSize;
2523
2524 //printf("write_event: file_size %jd, nrec %jd, data_end %jd\n", (intmax_t)file_size, (intmax_t)nrec, (intmax_t)data_end);
2525
2526 if (data_end != file_size) {
2527 if (nrec > 0)
2528 cm_msg(MERROR, "FileHistory::write_event", "File \'%s\' may be truncated, data offset %jd, record size %zu, file size: %jd, should be %jd, making it so", s->fFileName.c_str(), (intmax_t)s->fDataOffset, s->fRecordSize, (intmax_t)file_size, (intmax_t)data_end);
2529
2530 off64_t status64 = ::lseek64(s->fWriterFd, data_end, SEEK_SET);
2531
2532 if (status64 < 0) {
2533 cm_msg(MERROR, "FileHistory::write_event", "Cannot seek \'%s\' to offset %jd, lseek64() errno %d (%s)", s->fFileName.c_str(), (intmax_t)data_end, errno, strerror(errno));
2534 return HS_FILE_ERROR;
2535 }
2536
2537 int status = ::ftruncate64(s->fWriterFd, data_end);
2538
2539 if (status < 0) {
2540 cm_msg(MERROR, "FileHistory::write_event", "Cannot truncate \'%s\' to size %jd, ftruncate64() errno %d (%s)", s->fFileName.c_str(), (intmax_t)data_end, errno, strerror(errno));
2541 return HS_FILE_ERROR;
2542 }
2543 }
2544
2545 s->fFileSizeInitial = data_end;
2546 s->fFileSize = data_end;
2547 }
2548
2549 size_t expected_size = s->fRecordSize - 4;
2550
2551 // sanity check: record_size and n_bytes are computed from the byte counts in the file header
2552 assert(expected_size == s->fNumBytes);
2553
2554 if (s->fLastSize == 0)
2555 s->fLastSize = expected_size;
2556
2557 if (data_size != s->fLastSize) {
2558 cm_msg(MERROR, "FileHistory::write_event", "Event \'%s\' data size mismatch, expected %zu bytes, got %zu bytes, previously %zu bytes", s->fEventName.c_str(), expected_size, data_size, s->fLastSize);
2559 //printf("schema:\n");
2560 //s->print();
2561
2562 if (data_size < expected_size)
2563 return HS_FILE_ERROR;
2564
2565 // truncate for now
2566 // data_size = expected_size;
2567 s->fLastSize = data_size;
2568 }
2569
2570 size_t size = 4 + expected_size;
2571
2572 if (size != s->fRecordBufferSize) {
2573 s->fRecordBuffer = (char*)realloc(s->fRecordBuffer, size);
2574 assert(s->fRecordBuffer != NULL);
2575 s->fRecordBufferSize = size;
2576 }
2577
2578 memcpy(s->fRecordBuffer, &t, 4);
2579 memcpy(s->fRecordBuffer+4, data, expected_size);
2580
2581 ssize_t wr = write(s->fWriterFd, s->fRecordBuffer, size);
2582 if ((size_t)wr != size) {
2583 cm_msg(MERROR, "FileHistory::write_event", "Cannot write to \'%s\', write(%zu) returned %zd, errno %d (%s)", s->fFileName.c_str(), size, wr, errno, strerror(errno));
2584 return HS_FILE_ERROR;
2585 }
2586
2587 s->fFileSize += size;
2588
2589#if 0
2590 status = write(s->fWriterFd, &t, 4);
2591 if (status != 4) {
2592 cm_msg(MERROR, "FileHistory::write_event", "Cannot write to \'%s\', write(timestamp) errno %d (%s)", s->fFileName.c_str(), errno, strerror(errno));
2593 return HS_FILE_ERROR;
2594 }
2595
2596 status = write(s->fWriterFd, data, expected_size);
2597 if (status != expected_size) {
2598 cm_msg(MERROR, "FileHistory::write_event", "Cannot write to \'%s\', write(%d) errno %d (%s)", s->fFileName.c_str(), data_size, errno, strerror(errno));
2599 return HS_FILE_ERROR;
2600 }
2601#endif
2602
2603 return HS_SUCCESS;
2604}
2605
2607{
2608 if (fWriterFd >= 0) {
2610 fWriterFd = -1;
2611 }
2612 return HS_SUCCESS;
2613}
2614
2615static int ReadRecord(const char* file_name, int fd, off64_t offset, size_t recsize, off64_t irec, char* rec)
2616{
2617 off64_t fpos = offset + irec*recsize;
2618
2619 off64_t status64 = ::lseek64(fd, fpos, SEEK_SET);
2620
2621 if (status64 < 0) {
2622 cm_msg(MERROR, "FileHistory::ReadRecord", "Cannot read \'%s\', lseek64(%jd) errno %d (%s)", file_name, (intmax_t)fpos, errno, strerror(errno));
2623 return -1;
2624 }
2625
2626 ssize_t rd = ::read(fd, rec, recsize);
2627
2628 if (rd < 0) {
2629 cm_msg(MERROR, "FileHistory::ReadRecord", "Cannot read \'%s\', read() errno %d (%s)", file_name, errno, strerror(errno));
2630 return -1;
2631 }
2632
2633 if (rd == 0) {
2634 cm_msg(MERROR, "FileHistory::ReadRecord", "Cannot read \'%s\', unexpected end of file on read()", file_name);
2635 return -1;
2636 }
2637
2638 if ((size_t)rd != recsize) {
2639 cm_msg(MERROR, "FileHistory::ReadRecord", "Cannot read \'%s\', short read() returned %zd instead of %zu bytes", file_name, rd, recsize);
2640 return -1;
2641 }
2642
2643 return HS_SUCCESS;
2644}
2645
2646static int FindTime(const char* file_name, int fd, off64_t offset, size_t recsize, off64_t nrec, time_t timestamp, off64_t* i1p, time_t* t1p, off64_t* i2p, time_t* t2p, time_t* tstart, time_t* tend, int debug)
2647{
2648 //
2649 // purpose: find location time timestamp inside given file.
2650 // uses binary search
2651 // returns:
2652 // tstart, tend - time of first and last data in a file
2653 // i1p,t1p - data just before timestamp, used as "last_written"
2654 // i2p,t2p - data at timestamp or after timestamp, used as starting point to read data from file
2655 // assertions:
2656 // tstart <= t1p < t2p <= tend
2657 // i1p+1==i2p
2658 // t1p < timestamp <= t2p
2659 //
2660 // special cases:
2661 // 1) timestamp <= tstart - all data is in the future, return i1p==-1, t1p==-1, i2p==0, t2p==tstart
2662 // 2) tend < timestamp - all the data is in the past, return i1p = nrec-1, t1p = tend, i2p = nrec, t2p = 0;
2663 // 3) nrec == 1 only one record in this file and it is older than the timestamp (tstart == tend < timestamp)
2664 //
2665
2666 int status;
2667 char* buf = new char[recsize];
2668
2669 assert(nrec > 0);
2670
2671 off64_t rec1 = 0;
2672 off64_t rec2 = nrec-1;
2673
2674 status = ReadRecord(file_name, fd, offset, recsize, rec1, buf);
2675 if (status != HS_SUCCESS) {
2676 delete[] buf;
2677 return HS_FILE_ERROR;
2678 }
2679
2680 time_t t1 = *(DWORD*)buf;
2681
2682 *tstart = t1;
2683
2684 // timestamp is older than any data in this file
2685 if (timestamp <= t1) {
2686 *i1p = -1;
2687 *t1p = 0;
2688 *i2p = 0;
2689 *t2p = t1;
2690 *tend = 0;
2691 delete[] buf;
2692 return HS_SUCCESS;
2693 }
2694
2695 assert(t1 < timestamp);
2696
2697 if (nrec == 1) {
2698 *i1p = 0;
2699 *t1p = t1;
2700 *i2p = nrec; // == 1
2701 *t2p = 0;
2702 *tend = t1;
2703 delete[] buf;
2704 return HS_SUCCESS;
2705 }
2706
2707 status = ReadRecord(file_name, fd, offset, recsize, rec2, buf);
2708 if (status != HS_SUCCESS) {
2709 delete[] buf;
2710 return HS_FILE_ERROR;
2711 }
2712
2713 time_t t2 = *(DWORD*)buf;
2714
2715 *tend = t2;
2716
2717 // all the data is in the past
2718 if (t2 < timestamp) {
2719 *i1p = rec2;
2720 *t1p = t2;
2721 *i2p = nrec;
2722 *t2p = 0;
2723 delete[] buf;
2724 return HS_SUCCESS;
2725 }
2726
2727 assert(t1 < timestamp);
2728 assert(timestamp <= t2);
2729
2730 if (debug)
2731 printf("FindTime: rec %jd..(x)..%jd, time %s..(%s)..%s\n", (intmax_t)rec1, (intmax_t)rec2, TimeToString(t1).c_str(), TimeToString(timestamp).c_str(), TimeToString(t2).c_str());
2732
2733 // implement binary search
2734
2735 do {
2736 off64_t rec = (rec1+rec2)/2;
2737
2738 assert(rec >= 0);
2739 assert(rec < nrec);
2740
2741 status = ReadRecord(file_name, fd, offset, recsize, rec, buf);
2742 if (status != HS_SUCCESS) {
2743 delete[] buf;
2744 return HS_FILE_ERROR;
2745 }
2746
2747 time_t t = *(DWORD*)buf;
2748
2749 if (timestamp <= t) {
2750 if (debug)
2751 printf("FindTime: rec %jd..(x)..%jd..%jd, time %s..(%s)..%s..%s\n", (intmax_t)rec1, (intmax_t)rec, (intmax_t)rec2, TimeToString(t1).c_str(), TimeToString(timestamp).c_str(), TimeToString(t).c_str(), TimeToString(t2).c_str());
2752
2753 rec2 = rec;
2754 t2 = t;
2755 } else {
2756 if (debug)
2757 printf("FindTime: rec %jd..%jd..(x)..%jd, time %s..%s..(%s)..%s\n", (intmax_t)rec1, (intmax_t)rec, (intmax_t)rec2, TimeToString(t1).c_str(), TimeToString(t).c_str(), TimeToString(timestamp).c_str(), TimeToString(t2).c_str());
2758
2759 rec1 = rec;
2760 t1 = t;
2761 }
2762 } while (rec2 - rec1 > 1);
2763
2764 assert(rec1+1 == rec2);
2765 assert(t1 < timestamp);
2766 assert(timestamp <= t2);
2767
2768 if (debug)
2769 printf("FindTime: rec %jd..(x)..%jd, time %s..(%s)..%s, this is the result.\n", (intmax_t)rec1, (intmax_t)rec2, TimeToString(t1).c_str(), TimeToString(timestamp).c_str(), TimeToString(t2).c_str());
2770
2771 *i1p = rec1;
2772 *t1p = t1;
2773
2774 *i2p = rec2;
2775 *t2p = t2;
2776
2777 delete[] buf;
2778 return HS_SUCCESS;
2779}
2780
2781int HsFileSchema::read_last_written(const time_t timestamp,
2782 const int debug,
2783 time_t* last_written)
2784{
2785 int status;
2786 HsFileSchema* s = this;
2787
2788 if (debug)
2789 printf("FileHistory::read_last_written: file %s, schema time %s..%s, timestamp %s\n", s->fFileName.c_str(), TimeToString(s->fTimeFrom).c_str(), TimeToString(s->fTimeTo).c_str(), TimeToString(timestamp).c_str());
2790
2791 int fd = open(s->fFileName.c_str(), O_RDONLY);
2792 if (fd < 0) {
2793 cm_msg(MERROR, "FileHistory::read_last_written", "Cannot read \'%s\', open() errno %d (%s)", s->fFileName.c_str(), errno, strerror(errno));
2794 return HS_FILE_ERROR;
2795 }
2796
2797 off64_t file_size = ::lseek64(fd, 0, SEEK_END);
2798
2799 if (file_size < 0) {
2800 cm_msg(MERROR, "FileHistory::read_last_written", "Cannot read file size of \'%s\', lseek64(SEEK_END) errno %d (%s)", s->fFileName.c_str(), errno, strerror(errno));
2801 return HS_FILE_ERROR;
2802 }
2803
2804 if (file_size < s->fDataOffset) {
2805 // empty file
2806 ::close(fd);
2807 if (last_written)
2808 *last_written = 0;
2809 return HS_SUCCESS;
2810 }
2811
2812 off64_t nrec = (file_size - s->fDataOffset)/s->fRecordSize;
2813
2814 //printf("read_last_written: nrec %jd, file_size %jd, offset %jd, record size %zu\n", (intmax_t)nrec, (intmax_t)file_size, s->fDataOffset, s->fRecordSize);
2815
2816 if (nrec < 0)
2817 nrec = 0;
2818
2819 if (nrec < 1) {
2820 ::close(fd);
2821 if (last_written)
2822 *last_written = 0;
2823 return HS_SUCCESS;
2824 }
2825
2826 time_t lw = 0;
2827
2828 // read last record to check if desired time is inside or outside of the file
2829
2830 if (1) {
2831 char* buf = new char[s->fRecordSize];
2832
2833 status = ReadRecord(s->fFileName.c_str(), fd, s->fDataOffset, s->fRecordSize, nrec - 1, buf);
2834 if (status != HS_SUCCESS) {
2835 delete[] buf;
2836 ::close(fd);
2837 return HS_FILE_ERROR;
2838 }
2839
2840 lw = *(DWORD*)buf;
2841
2842 delete[] buf;
2843 }
2844
2845 if (lw >= timestamp) {
2846 off64_t irec = 0;
2847 time_t trec = 0;
2848 off64_t iunused = 0;
2849 time_t tunused = 0;
2850 time_t tstart = 0; // not used
2851 time_t tend = 0; // not used
2852
2853 status = FindTime(s->fFileName.c_str(), fd, s->fDataOffset, s->fRecordSize, nrec, timestamp, &irec, &trec, &iunused, &tunused, &tstart, &tend, 0*debug);
2854 if (status != HS_SUCCESS) {
2855 ::close(fd);
2856 return HS_FILE_ERROR;
2857 }
2858
2859 assert(trec < timestamp);
2860
2861 lw = trec;
2862 }
2863
2864 if (last_written)
2865 *last_written = lw;
2866
2867 if (debug)
2868 printf("FileHistory::read_last_written: file %s, schema time %s..%s, timestamp %s, last_written %s\n", s->fFileName.c_str(), TimeToString(s->fTimeFrom).c_str(), TimeToString(s->fTimeTo).c_str(), TimeToString(timestamp).c_str(), TimeToString(lw).c_str());
2869
2870 assert(lw < timestamp);
2871
2872 ::close(fd);
2873
2874 return HS_SUCCESS;
2875}
2876
2877int HsFileSchema::read_data(const time_t start_time,
2878 const time_t end_time,
2879 const int num_var, const std::vector<int>& var_schema_index, const int var_index[],
2880 const int debug,
2881 std::vector<time_t>& last_time,
2883{
2884 HsFileSchema* s = this;
2885
2886 if (debug)
2887 printf("FileHistory::read_data: file %s, schema time %s..%s, read time %s..%s, %d vars\n", s->fFileName.c_str(), TimeToString(s->fTimeFrom).c_str(), TimeToString(s->fTimeTo).c_str(), TimeToString(start_time).c_str(), TimeToString(end_time).c_str(), num_var);
2888
2889 //if (1) {
2890 // printf("Last time: ");
2891 // for (int i=0; i<num_var; i++) {
2892 // printf(" %s", TimeToString(last_time[i]).c_str());
2893 // }
2894 // printf("\n");
2895 //}
2896
2897 if (debug) {
2898 printf("FileHistory::read_data: file %s map", s->fFileName.c_str());
2899 for (size_t i=0; i<var_schema_index.size(); i++) {
2900 printf(" %2d", var_schema_index[i]);
2901 }
2902 printf("\n");
2903 }
2904
2905 int fd = ::open(s->fFileName.c_str(), O_RDONLY);
2906 if (fd < 0) {
2907 cm_msg(MERROR, "FileHistory::read_data", "Cannot read \'%s\', open() errno %d (%s)", s->fFileName.c_str(), errno, strerror(errno));
2908 return HS_FILE_ERROR;
2909 }
2910
2911 off64_t file_size = ::lseek64(fd, 0, SEEK_END);
2912
2913 if (file_size < 0) {
2914 cm_msg(MERROR, "FileHistory::read_data", "Cannot read file size of \'%s\', lseek64(SEEK_END) errno %d (%s)", s->fFileName.c_str(), errno, strerror(errno));
2915 ::close(fd);
2916 return HS_FILE_ERROR;
2917 }
2918
2919 if (file_size < s->fDataOffset) {
2920 // empty file
2921 ::close(fd);
2922 return HS_SUCCESS;
2923 }
2924
2925 off64_t nrec = (file_size - s->fDataOffset)/s->fRecordSize;
2926
2927 //printf("read_data: nrec %jd, file_size %jd, offset %jd, record size %zu\n", (intmax_t)nrec, (intmax_t)file_size, s->fDataOffset, s->fRecordSize);
2928
2929 if (nrec < 0)
2930 nrec = 0;
2931
2932 if (nrec < 1) {
2933 ::close(fd);
2934 return HS_SUCCESS;
2935 }
2936
2937 off64_t iunused = 0;
2938 time_t tunused = 0;
2939 off64_t irec = 0;
2940 time_t trec = 0;
2941 time_t tstart = 0;
2942 time_t tend = 0;
2943
2944 int istatus = FindTime(s->fFileName.c_str(), fd, s->fDataOffset, s->fRecordSize, nrec, start_time, &iunused, &tunused, &irec, &trec, &tstart, &tend, 0*debug);
2945
2946 if (istatus != HS_SUCCESS) {
2947 ::close(fd);
2948 return HS_FILE_ERROR;
2949 }
2950
2951 if (debug) {
2952 printf("FindTime %d, nrec %jd, (%jd, %s) (%jd, %s), tstart %s, tend %s, want %s\n", istatus, (intmax_t)nrec, (intmax_t)iunused, TimeToString(tunused).c_str(), (intmax_t)irec, TimeToString(trec).c_str(), TimeToString(tstart).c_str(), TimeToString(tend).c_str(), TimeToString(start_time).c_str());
2953 }
2954
2955 if (irec < 0 || irec >= nrec) {
2956 // all data in this file is older than start_time
2957
2958 ::close(fd);
2959
2960 if (debug)
2961 printf("FileHistory::read: file %s, schema time %s..%s, read time %s..%s, file time %s..%s, data in this file is too old\n", s->fFileName.c_str(), TimeToString(s->fTimeFrom).c_str(), TimeToString(s->fTimeTo).c_str(), TimeToString(start_time).c_str(), TimeToString(end_time).c_str(), TimeToString(tstart).c_str(), TimeToString(tend).c_str());
2962
2963 return HS_SUCCESS;
2964 }
2965
2966 if (tstart < s->fTimeFrom) {
2967 // data starts before time declared in schema
2968
2969 ::close(fd);
2970
2971 cm_msg(MERROR, "FileHistory::read_data", "Bad history file \'%s\': timestamp of first data %s is before schema start time %s", s->fFileName.c_str(), TimeToString(tstart).c_str(), TimeToString(s->fTimeFrom).c_str());
2972
2973 return HS_FILE_ERROR;
2974 }
2975
2976 if (tend && s->fTimeTo && tend > s->fTimeTo) {
2977 // data ends after time declared in schema (overlaps with next file)
2978
2979 ::close(fd);
2980
2981 cm_msg(MERROR, "FileHistory::read_data", "Bad history file \'%s\': timestamp of last data %s is after schema end time %s", s->fFileName.c_str(), TimeToString(tend).c_str(), TimeToString(s->fTimeTo).c_str());
2982
2983 return HS_FILE_ERROR;
2984 }
2985
2986 for (int i=0; i<num_var; i++) {
2987 int si = var_schema_index[i];
2988 if (si < 0)
2989 continue;
2990
2991 if (trec < last_time[i]) { // protect against duplicate and non-monotonous data
2992 ::close(fd);
2993
2994 cm_msg(MERROR, "FileHistory::read_data", "Internal history error at file \'%s\': variable %d data timestamp %s is before last timestamp %s", s->fFileName.c_str(), i, TimeToString(trec).c_str(), TimeToString(last_time[i]).c_str());
2995
2996 return HS_FILE_ERROR;
2997 }
2998 }
2999
3000 int count = 0;
3001
3002 off64_t fpos = s->fDataOffset + irec*s->fRecordSize;
3003
3004 off64_t xpos = ::lseek64(fd, fpos, SEEK_SET);
3005
3006 //printf("read_data: lseek64() returned %jd, irec %jd, offset %jd, record size %zu, fpos %jd\n", xpos, irec, s->fDataOffset, s->fRecordSize, fpos);
3007
3008 if (xpos < 0) {
3009 cm_msg(MERROR, "FileHistory::read_data", "Cannot read \'%s\', lseek64(%jd) errno %d (%s)", s->fFileName.c_str(), (intmax_t)fpos, errno, strerror(errno));
3010 ::close(fd);
3011 return HS_FILE_ERROR;
3012 }
3013
3014 char* buf = new char[s->fRecordSize];
3015
3016 off64_t prec = irec;
3017
3018 while (1) {
3019 ssize_t rd = ::read(fd, buf, s->fRecordSize);
3020
3021 if (rd < 0) {
3022 cm_msg(MERROR, "FileHistory::read_data", "Cannot read \'%s\', read() errno %d (%s)", s->fFileName.c_str(), errno, strerror(errno));
3023 break;
3024 }
3025
3026 if (rd == 0) {
3027 // EOF
3028 break;
3029 }
3030
3031 if ((size_t)rd != s->fRecordSize) {
3032 cm_msg(MERROR, "FileHistory::read_data", "Cannot read \'%s\', short read() returned %zd instead of %zu bytes", s->fFileName.c_str(), rd, s->fRecordSize);
3033 break;
3034 }
3035
3036 prec++;
3037
3038 bool past_end_of_last_file = (s->fTimeTo == 0) && (prec > nrec);
3039
3040 time_t t = *(DWORD*)buf;
3041
3042 if (debug > 1)
3043 printf("FileHistory::read: file %s, schema time %s..%s, read time %s..%s, row time %s\n", s->fFileName.c_str(), TimeToString(s->fTimeFrom).c_str(), TimeToString(s->fTimeTo).c_str(), TimeToString(start_time).c_str(), TimeToString(end_time).c_str(), TimeToString(t).c_str());
3044
3045 if (t < trec) {
3046 delete[] buf;
3047 ::close(fd);
3048 cm_msg(MERROR, "FileHistory::read_data", "Bad history file \'%s\': record %jd timestamp %s is before start time %s", s->fFileName.c_str(), (intmax_t)(irec + count), TimeToString(t).c_str(), TimeToString(trec).c_str());
3049 return HS_FILE_ERROR;
3050 }
3051
3052 if (tend && (t > tend) && !past_end_of_last_file) {
3053 delete[] buf;
3054 ::close(fd);
3055 cm_msg(MERROR, "FileHistory::read_data", "Bad history file \'%s\': record %jd timestamp %s is after last timestamp %s", s->fFileName.c_str(), (intmax_t)(irec + count), TimeToString(t).c_str(), TimeToString(tend).c_str());
3056 return HS_FILE_ERROR;
3057 }
3058
3059 if (t > end_time)
3060 break;
3061
3062 char* data = buf + 4;
3063
3064 for (int i=0; i<num_var; i++) {
3065 int si = var_schema_index[i];
3066 if (si < 0)
3067 continue;
3068
3069 if (t < last_time[i]) { // protect against duplicate and non-monotonous data
3070 delete[] buf;
3071 ::close(fd);
3072
3073 cm_msg(MERROR, "FileHistory::read_data", "Bad history file \'%s\': record %jd timestamp %s is before timestamp %s of variable %d", s->fFileName.c_str(), (intmax_t)(irec + count), TimeToString(t).c_str(), TimeToString(last_time[i]).c_str(), i);
3074
3075 return HS_FILE_ERROR;
3076 }
3077
3078 double v = 0;
3079 void* ptr = data + s->fOffsets[si];
3080
3081 int ii = var_index[i];
3082 assert(ii >= 0);
3083 assert(ii < s->fVariables[si].n_data);
3084
3085 switch (s->fVariables[si].type) {
3086 default:
3087 // unknown data type
3088 v = 0;
3089 break;
3090 case TID_BYTE:
3091 v = ((unsigned char*)ptr)[ii];
3092 break;
3093 case TID_SBYTE:
3094 v = ((signed char *)ptr)[ii];
3095 break;
3096 case TID_CHAR:
3097 v = ((char*)ptr)[ii];
3098 break;
3099 case TID_WORD:
3100 v = ((unsigned short *)ptr)[ii];
3101 break;
3102 case TID_SHORT:
3103 v = ((signed short *)ptr)[ii];
3104 break;
3105 case TID_DWORD:
3106 v = ((unsigned int *)ptr)[ii];
3107 break;
3108 case TID_INT:
3109 v = ((int *)ptr)[ii];
3110 break;
3111 case TID_BOOL:
3112 v = ((unsigned int *)ptr)[ii];
3113 break;
3114 case TID_FLOAT:
3115 v = ((float*)ptr)[ii];
3116 break;
3117 case TID_DOUBLE:
3118 v = ((double*)ptr)[ii];
3119 break;
3120 }
3121
3122 buffer[i]->Add(t, v);
3123 last_time[i] = t;
3124 }
3125 count++;
3126 }
3127
3128 delete[] buf;
3129
3130 ::close(fd);
3131
3132 if (debug) {
3133 printf("FileHistory::read_data: file %s map", s->fFileName.c_str());
3134 for (size_t i=0; i<var_schema_index.size(); i++) {
3135 printf(" %2d", var_schema_index[i]);
3136 }
3137 printf(" read %d rows\n", count);
3138 }
3139
3140 if (debug)
3141 printf("FileHistory::read: file %s, schema time %s..%s, read time %s..%s, %d vars, read %d rows\n", s->fFileName.c_str(), TimeToString(s->fTimeFrom).c_str(), TimeToString(s->fTimeTo).c_str(), TimeToString(start_time).c_str(), TimeToString(end_time).c_str(), num_var, count);
3142
3143 return HS_SUCCESS;
3144}
3145
3147// Implementation of the MidasHistoryInterface //
3149
3151{
3152protected:
3153 int fDebug = 0;
3154 std::string fConnectString;
3155
3156 // writer data
3158 std::vector<HsSchema*> fWriterEvents;
3159
3160 // reader data
3162
3163public:
3165 {
3166 // empty
3167 }
3168
3170 {
3171 for (size_t i=0; i<fWriterEvents.size(); i++) {
3172 //printf("fWriterEvents[%zu] is %p\n", i, fWriterEvents[i]);
3173 if (fWriterEvents[i]) {
3174 delete fWriterEvents[i];
3175 fWriterEvents[i] = NULL;
3176 }
3177 }
3178 fWriterEvents.clear();
3179 }
3180
3181 virtual int hs_set_debug(int debug)
3182 {
3183 int old = fDebug;
3184 fDebug = debug;
3185 return old;
3186 }
3187
3188 virtual int hs_connect(const char* connect_string) = 0;
3189 virtual int hs_disconnect() = 0;
3190
3191protected:
3193 // Schema functions //
3195
3196 // load existing schema
3197 virtual int read_schema(HsSchemaVector* sv, const char* event_name, const time_t timestamp) = 0; // event_name: =NULL means read only event names, =event_name means load tag names for all matching events; timestamp: =0 means read all schema all the way to the beginning of time, =time means read schema in effect at this time and all newer schema
3198
3199 // return a new copy of a schema for writing into history. If schema for this event does not exist, it will be created
3200 virtual HsSchema* new_event(const char* event_name, time_t timestamp, int ntags, const TAG tags[]) = 0;
3201
3202 // if output file is too big, close it, create a new output file
3203 virtual HsSchema* maybe_reopen(const char* event_name, time_t timestamp, HsSchema* s) = 0;
3204
3205public:
3207 // Functions used by mlogger //
3209
3210 int hs_define_event(const char* event_name, time_t timestamp, int ntags, const TAG tags[]);
3211 int hs_write_event(const char* event_name, time_t timestamp, int buffer_size, const char* buffer);
3212 int hs_flush_buffers();
3213
3215 // Functions used by mhttpd //
3217
3218 int hs_clear_cache();
3219 int hs_get_events(time_t t, std::vector<std::string> *pevents);
3220 int hs_get_tags(const char* event_name, time_t t, std::vector<TAG> *ptags);
3221 int hs_get_last_written(time_t timestamp, int num_var, const char* const event_name[], const char* const var_name[], const int var_index[], time_t last_written[]);
3222 int hs_read_buffer(time_t start_time, time_t end_time,
3223 int num_var, const char* const event_name[], const char* const var_name[], const int var_index[],
3225 int hs_status[]);
3226
3227 /*------------------------------------------------------------------*/
3228
3230 {
3231 public:
3235
3237
3240 time_t **fTimeBuffer;
3241 double **fDataBuffer;
3242
3244
3245 ReadBuffer(time_t first_time, time_t last_time, time_t interval) // ctor
3246 {
3247 fNumAdded = 0;
3248
3249 fFirstTime = first_time;
3251 fInterval = interval;
3252
3253 fNumAlloc = 0;
3254 fNumEntries = NULL;
3255 fTimeBuffer = NULL;
3256 fDataBuffer = NULL;
3257
3258 fPrevTime = 0;
3259 }
3260
3261 ~ReadBuffer() // dtor
3262 {
3263 }
3264
3265 void Realloc(int wantalloc)
3266 {
3267 if (wantalloc < fNumAlloc - 10)
3268 return;
3269
3270 int newalloc = fNumAlloc*2;
3271
3272 if (newalloc <= 1000)
3273 newalloc = wantalloc + 1000;
3274
3275 //printf("wantalloc %d, fNumEntries %d, fNumAlloc %d, newalloc %d\n", wantalloc, *fNumEntries, fNumAlloc, newalloc);
3276
3277 *fTimeBuffer = (time_t*)realloc(*fTimeBuffer, sizeof(time_t)*newalloc);
3278 assert(*fTimeBuffer);
3279
3280 *fDataBuffer = (double*)realloc(*fDataBuffer, sizeof(double)*newalloc);
3281 assert(*fDataBuffer);
3282
3283 fNumAlloc = newalloc;
3284 }
3285
3286 void Add(time_t t, double v)
3287 {
3288 if (t < fFirstTime)
3289 return;
3290 if (t > fLastTime)
3291 return;
3292
3293 fNumAdded++;
3294
3295 if ((fPrevTime==0) || (t >= fPrevTime + fInterval)) {
3296 int pos = *fNumEntries;
3297
3298 Realloc(pos + 1);
3299
3300 (*fTimeBuffer)[pos] = t;
3301 (*fDataBuffer)[pos] = v;
3302
3303 (*fNumEntries) = pos + 1;
3304
3305 fPrevTime = t;
3306 }
3307 }
3308
3309 void Finish()
3310 {
3311
3312 }
3313 };
3314
3315 /*------------------------------------------------------------------*/
3316
3317 int hs_read(time_t start_time, time_t end_time, time_t interval,
3318 int num_var,
3319 const char* const event_name[], const char* const var_name[], const int var_index[],
3320 int num_entries[],
3321 time_t* time_buffer[], double* data_buffer[],
3322 int st[]);
3323 /*------------------------------------------------------------------*/
3324
3325
3326 int hs_read_binned(time_t start_time, time_t end_time, int num_bins,
3327 int num_var, const char* const event_name[], const char* const var_name[], const int var_index[],
3328 int num_entries[],
3329 int* count_bins[], double* mean_bins[], double* rms_bins[], double* min_bins[], double* max_bins[],
3330 time_t* bins_first_time[], double* bins_first_value[],
3331 time_t* bins_last_time[], double* bins_last_value[],
3332 time_t last_time[], double last_value[],
3333 int st[]);
3334};
3335
3336MidasHistoryBinnedBuffer::MidasHistoryBinnedBuffer(time_t first_time, time_t last_time, int num_bins) // ctor
3337{
3338 fNumEntries = 0;
3339
3340 fNumBins = num_bins;
3341 fFirstTime = first_time;
3343
3344 fSum0 = new double[num_bins];
3345 fSum1 = new double[num_bins];
3346 fSum2 = new double[num_bins];
3347
3348 for (int i=0; i<num_bins; i++) {
3349 fSum0[i] = 0;
3350 fSum1[i] = 0;
3351 fSum2[i] = 0;
3352 }
3353}
3354
3356{
3357 delete fSum0; fSum0 = NULL;
3358 delete fSum1; fSum1 = NULL;
3359 delete fSum2; fSum2 = NULL;
3360 // poison the pointers
3361 fCount = NULL;
3362 fMean = NULL;
3363 fRms = NULL;
3364 fMin = NULL;
3365 fMax = NULL;
3366 fBinsFirstTime = NULL;
3367 fBinsFirstValue = NULL;
3368 fBinsLastTime = NULL;
3369 fBinsLastValue = NULL;
3370 fLastTimePtr = NULL;
3371 fLastValuePtr = NULL;
3372}
3373
3375{
3376 for (int ibin = 0; ibin < fNumBins; ibin++) {
3377 if (fMin)
3378 fMin[ibin] = 0;
3379 if (fMax)
3380 fMax[ibin] = 0;
3381 if (fBinsFirstTime)
3382 fBinsFirstTime[ibin] = 0;
3383 if (fBinsFirstValue)
3384 fBinsFirstValue[ibin] = 0;
3385 if (fBinsLastTime)
3386 fBinsLastTime[ibin] = 0;
3387 if (fBinsLastValue)
3388 fBinsLastValue[ibin] = 0;
3389 }
3390 if (fLastTimePtr)
3391 *fLastTimePtr = 0;
3392 if (fLastValuePtr)
3393 *fLastValuePtr = 0;
3394}
3395
3396void MidasHistoryBinnedBuffer::Add(time_t t, double v)
3397{
3398 if (t < fFirstTime)
3399 return;
3400 if (t > fLastTime)
3401 return;
3402
3403 fNumEntries++;
3404
3405 double a = (double)(t - fFirstTime);
3406 double b = (double)(fLastTime - fFirstTime);
3407 double fbin = fNumBins*a/b;
3408
3409 int ibin = (int)fbin;
3410
3411 if (ibin < 0)
3412 ibin = 0;
3413 else if (ibin >= fNumBins)
3414 ibin = fNumBins-1;
3415
3416 if (fSum0[ibin] == 0) {
3417 if (fMin)
3418 fMin[ibin] = v;
3419 if (fMax)
3420 fMax[ibin] = v;
3421 if (fBinsFirstTime)
3422 fBinsFirstTime[ibin] = t;
3423 if (fBinsFirstValue)
3424 fBinsFirstValue[ibin] = v;
3425 if (fBinsLastTime)
3426 fBinsLastTime[ibin] = t;
3427 if (fBinsLastValue)
3428 fBinsLastValue[ibin] = v;
3429 if (fLastTimePtr)
3430 *fLastTimePtr = t;
3431 if (fLastValuePtr)
3432 *fLastValuePtr = v;
3433 }
3434
3435 fSum0[ibin] += 1.0;
3436 fSum1[ibin] += v;
3437 fSum2[ibin] += v*v;
3438
3439 if (fMin)
3440 if (v < fMin[ibin])
3441 fMin[ibin] = v;
3442
3443 if (fMax)
3444 if (v > fMax[ibin])
3445 fMax[ibin] = v;
3446
3447 // NOTE: this assumes t and v are sorted by time.
3448 if (fBinsLastTime)
3449 fBinsLastTime[ibin] = t;
3450 if (fBinsLastValue)
3451 fBinsLastValue[ibin] = v;
3452
3453 if (fLastTimePtr)
3454 if (t > *fLastTimePtr) {
3455 *fLastTimePtr = t;
3456 if (fLastValuePtr)
3457 *fLastValuePtr = v;
3458 }
3459}
3460
3462{
3463 for (int i=0; i<fNumBins; i++) {
3464 double num = fSum0[i];
3465 double mean = 0;
3466 double variance = 0;
3467 if (num > 0) {
3468 mean = fSum1[i]/num;
3469 variance = fSum2[i]/num-mean*mean;
3470 }
3471 double rms = 0;
3472 if (variance > 0)
3473 rms = sqrt(variance);
3474
3475 if (fCount)
3476 fCount[i] = (int)num;
3477
3478 if (fMean)
3479 fMean[i] = mean;
3480
3481 if (fRms)
3482 fRms[i] = rms;
3483
3484 if (num == 0) {
3485 if (fMin)
3486 fMin[i] = 0;
3487 if (fMax)
3488 fMax[i] = 0;
3489 }
3490 }
3491}
3492
3493int SchemaHistoryBase::hs_define_event(const char* event_name, time_t timestamp, int ntags, const TAG tags[])
3494{
3495 if (fDebug) {
3496 printf("hs_define_event: event name [%s] with %d tags\n", event_name, ntags);
3497 if (fDebug > 1)
3498 PrintTags(ntags, tags);
3499 }
3500
3501 // delete all events with the same name
3502 for (size_t i=0; i<fWriterEvents.size(); i++)
3503 if (fWriterEvents[i])
3504 if (event_name_cmp(fWriterEvents[i]->fEventName, event_name)==0) {
3505 if (fDebug)
3506 printf("deleting exising event %s\n", event_name);
3507 fWriterEvents[i]->close();
3508 delete fWriterEvents[i];
3509 fWriterEvents[i] = NULL;
3510 }
3511
3512 // check for wrong types etc
3513 for (int i=0; i<ntags; i++) {
3514 if (strlen(tags[i].name) < 1) {
3515 cm_msg(MERROR, "hs_define_event", "Error: History event \'%s\' has empty name at index %d", event_name, i);
3516 return HS_FILE_ERROR;
3517 }
3518 if (tags[i].name[0] == ' ') {
3519 cm_msg(MERROR, "hs_define_event", "Error: History event \'%s\' has name \'%s\' starting with a blank", event_name, tags[i].name);
3520 return HS_FILE_ERROR;
3521 }
3522 if (tags[i].type <= 0 || tags[i].type >= TID_LAST) {
3523 cm_msg(MERROR, "hs_define_event", "Error: History event \'%s\' tag \'%s\' at index %d has invalid type %d",
3524 event_name, tags[i].name, i, tags[i].type);
3525 return HS_FILE_ERROR;
3526 }
3527 if (tags[i].type == TID_STRING) {
3528 cm_msg(MERROR, "hs_define_event",
3529 "Error: History event \'%s\' tag \'%s\' at index %d has forbidden type TID_STRING", event_name,
3530 tags[i].name, i);
3531 return HS_FILE_ERROR;
3532 }
3533 if (tags[i].n_data <= 0) {
3534 cm_msg(MERROR, "hs_define_event", "Error: History event \'%s\' tag \'%s\' at index %d has invalid n_data %d",
3535 event_name, tags[i].name, i, tags[i].n_data);
3536 return HS_FILE_ERROR;
3537 }
3538 }
3539
3540 // check for duplicate names. Done by sorting, since this takes only O(N*log*N))
3541 std::vector<std::string> names;
3542 for (int i=0; i<ntags; i++) {
3543 std::string str(tags[i].name);
3544 std::transform(str.begin(), str.end(), str.begin(), ::toupper);
3545 names.push_back(str);
3546 }
3547 std::sort(names.begin(), names.end());
3548 for (int i=0; i<ntags-1; i++) {
3549 if (names[i] == names[i + 1]) {
3550 cm_msg(MERROR, "hs_define_event",
3551 "Error: History event \'%s\' has duplicate tag name \'%s\'", event_name,
3552 names[i].c_str());
3553 return HS_FILE_ERROR;
3554 }
3555 }
3556
3557 HsSchema* s = new_event(event_name, timestamp, ntags, tags);
3558 if (!s)
3559 return HS_FILE_ERROR;
3560
3561 s->fDisabled = false;
3562
3564
3565 // find empty slot in events list
3566 for (size_t i=0; i<fWriterEvents.size(); i++)
3567 if (!fWriterEvents[i]) {
3568 fWriterEvents[i] = s;
3569 s = NULL;
3570 break;
3571 }
3572
3573 // if no empty slots, add at the end
3574 if (s)
3575 fWriterEvents.push_back(s);
3576
3577 return HS_SUCCESS;
3578}
3579
3580int SchemaHistoryBase::hs_write_event(const char* event_name, time_t timestamp, int xbuffer_size, const char* buffer)
3581{
3582 if (fDebug)
3583 printf("hs_write_event: write event \'%s\', time %d, size %d\n", event_name, (int)timestamp, xbuffer_size);
3584
3585 assert(xbuffer_size > 0);
3586
3587 size_t buffer_size = xbuffer_size;
3588
3589 HsSchema *s = NULL;
3590
3591 // find this event
3592 for (size_t i=0; i<fWriterEvents.size(); i++)
3593 if (fWriterEvents[i] && (event_name_cmp(fWriterEvents[i]->fEventName, event_name)==0)) {
3594 s = fWriterEvents[i];
3595 break;
3596 }
3597
3598 // not found
3599 if (!s)
3600 return HS_UNDEFINED_EVENT;
3601
3602 // deactivated because of error?
3603 if (s->fDisabled) {
3604 //printf("HERE!\n");
3605 return HS_FILE_ERROR;
3606 }
3607
3608 s = maybe_reopen(event_name, timestamp, s);
3609
3610 assert(s != NULL);
3611
3612 if (s->fNumBytes == 0) { // compute expected data size
3613 // NB: history data does not have any padding!
3614 for (size_t i=0; i<s->fVariables.size(); i++) {
3615 s->fNumBytes += s->fVariables[i].n_bytes;
3616 }
3617 }
3618
3619 int status;
3620
3621 if (buffer_size > s->fNumBytes) { // too many bytes!
3622 if (s->fCountWriteOversize == 0) {
3623 // only report first occurence
3624 // count of all occurences is reported by HsSchema destructor
3625 cm_msg(MERROR, "hs_write_event", "Event \'%s\' data size mismatch: expected %zu bytes, got %zu bytes", s->fEventName.c_str(), s->fNumBytes, buffer_size);
3626 }
3628 if (buffer_size > s->fWriteMaxSize)
3629 s->fWriteMaxSize = buffer_size;
3630 status = s->write_event(timestamp, buffer, s->fNumBytes);
3631 } else if (buffer_size < s->fNumBytes) { // too few bytes
3632 if (s->fCountWriteUndersize == 0) {
3633 // only report first occurence
3634 // count of all occurences is reported by HsSchema destructor
3635 cm_msg(MERROR, "hs_write_event", "Event \'%s\' data size mismatch: expected %zu bytes, got %zu bytes", s->fEventName.c_str(), s->fNumBytes, buffer_size);
3636 }
3638 if (s->fWriteMinSize == 0)
3639 s->fWriteMinSize = buffer_size;
3640 else if (buffer_size < s->fWriteMinSize)
3641 s->fWriteMinSize = buffer_size;
3642 char* tmp = (char*)malloc(s->fNumBytes);
3643 memcpy(tmp, buffer, buffer_size);
3644 memset(tmp + buffer_size, 0, s->fNumBytes - buffer_size);
3645 status = s->write_event(timestamp, tmp, s->fNumBytes);
3646 free(tmp);
3647 } else {
3648 assert(buffer_size == s->fNumBytes); // obviously
3649 status = s->write_event(timestamp, buffer, buffer_size);
3650 }
3651
3652 // if could not write event, deactivate it
3653 if (status != HS_SUCCESS) {
3654 s->fDisabled = true;
3655 cm_msg(MERROR, "hs_write_event", "Event \'%s\' disabled after write error %d", event_name, status);
3656 return HS_FILE_ERROR;
3657 }
3658
3659 return HS_SUCCESS;
3660}
3661
3663{
3664 int status = HS_SUCCESS;
3665
3666 if (fDebug)
3667 printf("hs_flush_buffers!\n");
3668
3669 for (size_t i=0; i<fWriterEvents.size(); i++)
3670 if (fWriterEvents[i]) {
3671 int xstatus = fWriterEvents[i]->flush_buffers();
3672 if (xstatus != HS_SUCCESS)
3673 status = xstatus;
3674 }
3675
3676 return status;
3677}
3678
3680// Functions used by mhttpd //
3682
3684{
3685 if (fDebug)
3686 printf("SchemaHistoryBase::hs_clear_cache!\n");
3687
3690
3691 return HS_SUCCESS;
3692}
3693
3694int SchemaHistoryBase::hs_get_events(time_t t, std::vector<std::string> *pevents)
3695{
3696 if (fDebug)
3697 printf("hs_get_events, time %s\n", TimeToString(t).c_str());
3698
3699 int status = read_schema(&fReaderSchema, NULL, t);
3700 if (status != HS_SUCCESS)
3701 return status;
3702
3703 if (fDebug) {
3704 printf("hs_get_events: available schema:\n");
3705 fReaderSchema.print(false);
3706 }
3707
3708 assert(pevents);
3709
3710 for (size_t i=0; i<fReaderSchema.size(); i++) {
3711 HsSchema* s = fReaderSchema[i];
3712 if (t && s->fTimeTo && s->fTimeTo < t)
3713 continue;
3714 bool dupe = false;
3715 for (size_t j=0; j<pevents->size(); j++)
3716 if (event_name_cmp((*pevents)[j], s->fEventName.c_str())==0) {
3717 dupe = true;
3718 break;
3719 }
3720
3721 if (!dupe)
3722 pevents->push_back(s->fEventName);
3723 }
3724
3725 std::sort(pevents->begin(), pevents->end());
3726
3727 if (fDebug) {
3728 printf("hs_get_events: returning %zu events\n", pevents->size());
3729 for (size_t i=0; i<pevents->size(); i++) {
3730 printf(" %zu: [%s]\n", i, (*pevents)[i].c_str());
3731 }
3732 }
3733
3734 return HS_SUCCESS;
3735}
3736
3737int SchemaHistoryBase::hs_get_tags(const char* event_name, time_t t, std::vector<TAG> *ptags)
3738{
3739 if (fDebug)
3740 printf("hs_get_tags: event [%s], time %s\n", event_name, TimeToString(t).c_str());
3741
3742 assert(ptags);
3743
3744 int status = read_schema(&fReaderSchema, event_name, t);
3745 if (status != HS_SUCCESS)
3746 return status;
3747
3748 bool found_event = false;
3749 for (size_t i=0; i<fReaderSchema.size(); i++) {
3750 HsSchema* s = fReaderSchema[i];
3751 if (t && s->fTimeTo && s->fTimeTo < t)
3752 continue;
3753
3754 if (event_name_cmp(s->fEventName, event_name) != 0)
3755 continue;
3756
3757 found_event = true;
3758
3759 for (size_t i=0; i<s->fVariables.size(); i++) {
3760 const char* tagname = s->fVariables[i].name.c_str();
3761 //printf("event_name [%s], table_name [%s], column name [%s], tag name [%s]\n", event_name, tn.c_str(), cn.c_str(), tagname);
3762
3763 bool dupe = false;
3764
3765 for (size_t k=0; k<ptags->size(); k++)
3766 if (strcasecmp((*ptags)[k].name, tagname) == 0) {
3767 dupe = true;
3768 break;
3769 }
3770
3771 if (!dupe) {
3772 TAG t;
3773 mstrlcpy(t.name, tagname, sizeof(t.name));
3774 t.type = s->fVariables[i].type;
3775 t.n_data = s->fVariables[i].n_data;
3776
3777 ptags->push_back(t);
3778 }
3779 }
3780 }
3781
3782 if (!found_event)
3783 return HS_UNDEFINED_EVENT;
3784
3785 if (fDebug) {
3786 printf("hs_get_tags: event [%s], returning %zu tags\n", event_name, ptags->size());
3787 for (size_t i=0; i<ptags->size(); i++) {
3788 printf(" tag[%zu]: %s[%d] type %d\n", i, (*ptags)[i].name, (*ptags)[i].n_data, (*ptags)[i].type);
3789 }
3790 }
3791
3792 return HS_SUCCESS;
3793}
3794
3795int SchemaHistoryBase::hs_get_last_written(time_t timestamp, int num_var, const char* const event_name[], const char* const var_name[], const int var_index[], time_t last_written[])
3796{
3797 if (fDebug) {
3798 printf("hs_get_last_written: timestamp %s, num_var %d\n", TimeToString(timestamp).c_str(), num_var);
3799 }
3800
3801 for (int j=0; j<num_var; j++) {
3802 last_written[j] = 0;
3803 }
3804
3805 for (int i=0; i<num_var; i++) {
3806 int status = read_schema(&fReaderSchema, event_name[i], 0);
3807 if (status != HS_SUCCESS)
3808 return status;
3809 }
3810
3811 //fReaderSchema.print(false);
3812
3813 for (int i=0; i<num_var; i++) {
3814 for (size_t ss=0; ss<fReaderSchema.size(); ss++) {
3815 HsSchema* s = fReaderSchema[ss];
3816 // schema is too new
3817 if (s->fTimeFrom && s->fTimeFrom >= timestamp)
3818 continue;
3819 // this schema is newer than last_written and may contain newer data?
3820 if (s->fTimeFrom && s->fTimeFrom < last_written[i])
3821 continue;
3822 // schema for the variables we want?
3823 int sindex = s->match_event_var(event_name[i], var_name[i], var_index[i]);
3824 if (sindex < 0)
3825 continue;
3826
3827 time_t lw = 0;
3828
3829 int status = s->read_last_written(timestamp, fDebug, &lw);
3830
3831 if (status == HS_SUCCESS && lw != 0) {
3832 for (int j=0; j<num_var; j++) {
3833 int sj = s->match_event_var(event_name[j], var_name[j], var_index[j]);
3834 if (sj < 0)
3835 continue;
3836
3837 if (lw > last_written[j])
3838 last_written[j] = lw;
3839 }
3840 }
3841 }
3842 }
3843
3844 if (fDebug) {
3845 printf("hs_get_last_written: timestamp time %s, num_var %d, result:\n", TimeToString(timestamp).c_str(), num_var);
3846 for (int i=0; i<num_var; i++) {
3847 printf(" event [%s] tag [%s] index [%d] last_written %s\n", event_name[i], var_name[i], var_index[i], TimeToString(last_written[i]).c_str());
3848 }
3849 }
3850
3851 return HS_SUCCESS;
3852}
3853
3854int SchemaHistoryBase::hs_read_buffer(time_t start_time, time_t end_time,
3855 int num_var, const char* const event_name[], const char* const var_name[], const int var_index[],
3857 int hs_status[])
3858{
3859 if (fDebug)
3860 printf("hs_read_buffer: %d variables, start time %s, end time %s\n", num_var, TimeToString(start_time).c_str(), TimeToString(end_time).c_str());
3861
3862 for (int i=0; i<num_var; i++) {
3863 int status = read_schema(&fReaderSchema, event_name[i], start_time);
3864 if (status != HS_SUCCESS)
3865 return status;
3866 }
3867
3868#if 0
3869 if (fDebug)
3870 fReaderSchema.print(false);
3871#endif
3872
3873 for (int i=0; i<num_var; i++) {
3874 hs_status[i] = HS_UNDEFINED_VAR;
3875 }
3876
3877 //for (size_t ss=0; ss<fReaderSchema.size(); ss++) {
3878 // HsSchema* s = fReaderSchema[ss];
3879 // HsFileSchema* fs = dynamic_cast<HsFileSchema*>(s);
3880 // assert(fs != NULL);
3881 // printf("schema %d from %s to %s, filename %s\n", ss, TimeToString(fs->fTimeFrom).c_str(), TimeToString(fs->fTimeTo).c_str(), fs->fFileName.c_str());
3882 //}
3883
3884 // check that schema are sorted by time
3885
3886#if 0
3887 // check that schema list is sorted by time, descending fTimeFrom, newest schema first
3888 for (size_t ss=0; ss<fReaderSchema.size(); ss++) {
3889 if (fDebug) {
3890 //printf("Check schema %zu/%zu: prev from %s, this from %s to %s, compare %d %d %d\n", ss, fReaderSchema.size(),
3891 // TimeToString(fReaderSchema[ss-1]->fTimeFrom).c_str(),
3892 // TimeToString(fReaderSchema[ss]->fTimeFrom).c_str(),
3893 // TimeToString(fReaderSchema[ss]->fTimeTo).c_str(),
3894 // fReaderSchema[ss-1]->fTimeFrom >= fReaderSchema[ss]->fTimeTo,
3895 // fReaderSchema[ss-1]->fTimeFrom > fReaderSchema[ss]->fTimeFrom,
3896 // (fReaderSchema[ss-1]->fTimeFrom >= fReaderSchema[ss]->fTimeTo) && (fReaderSchema[ss-1]->fTimeFrom > fReaderSchema[ss]->fTimeFrom));
3897 printf("Schema %zu/%zu: from %s to %s, name %s\n", ss, fReaderSchema.size(),
3898 TimeToString(fReaderSchema[ss]->fTimeFrom).c_str(),
3899 TimeToString(fReaderSchema[ss]->fTimeTo).c_str(),
3900 fReaderSchema[ss]->fEventName.c_str());
3901 }
3902
3903 if (ss > 0) {
3904 //if ((fReaderSchema[ss-1]->fTimeFrom >= fReaderSchema[ss]->fTimeTo) && (fReaderSchema[ss-1]->fTimeFrom > fReaderSchema[ss]->fTimeFrom)) {
3905 if ((fReaderSchema[ss-1]->fTimeFrom >= fReaderSchema[ss]->fTimeFrom)) {
3906 // good
3907 } else {
3908 cm_msg(MERROR, "SchemaHistoryBase::hs_read_buffer", "History internal error, schema is not ordered by time. Please report this error to the midas forum.");
3909 return HS_FILE_ERROR;
3910 }
3911 }
3912 }
3913#endif
3914
3915 std::vector<HsSchema*> slist;
3916 std::vector<std::vector<int>> smap;
3917
3918 for (size_t ss=0; ss<fReaderSchema.size(); ss++) {
3919 HsSchema* s = fReaderSchema[ss];
3920 // schema is too new?
3921 if (s->fTimeFrom && s->fTimeFrom > end_time)
3922 continue;
3923 // schema is too old
3924 if (s->fTimeTo && s->fTimeTo < start_time)
3925 continue;
3926
3927 std::vector<int> sm;
3928
3929 for (int i=0; i<num_var; i++) {
3930 // schema for the variables we want?
3931 int sindex = s->match_event_var(event_name[i], var_name[i], var_index[i]);
3932 if (sindex < 0)
3933 continue;
3934
3935 if (sm.empty()) {
3936 for (int i=0; i<num_var; i++) {
3937 sm.push_back(-1);
3938 }
3939 }
3940
3941 sm[i] = sindex;
3942 }
3943
3944 if (!sm.empty()) {
3945 slist.push_back(s);
3946 smap.push_back(sm);
3947 }
3948 }
3949
3950 if (0||fDebug) {
3951 printf("Found %zu matching schema:\n", slist.size());
3952
3953 for (size_t i=0; i<slist.size(); i++) {
3954 HsSchema* s = slist[i];
3955 s->print();
3956 for (int k=0; k<num_var; k++)
3957 printf(" tag %s[%d] sindex %d\n", var_name[k], var_index[k], smap[i][k]);
3958 }
3959 }
3960
3961 //for (size_t ss=0; ss<slist.size(); ss++) {
3962 // HsSchema* s = slist[ss];
3963 // HsFileSchema* fs = dynamic_cast<HsFileSchema*>(s);
3964 // assert(fs != NULL);
3965 // printf("schema %zu from %s to %s, filename %s", ss, TimeToString(fs->fTimeFrom).c_str(), TimeToString(fs->fTimeTo).c_str(), fs->fFileName.c_str());
3966 // printf(" smap ");
3967 // for (int k=0; k<num_var; k++)
3968 // printf(" %2d", smap[ss][k]);
3969 // printf("\n");
3970 //}
3971
3972 for (size_t ss=1; ss<slist.size(); ss++) {
3973 if (fDebug) {
3974 printf("Check schema %zu/%zu: prev from %s, this from %s to %s, compare %d\n", ss, slist.size(),
3975 TimeToString(slist[ss-1]->fTimeFrom).c_str(),
3976 TimeToString(slist[ss]->fTimeFrom).c_str(),
3977 TimeToString(slist[ss]->fTimeTo).c_str(),
3978 slist[ss-1]->fTimeFrom >= slist[ss]->fTimeFrom);
3979 }
3980 if (slist[ss-1]->fTimeFrom >= slist[ss]->fTimeFrom) {
3981 // good
3982 } else {
3983 cm_msg(MERROR, "SchemaHistoryBase::hs_read_buffer", "History internal error, selected schema is not ordered by time. Please report this error to the midas forum.");
3984 return HS_FILE_ERROR;
3985 }
3986 }
3987
3988 std::vector<time_t> last_time;
3989
3990 for (int i=0; i<num_var; i++) {
3991 last_time.push_back(start_time);
3992 }
3993
3994 if (slist.size() > 0) {
3995 for (size_t i=slist.size()-1; ; i--) {
3996 HsSchema* s = slist[i];
3997
3998 int status = s->read_data(start_time, end_time, num_var, smap[i], var_index, fDebug, last_time, buffer);
3999
4000 if (status == HS_SUCCESS) {
4001 for (int j=0; j<num_var; j++) {
4002 if (smap[i][j] >= 0)
4003 hs_status[j] = HS_SUCCESS;
4004 }
4005 }
4006
4007 if (i==0)
4008 break;
4009 }
4010 }
4011
4012 return HS_SUCCESS;
4013}
4014
4015int SchemaHistoryBase::hs_read(time_t start_time, time_t end_time, time_t interval,
4016 int num_var,
4017 const char* const event_name[], const char* const var_name[], const int var_index[],
4018 int num_entries[],
4019 time_t* time_buffer[], double* data_buffer[],
4020 int st[])
4021{
4022 int status;
4023
4024 ReadBuffer** buffer = new ReadBuffer*[num_var];
4026
4027 for (int i=0; i<num_var; i++) {
4028 buffer[i] = new ReadBuffer(start_time, end_time, interval);
4029 bi[i] = buffer[i];
4030
4031 // make sure outputs are initialized to something sane
4032 if (num_entries)
4033 num_entries[i] = 0;
4034 if (time_buffer)
4035 time_buffer[i] = NULL;
4036 if (data_buffer)
4037 data_buffer[i] = NULL;
4038 if (st)
4039 st[i] = 0;
4040
4041 if (num_entries)
4042 buffer[i]->fNumEntries = &num_entries[i];
4043 if (time_buffer)
4044 buffer[i]->fTimeBuffer = &time_buffer[i];
4045 if (data_buffer)
4046 buffer[i]->fDataBuffer = &data_buffer[i];
4047 }
4048
4049 status = hs_read_buffer(start_time, end_time,
4050 num_var, event_name, var_name, var_index,
4051 bi, st);
4052
4053 for (int i=0; i<num_var; i++) {
4054 buffer[i]->Finish();
4055 delete buffer[i];
4056 }
4057
4058 delete[] buffer;
4059 delete[] bi;
4060
4061 return status;
4062}
4063
4064int SchemaHistoryBase::hs_read_binned(time_t start_time, time_t end_time, int num_bins,
4065 int num_var, const char* const event_name[], const char* const var_name[], const int var_index[],
4066 int num_entries[],
4067 int* count_bins[], double* mean_bins[], double* rms_bins[], double* min_bins[], double* max_bins[],
4068 time_t* bins_first_time[], double* bins_first_value[],
4069 time_t* bins_last_time[], double* bins_last_value[],
4070 time_t last_time[], double last_value[],
4071 int st[])
4072{
4073 int status;
4074
4075 MidasHistoryBinnedBuffer** buffer = new MidasHistoryBinnedBuffer*[num_var];
4077
4078 for (int i=0; i<num_var; i++) {
4079 buffer[i] = new MidasHistoryBinnedBuffer(start_time, end_time, num_bins);
4080 xbuffer[i] = buffer[i];
4081
4082 if (count_bins)
4083 buffer[i]->fCount = count_bins[i];
4084 if (mean_bins)
4085 buffer[i]->fMean = mean_bins[i];
4086 if (rms_bins)
4087 buffer[i]->fRms = rms_bins[i];
4088 if (min_bins)
4089 buffer[i]->fMin = min_bins[i];
4090 if (max_bins)
4091 buffer[i]->fMax = max_bins[i];
4092 if (bins_first_time)
4093 buffer[i]->fBinsFirstTime = bins_first_time[i];
4094 if (bins_first_value)
4095 buffer[i]->fBinsFirstValue = bins_first_value[i];
4096 if (bins_last_time)
4097 buffer[i]->fBinsLastTime = bins_last_time[i];
4098 if (bins_last_value)
4099 buffer[i]->fBinsLastValue = bins_last_value[i];
4100 if (last_time)
4101 buffer[i]->fLastTimePtr = &last_time[i];
4102 if (last_value)
4103 buffer[i]->fLastValuePtr = &last_value[i];
4104
4105 buffer[i]->Start();
4106 }
4107
4108 status = hs_read_buffer(start_time, end_time,
4109 num_var, event_name, var_name, var_index,
4110 xbuffer,
4111 st);
4112
4113 for (int i=0; i<num_var; i++) {
4114 buffer[i]->Finish();
4115 if (num_entries)
4116 num_entries[i] = buffer[i]->fNumEntries;
4117 if (0) {
4118 for (int j=0; j<num_bins; j++) {
4119 printf("var %d bin %d count %d, first %s last %s value first %f last %f\n", i, j, count_bins[i][j], TimeToString(bins_first_time[i][j]).c_str(), TimeToString(bins_last_time[i][j]).c_str(), bins_first_value[i][j], bins_last_value[i][j]);
4120 }
4121 }
4122 delete buffer[i];
4123 }
4124
4125 delete[] buffer;
4126 delete[] xbuffer;
4127
4128 return status;
4129}
4130
4132// SQL schema //
4134
4136{
4137 if (!fSql->IsConnected()) {
4138 return HS_SUCCESS;
4139 }
4140
4141 int status = HS_SUCCESS;
4142 if (get_transaction_count() > 0) {
4145 }
4146 return status;
4147}
4148
4149int HsSchema::match_event_var(const char* event_name, const char* var_name, const int var_index)
4150{
4151 if (!MatchEventName(this->fEventName.c_str(), event_name))
4152 return -1;
4153
4154 for (size_t j=0; j<this->fVariables.size(); j++) {
4155 if (MatchTagName(this->fVariables[j].name.c_str(), this->fVariables[j].n_data, var_name, var_index)) {
4156 // Second clause in if() is case where MatchTagName used the "alternate tag name".
4157 // E.g. our variable name is "IM05[3]" (n_data=1), but we're looking for var_name="IM05" and var_index=3.
4158 if (var_index < this->fVariables[j].n_data || (this->fVariables[j].n_data == 1 && this->fVariables[j].name.find("[") != std::string::npos)) {
4159 return j;
4160 }
4161 }
4162 }
4163
4164 return -1;
4165}
4166
4167int HsSqlSchema::match_event_var(const char* event_name, const char* var_name, const int var_index)
4168{
4169 if (event_name_cmp(this->fTableName, event_name)==0) {
4170 for (size_t j=0; j<this->fVariables.size(); j++) {
4171 if (var_name_cmp(this->fColumnNames[j], var_name)==0)
4172 return j;
4173 }
4174 }
4175
4176 return HsSchema::match_event_var(event_name, var_name, var_index);
4177}
4178
4179static HsSqlSchema* NewSqlSchema(HsSchemaVector* sv, const char* table_name, time_t t)
4180{
4181 time_t tt = 0;
4182 int j=-1;
4183 int jjx=-1; // remember oldest schema
4184 time_t ttx = 0;
4185 for (size_t i=0; i<sv->size(); i++) {
4186 HsSqlSchema* s = (HsSqlSchema*)(*sv)[i];
4187 if (s->fTableName != table_name)
4188 continue;
4189
4190 if (s->fTimeFrom == t) {
4191 return s;
4192 }
4193
4194 // remember the last schema before time t
4195 if (s->fTimeFrom < t) {
4196 if (s->fTimeFrom > tt) {
4197 tt = s->fTimeFrom;
4198 j = i;
4199 }
4200 }
4201
4202 if (jjx < 0) {
4203 jjx = i;
4204 ttx = s->fTimeFrom;
4205 }
4206
4207 if (s->fTimeFrom < ttx) {
4208 jjx = i;
4209 ttx = s->fTimeFrom;
4210 }
4211
4212 //printf("table_name [%s], t=%s, i=%d, j=%d %s, tt=%s, dt is %d\n", table_name, TimeToString(t).c_str(), i, j, TimeToString(s->fTimeFrom).c_str(), TimeToString(tt).c_str(), (int)(s->fTimeFrom-t));
4213 }
4214
4215 //printf("NewSqlSchema: will copy schema j=%d, tt=%d at time %d\n", j, tt, t);
4216
4217 //printf("cloned schema at time %s: ", TimeToString(t).c_str());
4218 //(*sv)[j]->print(false);
4219
4220 //printf("schema before:\n");
4221 //sv->print(false);
4222
4223 if (j >= 0) {
4224 HsSqlSchema* s = new HsSqlSchema;
4225 *s = *(HsSqlSchema*)(*sv)[j]; // make a copy
4226 s->fTimeFrom = t;
4227 sv->add(s);
4228
4229 //printf("schema after:\n");
4230 //sv->print(false);
4231
4232 return s;
4233 }
4234
4235 if (jjx >= 0) {
4236 cm_msg(MERROR, "NewSqlSchema", "Error: Unexpected ordering of schema for table \'%s\', good luck!", table_name);
4237
4238 HsSqlSchema* s = new HsSqlSchema;
4239 *s = *(HsSqlSchema*)(*sv)[jjx]; // make a copy
4240 s->fTimeFrom = t;
4241 s->fTimeTo = ttx;
4242 sv->add(s);
4243
4244 //printf("schema after:\n");
4245 //sv->print(false);
4246
4247 return s;
4248 }
4249
4250 cm_msg(MERROR, "NewSqlSchema", "Error: Cannot clone schema for table \'%s\', good luck!", table_name);
4251 return NULL;
4252}
4253
4255{
4256 assert(fVariables.size() == fColumnInactive.size());
4257 assert(fVariables.size() == fColumnNames.size());
4258 assert(fVariables.size() == fColumnTypes.size());
4259 assert(fVariables.size() == fOffsets.size());
4260
4261 size_t count_active = 0;
4262 size_t count_inactive = 0;
4263
4264 for (size_t i=0; i<fColumnInactive.size(); i++) {
4265 if (fColumnInactive[i])
4266 count_inactive += 1;
4267 else
4268 count_active += 1;
4269 }
4270
4271 //printf("remove_inactive_columns: enter! count_active: %zu, count_inactive: %zu\n", count_active, count_inactive);
4272 //print();
4273
4274 if (count_inactive > 0) {
4275 size_t j=0;
4276
4277 for (size_t i=0; i<fColumnInactive.size(); i++) {
4278 if (fColumnInactive[i]) {
4279 // skip this entry
4280 } else {
4281 if (j != i) {
4286 fOffsets[j] = fOffsets[i];
4287 }
4288 j++;
4289 }
4290 }
4291
4292 //print();
4293 //printf("%zu %zu\n", j, count_active);
4294
4295 assert(j == count_active);
4296
4297 //print();
4298
4299 fVariables.resize(count_active);
4300 fColumnInactive.resize(count_active);
4301 fColumnNames.resize(count_active);
4302 fColumnTypes.resize(count_active);
4303 fOffsets.resize(count_active);
4304
4305 assert(fVariables.size() == fColumnInactive.size());
4306 assert(fVariables.size() == fColumnNames.size());
4307 assert(fVariables.size() == fColumnTypes.size());
4308 assert(fVariables.size() == fOffsets.size());
4309
4310 //printf("remove_inactice_columns: exit!\n");
4311 //print();
4312 }
4313}
4314
4315int HsSqlSchema::write_event(const time_t t, const char* data, const size_t data_size)
4316{
4317 HsSqlSchema* s = this;
4318
4319 assert(s->fVariables.size() == s->fColumnInactive.size());
4320 assert(s->fVariables.size() == s->fColumnNames.size());
4321 assert(s->fVariables.size() == s->fColumnTypes.size());
4322 assert(s->fVariables.size() == s->fOffsets.size());
4323
4324 std::string tags;
4325 std::string values;
4326
4327 for (size_t i=0; i<s->fVariables.size(); i++) {
4328 // NB: inactive columns should have been removed from the schema. K.O.
4329
4330 if (s->fColumnInactive[i]) {
4331 cm_msg(MERROR, "HsSqlSchema::write_event", "Internal error, unexpected inactive column %zu", i);
4333 return HS_FILE_ERROR;
4334 }
4335
4336 int type = s->fVariables[i].type;
4337 int n_data = s->fVariables[i].n_data;
4338 int offset = s->fOffsets[i];
4339 const char* column_name = s->fColumnNames[i].c_str();
4340
4341 if (offset < 0) {
4342 cm_msg(MERROR, "HsSqlSchema::write_event", "Internal error, unexpected negative offset %d for column %zu", offset, i);
4344 return HS_FILE_ERROR;
4345 }
4346
4347 assert(n_data == 1);
4348 assert(strlen(column_name) > 0);
4349 assert(offset >= 0);
4350 assert((size_t)offset < data_size);
4351
4352 void* ptr = (void*)(data+offset);
4353
4354 tags += ", ";
4355 tags += fSql->QuoteId(column_name);
4356
4357 values += ", ";
4358
4359 char buf[1024];
4360 int j=0;
4361
4362 switch (type) {
4363 default:
4364 sprintf(buf, "unknownType%d", type);
4365 break;
4366 case TID_BYTE:
4367 sprintf(buf, "%u",((unsigned char *)ptr)[j]);
4368 break;
4369 case TID_SBYTE:
4370 sprintf(buf, "%d",((signed char*)ptr)[j]);
4371 break;
4372 case TID_CHAR:
4373 // FIXME: quotes
4374 sprintf(buf, "\'%c\'",((char*)ptr)[j]);
4375 break;
4376 case TID_WORD:
4377 sprintf(buf, "%u",((unsigned short *)ptr)[j]);
4378 break;
4379 case TID_SHORT:
4380 sprintf(buf, "%d",((short *)ptr)[j]);
4381 break;
4382 case TID_DWORD:
4383 sprintf(buf, "%u",((unsigned int *)ptr)[j]);
4384 break;
4385 case TID_INT:
4386 sprintf(buf, "%d",((int *)ptr)[j]);
4387 break;
4388 case TID_BOOL:
4389 sprintf(buf, "%u",((unsigned int *)ptr)[j]);
4390 break;
4391 case TID_FLOAT:
4392 // FIXME: quotes
4393 sprintf(buf, "\'%.8g\'",((float*)ptr)[j]);
4394 break;
4395 case TID_DOUBLE:
4396 // FIXME: quotes
4397 sprintf(buf, "\'%.16g\'",((double*)ptr)[j]);
4398 break;
4399 }
4400
4401 values += buf;
4402 }
4403
4404 // 2001-02-16 20:38:40.1
4405 struct tm tms;
4406 localtime_r(&t, &tms); // somebody must call tzset() before this.
4407 char buf[1024];
4408 strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S.0", &tms);
4409
4410 std::string cmd;
4411 cmd = "INSERT INTO ";
4412 cmd += fSql->QuoteId(s->fTableName.c_str());
4413 cmd += " (_t_time, _i_time";
4414 cmd += tags;
4415 cmd += ") VALUES (";
4416 cmd += fSql->QuoteString(buf);
4417 cmd += ", ";
4418 cmd += fSql->QuoteString(TimeToString(t).c_str());
4419 cmd += "";
4420 cmd += values;
4421 cmd += ");";
4422
4423 if (fSql->IsConnected()) {
4424 if (s->get_transaction_count() == 0)
4425 fSql->OpenTransaction(s->fTableName.c_str());
4426
4428
4429 int status = fSql->Exec(s->fTableName.c_str(), cmd.c_str());
4430
4431 // mh2sql who does not call hs_flush_buffers()
4432 // so we should flush the transaction by hand
4433 // some SQL engines have limited transaction buffers... K.O.
4434 if (s->get_transaction_count() > 100000) {
4435 //printf("flush table %s\n", table_name);
4436 fSql->CommitTransaction(s->fTableName.c_str());
4438 }
4439
4440 if (status != DB_SUCCESS) {
4441 return status;
4442 }
4443 } else {
4444 int status = fSql->ExecDisconnected(s->fTableName.c_str(), cmd.c_str());
4445 if (status != DB_SUCCESS) {
4446 return status;
4447 }
4448 }
4449
4450 return HS_SUCCESS;
4451}
4452
4453int HsSqlSchema::read_last_written(const time_t timestamp,
4454 const int debug,
4455 time_t* last_written)
4456{
4457 if (debug)
4458 printf("SqlHistory::read_last_written: table [%s], timestamp %s\n", fTableName.c_str(), TimeToString(timestamp).c_str());
4459
4460 std::string cmd;
4461 cmd += "SELECT _i_time FROM ";
4462 cmd += fSql->QuoteId(fTableName.c_str());
4463 cmd += " WHERE _i_time < ";
4464 cmd += TimeToString(timestamp);
4465 cmd += " ORDER BY _i_time DESC LIMIT 2;";
4466
4467 int status = fSql->Prepare(fTableName.c_str(), cmd.c_str());
4468
4469 if (status != DB_SUCCESS)
4470 return status;
4471
4472 time_t lw = 0;
4473
4474 /* Loop through the rows in the result-set */
4475
4476 while (1) {
4477 status = fSql->Step();
4478 if (status != DB_SUCCESS)
4479 break;
4480
4481 time_t t = fSql->GetTime(0);
4482
4483 if (t >= timestamp)
4484 continue;
4485
4486 if (t > lw)
4487 lw = t;
4488 }
4489
4490 fSql->Finalize();
4491
4492 *last_written = lw;
4493
4494 if (debug)
4495 printf("SqlHistory::read_last_written: table [%s], timestamp %s, last_written %s\n", fTableName.c_str(), TimeToString(timestamp).c_str(), TimeToString(lw).c_str());
4496
4497 return HS_SUCCESS;
4498}
4499
4500int HsSqlSchema::read_data(const time_t start_time,
4501 const time_t end_time,
4502 const int num_var, const std::vector<int>& var_schema_index, const int var_index[],
4503 const int debug,
4504 std::vector<time_t>& last_time,
4506{
4507 bool bad_last_time = false;
4508
4509 if (debug)
4510 printf("SqlHistory::read_data: table [%s], start %s, end %s\n", fTableName.c_str(), TimeToString(start_time).c_str(), TimeToString(end_time).c_str());
4511
4512 std::string collist;
4513
4514 for (int i=0; i<num_var; i++) {
4515 int j = var_schema_index[i];
4516 if (j < 0)
4517 continue;
4518 if (collist.length() > 0)
4519 collist += ", ";
4520 collist += fSql->QuoteId(fColumnNames[j].c_str());
4521 }
4522
4523 std::string cmd;
4524 cmd += "SELECT _i_time, ";
4525 cmd += collist;
4526 cmd += " FROM ";
4527 cmd += fSql->QuoteId(fTableName.c_str());
4528 cmd += " WHERE _i_time>=";
4529 cmd += TimeToString(start_time);
4530 cmd += " and _i_time<=";
4531 cmd += TimeToString(end_time);
4532 cmd += " ORDER BY _i_time;";
4533
4534 int status = fSql->Prepare(fTableName.c_str(), cmd.c_str());
4535
4536 if (status != DB_SUCCESS)
4537 return HS_FILE_ERROR;
4538
4539 /* Loop through the rows in the result-set */
4540
4541 int count = 0;
4542
4543 while (1) {
4544 status = fSql->Step();
4545 if (status != DB_SUCCESS)
4546 break;
4547
4548 count++;
4549
4550 time_t t = fSql->GetTime(0);
4551
4552 if (t < start_time || t > end_time)
4553 continue;
4554
4555 int k = 0;
4556
4557 for (int i=0; i<num_var; i++) {
4558 int j = var_schema_index[i];
4559 if (j < 0)
4560 continue;
4561
4562 if (t < last_time[i]) { // protect against duplicate and non-monotonous data
4563 bad_last_time = true;
4564 } else {
4565 double v = fSql->GetDouble(1+k);
4566
4567 //printf("Column %d, index %d, Row %d, time %d, value %f\n", k, colindex[k], count, t, v);
4568
4569 buffer[i]->Add(t, v);
4570 last_time[i] = t;
4571 }
4572
4573 k++;
4574 }
4575 }
4576
4577 fSql->Finalize();
4578
4579 if (bad_last_time) {
4580 cm_msg(MERROR, "SqlHistory::read_data", "Detected duplicate or non-monotonous data in table \"%s\" for start time %s and end time %s", fTableName.c_str(), TimeToString(start_time).c_str(), TimeToString(end_time).c_str());
4581 }
4582
4583 if (debug)
4584 printf("SqlHistory::read_data: table [%s], start %s, end %s, read %d rows\n", fTableName.c_str(), TimeToString(start_time).c_str(), TimeToString(end_time).c_str(), count);
4585
4586 return HS_SUCCESS;
4587}
4588
4590 if (!fSql || fSql->fTransactionPerTable) {
4592 } else {
4593 return gfTransactionCount[fSql];
4594 }
4595}
4596
4598 if (!fSql || fSql->fTransactionPerTable) {
4600 } else {
4602 }
4603}
4604
4612
4614// SQL history functions //
4616
4617static int StartSqlTransaction(SqlBase* sql, const char* table_name, bool* have_transaction)
4618{
4619 if (*have_transaction)
4620 return HS_SUCCESS;
4621
4622 int status = sql->OpenTransaction(table_name);
4623 if (status != DB_SUCCESS)
4624 return HS_FILE_ERROR;
4625
4626 *have_transaction = true;
4627 return HS_SUCCESS;
4628}
4629
4630static int CreateSqlTable(SqlBase* sql, const char* table_name, bool* have_transaction, bool set_default_timestamp = false)
4631{
4632 int status;
4633
4634 status = StartSqlTransaction(sql, table_name, have_transaction);
4635 if (status != DB_SUCCESS)
4636 return HS_FILE_ERROR;
4637
4638 std::string cmd;
4639
4640 cmd = "CREATE TABLE ";
4641 cmd += sql->QuoteId(table_name);
4642 if (set_default_timestamp) {
4643 cmd += " (_t_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, _i_time INTEGER NOT NULL DEFAULT 0);";
4644 } else {
4645 cmd += " (_t_time TIMESTAMP NOT NULL, _i_time INTEGER NOT NULL);";
4646 }
4647
4648 status = sql->Exec(table_name, cmd.c_str());
4649
4650
4651 if (status == DB_KEY_EXIST) {
4652 cm_msg(MINFO, "CreateSqlTable", "Adding SQL table \"%s\", but it already exists", table_name);
4654 return status;
4655 }
4656
4657 if (status != DB_SUCCESS) {
4658 cm_msg(MINFO, "CreateSqlTable", "Adding SQL table \"%s\", error status %d", table_name, status);
4660 return HS_FILE_ERROR;
4661 }
4662
4663 cm_msg(MINFO, "CreateSqlTable", "Adding SQL table \"%s\"", table_name);
4665
4666 std::string i_index_name;
4667 i_index_name = table_name;
4668 i_index_name += "_i_time_index";
4669
4670 std::string t_index_name;
4671 t_index_name = table_name;
4672 t_index_name += "_t_time_index";
4673
4674 cmd = "CREATE INDEX ";
4675 cmd += sql->QuoteId(i_index_name.c_str());
4676 cmd += " ON ";
4677 cmd += sql->QuoteId(table_name);
4678 cmd += " (_i_time ASC);";
4679
4680 status = sql->Exec(table_name, cmd.c_str());
4681 if (status != DB_SUCCESS)
4682 return HS_FILE_ERROR;
4683
4684 cmd = "CREATE INDEX ";
4685 cmd += sql->QuoteId(t_index_name.c_str());
4686 cmd += " ON ";
4687 cmd += sql->QuoteId(table_name);
4688 cmd += " (_t_time);";
4689
4690 status = sql->Exec(table_name, cmd.c_str());
4691 if (status != DB_SUCCESS)
4692 return HS_FILE_ERROR;
4693
4694 return status;
4695}
4696
4697static int CreateSqlHyperTable(SqlBase* sql, const char* table_name, bool* have_transaction) {
4698 int status;
4699
4700 status = StartSqlTransaction(sql, table_name, have_transaction);
4701 if (status != DB_SUCCESS)
4702 return HS_FILE_ERROR;
4703
4704 std::string cmd;
4705
4706 cmd = "CREATE TABLE ";
4707 cmd += sql->QuoteId(table_name);
4708 cmd += " (_t_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, _i_time INTEGER NOT NULL DEFAULT 0);";
4709
4710 status = sql->Exec(table_name, cmd.c_str());
4711
4712 if (status == DB_KEY_EXIST) {
4713 cm_msg(MINFO, "CreateSqlHyperTable", "Adding SQL table \"%s\", but it already exists", table_name);
4715 return status;
4716 }
4717
4718 if (status != DB_SUCCESS) {
4719 cm_msg(MINFO, "CreateSqlHyperTable", "Adding SQL table \"%s\", error status %d", table_name, status);
4721 return HS_FILE_ERROR;
4722 }
4723
4724 cm_msg(MINFO, "CreateSqlHyperTable", "Adding SQL table \"%s\"", table_name);
4726
4727 cmd = "SELECT create_hypertable(";
4728 cmd += sql->QuoteString(table_name);
4729 cmd += ", '_t_time');";
4730
4731 // convert regular table to hypertable
4732 status = sql->Exec(table_name, cmd.c_str());
4733
4734 if (status != DB_SUCCESS) {
4735 cm_msg(MINFO, "CreateSqlHyperTable", "Converting SQL table to hypertable \"%s\", error status %d", table_name, status);
4737 return HS_FILE_ERROR;
4738 }
4739
4740 std::string i_index_name;
4741 i_index_name = table_name;
4742 i_index_name += "_i_time_index";
4743
4744 std::string t_index_name;
4745 t_index_name = table_name;
4746 t_index_name += "_t_time_index";
4747
4748 cmd = "CREATE INDEX ";
4749 cmd += sql->QuoteId(i_index_name.c_str());
4750 cmd += " ON ";
4751 cmd += sql->QuoteId(table_name);
4752 cmd += " (_i_time ASC);";
4753
4754 status = sql->Exec(table_name, cmd.c_str());
4755 if (status != DB_SUCCESS)
4756 return HS_FILE_ERROR;
4757
4758 cmd = "CREATE INDEX ";
4759 cmd += sql->QuoteId(t_index_name.c_str());
4760 cmd += " ON ";
4761 cmd += sql->QuoteId(table_name);
4762 cmd += " (_t_time);";
4763
4764 status = sql->Exec(table_name, cmd.c_str());
4765 if (status != DB_SUCCESS)
4766 return HS_FILE_ERROR;
4767
4768 return status;
4769}
4770
4771static int CreateSqlColumn(SqlBase* sql, const char* table_name, const char* column_name, const char* column_type, bool* have_transaction, int debug)
4772{
4773 if (debug)
4774 printf("CreateSqlColumn: table [%s], column [%s], type [%s]\n", table_name, column_name, column_type);
4775
4776 int status = StartSqlTransaction(sql, table_name, have_transaction);
4777 if (status != HS_SUCCESS)
4778 return status;
4779
4780 std::string cmd;
4781 cmd = "ALTER TABLE ";
4782 cmd += sql->QuoteId(table_name);
4783 cmd += " ADD COLUMN ";
4784 cmd += sql->QuoteId(column_name);
4785 cmd += " ";
4786 cmd += column_type;
4787 cmd += ";";
4788
4789 status = sql->Exec(table_name, cmd.c_str());
4790
4791 cm_msg(MINFO, "CreateSqlColumn", "Adding column \"%s\" to SQL table \"%s\", status %d", column_name, table_name, status);
4793
4794 return status;
4795}
4796
4798// SQL history base classes //
4800
4802{
4803public:
4805
4807 {
4808 fSql = NULL;
4810 }
4811
4812 virtual ~SqlHistoryBase() // dtor
4813 {
4814 hs_disconnect();
4815 if (fSql)
4816 delete fSql;
4817 fSql = NULL;
4818 }
4819
4821 {
4822 if (fSql)
4823 fSql->fDebug = debug;
4825 }
4826
4827 int hs_connect(const char* connect_string);
4828 int hs_disconnect();
4829 HsSchema* new_event(const char* event_name, time_t timestamp, int ntags, const TAG tags[]);
4830 int read_schema(HsSchemaVector* sv, const char* event_name, const time_t timestamp);
4831 HsSchema* maybe_reopen(const char* event_name, time_t timestamp, HsSchema* s) { return s; };
4832
4833
4834protected:
4836 virtual int read_column_names(HsSchemaVector *sv, const char* table_name, const char* event_name) = 0;
4837 virtual int create_table(HsSchemaVector* sv, const char* event_name, time_t timestamp) = 0;
4838 virtual int update_column(const char* event_name, const char* table_name, const char* column_name, const char* column_type, const char* tag_name, const char* tag_type, const time_t timestamp, bool active, bool* have_transaction) = 0;
4839
4840 int update_schema(HsSqlSchema* s, const time_t timestamp, const int ntags, const TAG tags[], bool write_enable);
4841 int update_schema1(HsSqlSchema* s, const time_t timestamp, const int ntags, const TAG tags[], bool write_enable, bool* have_transaction);
4842};
4843
4844int SqlHistoryBase::hs_connect(const char* connect_string)
4845{
4846 if (fDebug)
4847 printf("hs_connect [%s]!\n", connect_string);
4848
4849 assert(fSql);
4850
4851 if (fSql->IsConnected())
4852 if (strcmp(fConnectString.c_str(), connect_string) == 0)
4853 return HS_SUCCESS;
4854
4855 hs_disconnect();
4856
4857 if (!connect_string || strlen(connect_string) < 1) {
4858 // FIXME: should use "logger dir" or some such default, that code should be in hs_get_history(), not here
4859 connect_string = ".";
4860 }
4861
4862 fConnectString = connect_string;
4863
4864 if (fDebug)
4865 printf("hs_connect: connecting to SQL database \'%s\'\n", fConnectString.c_str());
4866
4867 int status = fSql->Connect(fConnectString.c_str());
4868 if (status != DB_SUCCESS)
4869 return status;
4870
4871 return HS_SUCCESS;
4872}
4873
4875{
4876 if (fDebug)
4877 printf("hs_disconnect!\n");
4878
4880
4881 fSql->Disconnect();
4882
4884
4885 return HS_SUCCESS;
4886}
4887
4888HsSchema* SqlHistoryBase::new_event(const char* event_name, time_t timestamp, int ntags, const TAG tags[])
4889{
4890 if (fDebug)
4891 printf("SqlHistory::new_event: event [%s], timestamp %s, ntags %d\n", event_name, TimeToString(timestamp).c_str(), ntags);
4892
4893 int status;
4894
4895 if (fWriterSchema.size() == 0) {
4897 if (status != HS_SUCCESS)
4898 return NULL;
4899 }
4900
4901 HsSqlSchema* s = (HsSqlSchema*)fWriterSchema.find_event(event_name, timestamp);
4902
4903 // schema does not exist, the SQL tables probably do not exist yet
4904
4905 if (!s) {
4906 status = create_table(&fWriterSchema, event_name, timestamp);
4907 if (status != HS_SUCCESS)
4908 return NULL;
4909
4910 s = (HsSqlSchema*)fWriterSchema.find_event(event_name, timestamp);
4911
4912 if (!s) {
4913 cm_msg(MERROR, "SqlHistory::new_event", "Error: Cannot create schema for event \'%s\', see previous messages", event_name);
4914 fWriterSchema.find_event(event_name, timestamp, 1);
4915 return NULL;
4916 }
4917 }
4918
4919 assert(s != NULL);
4920
4921 status = read_column_names(&fWriterSchema, s->fTableName.c_str(), s->fEventName.c_str());
4922 if (status != HS_SUCCESS)
4923 return NULL;
4924
4925 s = (HsSqlSchema*)fWriterSchema.find_event(event_name, timestamp);
4926
4927 if (!s) {
4928 cm_msg(MERROR, "SqlHistory::new_event", "Error: Cannot update schema database for event \'%s\', see previous messages", event_name);
4929 return NULL;
4930 }
4931
4932 if (0||fDebug) {
4933 printf("SqlHistory::new_event: schema for [%s] is %p\n", event_name, s);
4934 if (s)
4935 s->print();
4936 }
4937
4938 status = update_schema(s, timestamp, ntags, tags, true);
4939 if (status != HS_SUCCESS) {
4940 cm_msg(MERROR, "SqlHistory::new_event", "Error: Cannot create schema for event \'%s\', see previous messages", event_name);
4941 return NULL;
4942 }
4943
4944 status = read_column_names(&fWriterSchema, s->fTableName.c_str(), s->fEventName.c_str());
4945 if (status != HS_SUCCESS)
4946 return NULL;
4947
4948 s = (HsSqlSchema*)fWriterSchema.find_event(event_name, timestamp);
4949
4950 if (!s) {
4951 cm_msg(MERROR, "SqlHistory::new_event", "Error: Cannot update schema database for event \'%s\', see previous messages", event_name);
4952 return NULL;
4953 }
4954
4955 if (0||fDebug) {
4956 printf("SqlHistory::new_event: schema for [%s] is %p\n", event_name, s);
4957 if (s)
4958 s->print();
4959 }
4960
4961 // last call to UpdateMysqlSchema with "false" will check that new schema matches the new tags
4962
4963 status = update_schema(s, timestamp, ntags, tags, false);
4964 if (status != HS_SUCCESS) {
4965 cm_msg(MERROR, "SqlHistory::new_event", "Error: Cannot create schema for event \'%s\', see previous messages", event_name);
4966 //fDebug = 1;
4967 //update_schema(s, timestamp, ntags, tags, false);
4968 //abort();
4969 return NULL;
4970 }
4971
4972 HsSqlSchema* e = new HsSqlSchema();
4973
4974 *e = *s; // make a copy of the schema
4975
4976 return e;
4977}
4978
4979int SqlHistoryBase::read_schema(HsSchemaVector* sv, const char* event_name, const time_t timestamp)
4980{
4981 if (fDebug)
4982 printf("SqlHistory::read_schema: loading schema for event [%s] at time %s\n", event_name, TimeToString(timestamp).c_str());
4983
4984 int status;
4985
4986 if (sv->size() == 0) {
4988 if (status != HS_SUCCESS)
4989 return status;
4990 }
4991
4992 //sv->print(false);
4993
4994 if (event_name == NULL)
4995 return HS_SUCCESS;
4996
4997 for (size_t i=0; i<sv->size(); i++) {
4998 HsSqlSchema* h = (HsSqlSchema*)(*sv)[i];
4999 // skip schema with already read column names
5000 if (h->fVariables.size() > 0)
5001 continue;
5002 // skip schema with different name
5003 if (!MatchEventName(h->fEventName.c_str(), event_name))
5004 continue;
5005
5006 size_t nn = sv->size();
5007
5008 status = read_column_names(sv, h->fTableName.c_str(), h->fEventName.c_str());
5009
5010 // if new schema was added, loop all over again
5011 if (sv->size() != nn)
5012 i=0;
5013 }
5014
5015 //sv->print(false);
5016
5017 return HS_SUCCESS;
5018}
5019
5020int SqlHistoryBase::update_schema(HsSqlSchema* s, const time_t timestamp, const int ntags, const TAG tags[], bool write_enable)
5021{
5022 int status;
5023 bool have_transaction = false;
5024
5025 status = update_schema1(s, timestamp, ntags, tags, write_enable, &have_transaction);
5026
5027 if (have_transaction) {
5028 int xstatus;
5029
5030 if (status == HS_SUCCESS)
5031 xstatus = fSql->CommitTransaction(s->fTableName.c_str());
5032 else
5033 xstatus = fSql->RollbackTransaction(s->fTableName.c_str());
5034
5035 if (xstatus != DB_SUCCESS) {
5036 return HS_FILE_ERROR;
5037 }
5038 have_transaction = false;
5039 }
5040
5041 return status;
5042}
5043
5044int SqlHistoryBase::update_schema1(HsSqlSchema* s, const time_t timestamp, const int ntags, const TAG tags[], bool write_enable, bool* have_transaction)
5045{
5046 int status;
5047
5048 if (fDebug)
5049 printf("update_schema1\n");
5050
5051 // check that compare schema with tags[]
5052
5053 bool schema_ok = true;
5054
5055 int offset = 0;
5056 for (int i=0; i<ntags; i++) {
5057 for (unsigned int j=0; j<tags[i].n_data; j++) {
5058 int tagtype = tags[i].type;
5059 std::string tagname = tags[i].name;
5060 std::string maybe_colname = MidasNameToSqlName(tags[i].name);
5061
5062 if (tags[i].n_data > 1) {
5063 char s[256];
5064 sprintf(s, "[%d]", j);
5065 tagname += s;
5066
5067 sprintf(s, "_%d", j);
5068 maybe_colname += s;
5069 }
5070
5071 int count = 0;
5072
5073 for (size_t j=0; j<s->fVariables.size(); j++) {
5074 // NB: inactive columns will be reactivated or recreated by the if(count==0) branch. K.O.
5075 if (s->fColumnInactive[j])
5076 continue;
5077 if (tagname == s->fVariables[j].name) {
5078 if (s->fSql->TypesCompatible(tagtype, s->fColumnTypes[j].c_str())) {
5079 if (count == 0) {
5080 s->fOffsets[j] = offset;
5081 offset += rpc_tid_size(tagtype);
5082 }
5083 count++;
5084 if (count > 1) {
5085 cm_msg(MERROR, "SqlHistory::update_schema", "Duplicate SQL column \'%s\' type \'%s\' in table \"%s\" with MIDAS type \'%s\' history event \"%s\" tag \"%s\"", s->fColumnNames[j].c_str(), s->fColumnTypes[j].c_str(), s->fTableName.c_str(), rpc_tid_name(tagtype), s->fEventName.c_str(), tagname.c_str());
5087 }
5088 } else {
5089 // column with incompatible type, mark it as unused
5090 schema_ok = false;
5091 if (fDebug)
5092 printf("Incompatible column!\n");
5093 if (write_enable) {
5094 cm_msg(MINFO, "SqlHistory::update_schema", "Deactivating SQL column \'%s\' type \'%s\' in table \"%s\" as incompatible with MIDAS type \'%s\' history event \"%s\" tag \"%s\"", s->fColumnNames[j].c_str(), s->fColumnTypes[j].c_str(), s->fTableName.c_str(), rpc_tid_name(tagtype), s->fEventName.c_str(), tagname.c_str());
5096
5097 status = update_column(s->fEventName.c_str(), s->fTableName.c_str(), s->fColumnNames[j].c_str(), s->fColumnTypes[j].c_str(), s->fVariables[j].tag_name.c_str(), s->fVariables[i].tag_type.c_str(), timestamp, false, have_transaction);
5098 if (status != HS_SUCCESS)
5099 return status;
5100 }
5101 }
5102 }
5103 }
5104
5105 if (count == 0) {
5106 // tag does not have a corresponding column
5107 schema_ok = false;
5108 if (fDebug)
5109 printf("No column for tag %s!\n", tagname.c_str());
5110
5111 bool found_column = false;
5112
5113 if (write_enable) {
5114 for (size_t j=0; j<s->fVariables.size(); j++) {
5115 if (tagname == s->fVariables[j].tag_name) {
5116 bool typeok = s->fSql->TypesCompatible(tagtype, s->fColumnTypes[j].c_str());
5117 if (typeok) {
5118 cm_msg(MINFO, "SqlHistory::update_schema", "Reactivating SQL column \'%s\' type \'%s\' in table \"%s\" for history event \"%s\" tag \"%s\"", s->fColumnNames[j].c_str(), s->fColumnTypes[j].c_str(), s->fTableName.c_str(), s->fEventName.c_str(), tagname.c_str());
5120
5121 status = update_column(s->fEventName.c_str(), s->fTableName.c_str(), s->fColumnNames[j].c_str(), s->fColumnTypes[j].c_str(), s->fVariables[j].tag_name.c_str(), s->fVariables[j].tag_type.c_str(), timestamp, true, have_transaction);
5122 if (status != HS_SUCCESS)
5123 return status;
5124
5125 if (count == 0) {
5126 s->fOffsets[j] = offset;
5127 offset += rpc_tid_size(tagtype);
5128 }
5129 count++;
5130 found_column = true;
5131 if (count > 1) {
5132 cm_msg(MERROR, "SqlHistory::update_schema", "Duplicate SQL column \'%s\' type \'%s\' in table \"%s\" for history event \"%s\" tag \"%s\"", s->fColumnNames[j].c_str(), s->fColumnTypes[j].c_str(), s->fTableName.c_str(), s->fEventName.c_str(), tagname.c_str());
5134 }
5135 }
5136 }
5137 }
5138 }
5139
5140 // create column
5141 if (!found_column && write_enable) {
5142 std::string col_name = maybe_colname;
5143 const char* col_type = s->fSql->ColumnType(tagtype);
5144
5145 bool dupe = false;
5146 for (size_t kk=0; kk<s->fColumnNames.size(); kk++)
5147 if (s->fColumnNames[kk] == col_name) {
5148 dupe = true;
5149 break;
5150 }
5151
5152 time_t now = time(NULL);
5153
5154 bool retry = false;
5155 for (int t=0; t<20; t++) {
5156
5157 // if duplicate column name, change it, try again
5158 if (dupe || retry) {
5159 col_name = maybe_colname;
5160 col_name += "_";
5161 col_name += TimeToString(now);
5162 if (t > 0) {
5163 char s[256];
5164 sprintf(s, "_%d", t);
5165 col_name += s;
5166 }
5167 }
5168
5169 if (fDebug)
5170 printf("SqlHistory::update_schema: table [%s], add column [%s] type [%s] for tag [%s]\n", s->fTableName.c_str(), col_name.c_str(), col_type, tagname.c_str());
5171
5172 status = CreateSqlColumn(fSql, s->fTableName.c_str(), col_name.c_str(), col_type, have_transaction, fDebug);
5173
5174 if (status == DB_KEY_EXIST) {
5175 if (fDebug)
5176 printf("SqlHistory::update_schema: table [%s], add column [%s] type [%s] for tag [%s] failed: duplicate column name\n", s->fTableName.c_str(), col_name.c_str(), col_type, tagname.c_str());
5177 retry = true;
5178 continue;
5179 }
5180
5181 if (status != HS_SUCCESS)
5182 return status;
5183
5184 break;
5185 }
5186
5187 if (status != HS_SUCCESS)
5188 return status;
5189
5190 status = update_column(s->fEventName.c_str(), s->fTableName.c_str(), col_name.c_str(), col_type, tagname.c_str(), rpc_tid_name(tagtype), timestamp, true, have_transaction);
5191 if (status != HS_SUCCESS)
5192 return status;
5193 }
5194 }
5195
5196 if (count > 1) {
5197 // schema has duplicate tags
5198 schema_ok = false;
5199 cm_msg(MERROR, "SqlHistory::update_schema", "Duplicate tags or SQL columns for history event \"%s\" tag \"%s\"", s->fEventName.c_str(), tagname.c_str());
5201 }
5202 }
5203 }
5204
5205 // mark as unused all columns not listed in tags
5206
5207 for (size_t k=0; k<s->fColumnNames.size(); k++)
5208 if (s->fVariables[k].name.length() > 0) {
5209 bool found = false;
5210
5211 for (int i=0; i<ntags; i++) {
5212 for (unsigned int j=0; j<tags[i].n_data; j++) {
5213 std::string tagname = tags[i].name;
5214
5215 if (tags[i].n_data > 1) {
5216 char s[256];
5217 sprintf(s, "[%d]", j);
5218 tagname += s;
5219 }
5220
5221 if (s->fVariables[k].name == tagname) {
5222 found = true;
5223 break;
5224 }
5225 }
5226
5227 if (found)
5228 break;
5229 }
5230
5231 if (!found) {
5232 // column not found in tags list
5233 schema_ok = false;
5234 if (fDebug)
5235 printf("Event [%s] Column [%s] tag [%s] not listed in tags list!\n", s->fEventName.c_str(), s->fColumnNames[k].c_str(), s->fVariables[k].name.c_str());
5236 if (write_enable) {
5237 cm_msg(MINFO, "SqlHistory::update_schema", "Deactivating SQL column \'%s\' type \'%s\' in table \"%s\" for history event \"%s\" not used for any tags", s->fColumnNames[k].c_str(), s->fColumnTypes[k].c_str(), s->fTableName.c_str(), s->fEventName.c_str());
5239
5240 status = update_column(s->fEventName.c_str(), s->fTableName.c_str(), s->fColumnNames[k].c_str(), s->fColumnTypes[k].c_str(), s->fVariables[k].tag_name.c_str(), s->fVariables[k].tag_type.c_str(), timestamp, false, have_transaction);
5241 if (status != HS_SUCCESS)
5242 return status;
5243 }
5244 }
5245 }
5246
5247 if (!write_enable)
5248 if (!schema_ok) {
5249 if (fDebug)
5250 printf("Return error!\n");
5251 return HS_FILE_ERROR;
5252 }
5253
5254 return HS_SUCCESS;
5255}
5256
5258// SQLITE functions //
5260
5261static int ReadSqliteTableNames(SqlBase* sql, HsSchemaVector *sv, const char* table_name, int debug)
5262{
5263 if (debug)
5264 printf("ReadSqliteTableNames: table [%s]\n", table_name);
5265
5266 int status;
5267 std::string cmd;
5268
5269 // FIXME: quotes
5270 cmd = "SELECT event_name, _i_time FROM \'_event_name_";
5271 cmd += table_name;
5272 cmd += "\' WHERE table_name='";
5273 cmd += table_name;
5274 cmd += "';";
5275
5276 status = sql->Prepare(table_name, cmd.c_str());
5277
5278 if (status != DB_SUCCESS)
5279 return status;
5280
5281 while (1) {
5282 status = sql->Step();
5283
5284 if (status != DB_SUCCESS)
5285 break;
5286
5287 std::string xevent_name = sql->GetText(0);
5288 time_t xevent_time = sql->GetTime(1);
5289
5290 //printf("read event name [%s] time %s\n", xevent_name.c_str(), TimeToString(xevent_time).c_str());
5291
5292 HsSqlSchema* s = new HsSqlSchema;
5293 s->fSql = sql;
5294 s->fEventName = xevent_name;
5295 s->fTimeFrom = xevent_time;
5296 s->fTimeTo = 0;
5297 s->fTableName = table_name;
5298 sv->add(s);
5299 }
5300
5301 status = sql->Finalize();
5302
5303 return HS_SUCCESS;
5304}
5305
5306static int ReadSqliteTableSchema(SqlBase* sql, HsSchemaVector *sv, const char* table_name, int debug)
5307{
5308 if (debug)
5309 printf("ReadSqliteTableSchema: table [%s]\n", table_name);
5310
5311 if (1) {
5312 // seed schema with table names
5313 HsSqlSchema* s = new HsSqlSchema;
5314 s->fSql = sql;
5315 s->fEventName = table_name;
5316 s->fTimeFrom = 0;
5317 s->fTimeTo = 0;
5318 s->fTableName = table_name;
5319 sv->add(s);
5320 }
5321
5322 return ReadSqliteTableNames(sql, sv, table_name, debug);
5323}
5324
5326// SQLITE history classes //
5328
5330{
5331public:
5332 SqliteHistory() { // ctor
5333#ifdef HAVE_SQLITE
5334 fSql = new Sqlite();
5335#endif
5336 }
5337
5339 int read_column_names(HsSchemaVector *sv, const char* table_name, const char* event_name);
5340 int create_table(HsSchemaVector* sv, const char* event_name, time_t timestamp);
5341 int update_column(const char* event_name, const char* table_name, const char* column_name, const char* column_type, const char* tag_name, const char* tag_type, const time_t timestamp, bool active, bool* have_transaction);
5342};
5343
5345{
5346 int status;
5347
5348 if (fDebug)
5349 printf("SqliteHistory::read_table_and_event_names!\n");
5350
5351 // loop over all tables
5352
5353 std::vector<std::string> tables;
5354 status = fSql->ListTables(&tables);
5355 if (status != DB_SUCCESS)
5356 return status;
5357
5358 for (size_t i=0; i<tables.size(); i++) {
5359 const char* table_name = tables[i].c_str();
5360
5361 const char* s;
5362 s = strstr(table_name, "_event_name_");
5363 if (s == table_name)
5364 continue;
5365 s = strstr(table_name, "_column_names_");
5366 if (s == table_name)
5367 continue;
5368
5369 status = ReadSqliteTableSchema(fSql, sv, table_name, fDebug);
5370 }
5371
5372 return HS_SUCCESS;
5373}
5374
5375int SqliteHistory::read_column_names(HsSchemaVector *sv, const char* table_name, const char* event_name)
5376{
5377 if (fDebug)
5378 printf("SqliteHistory::read_column_names: table [%s], event [%s]\n", table_name, event_name);
5379
5380 // for all schema for table_name, prepopulate is with column names
5381
5382 std::vector<std::string> columns;
5383 fSql->ListColumns(table_name, &columns);
5384
5385 // first, populate column names
5386
5387 for (size_t i=0; i<sv->size(); i++) {
5388 HsSqlSchema* s = (HsSqlSchema*)(*sv)[i];
5389
5390 if (s->fTableName != table_name)
5391 continue;
5392
5393 // schema should be empty at this point
5394 //assert(s->fVariables.size() == 0);
5395
5396 for (size_t j=0; j<columns.size(); j+=2) {
5397 const char* cn = columns[j+0].c_str();
5398 const char* ct = columns[j+1].c_str();
5399
5400 if (strcmp(cn, "_t_time") == 0)
5401 continue;
5402 if (strcmp(cn, "_i_time") == 0)
5403 continue;
5404
5405 bool found = (s->find_column_index(cn) >= 0);
5406
5407 //printf("column [%s] sql type [%s]\n", cn.c_str(), ct);
5408
5409 if (!found) {
5410 HsSchemaEntry se;
5411 se.name = cn;
5412 se.type = 0;
5413 se.n_data = 1;
5414 se.n_bytes = 0;
5415 s->fColumnIndexCache[cn] = s->fColumnNames.size();
5416 s->fVariables.push_back(se);
5417 s->fColumnNames.push_back(cn);
5418 s->fColumnTypes.push_back(ct);
5419 s->fColumnInactive.push_back(false);
5420 s->fOffsets.push_back(-1);
5421 }
5422 }
5423 }
5424
5425 // then read column name information
5426
5427 std::string tn;
5428 tn += "_column_names_";
5429 tn += table_name;
5430
5431 std::string cmd;
5432 cmd = "SELECT column_name, tag_name, tag_type, _i_time FROM ";
5433 cmd += fSql->QuoteId(tn.c_str());
5434 cmd += " WHERE table_name=";
5435 cmd += fSql->QuoteString(table_name);
5436 cmd += " ORDER BY _i_time ASC;";
5437
5438 int status = fSql->Prepare(table_name, cmd.c_str());
5439
5440 if (status != DB_SUCCESS) {
5441 return status;
5442 }
5443
5444 while (1) {
5445 status = fSql->Step();
5446
5447 if (status != DB_SUCCESS)
5448 break;
5449
5450 // NOTE: SQL "SELECT ORDER BY _i_time ASC" returns data sorted by time
5451 // in this code we use the data from the last data row
5452 // so if multiple rows are present, the latest one is used
5453
5454 std::string col_name = fSql->GetText(0);
5455 std::string tag_name = fSql->GetText(1);
5456 std::string tag_type = fSql->GetText(2);
5457 time_t schema_time = fSql->GetTime(3);
5458
5459 //printf("read table [%s] column [%s] tag name [%s] time %s\n", table_name, col_name.c_str(), tag_name.c_str(), TimeToString(xxx_time).c_str());
5460
5461 // make sure a schema exists at this time point
5462 NewSqlSchema(sv, table_name, schema_time);
5463
5464 // add this information to all schema
5465
5466 for (size_t i=0; i<sv->size(); i++) {
5467 HsSqlSchema* s = (HsSqlSchema*)(*sv)[i];
5468 if (s->fTableName != table_name)
5469 continue;
5470 if (s->fTimeFrom < schema_time)
5471 continue;
5472
5473 //printf("add column to schema %d\n", s->fTimeFrom);
5474
5475 int j = s->find_column_index(col_name);
5476 if (j < 0)
5477 continue;
5478 s->fVariables[j].name = tag_name;
5479 s->fVariables[j].type = rpc_name_tid(tag_type.c_str());
5480 s->fVariables[j].n_data = 1;
5481 s->fVariables[j].n_bytes = rpc_tid_size(s->fVariables[j].type);
5482 }
5483 }
5484
5485 status = fSql->Finalize();
5486
5487 return HS_SUCCESS;
5488}
5489
5490int SqliteHistory::create_table(HsSchemaVector* sv, const char* event_name, time_t timestamp)
5491{
5492 if (fDebug)
5493 printf("SqliteHistory::create_table: event [%s], timestamp %s\n", event_name, TimeToString(timestamp).c_str());
5494
5495 int status;
5496 bool have_transaction = false;
5497 std::string table_name = MidasNameToSqlName(event_name);
5498
5499 // FIXME: what about duplicate table names?
5500 status = CreateSqlTable(fSql, table_name.c_str(), &have_transaction);
5501
5502 //if (status == DB_KEY_EXIST) {
5503 // return ReadSqliteTableSchema(fSql, sv, table_name.c_str(), fDebug);
5504 //}
5505
5506 if (status != HS_SUCCESS) {
5507 // FIXME: ???
5508 // FIXME: at least close or revert the transaction
5509 return status;
5510 }
5511
5512 std::string cmd;
5513
5514 std::string en;
5515 en += "_event_name_";
5516 en += table_name;
5517
5518 cmd = "CREATE TABLE ";
5519 cmd += fSql->QuoteId(en.c_str());
5520 cmd += " (table_name TEXT NOT NULL, event_name TEXT NOT NULL, _i_time INTEGER NOT NULL);";
5521
5522 status = fSql->Exec(table_name.c_str(), cmd.c_str());
5523
5524 cmd = "INSERT INTO ";
5525 cmd += fSql->QuoteId(en.c_str());
5526 cmd += " (table_name, event_name, _i_time) VALUES (";
5527 cmd += fSql->QuoteString(table_name.c_str());
5528 cmd += ", ";
5529 cmd += fSql->QuoteString(event_name);
5530 cmd += ", ";
5531 cmd += fSql->QuoteString(TimeToString(timestamp).c_str());
5532 cmd += ");";
5533
5534 status = fSql->Exec(table_name.c_str(), cmd.c_str());
5535
5536 std::string cn;
5537 cn += "_column_names_";
5538 cn += table_name;
5539
5540 cmd = "CREATE TABLE ";
5541 cmd += fSql->QuoteId(cn.c_str());
5542 cmd += " (table_name TEXT NOT NULL, column_name TEXT NOT NULL, tag_name TEXT NOT NULL, tag_type TEXT NOT NULL, column_type TEXT NOT NULL, _i_time INTEGER NOT NULL);";
5543
5544 status = fSql->Exec(table_name.c_str(), cmd.c_str());
5545
5546 status = fSql->CommitTransaction(table_name.c_str());
5547 if (status != DB_SUCCESS) {
5548 return HS_FILE_ERROR;
5549 }
5550
5551 return ReadSqliteTableSchema(fSql, sv, table_name.c_str(), fDebug);
5552}
5553
5554int SqliteHistory::update_column(const char* event_name, const char* table_name, const char* column_name, const char* column_type, const char* tag_name, const char* tag_type, const time_t timestamp, bool active, bool* have_transaction)
5555{
5556 if (fDebug)
5557 printf("SqliteHistory::update_column: event [%s], table [%s], column [%s], new name [%s], timestamp %s\n", event_name, table_name, column_name, tag_name, TimeToString(timestamp).c_str());
5558
5559 int status = StartSqlTransaction(fSql, table_name, have_transaction);
5560 if (status != HS_SUCCESS)
5561 return status;
5562
5563 // FIXME: quotes
5564 std::string cmd;
5565 cmd = "INSERT INTO \'_column_names_";
5566 cmd += table_name;
5567 cmd += "\' (table_name, column_name, tag_name, tag_type, column_type, _i_time) VALUES (\'";
5568 cmd += table_name;
5569 cmd += "\', \'";
5570 cmd += column_name;
5571 cmd += "\', \'";
5572 cmd += tag_name;
5573 cmd += "\', \'";
5574 cmd += tag_type;
5575 cmd += "\', \'";
5576 cmd += column_type;
5577 cmd += "\', \'";
5578 cmd += TimeToString(timestamp);
5579 cmd += "\');";
5580 status = fSql->Exec(table_name, cmd.c_str());
5581
5582 return status;
5583}
5584
5586// Mysql history classes //
5588
5590{
5591public:
5592 MysqlHistory() { // ctor
5593#ifdef HAVE_MYSQL
5594 fSql = new Mysql();
5595#endif
5596 }
5597
5599 int read_column_names(HsSchemaVector *sv, const char* table_name, const char* event_name);
5600 int create_table(HsSchemaVector* sv, const char* event_name, time_t timestamp);
5601 int update_column(const char* event_name, const char* table_name, const char* column_name, const char* column_type, const char* tag_name, const char* tag_type, const time_t timestamp, bool active, bool* have_transaction);
5602};
5603
5604static int ReadMysqlTableNames(SqlBase* sql, HsSchemaVector *sv, const char* table_name, int debug, const char* must_have_event_name, const char* must_have_table_name)
5605{
5606 if (debug)
5607 printf("ReadMysqlTableNames: table [%s], must have event [%s] table [%s]\n", table_name, must_have_event_name, must_have_table_name);
5608
5609 int status;
5610 std::string cmd;
5611
5612 if (table_name) {
5613 cmd = "SELECT event_name, table_name, itimestamp FROM _history_index WHERE table_name='";
5614 cmd += table_name;
5615 cmd += "';";
5616 } else {
5617 // Only enumerate the table-marker rows (one per table/version), not the far more
5618 // numerous per-column rows. Both carry a non-empty table_name, but the table-marker
5619 // INSERT omits column_name (leaving it NULL) while the column-record INSERT sets it.
5620 // Restricting to column_name IS NULL keeps the reader schema vector at ~(#tables)
5621 // instead of ~(#columns x #versions), which is what blows up on a large _history_index.
5622 cmd = "SELECT event_name, table_name, itimestamp FROM _history_index WHERE table_name!='' AND column_name IS NULL;";
5623 table_name = "_history_index";
5624 }
5625
5626 status = sql->Prepare(table_name, cmd.c_str());
5627
5628 if (status != DB_SUCCESS)
5629 return status;
5630
5631 bool found_must_have_table = false;
5632 int count = 0;
5633
5634 while (1) {
5635 status = sql->Step();
5636
5637 if (status != DB_SUCCESS)
5638 break;
5639
5640 const char* xevent_name = sql->GetText(0);
5641 const char* xtable_name = sql->GetText(1);
5642 time_t xevent_time = sql->GetTime(2);
5643
5644 if (debug == 999) {
5645 printf("entry %d event name [%s] table name [%s] time %s\n", count, xevent_name, xtable_name, TimeToString(xevent_time).c_str());
5646 }
5647
5648 if (must_have_table_name && (strcmp(xtable_name, must_have_table_name) == 0)) {
5649 assert(must_have_event_name != NULL);
5650 if (event_name_cmp(xevent_name, must_have_event_name) == 0) {
5651 found_must_have_table = true;
5652 //printf("Found table [%s]: event name [%s] table name [%s] time %s\n", must_have_table_name, xevent_name, xtable_name, TimeToString(xevent_time).c_str());
5653 } else {
5654 //printf("Found correct table [%s] with wrong event name [%s] expected [%s] time %s\n", must_have_table_name, xevent_name, must_have_event_name, TimeToString(xevent_time).c_str());
5655 }
5656 }
5657
5658 HsSqlSchema* s = new HsSqlSchema;
5659 s->fSql = sql;
5660 s->fEventName = xevent_name;
5661 s->fTimeFrom = xevent_time;
5662 s->fTimeTo = 0;
5663 s->fTableName = xtable_name;
5664 sv->add(s);
5665 count++;
5666 }
5667
5668 status = sql->Finalize();
5669
5670 if (must_have_table_name && !found_must_have_table) {
5671 cm_msg(MERROR, "ReadMysqlTableNames", "Error: Table [%s] for event [%s] missing from the history index\n", must_have_table_name, must_have_event_name);
5672 if (debug == 999)
5673 return HS_FILE_ERROR;
5674 // NB: recursion is broken by setting debug to 999.
5675 ReadMysqlTableNames(sql, sv, table_name, 999, must_have_event_name, must_have_table_name);
5676 cm_msg(MERROR, "ReadMysqlTableNames", "Error: Cannot continue, nothing will work after this error\n");
5678 abort();
5679 return HS_FILE_ERROR;
5680 }
5681
5682 if (0) {
5683 // print accumulated schema
5684 printf("ReadMysqlTableNames: table_name [%s] event_name [%s] table_name [%s]\n", table_name, must_have_event_name, must_have_table_name);
5685 sv->print(false);
5686 }
5687
5688 return HS_SUCCESS;
5689}
5690
5691int MysqlHistory::read_column_names(HsSchemaVector *sv, const char* table_name, const char* event_name)
5692{
5693 if (fDebug)
5694 printf("MysqlHistory::read_column_names: table [%s], event [%s]\n", table_name, event_name);
5695
5696 // for all schema for table_name, prepopulate is with column names
5697
5698 std::vector<std::string> columns;
5699 fSql->ListColumns(table_name, &columns);
5700
5701 // first, populate column names
5702
5703 for (size_t i=0; i<sv->size(); i++) {
5704 HsSqlSchema* s = (HsSqlSchema*)(*sv)[i];
5705
5706 if (s->fTableName != table_name)
5707 continue;
5708
5709 // schema should be empty at this point
5710 //assert(s->fVariables.size() == 0);
5711
5712 for (size_t j=0; j<columns.size(); j+=2) {
5713 const char* cn = columns[j+0].c_str();
5714 const char* ct = columns[j+1].c_str();
5715
5716 if (strcmp(cn, "_t_time") == 0)
5717 continue;
5718 if (strcmp(cn, "_i_time") == 0)
5719 continue;
5720
5721 bool found = (s->find_column_index(cn) >= 0);
5722
5723 //printf("column [%s] sql type [%s]\n", cn.c_str(), ct);
5724
5725 if (!found) {
5726 HsSchemaEntry se;
5727 se.tag_name = cn;
5728 se.tag_type = "";
5729 se.name = cn;
5730 se.type = 0;
5731 se.n_data = 1;
5732 se.n_bytes = 0;
5733 s->fColumnIndexCache[cn] = s->fColumnNames.size();
5734 s->fVariables.push_back(se);
5735 s->fColumnNames.push_back(cn);
5736 s->fColumnTypes.push_back(ct);
5737 s->fColumnInactive.push_back(false);
5738 s->fOffsets.push_back(-1);
5739 }
5740 }
5741 }
5742
5743 // then read column name information
5744
5745 std::string cmd;
5746 cmd = "SELECT column_name, column_type, tag_name, tag_type, itimestamp, active FROM _history_index WHERE event_name=";
5747 cmd += fSql->QuoteString(event_name);
5748 cmd += ";";
5749
5750 int status = fSql->Prepare(table_name, cmd.c_str());
5751
5752 if (status != DB_SUCCESS) {
5753 return status;
5754 }
5755
5756 while (1) {
5757 status = fSql->Step();
5758
5759 if (status != DB_SUCCESS)
5760 break;
5761
5762 const char* col_name = fSql->GetText(0);
5763 const char* col_type = fSql->GetText(1);
5764 const char* tag_name = fSql->GetText(2);
5765 const char* tag_type = fSql->GetText(3);
5766 time_t schema_time = fSql->GetTime(4);
5767 const char* active = fSql->GetText(5);
5768 int iactive = atoi(active);
5769
5770 //printf("read table [%s] column [%s] type [%s] tag name [%s] type [%s] time %s active [%s] %d\n", table_name, col_name, col_type, tag_name, tag_type, TimeToString(schema_time).c_str(), active, iactive);
5771
5772 if (!col_name)
5773 continue;
5774 if (!tag_name)
5775 continue;
5776 if (strlen(col_name) < 1)
5777 continue;
5778
5779 // make sure a schema exists at this time point
5780 NewSqlSchema(sv, table_name, schema_time);
5781
5782 // add this information to all schema
5783
5784 for (size_t i=0; i<sv->size(); i++) {
5785 HsSqlSchema* s = (HsSqlSchema*)(*sv)[i];
5786 if (s->fTableName != table_name)
5787 continue;
5788 if (s->fTimeFrom < schema_time)
5789 continue;
5790
5791 int tid = rpc_name_tid(tag_type);
5792 int tid_size = rpc_tid_size(tid);
5793
5794 int j = s->find_column_index(col_name);
5795 if (j < 0)
5796 continue;
5797
5798 s->fVariables[j].tag_name = tag_name;
5799 s->fVariables[j].tag_type = tag_type;
5800 if (!iactive) {
5801 s->fVariables[j].name = "";
5802 s->fColumnInactive[j] = true;
5803 } else {
5804 s->fVariables[j].name = tag_name;
5805 s->fColumnInactive[j] = false;
5806 }
5807 s->fVariables[j].type = tid;
5808 s->fVariables[j].n_data = 1;
5809 s->fVariables[j].n_bytes = tid_size;
5810
5811 // doctor column names in case MySQL returns different type
5812 // from the type used to create the column, but the types
5813 // are actually the same. K.O.
5814 DoctorSqlColumnType(&s->fColumnTypes[j], col_type);
5815 }
5816 }
5817
5818 status = fSql->Finalize();
5819
5820 return HS_SUCCESS;
5821}
5822
5823#if 0
5824static int ReadMysqlTableSchema(SqlBase* sql, HsSchemaVector *sv, const char* table_name, int debug)
5825{
5826 if (debug)
5827 printf("ReadMysqlTableSchema: table [%s]\n", table_name);
5828
5829 if (1) {
5830 // seed schema with table names
5831 HsSqlSchema* s = new HsSqlSchema;
5832 s->fSql = sql;
5833 s->fEventName = table_name;
5834 s->fTimeFrom = 0;
5835 s->fTimeTo = 0;
5836 s->fTableName = table_name;
5837 sv->add(s);
5838 }
5839
5840 return ReadMysqlTableNames(sql, sv, table_name, debug, NULL, NULL);
5841}
5842#endif
5843
5845{
5846 int status;
5847
5848 if (fDebug)
5849 printf("MysqlHistory::read_table_and_event_names!\n");
5850
5851 // loop over all tables
5852
5853 std::vector<std::string> tables;
5854 status = fSql->ListTables(&tables);
5855 if (status != DB_SUCCESS)
5856 return status;
5857
5858 for (size_t i=0; i<tables.size(); i++) {
5859 const char* table_name = tables[i].c_str();
5860
5861 const char* s;
5862 s = strstr(table_name, "_history_index");
5863 if (s == table_name)
5864 continue;
5865
5866 if (1) {
5867 // seed schema with table names
5868 HsSqlSchema* s = new HsSqlSchema;
5869 s->fSql = fSql;
5870 s->fEventName = table_name;
5871 s->fTimeFrom = 0;
5872 s->fTimeTo = 0;
5873 s->fTableName = table_name;
5874 sv->add(s);
5875 }
5876 }
5877
5878 if (0) {
5879 // print accumulated schema
5880 printf("read_table_and_event_names:\n");
5881 sv->print(false);
5882 }
5883
5884 status = ReadMysqlTableNames(fSql, sv, NULL, fDebug, NULL, NULL);
5885
5886 return HS_SUCCESS;
5887}
5888
5889int MysqlHistory::create_table(HsSchemaVector* sv, const char* event_name, time_t timestamp)
5890{
5891 if (fDebug)
5892 printf("MysqlHistory::create_table: event [%s], timestamp %s\n", event_name, TimeToString(timestamp).c_str());
5893
5894 int status;
5895 std::string table_name = MidasNameToSqlName(event_name);
5896
5897 // MySQL table name length limit is 64 bytes
5898 if (table_name.length() > 40) {
5899 table_name.resize(40);
5900 table_name += "_T";
5901 }
5902
5903 time_t now = time(NULL);
5904
5905 int max_attempts = 10;
5906 for (int i=0; i<max_attempts; i++) {
5907 status = fSql->OpenTransaction(table_name.c_str());
5908 if (status != DB_SUCCESS) {
5909 return HS_FILE_ERROR;
5910 }
5911
5912 bool have_transaction = true;
5913
5914 std::string xtable_name = table_name;
5915
5916 if (i>0) {
5917 xtable_name += "_";
5918 xtable_name += TimeToString(now);
5919 if (i>1) {
5920 xtable_name += "_";
5921 char buf[256];
5922 sprintf(buf, "%d", i);
5923 xtable_name += buf;
5924 }
5925 }
5926
5927 status = CreateSqlTable(fSql, xtable_name.c_str(), &have_transaction);
5928
5929 //printf("event [%s] create table [%s] status %d\n", event_name, xtable_name.c_str(), status);
5930
5931 if (status == DB_KEY_EXIST) {
5932 // already exists, try with different name!
5933 fSql->RollbackTransaction(table_name.c_str());
5934 continue;
5935 }
5936
5937 if (status != HS_SUCCESS) {
5938 // MYSQL cannot roll back "create table", if we cannot create SQL tables, nothing will work. Give up now.
5939 cm_msg(MERROR, "MysqlHistory::create_table", "Could not create table [%s] for event [%s], timestamp %s, please fix the SQL database configuration and try again", table_name.c_str(), event_name, TimeToString(timestamp).c_str());
5940 abort();
5941
5942 // fatal error, give up!
5943 fSql->RollbackTransaction(table_name.c_str());
5944 break;
5945 }
5946
5947 for (int j=0; j<2; j++) {
5948 std::string cmd;
5949 cmd += "INSERT INTO _history_index (event_name, table_name, itimestamp, active) VALUES (";
5950 cmd += fSql->QuoteString(event_name);
5951 cmd += ", ";
5952 cmd += fSql->QuoteString(xtable_name.c_str());
5953 cmd += ", ";
5954 char buf[256];
5955 sprintf(buf, "%.0f", (double)timestamp);
5956 cmd += fSql->QuoteString(buf);
5957 cmd += ", ";
5958 cmd += fSql->QuoteString("1");
5959 cmd += ");";
5960
5961 int status = fSql->Exec(table_name.c_str(), cmd.c_str());
5962 if (status == DB_SUCCESS)
5963 break;
5964
5965 status = CreateSqlTable(fSql, "_history_index", &have_transaction);
5966 status = CreateSqlColumn(fSql, "_history_index", "event_name", "varchar(256) character set binary not null", &have_transaction, fDebug);
5967 status = CreateSqlColumn(fSql, "_history_index", "table_name", "varchar(256)", &have_transaction, fDebug);
5968 status = CreateSqlColumn(fSql, "_history_index", "tag_name", "varchar(256) character set binary", &have_transaction, fDebug);
5969 status = CreateSqlColumn(fSql, "_history_index", "tag_type", "varchar(256)", &have_transaction, fDebug);
5970 status = CreateSqlColumn(fSql, "_history_index", "column_name", "varchar(256)", &have_transaction, fDebug);
5971 status = CreateSqlColumn(fSql, "_history_index", "column_type", "varchar(256)", &have_transaction, fDebug);
5972 status = CreateSqlColumn(fSql, "_history_index", "itimestamp", "integer not null", &have_transaction, fDebug);
5973 status = CreateSqlColumn(fSql, "_history_index", "active", "boolean", &have_transaction, fDebug);
5974 }
5975
5976 status = fSql->CommitTransaction(table_name.c_str());
5977
5978 if (status != DB_SUCCESS) {
5979 return HS_FILE_ERROR;
5980 }
5981
5982 return ReadMysqlTableNames(fSql, sv, xtable_name.c_str(), fDebug, event_name, xtable_name.c_str());
5983 }
5984
5985 cm_msg(MERROR, "MysqlHistory::create_table", "Could not create table [%s] for event [%s], timestamp %s, after %d attempts", table_name.c_str(), event_name, TimeToString(timestamp).c_str(), max_attempts);
5986
5987 return HS_FILE_ERROR;
5988}
5989
5990int MysqlHistory::update_column(const char* event_name, const char* table_name, const char* column_name, const char* column_type, const char* tag_name, const char* tag_type, const time_t timestamp, bool active, bool* have_transaction)
5991{
5992 if (fDebug)
5993 printf("MysqlHistory::update_column: event [%s], table [%s], column [%s], type [%s] new name [%s], timestamp %s\n", event_name, table_name, column_name, column_type, tag_name, TimeToString(timestamp).c_str());
5994
5995 std::string cmd;
5996 cmd += "INSERT INTO _history_index (event_name, table_name, tag_name, tag_type, column_name, column_type, itimestamp, active) VALUES (";
5997 cmd += fSql->QuoteString(event_name);
5998 cmd += ", ";
5999 cmd += fSql->QuoteString(table_name);
6000 cmd += ", ";
6001 cmd += fSql->QuoteString(tag_name);
6002 cmd += ", ";
6003 cmd += fSql->QuoteString(tag_type);
6004 cmd += ", ";
6005 cmd += fSql->QuoteString(column_name);
6006 cmd += ", ";
6007 cmd += fSql->QuoteString(column_type);
6008 cmd += ", ";
6009 char buf[256];
6010 sprintf(buf, "%.0f", (double)timestamp);
6011 cmd += fSql->QuoteString(buf);
6012 cmd += ", ";
6013 if (active)
6014 cmd += fSql->QuoteString("1");
6015 else
6016 cmd += fSql->QuoteString("0");
6017 cmd += ");";
6018
6019 int status = fSql->Exec(table_name, cmd.c_str());
6020 if (status != DB_SUCCESS)
6021 return HS_FILE_ERROR;
6022
6023 return HS_SUCCESS;
6024}
6025
6027// PostgreSQL history classes //
6029
6030#ifdef HAVE_PGSQL
6031
6032class PgsqlHistory: public SqlHistoryBase
6033{
6034public:
6035 Pgsql *fPgsql = NULL;
6036public:
6037 PgsqlHistory() { // ctor
6038 fPgsql = new Pgsql();
6039 fSql = fPgsql;
6040 }
6041
6043 int read_column_names(HsSchemaVector *sv, const char* table_name, const char* event_name);
6044 int create_table(HsSchemaVector* sv, const char* event_name, time_t timestamp);
6045 int update_column(const char* event_name, const char* table_name, const char* column_name, const char* column_type, const char* tag_name, const char* tag_type, const time_t timestamp, bool active, bool* have_transaction);
6046};
6047
6048static int ReadPgsqlTableNames(SqlBase* sql, HsSchemaVector *sv, const char* table_name, int debug, const char* must_have_event_name, const char* must_have_table_name)
6049{
6050 if (debug)
6051 printf("ReadPgsqlTableNames: table [%s], must have event [%s] table [%s]\n", table_name, must_have_event_name, must_have_table_name);
6052
6053 int status;
6054 std::string cmd;
6055
6056 if (table_name) {
6057 cmd = "SELECT event_name, table_name, itimestamp FROM _history_index WHERE table_name='";
6058 cmd += table_name;
6059 cmd += "';";
6060 } else {
6061 // Only enumerate the table-marker rows (one per table/version), not the far more
6062 // numerous per-column rows. Both carry a non-empty table_name, but the table-marker
6063 // INSERT omits column_name (leaving it NULL) while the column-record INSERT sets it.
6064 // Restricting to column_name IS NULL keeps the reader schema vector at ~(#tables)
6065 // instead of ~(#columns x #versions), which is what blows up on a large _history_index.
6066 cmd = "SELECT event_name, table_name, itimestamp FROM _history_index WHERE table_name!='' AND column_name IS NULL;";
6067 table_name = "_history_index";
6068 }
6069
6070 status = sql->Prepare(table_name, cmd.c_str());
6071
6072 if (status != DB_SUCCESS)
6073 return status;
6074
6075 bool found_must_have_table = false;
6076 int count = 0;
6077
6078 while (1) {
6079 status = sql->Step();
6080
6081 if (status != DB_SUCCESS)
6082 break;
6083
6084 const char* xevent_name = sql->GetText(0);
6085 const char* xtable_name = sql->GetText(1);
6086 time_t xevent_time = sql->GetTime(2);
6087
6088 if (debug == 999) {
6089 printf("entry %d event name [%s] table name [%s] time %s\n", count, xevent_name, xtable_name, TimeToString(xevent_time).c_str());
6090 }
6091
6092 if (must_have_table_name && (strcmp(xtable_name, must_have_table_name) == 0)) {
6093 assert(must_have_event_name != NULL);
6094 if (event_name_cmp(xevent_name, must_have_event_name) == 0) {
6095 found_must_have_table = true;
6096 //printf("Found table [%s]: event name [%s] table name [%s] time %s\n", must_have_table_name, xevent_name, xtable_name, TimeToString(xevent_time).c_str());
6097 } else {
6098 //printf("Found correct table [%s] with wrong event name [%s] expected [%s] time %s\n", must_have_table_name, xevent_name, must_have_event_name, TimeToString(xevent_time).c_str());
6099 }
6100 }
6101
6102 HsSqlSchema* s = new HsSqlSchema;
6103 s->fSql = sql;
6104 s->fEventName = xevent_name;
6105 s->fTimeFrom = xevent_time;
6106 s->fTimeTo = 0;
6107 s->fTableName = xtable_name;
6108 sv->add(s);
6109 count++;
6110 }
6111
6112 status = sql->Finalize();
6113
6114 if (must_have_table_name && !found_must_have_table) {
6115 cm_msg(MERROR, "ReadPgsqlTableNames", "Error: Table [%s] for event [%s] missing from the history index\n", must_have_table_name, must_have_event_name);
6116 if (debug == 999)
6117 return HS_FILE_ERROR;
6118 // NB: recursion is broken by setting debug to 999.
6119 ReadPgsqlTableNames(sql, sv, table_name, 999, must_have_event_name, must_have_table_name);
6120 cm_msg(MERROR, "ReadPgsqlTableNames", "Error: Cannot continue, nothing will work after this error\n");
6122 abort();
6123 return HS_FILE_ERROR;
6124 }
6125
6126 if (0) {
6127 // print accumulated schema
6128 printf("ReadPgsqlTableNames: table_name [%s] event_name [%s] table_name [%s]\n", table_name, must_have_event_name, must_have_table_name);
6129 sv->print(false);
6130 }
6131
6132 return HS_SUCCESS;
6133}
6134
6135int PgsqlHistory::read_column_names(HsSchemaVector *sv, const char* table_name, const char* event_name)
6136{
6137 if (fDebug)
6138 printf("PgsqlHistory::read_column_names: table [%s], event [%s]\n", table_name, event_name);
6139
6140 // for all schema for table_name, prepopulate is with column names
6141
6142 std::vector<std::string> columns;
6143 fSql->ListColumns(table_name, &columns);
6144
6145 // first, populate column names
6146
6147 for (size_t i=0; i<sv->size(); i++) {
6148 HsSqlSchema* s = (HsSqlSchema*)(*sv)[i];
6149
6150 if (s->fTableName != table_name)
6151 continue;
6152
6153 // schema should be empty at this point
6154 //assert(s->fVariables.size() == 0);
6155
6156 for (size_t j=0; j<columns.size(); j+=2) {
6157 const char* cn = columns[j+0].c_str();
6158 const char* ct = columns[j+1].c_str();
6159
6160 if (strcmp(cn, "_t_time") == 0)
6161 continue;
6162 if (strcmp(cn, "_i_time") == 0)
6163 continue;
6164
6165 bool found = (s->find_column_index(cn) >= 0);
6166
6167 if (!found) {
6168 HsSchemaEntry se;
6169 se.tag_name = cn;
6170 se.tag_type = "";
6171 se.name = cn;
6172 se.type = 0;
6173 se.n_data = 1;
6174 se.n_bytes = 0;
6175 s->fColumnIndexCache[cn] = s->fColumnNames.size();
6176 s->fVariables.push_back(se);
6177 s->fColumnNames.push_back(cn);
6178 s->fColumnTypes.push_back(ct);
6179 s->fColumnInactive.push_back(false);
6180 s->fOffsets.push_back(-1);
6181 }
6182 }
6183 }
6184
6185 // then read column name information
6186
6187 std::string cmd;
6188 cmd = "SELECT column_name, column_type, tag_name, tag_type, itimestamp, active FROM _history_index WHERE event_name=";
6189 cmd += fSql->QuoteString(event_name);
6190 cmd += ";";
6191
6192 int status = fSql->Prepare(table_name, cmd.c_str());
6193
6194 if (status != DB_SUCCESS) {
6195 return status;
6196 }
6197
6198 while (1) {
6199 status = fSql->Step();
6200
6201 if (status != DB_SUCCESS)
6202 break;
6203
6204 const char* col_name = fSql->GetText(0);
6205 const char* col_type = fSql->GetText(1);
6206 const char* tag_name = fSql->GetText(2);
6207 const char* tag_type = fSql->GetText(3);
6208 time_t schema_time = fSql->GetTime(4);
6209 const char* active = fSql->GetText(5);
6210 int iactive = atoi(active);
6211
6212 //printf("read table [%s] column [%s] type [%s] tag name [%s] type [%s] time %s active [%s] %d\n", table_name, col_name, col_type, tag_name, tag_type, TimeToString(schema_time).c_str(), active, iactive);
6213
6214 if (!col_name)
6215 continue;
6216 if (!tag_name)
6217 continue;
6218 if (strlen(col_name) < 1)
6219 continue;
6220
6221 // make sure a schema exists at this time point
6222 NewSqlSchema(sv, table_name, schema_time);
6223
6224 // add this information to all schema
6225 for (size_t i=0; i<sv->size(); i++) {
6226 HsSqlSchema* s = (HsSqlSchema*)(*sv)[i];
6227 if (s->fTableName != table_name)
6228 continue;
6229 if (s->fTimeFrom < schema_time)
6230 continue;
6231
6232 int tid = rpc_name_tid(tag_type);
6233 int tid_size = rpc_tid_size(tid);
6234
6235 int j = s->find_column_index(col_name);
6236 if (j < 0)
6237 continue;
6238
6239 s->fVariables[j].tag_name = tag_name;
6240 s->fVariables[j].tag_type = tag_type;
6241 if (!iactive) {
6242 s->fVariables[j].name = "";
6243 s->fColumnInactive[j] = true;
6244 } else {
6245 s->fVariables[j].name = tag_name;
6246 s->fColumnInactive[j] = false;
6247 }
6248 s->fVariables[j].type = tid;
6249 s->fVariables[j].n_data = 1;
6250 s->fVariables[j].n_bytes = tid_size;
6251
6252 // doctor column names in case MySQL returns different type
6253 // from the type used to create the column, but the types
6254 // are actually the same. K.O.
6255 DoctorPgsqlColumnType(&s->fColumnTypes[j], col_type);
6256 }
6257 }
6258
6259 status = fSql->Finalize();
6260
6261 return HS_SUCCESS;
6262}
6263
6264int PgsqlHistory::read_table_and_event_names(HsSchemaVector *sv)
6265{
6266 int status;
6267
6268 if (fDebug)
6269 printf("PgsqlHistory::read_table_and_event_names!\n");
6270
6271 // loop over all tables
6272
6273 std::vector<std::string> tables;
6274 status = fSql->ListTables(&tables);
6275 if (status != DB_SUCCESS)
6276 return status;
6277
6278 for (size_t i=0; i<tables.size(); i++) {
6279 const char* table_name = tables[i].c_str();
6280
6281 const char* s;
6282 s = strstr(table_name, "_history_index");
6283 if (s == table_name)
6284 continue;
6285
6286 if (1) {
6287 // seed schema with table names
6288 HsSqlSchema* s = new HsSqlSchema;
6289 s->fSql = fSql;
6290 s->fEventName = table_name;
6291 s->fTimeFrom = 0;
6292 s->fTimeTo = 0;
6293 s->fTableName = table_name;
6294 sv->add(s);
6295 }
6296 }
6297
6298 if (0) {
6299 // print accumulated schema
6300 printf("read_table_and_event_names:\n");
6301 sv->print(false);
6302 }
6303
6304 status = ReadPgsqlTableNames(fSql, sv, NULL, fDebug, NULL, NULL);
6305
6306 return HS_SUCCESS;
6307}
6308
6309int PgsqlHistory::create_table(HsSchemaVector* sv, const char* event_name, time_t timestamp)
6310{
6311 if (fDebug)
6312 printf("PgsqlHistory::create_table: event [%s], timestamp %s\n", event_name, TimeToString(timestamp).c_str());
6313
6314 int status;
6315 std::string table_name = MidasNameToSqlName(event_name);
6316
6317 // PostgreSQL table name length limit is 64 bytes
6318 if (table_name.length() > 40) {
6319 table_name.resize(40);
6320 table_name += "_T";
6321 }
6322
6323 time_t now = time(NULL);
6324
6325 int max_attempts = 10;
6326 for (int i=0; i<max_attempts; i++) {
6327 status = fSql->OpenTransaction(table_name.c_str());
6328 if (status != DB_SUCCESS) {
6329 return HS_FILE_ERROR;
6330 }
6331
6332 bool have_transaction = true;
6333
6334 std::string xtable_name = table_name;
6335
6336 if (i>0) {
6337 xtable_name += "_";
6338 xtable_name += TimeToString(now);
6339 if (i>1) {
6340 xtable_name += "_";
6341 char buf[256];
6342 sprintf(buf, "%d", i);
6343 xtable_name += buf;
6344 }
6345 }
6346
6347 if (fPgsql->fDownsample)
6348 status = CreateSqlHyperTable(fSql, xtable_name.c_str(), &have_transaction);
6349 else
6350 status = CreateSqlTable(fSql, xtable_name.c_str(), &have_transaction);
6351
6352 //printf("event [%s] create table [%s] status %d\n", event_name, xtable_name.c_str(), status);
6353
6354 if (status == DB_KEY_EXIST) {
6355 // already exists, try with different name!
6356 fSql->RollbackTransaction(table_name.c_str());
6357 continue;
6358 }
6359
6360 if (status != HS_SUCCESS) {
6361 fSql->RollbackTransaction(table_name.c_str());
6362 continue;
6363 }
6364
6365 fSql->Exec(table_name.c_str(), "SAVEPOINT t0");
6366
6367 for (int j=0; j<2; j++) {
6368 std::string cmd;
6369 cmd += "INSERT INTO _history_index (event_name, table_name, itimestamp, active) VALUES (";
6370 cmd += fSql->QuoteString(event_name);
6371 cmd += ", ";
6372 cmd += fSql->QuoteString(xtable_name.c_str());
6373 cmd += ", ";
6374 char buf[256];
6375 sprintf(buf, "%.0f", (double)timestamp);
6376 cmd += buf;
6377 cmd += ", ";
6378 cmd += fSql->QuoteString("1");
6379 cmd += ");";
6380
6381 int status = fSql->Exec(table_name.c_str(), cmd.c_str());
6382 if (status == DB_SUCCESS)
6383 break;
6384
6385 // if INSERT failed _history_index does not exist then recover to savepoint t0
6386 // to prevent whole transition abort
6387 fSql->Exec(table_name.c_str(), "ROLLBACK TO SAVEPOINT t0");
6388
6389 status = CreateSqlTable(fSql, "_history_index", &have_transaction, true);
6390 status = CreateSqlColumn(fSql, "_history_index", "event_name", "text not null", &have_transaction, fDebug);
6391 status = CreateSqlColumn(fSql, "_history_index", "table_name", "text", &have_transaction, fDebug);
6392 status = CreateSqlColumn(fSql, "_history_index", "tag_name", "text", &have_transaction, fDebug);
6393 status = CreateSqlColumn(fSql, "_history_index", "tag_type", "text", &have_transaction, fDebug);
6394 status = CreateSqlColumn(fSql, "_history_index", "column_name", "text", &have_transaction, fDebug);
6395 status = CreateSqlColumn(fSql, "_history_index", "column_type", "text", &have_transaction, fDebug);
6396 status = CreateSqlColumn(fSql, "_history_index", "itimestamp", "integer not null", &have_transaction, fDebug);
6397 status = CreateSqlColumn(fSql, "_history_index", "active", "smallint", &have_transaction, fDebug);
6398
6399 status = fSql->CommitTransaction(table_name.c_str());
6400 }
6401
6402 if (status != DB_SUCCESS) {
6403 return HS_FILE_ERROR;
6404 }
6405
6406 return ReadPgsqlTableNames(fSql, sv, xtable_name.c_str(), fDebug, event_name, xtable_name.c_str());
6407 }
6408
6409 cm_msg(MERROR, "PgsqlHistory::create_table", "Could not create table [%s] for event [%s], timestamp %s, after %d attempts", table_name.c_str(), event_name, TimeToString(timestamp).c_str(), max_attempts);
6410
6411 return HS_FILE_ERROR;
6412}
6413
6414int PgsqlHistory::update_column(const char* event_name, const char* table_name, const char* column_name, const char* column_type, const char* tag_name, const char* tag_type, const time_t timestamp, bool active, bool* have_transaction)
6415{
6416 if (fDebug)
6417 printf("PgsqlHistory::update_column: event [%s], table [%s], column [%s], type [%s] new name [%s], timestamp %s\n", event_name, table_name, column_name, column_type, tag_name, TimeToString(timestamp).c_str());
6418
6419 std::string cmd;
6420 cmd += "INSERT INTO _history_index (event_name, table_name, tag_name, tag_type, column_name, column_type, itimestamp, active) VALUES (";
6421 cmd += fSql->QuoteString(event_name);
6422 cmd += ", ";
6423 cmd += fSql->QuoteString(table_name);
6424 cmd += ", ";
6425 cmd += fSql->QuoteString(tag_name);
6426 cmd += ", ";
6427 cmd += fSql->QuoteString(tag_type);
6428 cmd += ", ";
6429 cmd += fSql->QuoteString(column_name);
6430 cmd += ", ";
6431 cmd += fSql->QuoteString(column_type);
6432 cmd += ", ";
6433 char buf[256];
6434 sprintf(buf, "%.0f", (double)timestamp);
6435 cmd += buf;
6436 cmd += ", ";
6437 if (active)
6438 cmd += fSql->QuoteString("1");
6439 else
6440 cmd += fSql->QuoteString("0");
6441 cmd += ");";
6442
6443 int status = fSql->Exec(table_name, cmd.c_str());
6444 if (status != DB_SUCCESS)
6445 return HS_FILE_ERROR;
6446
6447 return HS_SUCCESS;
6448}
6449
6450#endif // HAVE_PGSQL
6451
6453// File history class //
6455
6456const time_t kDay = 24*60*60;
6457const time_t kMonth = 30*kDay;
6458
6459const double KiB = 1024;
6460const double MiB = KiB*KiB;
6461//const double GiB = KiB*MiB;
6462
6464{
6465protected:
6466 std::string fPath;
6467 time_t fPathLastMtime = 0;
6468 std::vector<std::string> fSortedFiles;
6469 std::vector<bool> fSortedRead;
6471 off64_t fConfMaxFileSize = 100*MiB;
6472
6473public:
6474 FileHistory() // ctor
6475 {
6476 // empty
6477 }
6478
6479 int hs_connect(const char* connect_string);
6480 int hs_disconnect();
6481 int hs_clear_cache();
6482 int read_schema(HsSchemaVector* sv, const char* event_name, const time_t timestamp);
6483 HsSchema* new_event(const char* event_name, time_t timestamp, int ntags, const TAG tags[]);
6484
6485protected:
6486 int create_file(const char* event_name, time_t timestamp, const std::vector<HsSchemaEntry>& vars, std::string* filenamep);
6487 HsFileSchema* read_file_schema(const char* filename);
6488 int read_file_list(bool *pchanged);
6489 void clear_file_list();
6490 void tags_to_variables(int ntags, const TAG tags[], std::vector<HsSchemaEntry>& variables);
6491 HsSchema* maybe_reopen(const char* event_name, time_t timestamp, HsSchema* s);
6492};
6493
6494int FileHistory::hs_connect(const char* connect_string)
6495{
6496 if (fDebug)
6497 printf("hs_connect [%s]!\n", connect_string);
6498
6499 hs_disconnect();
6500
6501 fConnectString = connect_string;
6502 fPath = connect_string;
6503
6504 // add trailing '/'
6505 if (fPath.length() > 0) {
6506 if (fPath[fPath.length()-1] != DIR_SEPARATOR)
6508 }
6509
6510 return HS_SUCCESS;
6511}
6512
6514{
6515 if (fDebug)
6516 printf("FileHistory::hs_clear_cache!\n");
6517 fPathLastMtime = 0;
6519}
6520
6522{
6523 if (fDebug)
6524 printf("FileHistory::hs_disconnect!\n");
6525
6528
6529 return HS_SUCCESS;
6530}
6531
6533{
6534 fPathLastMtime = 0;
6535 fSortedFiles.clear();
6536 fSortedRead.clear();
6537}
6538
6540{
6541 int status;
6542 double start_time = ss_time_sec();
6543
6544 if (pchanged)
6545 *pchanged = false;
6546
6547 struct stat stat_buf;
6548 status = stat(fPath.c_str(), &stat_buf);
6549 if (status != 0) {
6550 cm_msg(MERROR, "FileHistory::read_file_list", "Cannot stat(%s), errno %d (%s)", fPath.c_str(), errno, strerror(errno));
6551 return HS_FILE_ERROR;
6552 }
6553
6554 //printf("dir [%s], mtime: %d %d last: %d %d, mtime %s", fPath.c_str(), stat_buf.st_mtimespec.tv_sec, stat_buf.st_mtimespec.tv_nsec, last_mtimespec.tv_sec, last_mtimespec.tv_nsec, ctime(&stat_buf.st_mtimespec.tv_sec));
6555
6556 if (stat_buf.st_mtime == fPathLastMtime) {
6557 if (fDebug)
6558 printf("FileHistory::read_file_list: history directory \"%s\" mtime %d did not change\n", fPath.c_str(), int(stat_buf.st_mtime));
6559 return HS_SUCCESS;
6560 }
6561
6562 fPathLastMtime = stat_buf.st_mtime;
6563
6564 if (fDebug)
6565 printf("FileHistory::read_file_list: reading list of history files in \"%s\"\n", fPath.c_str());
6566
6567 std::vector<std::string> flist;
6568
6569 ss_file_find(fPath.c_str(), "mhf_*.dat", &flist);
6570
6571 double ls_time = ss_time_sec();
6572 double ls_elapsed = ls_time - start_time;
6573 if (ls_elapsed > 5.000) {
6574 cm_msg(MINFO, "FileHistory::read_file_list", "\"ls -l\" of \"%s\" took %.1f sec", fPath.c_str(), ls_elapsed);
6576 }
6577
6578 // note: reverse iterator is used to sort filenames by time, newest first
6579 std::sort(flist.rbegin(), flist.rend());
6580
6581#if 0
6582 {
6583 printf("file names sorted by time:\n");
6584 for (size_t i=0; i<flist.size(); i++) {
6585 printf("%d: %s\n", i, flist[i].c_str());
6586 }
6587 }
6588#endif
6589
6590 std::vector<bool> fread;
6591 fread.resize(flist.size()); // fill with "false"
6592
6593 // loop over the old list of files,
6594 // for files we already read, loop over new file
6595 // list and mark the same file as read. K.O.
6596 for (size_t j=0; j<fSortedFiles.size(); j++) {
6597 if (fSortedRead[j]) {
6598 for (size_t i=0; i<flist.size(); i++) {
6599 if (flist[i] == fSortedFiles[j]) {
6600 fread[i] = true;
6601 break;
6602 }
6603 }
6604 }
6605 }
6606
6607 fSortedFiles = flist;
6608 fSortedRead = fread;
6609
6610 if (pchanged)
6611 *pchanged = true;
6612
6613 return HS_SUCCESS;
6614}
6615
6616int FileHistory::read_schema(HsSchemaVector* sv, const char* event_name, const time_t timestamp)
6617{
6618 if (fDebug)
6619 printf("FileHistory::read_schema: event [%s] at time %s\n", event_name, TimeToString(timestamp).c_str());
6620
6621 if (sv->size() == 0) {
6622 if (fDebug)
6623 printf("FileHistory::read_schema: schema is empty, do a full reload from disk\n");
6625 }
6626
6627 BOOL old_call_watchdog = FALSE;
6628 DWORD old_timeout = 0;
6629 cm_get_watchdog_params(&old_call_watchdog, &old_timeout);
6630 cm_set_watchdog_params(old_call_watchdog, 0);
6631
6632 bool changed = false;
6633
6634 int status = read_file_list(&changed);
6635
6636 if (status != HS_SUCCESS) {
6637 cm_set_watchdog_params(old_call_watchdog, old_timeout);
6638 return status;
6639 }
6640
6641 if (!changed) {
6642 if ((*sv).find_event(event_name, timestamp)) {
6643 if (fDebug)
6644 printf("FileHistory::read_schema: event [%s] at time %s, no new history files, already have this schema\n", event_name, TimeToString(timestamp).c_str());
6645 cm_set_watchdog_params(old_call_watchdog, old_timeout);
6646 return HS_SUCCESS;
6647 }
6648 }
6649
6650 double start_time = ss_time_sec();
6651
6652 int count_read = 0;
6653
6654 for (size_t i=0; i<fSortedFiles.size(); i++) {
6655 std::string file_name = fPath + fSortedFiles[i];
6656 if (fSortedRead[i])
6657 continue;
6658 //bool dupe = false;
6659 //for (size_t ss=0; ss<sv->size(); ss++) {
6660 // HsFileSchema* ssp = (HsFileSchema*)(*sv)[ss];
6661 // if (file_name == ssp->fFileName) {
6662 // dupe = true;
6663 // break;
6664 // }
6665 //}
6666 //if (dupe)
6667 // continue;
6668 fSortedRead[i] = true;
6670 if (!s)
6671 continue;
6672 sv->add(s);
6673 count_read++;
6674
6675 if (event_name) {
6676 if (s->fEventName == event_name) {
6677 //printf("file %s event_name %s time %s, age %f\n", file_name.c_str(), s->fEventName.c_str(), TimeToString(s->fTimeFrom).c_str(), double(timestamp - s->fTimeFrom));
6678 if (s->fTimeFrom <= timestamp) {
6679 // this file is older than the time requested,
6680 // subsequent files will be even older,
6681 // we can stop reading here.
6682 break;
6683 }
6684 }
6685 }
6686 }
6687
6688 double end_time = ss_time_sec();
6689 double read_elapsed = end_time - start_time;
6690 if (read_elapsed > 5.000) {
6691 cm_msg(MINFO, "FileHistory::read_schema", "Loading schema for event \"%s\" timestamp %s, reading %d history files took %.1f sec", event_name, TimeToString(timestamp).c_str(), count_read, read_elapsed);
6693 }
6694
6695 cm_set_watchdog_params(old_call_watchdog, old_timeout);
6696
6697 return HS_SUCCESS;
6698}
6699
6700void FileHistory::tags_to_variables(int ntags, const TAG tags[], std::vector<HsSchemaEntry>& variables)
6701{
6702 for (int i=0; i<ntags; i++) {
6704
6705 int tsize = rpc_tid_size(tags[i].type);
6706
6707 e.tag_name = tags[i].name;
6708 e.tag_type = rpc_tid_name(tags[i].type);
6709 e.name = tags[i].name;
6710 e.type = tags[i].type;
6711 e.n_data = tags[i].n_data;
6712 e.n_bytes = tags[i].n_data*tsize;
6713
6714 variables.push_back(e);
6715 }
6716}
6717
6718HsSchema* FileHistory::new_event(const char* event_name, time_t timestamp, int ntags, const TAG tags[])
6719{
6720 if (fDebug)
6721 printf("FileHistory::new_event: event [%s], timestamp %s, ntags %d\n", event_name, TimeToString(timestamp).c_str(), ntags);
6722
6723 int status;
6724
6725 HsFileSchema* s = (HsFileSchema*)fWriterSchema.find_event(event_name, timestamp);
6726
6727 if (!s) {
6728 //printf("hs_define_event: no schema for event %s\n", event_name);
6729 status = read_schema(&fWriterSchema, event_name, timestamp);
6730 if (status != HS_SUCCESS)
6731 return NULL;
6732 s = (HsFileSchema*)fWriterSchema.find_event(event_name, timestamp);
6733 } else {
6734 //printf("hs_define_event: already have schema for event %s\n", s->fEventName.c_str());
6735 }
6736
6737 bool xdebug = false;
6738
6739 if (s) { // is existing schema the same as new schema?
6740 bool same = true;
6741
6742 if (same) {
6743 if (s->fEventName != event_name) {
6744 if (xdebug)
6745 printf("AAA: [%s] [%s]!\n", s->fEventName.c_str(), event_name);
6746 same = false;
6747 }
6748 }
6749
6750 if (same) {
6751 if (s->fVariables.size() != (size_t)ntags) {
6752 if (xdebug)
6753 printf("BBB: event [%s]: ntags: %zu -> %d!\n", event_name, s->fVariables.size(), ntags);
6754 same = false;
6755 }
6756 }
6757
6758 if (same) {
6759 for (size_t i=0; i<s->fVariables.size(); i++) {
6760 if (s->fVariables[i].name != tags[i].name) {
6761 if (xdebug)
6762 printf("CCC: event [%s] index %zu: name [%s] -> [%s]!\n", event_name, i, s->fVariables[i].name.c_str(), tags[i].name);
6763 same = false;
6764 }
6765 if (s->fVariables[i].type != (int)tags[i].type) {
6766 if (xdebug)
6767 printf("DDD: event [%s] index %zu: type %d -> %d!\n", event_name, i, s->fVariables[i].type, tags[i].type);
6768 same = false;
6769 }
6770 if (s->fVariables[i].n_data != (int)tags[i].n_data) {
6771 if (xdebug)
6772 printf("EEE: event [%s] index %zu: n_data %d -> %d!\n", event_name, i, s->fVariables[i].n_data, tags[i].n_data);
6773 same = false;
6774 }
6775 if (!same)
6776 break;
6777 }
6778 }
6779
6780 if (!same) {
6781 if (xdebug) {
6782 printf("*** Schema for event %s has changed!\n", event_name);
6783
6784 printf("*** Old schema for event [%s] time %s:\n", event_name, TimeToString(timestamp).c_str());
6785 s->print();
6786 printf("*** New tags:\n");
6787 PrintTags(ntags, tags);
6788 }
6789
6790 if (fDebug)
6791 printf("FileHistory::new_event: event [%s], timestamp %s, ntags %d: schema mismatch, starting a new file.\n", event_name, TimeToString(timestamp).c_str(), ntags);
6792
6793 s = NULL;
6794 }
6795 }
6796
6797 if (s) {
6798 // maybe this schema is too old - rotate files every so often
6799 time_t age = timestamp - s->fTimeFrom;
6800 //printf("*** age %s (%.1f months), timestamp %s, time_from %s, file %s\n", TimeToString(age).c_str(), (double)age/(double)kMonth, TimeToString(timestamp).c_str(), TimeToString(s->fTimeFrom).c_str(), s->fFileName.c_str());
6801 if (age > fConfMaxFileAge) {
6802 if (fDebug)
6803 printf("FileHistory::new_event: event [%s], timestamp %s, ntags %d: schema is too old, age %.1f months, starting a new file.\n", event_name, TimeToString(timestamp).c_str(), ntags, (double)age/(double)kMonth);
6804
6805 // force creation of a new file
6806 s = NULL;
6807 }
6808 }
6809
6810 if (s) {
6811 // maybe this file is too big - rotate files to limit maximum size
6812 double size = ss_file_size(s->fFileName.c_str());
6813 //printf("*** size %.0f, file %s\n", size, s->fFileName.c_str());
6814 if (size > fConfMaxFileSize) {
6815 if (fDebug)
6816 printf("FileHistory::new_event: event [%s], timestamp %s, ntags %d: file too big, size %.1f MiBytes, max size %.1f MiBytes, starting a new file.\n", event_name, TimeToString(timestamp).c_str(), ntags, size/MiB, fConfMaxFileSize/MiB);
6817
6818 // force creation of a new file
6819 s = NULL;
6820 }
6821 }
6822
6823 if (!s) {
6824 std::string filename;
6825
6826 std::vector<HsSchemaEntry> vars;
6827
6828 tags_to_variables(ntags, tags, vars);
6829
6830 status = create_file(event_name, timestamp, vars, &filename);
6831 if (status != HS_SUCCESS)
6832 return NULL;
6833
6834 HsFileSchema* ss = read_file_schema(filename.c_str());
6835 if (!ss) {
6836 cm_msg(MERROR, "FileHistory::new_event", "Error: Cannot create schema for event \'%s\', see previous messages", event_name);
6837 return NULL;
6838 }
6839
6840 fWriterSchema.add(ss);
6841
6842 s = (HsFileSchema*)fWriterSchema.find_event(event_name, timestamp);
6843
6844 if (!s) {
6845 cm_msg(MERROR, "FileHistory::new_event", "Error: Cannot create schema for event \'%s\', see previous messages", event_name);
6846 return NULL;
6847 }
6848
6849 if (xdebug) {
6850 printf("*** New schema for event [%s] time %s:\n", event_name, TimeToString(timestamp).c_str());
6851 s->print();
6852 }
6853 }
6854
6855 assert(s != NULL);
6856
6857#if 0
6858 {
6859 printf("schema for [%s] is %p\n", event_name, s);
6860 if (s)
6861 s->print();
6862 }
6863#endif
6864
6865 HsFileSchema* e = new HsFileSchema();
6866
6867 *e = *s; // make a copy of the schema
6868
6869 return e;
6870}
6871
6872int FileHistory::create_file(const char* event_name, time_t timestamp, const std::vector<HsSchemaEntry>& vars, std::string* filenamep)
6873{
6874 if (fDebug)
6875 printf("FileHistory::create_file: event [%s]\n", event_name);
6876
6877 // NB: file names are constructed in such a way
6878 // that when sorted lexicographically ("ls -1 | sort")
6879 // they *also* become sorted by time
6880
6881 struct tm tm;
6882 localtime_r(&timestamp, &tm); // somebody must call tzset() before this.
6883
6884 char buf[256];
6885 strftime(buf, sizeof(buf), "%Y%m%d", &tm);
6886
6887 std::string filename;
6888 filename += fPath;
6889 filename += "mhf_";
6890 filename += TimeToString(timestamp);
6891 filename += "_";
6892 filename += buf;
6893 filename += "_";
6894 filename += MidasNameToFileName(event_name);
6895
6896 std::string try_filename = filename + ".dat";
6897
6898 FILE *fp = NULL;
6899 for (int itry=0; itry<10; itry++) {
6900 if (itry > 0) {
6901 char s[256];
6902 sprintf(s, "_%d", rand());
6903 try_filename = filename + s + ".dat";
6904 }
6905
6906 fp = fopen(try_filename.c_str(), "r");
6907 if (fp != NULL) {
6908 // this file already exists, try with a different name
6909 fclose(fp);
6910 continue;
6911 }
6912
6913 fp = fopen(try_filename.c_str(), "w");
6914 if (fp == NULL) {
6915 // somehow cannot create this file, try again
6916 cm_msg(MERROR, "FileHistory::create_file", "Error: Cannot create file \'%s\' for event \'%s\', fopen() errno %d (%s)", try_filename.c_str(), event_name, errno, strerror(errno));
6917 continue;
6918 }
6919
6920 // file opened
6921 break;
6922 }
6923
6924 if (fp == NULL) {
6925 // somehow cannot create any file, whine!
6926 cm_msg(MERROR, "FileHistory::create_file", "Error: Cannot create file \'%s\' for event \'%s\'", filename.c_str(), event_name);
6927 return HS_FILE_ERROR;
6928 }
6929
6930 std::string ss;
6931
6932 ss += "version: 2.0\n";
6933 ss += "event_name: ";
6934 ss += event_name;
6935 ss += "\n";
6936 ss += "time: ";
6937 ss += TimeToString(timestamp);
6938 ss += "\n";
6939
6940 int recsize = 0;
6941
6942 ss += "tag: /DWORD 1 4 /timestamp\n";
6943 recsize += 4;
6944
6945 bool padded = false;
6946 int offset = 0;
6947
6948 bool xdebug = false; // (strcmp(event_name, "u_Beam") == 0);
6949
6950 for (size_t i=0; i<vars.size(); i++) {
6951 int tsize = rpc_tid_size(vars[i].type);
6952 int n_bytes = vars[i].n_data*tsize;
6953 int xalign = (offset % tsize);
6954
6955 if (xdebug)
6956 printf("tag %zu, tsize %d, n_bytes %d, xalign %d\n", i, tsize, n_bytes, xalign);
6957
6958#if 0
6959 // looks like history data does not do alignement and padding
6960 if (xalign != 0) {
6961 padded = true;
6962 int pad_bytes = tsize - xalign;
6963 assert(pad_bytes > 0);
6964
6965 ss += "tag: ";
6966 ss += "XPAD";
6967 ss += " ";
6968 ss += SmallIntToString(1);
6969 ss += " ";
6970 ss += SmallIntToString(pad_bytes);
6971 ss += " ";
6972 ss += "pad_";
6973 ss += SmallIntToString(i);
6974 ss += "\n";
6975
6976 offset += pad_bytes;
6977 recsize += pad_bytes;
6978
6979 assert((offset % tsize) == 0);
6980 fprintf(stderr, "FIXME: need to debug padding!\n");
6981 //abort();
6982 }
6983#endif
6984
6985 ss += "tag: ";
6986 ss += rpc_tid_name(vars[i].type);
6987 ss += " ";
6988 ss += SmallIntToString(vars[i].n_data);
6989 ss += " ";
6990 ss += SmallIntToString(n_bytes);
6991 ss += " ";
6992 ss += vars[i].name;
6993 ss += "\n";
6994
6995 recsize += n_bytes;
6996 offset += n_bytes;
6997 }
6998
6999 ss += "record_size: ";
7000 ss += SmallIntToString(recsize);
7001 ss += "\n";
7002
7003 // reserve space for "data_offset: ..."
7004 int sslength = ss.length() + 127;
7005
7006 int block = 1024;
7007 int nb = (sslength + block - 1)/block;
7008 int data_offset = block * nb;
7009
7010 ss += "data_offset: ";
7011 ss += SmallIntToString(data_offset);
7012 ss += "\n";
7013
7014 fprintf(fp, "%s", ss.c_str());
7015
7016 fclose(fp);
7017
7018 if (1 && padded) {
7019 printf("Schema in file %s has padding:\n", try_filename.c_str());
7020 printf("%s", ss.c_str());
7021 }
7022
7023 if (filenamep)
7024 *filenamep = try_filename;
7025
7026 return HS_SUCCESS;
7027}
7028
7030{
7031 if (fDebug)
7032 printf("FileHistory::read_file_schema: file %s\n", filename);
7033
7034 FILE* fp = fopen(filename, "r");
7035 if (!fp) {
7036 cm_msg(MERROR, "FileHistory::read_file_schema", "Cannot read \'%s\', fopen() errno %d (%s)", filename, errno, strerror(errno));
7037 return NULL;
7038 }
7039
7040 HsFileSchema* s = NULL;
7041
7042 // File format looks like this:
7043 // version: 2.0
7044 // event_name: u_Beam
7045 // time: 1023174012
7046 // tag: /DWORD 1 4 /timestamp
7047 // tag: FLOAT 1 4 B1
7048 // ...
7049 // tag: FLOAT 1 4 Ref Heater
7050 // record_size: 84
7051 // data_offset: 1024
7052
7053 size_t rd_recsize = 0;
7054 int offset = 0;
7055
7056 while (1) {
7057 char buf[1024];
7058 char* b = fgets(buf, sizeof(buf), fp);
7059
7060 //printf("read: %s\n", b);
7061
7062 if (!b) {
7063 break; // end of file
7064 }
7065
7066 char*bb;
7067
7068 bb = strchr(b, '\n');
7069 if (bb)
7070 *bb = 0;
7071
7072 bb = strchr(b, '\r');
7073 if (bb)
7074 *bb = 0;
7075
7076 bb = strstr(b, "version: 2.0");
7077 if (bb == b) {
7078 s = new HsFileSchema();
7079 assert(s);
7080
7081 s->fFileName = filename;
7082 continue;
7083 }
7084
7085 if (!s) {
7086 // malformed history file
7087 break;
7088 }
7089
7090 bb = strstr(b, "event_name: ");
7091 if (bb == b) {
7092 s->fEventName = bb + 12;
7093 continue;
7094 }
7095
7096 bb = strstr(b, "time: ");
7097 if (bb == b) {
7098 s->fTimeFrom = strtoul(bb + 6, NULL, 10);
7099 continue;
7100 }
7101
7102 // tag format is like this:
7103 //
7104 // tag: FLOAT 1 4 Ref Heater
7105 //
7106 // "FLOAT" is the MIDAS type, "/DWORD" is special tag for the timestamp
7107 // "1" is the number of array elements
7108 // "4" is the total tag size in bytes (n_data*tid_size)
7109 // "Ref Heater" is the tag name
7110
7111 bb = strstr(b, "tag: ");
7112 if (bb == b) {
7113 bb += 5; // now points to the tag MIDAS type
7114 const char* midas_type = bb;
7115 char* bbb = strchr(bb, ' ');
7116 if (bbb) {
7117 *bbb = 0;
7118 HsSchemaEntry t;
7119 if (midas_type[0] == '/') {
7120 t.type = 0;
7121 } else {
7122 t.type = rpc_name_tid(midas_type);
7123 if (t.type == 0) {
7124 cm_msg(MERROR, "FileHistory::read_file_schema", "Unknown MIDAS data type \'%s\' in history file \'%s\'", midas_type, filename);
7125 if (s)
7126 delete s;
7127 s = NULL;
7128 break;
7129 }
7130 }
7131 bbb++;
7132 while (*bbb == ' ')
7133 bbb++;
7134 if (*bbb) {
7135 t.n_data = strtoul(bbb, &bbb, 10);
7136 while (*bbb == ' ')
7137 bbb++;
7138 if (*bbb) {
7139 t.n_bytes = strtoul(bbb, &bbb, 10);
7140 while (*bbb == ' ')
7141 bbb++;
7142 t.name = bbb;
7143 }
7144 }
7145
7146 if (midas_type[0] != '/') {
7147 s->fVariables.push_back(t);
7148 s->fOffsets.push_back(offset);
7149 offset += t.n_bytes;
7150 }
7151
7152 rd_recsize += t.n_bytes;
7153 }
7154 continue;
7155 }
7156
7157 bb = strstr(b, "record_size: ");
7158 if (bb == b) {
7159 s->fRecordSize = atoi(bb + 12);
7160 continue;
7161 }
7162
7163 bb = strstr(b, "data_offset: ");
7164 if (bb == b) {
7165 s->fDataOffset = atoi(bb + 12);
7166 // data offset is the last entry in the file
7167 break;
7168 }
7169 }
7170
7171 fclose(fp);
7172
7173 if (!s) {
7174 cm_msg(MERROR, "FileHistory::read_file_schema", "Malformed history schema in \'%s\', maybe it is not a history file", filename);
7175 return NULL;
7176 }
7177
7178 if (rd_recsize != s->fRecordSize) {
7179 cm_msg(MERROR, "FileHistory::read_file_schema", "Record size mismatch in history schema from \'%s\', file says %zu while total of all tags is %zu", filename, s->fRecordSize, rd_recsize);
7180 if (s)
7181 delete s;
7182 return NULL;
7183 }
7184
7185 if (!s) {
7186 cm_msg(MERROR, "FileHistory::read_file_schema", "Could not read history schema from \'%s\', maybe it is not a history file", filename);
7187 if (s)
7188 delete s;
7189 return NULL;
7190 }
7191
7192 if (fDebug > 1)
7193 s->print();
7194
7195 return s;
7196}
7197
7198HsSchema* FileHistory::maybe_reopen(const char* event_name, time_t timestamp, HsSchema* s)
7199{
7200 HsFileSchema* fs = dynamic_cast<HsFileSchema*>(s);
7201
7202 assert(fs != NULL); // FileHistory::maybe_reopen() must be called only with file history schema.
7203
7204 if (fs->fFileSize <= fConfMaxFileSize) {
7205 // not big enough, let it grow
7206 return s;
7207 }
7208
7209 // must rotate the file
7210
7211 if (fDebug) {
7212#ifdef OS_DARWIN
7213 printf("FileHistory::maybe_reopen: reopen file \"%s\", size %lld, max size %lld\n", fs->fFileName.c_str(), fs->fFileSize, fConfMaxFileSize);
7214#else
7215 printf("FileHistory::maybe_reopen: reopen file \"%s\", size %jd, max size %jd\n", fs->fFileName.c_str(), fs->fFileSize, fConfMaxFileSize);
7216#endif
7217 }
7218
7219 std::string new_filename;
7220
7221 int status = create_file(event_name, timestamp, fs->fVariables, &new_filename);
7222
7223 if (status != HS_SUCCESS) {
7224 // cannot create new history file
7225 return s;
7226 }
7227
7228 HsFileSchema* new_fs = read_file_schema(new_filename.c_str());
7229
7230 if (!new_fs) {
7231 // cannot open new history file
7232 return s;
7233 }
7234
7235 new_fs->fDisabled = false;
7236
7237 HsFileSchema* new_fs_copy = new HsFileSchema;
7238 *new_fs_copy = *new_fs; // make a copy
7239
7240 //printf("replacing schema %p %p with %p %p\n", s, fs, new_fs, new_fs_copy);
7241
7242 for (size_t i=0; i<fWriterEvents.size(); i++) {
7243 if (s == fWriterEvents[i]) {
7244 delete fWriterEvents[i];
7245 fWriterEvents[i] = new_fs;
7246 s = NULL; // pointer to fEvents[i]
7247 fs = NULL; // pointer to fEvents[i]
7248 }
7249 }
7250
7251 assert(s == NULL); // the schema we are replacing must be in fWriterEvents.
7252
7253 fWriterSchema.add(new_fs_copy); // make sure new file is added to the list of files
7254
7255 assert(new_fs->fFileSize < fConfMaxFileSize); // check that we are not returning the original big file
7256
7257 //new_fs->print();
7258
7259 return new_fs;
7260}
7261
7263// Factory constructors //
7265
7267{
7268#ifdef HAVE_SQLITE
7269 return new SqliteHistory();
7270#else
7271 cm_msg(MERROR, "MakeMidasHistorySqlite", "Error: Cannot initialize SQLITE history - this MIDAS was built without SQLITE support - HAVE_SQLITE is not defined");
7272 return NULL;
7273#endif
7274}
7275
7277{
7278#ifdef HAVE_MYSQL
7279 return new MysqlHistory();
7280#else
7281 cm_msg(MERROR, "MakeMidasHistoryMysql", "Error: Cannot initialize MySQL history - this MIDAS was built without MySQL support - HAVE_MYSQL is not defined");
7282 return NULL;
7283#endif
7284}
7285
7287{
7288#ifdef HAVE_PGSQL
7289 return new PgsqlHistory();
7290#else
7291 cm_msg(MERROR, "MakeMidasHistoryPgsql", "Error: Cannot initialize PgSQL history - this MIDAS was built without PostgreSQL support - HAVE_PGSQL is not defined");
7292 return NULL;
7293#endif
7294}
7295
7300
7301/* emacs
7302 * Local Variables:
7303 * tab-width: 8
7304 * c-basic-offset: 3
7305 * indent-tabs-mode: nil
7306 * End:
7307 */
#define FALSE
Definition cfortran.h:309
HsSchema * maybe_reopen(const char *event_name, time_t timestamp, HsSchema *s)
std::string fPath
HsFileSchema * read_file_schema(const char *filename)
std::vector< std::string > fSortedFiles
HsSchema * new_event(const char *event_name, time_t timestamp, int ntags, const TAG tags[])
off64_t fConfMaxFileSize
std::vector< bool > fSortedRead
int hs_connect(const char *connect_string)
returns HS_SUCCESS
void tags_to_variables(int ntags, const TAG tags[], std::vector< HsSchemaEntry > &variables)
int create_file(const char *event_name, time_t timestamp, const std::vector< HsSchemaEntry > &vars, std::string *filenamep)
int hs_clear_cache()
clear internal cache, returns HS_SUCCESS
int read_schema(HsSchemaVector *sv, const char *event_name, const time_t timestamp)
int read_file_list(bool *pchanged)
int hs_disconnect()
disconnect from history, returns HS_SUCCESS
void remove_inactive_columns()
off64_t fFileSizeInitial
std::string fFileName
int read_data(const time_t start_time, const time_t end_time, const int num_var, const std::vector< int > &var_schema_index, const int var_index[], const int debug, std::vector< time_t > &last_time, MidasHistoryBufferInterface *buffer[])
void print(bool print_tags=true) const
int write_event(const time_t t, const char *data, const size_t data_size)
int read_last_written(const time_t timestamp, const int debug, time_t *last_written)
int fCountWriteUndersize
virtual void print(bool print_tags=true) const
std::vector< int > fOffsets
virtual ~HsSchema()
virtual void remove_inactive_columns()=0
std::vector< HsSchemaEntry > fVariables
virtual int read_data(const time_t start_time, const time_t end_time, const int num_var, const std::vector< int > &var_schema_index, const int var_index[], const int debug, std::vector< time_t > &last_time, MidasHistoryBufferInterface *buffer[])=0
size_t fWriteMinSize
virtual int write_event(const time_t t, const char *data, const size_t data_size)=0
virtual int read_last_written(const time_t timestamp, const int debug, time_t *last_written)=0
virtual int flush_buffers()=0
virtual int close()=0
int fCountWriteOversize
size_t fWriteMaxSize
std::string fEventName
virtual int match_event_var(const char *event_name, const char *var_name, const int var_index)
void print(bool print_tags=true) const
std::vector< HsSchema * > fData
size_t size() const
HsSchema * find_event(const char *event_name, const time_t timestamp, int debug=0)
void add(HsSchema *s)
HsSchema * operator[](int index) const
int get_transaction_count()
int read_last_written(const time_t timestamp, const int debug, time_t *last_written)
int read_data(const time_t start_time, const time_t end_time, const int num_var, const std::vector< int > &var_schema_index, const int var_index[], const int debug, std::vector< time_t > &last_time, MidasHistoryBufferInterface *buffer[])
std::vector< std::string > fColumnNames
void print(bool print_tags=true) const
void remove_inactive_columns()
std::vector< std::string > fColumnTypes
std::unordered_map< std::string, size_t > fColumnIndexCache
int find_column_index(const std::string &column_name)
std::vector< bool > fColumnInactive
void increment_transaction_count()
int match_event_var(const char *event_name, const char *var_name, const int var_index)
std::string fTableName
static std::map< SqlBase *, int > gfTransactionCount
void reset_transaction_count()
int write_event(const time_t t, const char *data, const size_t data_size)
MidasHistoryBinnedBuffer(time_t first_time, time_t last_time, int num_bins)
void Add(time_t t, double v)
virtual void Add(time_t time, double value)=0
char type[NAME_LENGTH]
history channel name
Definition history.h:112
char name[NAME_LENGTH]
Definition history.h:111
int update_column(const char *event_name, const char *table_name, const char *column_name, const char *column_type, const char *tag_name, const char *tag_type, const time_t timestamp, bool active, bool *have_transaction)
int read_table_and_event_names(HsSchemaVector *sv)
int create_table(HsSchemaVector *sv, const char *event_name, time_t timestamp)
int read_column_names(HsSchemaVector *sv, const char *table_name, const char *event_name)
void Add(time_t t, double v)
ReadBuffer(time_t first_time, time_t last_time, time_t interval)
int hs_get_tags(const char *event_name, time_t t, std::vector< TAG > *ptags)
get list of history variables for given event (use event names returned by hs_get_events()) that exis...
virtual int hs_connect(const char *connect_string)=0
returns HS_SUCCESS
int hs_write_event(const char *event_name, time_t timestamp, int buffer_size, const char *buffer)
see hs_write_event(), returns HS_SUCCESS or HS_FILE_ERROR
int hs_read_buffer(time_t start_time, time_t end_time, int num_var, const char *const event_name[], const char *const var_name[], const int var_index[], MidasHistoryBufferInterface *buffer[], int hs_status[])
returns HS_SUCCESS
virtual HsSchema * maybe_reopen(const char *event_name, time_t timestamp, HsSchema *s)=0
int hs_define_event(const char *event_name, time_t timestamp, int ntags, const TAG tags[])
see hs_define_event(), returns HS_SUCCESS or HS_FILE_ERROR
int hs_read_binned(time_t start_time, time_t end_time, int num_bins, int num_var, const char *const event_name[], const char *const var_name[], const int var_index[], int num_entries[], int *count_bins[], double *mean_bins[], double *rms_bins[], double *min_bins[], double *max_bins[], time_t *bins_first_time[], double *bins_first_value[], time_t *bins_last_time[], double *bins_last_value[], time_t last_time[], double last_value[], int st[])
returns HS_SUCCESS
HsSchemaVector fWriterSchema
virtual HsSchema * new_event(const char *event_name, time_t timestamp, int ntags, const TAG tags[])=0
int hs_get_events(time_t t, std::vector< std::string > *pevents)
get list of events that exist(ed) at given time and later (value 0 means "return all events from begi...
std::vector< HsSchema * > fWriterEvents
int hs_get_last_written(time_t timestamp, int num_var, const char *const event_name[], const char *const var_name[], const int var_index[], time_t last_written[])
virtual int hs_set_debug(int debug)
set debug level, returns previous debug level
virtual int hs_disconnect()=0
disconnect from history, returns HS_SUCCESS
HsSchemaVector fReaderSchema
int hs_read(time_t start_time, time_t end_time, time_t interval, int num_var, const char *const event_name[], const char *const var_name[], const int var_index[], int num_entries[], time_t *time_buffer[], double *data_buffer[], int st[])
see hs_read(), returns HS_SUCCESS
int hs_clear_cache()
clear internal cache, returns HS_SUCCESS
int hs_flush_buffers()
flush buffered data to storage where it is visible to mhttpd
virtual int read_schema(HsSchemaVector *sv, const char *event_name, const time_t timestamp)=0
virtual int ListColumns(const char *table_name, std::vector< std::string > *plist)=0
virtual int Finalize()=0
virtual int Connect(const char *path)=0
virtual int ListColumns(const char *table, std::vector< std::string > *plist)=0
virtual double GetDouble(int column)=0
virtual int RollbackTransaction(const char *table_name)=0
virtual bool IsConnected()=0
virtual int CommitTransaction(const char *table_name)=0
virtual ~SqlBase()
virtual int ListTables(std::vector< std::string > *plist)=0
virtual std::string QuoteId(const char *s)=0
virtual int Disconnect()=0
virtual bool TypesCompatible(int midas_tid, const char *sql_type)=0
virtual int Prepare(const char *table_name, const char *sql)=0
virtual int Exec(const char *sql)=0
virtual int Connect(const char *dsn=0)=0
virtual int Exec(const char *table_name, const char *sql)=0
virtual std::string QuoteString(const char *s)=0
bool fTransactionPerTable
virtual time_t GetTime(int column)=0
virtual int Step()=0
virtual const char * GetText(int column)=0
virtual int ExecDisconnected(const char *table_name, const char *sql)=0
virtual int OpenTransaction(const char *table_name)=0
virtual const char * ColumnType(int midas_tid)=0
int update_schema1(HsSqlSchema *s, const time_t timestamp, const int ntags, const TAG tags[], bool write_enable, bool *have_transaction)
int hs_disconnect()
disconnect from history, returns HS_SUCCESS
int hs_set_debug(int debug)
set debug level, returns previous debug level
int read_schema(HsSchemaVector *sv, const char *event_name, const time_t timestamp)
HsSchema * maybe_reopen(const char *event_name, time_t timestamp, HsSchema *s)
virtual ~SqlHistoryBase()
virtual int create_table(HsSchemaVector *sv, const char *event_name, time_t timestamp)=0
virtual int update_column(const char *event_name, const char *table_name, const char *column_name, const char *column_type, const char *tag_name, const char *tag_type, const time_t timestamp, bool active, bool *have_transaction)=0
virtual int read_column_names(HsSchemaVector *sv, const char *table_name, const char *event_name)=0
virtual int read_table_and_event_names(HsSchemaVector *sv)=0
int hs_connect(const char *connect_string)
returns HS_SUCCESS
HsSchema * new_event(const char *event_name, time_t timestamp, int ntags, const TAG tags[])
int update_schema(HsSqlSchema *s, const time_t timestamp, const int ntags, const TAG tags[], bool write_enable)
int update_column(const char *event_name, const char *table_name, const char *column_name, const char *column_type, const char *tag_name, const char *tag_type, const time_t timestamp, bool active, bool *have_transaction)
int read_column_names(HsSchemaVector *sv, const char *table_name, const char *event_name)
int create_table(HsSchemaVector *sv, const char *event_name, time_t timestamp)
int read_table_and_event_names(HsSchemaVector *sv)
INT cm_get_watchdog_params(BOOL *call_watchdog, DWORD *timeout)
Definition midas.cxx:3341
INT cm_set_watchdog_params(BOOL call_watchdog, DWORD timeout)
Definition midas.cxx:3299
#define DB_KEY_EXIST
Definition midas.h:642
#define DB_FILE_ERROR
Definition midas.h:648
#define DB_SUCCESS
Definition midas.h:632
#define DB_NO_MORE_SUBKEYS
Definition midas.h:647
#define HS_UNDEFINED_VAR
Definition midas.h:734
#define HS_SUCCESS
Definition midas.h:728
#define HS_FILE_ERROR
Definition midas.h:729
#define HS_UNDEFINED_EVENT
Definition midas.h:733
unsigned int DWORD
Definition mcstd.h:51
#define TID_DOUBLE
Definition midas.h:343
#define TID_SBYTE
Definition midas.h:329
#define TID_BOOL
Definition midas.h:340
#define TID_SHORT
Definition midas.h:334
#define TID_WORD
Definition midas.h:332
#define MINFO
Definition midas.h:560
#define TID_BYTE
Definition midas.h:327
#define TID_STRING
Definition midas.h:346
#define MERROR
Definition midas.h:559
#define TID_CHAR
Definition midas.h:331
#define TID_INT
Definition midas.h:338
#define TID_FLOAT
Definition midas.h:341
#define TID_LAST
Definition midas.h:354
#define TID_DWORD
Definition midas.h:336
double ss_file_size(const char *path)
Definition system.cxx:7057
double ss_time_sec()
Definition system.cxx:3546
INT ss_file_find(const char *path, const char *pattern, char **plist)
Definition system.cxx:6798
INT cm_msg_flush_buffer()
Definition midas.cxx:881
INT cm_msg(INT message_type, const char *filename, INT line, const char *routine, const char *format,...)
Definition midas.cxx:931
const char * rpc_tid_name(INT id)
Definition midas.cxx:11895
int rpc_name_tid(const char *name)
Definition midas.cxx:11909
INT rpc_tid_size(INT id)
Definition midas.cxx:11888
static std::string q(const char *s)
static const int tid_size[]
static const char * sql_type_mysql[]
static const char ** sql_type
static std::string MidasNameToSqlName(const char *s)
const time_t kMonth
const double KiB
static int CreateSqlColumn(SqlBase *sql, const char *table_name, const char *column_name, const char *column_type, bool *have_transaction, int debug)
static int ReadSqliteTableNames(SqlBase *sql, HsSchemaVector *sv, const char *table_name, int debug)
void DoctorPgsqlColumnType(std::string *col_type, const char *index_type)
static int ReadRecord(const char *file_name, int fd, off64_t offset, size_t recsize, off64_t irec, char *rec)
static int CreateSqlHyperTable(SqlBase *sql, const char *table_name, bool *have_transaction)
void DoctorSqlColumnType(std::string *col_type, const char *index_type)
static bool MatchTagName(const char *tag_name, int n_data, const char *var_tag_name, const int var_tag_index)
const double MiB
static int var_name_cmp(const std::string &v1, const char *v2)
static std::string TimeToString(time_t t)
MidasHistoryInterface * MakeMidasHistorySqlite()
static void PrintTags(int ntags, const TAG tags[])
static char * skip_spaces(char *s)
static bool MatchEventName(const char *event_name, const char *var_event_name)
static HsSqlSchema * NewSqlSchema(HsSchemaVector *sv, const char *table_name, time_t t)
static int StartSqlTransaction(SqlBase *sql, const char *table_name, bool *have_transaction)
const time_t kDay
MidasHistoryInterface * MakeMidasHistoryMysql()
static int ReadSqliteTableSchema(SqlBase *sql, HsSchemaVector *sv, const char *table_name, int debug)
static int event_name_cmp(const std::string &e1, const char *e2)
MidasHistoryInterface * MakeMidasHistoryPgsql()
MidasHistoryInterface * MakeMidasHistoryFile()
static int ReadMysqlTableNames(SqlBase *sql, HsSchemaVector *sv, const char *table_name, int debug, const char *must_have_event_name, const char *must_have_table_name)
static std::string SmallIntToString(int i)
static int CreateSqlTable(SqlBase *sql, const char *table_name, bool *have_transaction, bool set_default_timestamp=false)
static std::string MidasNameToFileName(const char *s)
static int FindTime(const char *file_name, int fd, off64_t offset, size_t recsize, off64_t nrec, time_t timestamp, off64_t *i1p, time_t *t1p, off64_t *i2p, time_t *t2p, time_t *tstart, time_t *tend, int debug)
INT index
Definition mana.cxx:271
DWORD last_time
Definition mana.cxx:3070
void * data
Definition mana.cxx:268
BOOL debug
debug printouts
Definition mana.cxx:254
INT type
Definition mana.cxx:269
char host_name[HOST_NAME_LENGTH]
Definition mana.cxx:242
double count
Definition mdump.cxx:33
INT i
Definition mdump.cxx:32
static int offset
Definition mgd.cxx:1500
#define DIR_SEPARATOR
Definition midas.h:193
DWORD BOOL
Definition midas.h:105
#define DIR_SEPARATOR_STR
Definition midas.h:194
#define read(n, a, f)
#define write(n, a, f, d)
#define name(x)
Definition midas_macro.h:24
static FILE * fp
struct callback_addr callback
Definition mserver.cxx:22
MUTEX_T * tm
Definition odbedit.cxx:39
BOOL match(char *pat, char *str)
Definition odbedit.cxx:189
INT j
Definition odbhist.cxx:40
INT k
Definition odbhist.cxx:40
char str[256]
Definition odbhist.cxx:33
char file_name[256]
Definition odbhist.cxx:41
INT add
Definition odbhist.cxx:40
DWORD status
Definition odbhist.cxx:39
char var_name[256]
Definition odbhist.cxx:41
std::string tag_name
int type
std::string tag_type
int n_data
std::string name
int n_bytes
Definition midas.h:1233
DWORD type
Definition midas.h:1235
DWORD n_data
Definition midas.h:1236
char name[NAME_LENGTH]
Definition midas.h:1234
char c
Definition system.cxx:1312
@ DIR
Definition test_init.cxx:7
static double e(void)
Definition tinyexpr.c:136