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