source: trunk/FACT++/src/rootifysql.cc@ 19181

Last change on this file since 19181 was 19180, checked in by tbretz, 7 years ago
For debugging purpose print the client version before the connection can fail.
File size: 32.2 KB
Line 
1#include "Database.h"
2
3#include <regex>
4
5#include <boost/algorithm/string/join.hpp>
6#include <boost/tokenizer.hpp>
7#include <boost/algorithm/string.hpp>
8
9#include "tools.h"
10#include "Time.h"
11#include "Configuration.h"
12
13#include <TROOT.h>
14#include <TSystem.h>
15#include <TFile.h>
16#include <TTree.h>
17
18using namespace std;
19
20// ------------------------------------------------------------------------
21
22void SetupConfiguration(Configuration &conf)
23{
24 po::options_description control("Database options");
25 control.add_options()
26 ("uri,u", var<string>()->required(), "Database link as in\n\tuser:password@server[:port]/database[?compress=0|1].")
27 ("query,q", var<string>(""), "MySQL query (overwrites --file)")
28 ("file", var<string>("rootify.sql"), "An ASCII file with the MySQL query (overwrites --query)")
29 ("ignore-null,i", po_switch(), "Do not skip rows containing any NULL field")
30 ("display,d", po_switch(), "Displays contents on the screen (most usefull in combination with mysql statements as SHOW or EXPLAIN)")
31 ("write,w", var<string>(""), "Write output to an ascii file")
32 ("delimiter", var<string>(""), "The delimiter used if contents are displayed with --display (default=\\t)")
33 ("explain", po_switch(), "Requests an EXPLAIN from the server (shows the server optimized query)\nsee also https://dev.mysql.com/doc/refman/explain-output.html")
34 ("profiling", po_switch(), "Turn on profiling and print profile")
35 ("var.*", var<string>(), "Predefined SQL user variables (@VAR)")
36 ("env.*", vars<string>(), "Predefined environment for substitutions in the query ($ENV)")
37 ("list.*", var<string>(), "Predefined environment for substitutions in the query ($ENV). The list is read from the given file (one list entry per line)")
38 ("print-connection", po_switch(), "Print database connection information")
39 ("verbose,v", var<uint16_t>(1), "Verbosity (0: quiet, 1: default, 2: more, 3, ...)")
40 ;
41
42 po::options_description root("Root file options");
43 root.add_options()
44 ("out,o", var<string>("rootify.root"), "Output root file name")
45 ("force,f", po_switch(), "Force overwriting an existing root file ('RECREATE')")
46 ("update", po_switch(), "Update an existing root file with the new tree ('UPDATE')")
47 ("compression,c", var<uint16_t>(1), "zlib compression level for the root file")
48 ("tree,t", var<string>("Result"), "Name of the root tree")
49 ("ignore", vars<string>(), "Ignore the given columns")
50 ("null,n", po_switch(), "Redirect the output file to /dev/null (mainly for debugging purposes, e.g. performance studies)")
51 ("no-fill", po_switch(), "Do not fill events into the root file (mainly for debugging purposes, e.g. performance studies)")
52 ;
53
54 po::positional_options_description p;
55 p.add("file", 1); // The 1st positional options (n=1)
56 p.add("out", 1); // The 2nd positional options (n=1)
57
58 conf.AddOptions(control);
59 conf.AddOptions(root);
60 conf.SetArgumentPositions(p);
61}
62
63void PrintUsage()
64{
65 cout <<
66 "rootifysql - Converts the result of a mysql query into a root file\n"
67 "\n"
68 "For convenience, this documentation uses the extended version of the options, "
69 "refer to the output below to get the abbreviations.\n"
70 "\n"
71 "Writes the result of a mysql query into a root file. For each column, a branch is "
72 "created of type double with the field name as name. This is usually the column name "
73 "if not specified otherwise by the AS mysql directive.\n"
74 "\n"
75 "Columns with CHAR or VARCHAR as field type are ignored. DATETIME, DATE and TIME "
76 "columns are converted to unix time (time_t). Rows containing any file which is "
77 "NULL are skipped if not suppressed by the --ignore-null option. Ideally, the query "
78 "is compiled in a way that no NULL field is returned. With the --display option the "
79 "result of the request is printed on the screen (NULL skipping still in action). "
80 "This can be useful to create an ascii file or to show results as 'SHOW DATABASES' "
81 "or 'EXPLAIN table'. To redirect the contents into an ascii file, the option -v0 "
82 "is useful. To suppress writing to an output file --null can be used.\n"
83 "\n"
84 "The default is to read the query from a file called rootify.sql. Except if a different "
85 "filename is specified by the --file option or a query is given with --query.\n"
86 "\n"
87 "As a trick, the rootify.sql file can be made excutable (chmod u+x rootify.sql). "
88 "If the first line contains '#!rootifysql', the script can be executed directly.\n"
89 "\n"
90 "Columns whose name start with @ are skipped. If you want them in your output file "
91 "give them a name using AS, e.g. 'SELECT @A:=5 AS A'.\n"
92 "\n"
93 "You can use variables in your sql query like @MyVar and define them on the "
94 "command line. In this example with --var.MyVar=5\n"
95 "\n"
96 "You can use environment definitions for substitutions in your SQL query. "
97 "For example --env.TEST=5 would replace $TEST or ${TEST} in your query by 5."
98 "If you specify one environment variable more than once, a list is created. "
99 "For example --env.TEST=1 --env.TEST=2 --env.TEST=3 would substitute "
100 "$TEST or ${TEST} by '1, 2, 3'. This is useful for the SQL `IN` keyword. "
101 "You can also read the values for an enviroment substitution from a file "
102 "(one element per line), e.g. --env.TEST=file.txt. Empty lines and lines "
103 "starting with a # are skipped.\n"
104 "\n"
105 "Comments in the query-file can be placed according to the SQL standard inline "
106 "/*comment*/ or introduced with # (shell script style) or -- (SQL style).\n"
107 "\n"
108 "In case of success, 0 is returned, a value>0 otherwise.\n"
109 "\n"
110 "Usage: rootifysql [rootify.sql [rootify.root]] [-u URI] [-q query|-f file] [-i] [-o out] [-f] [-cN] [-t tree] [-vN]\n"
111 "\n"
112 ;
113 cout << endl;
114}
115
116struct ExplainParser
117{
118 string sql;
119
120 vector<string> vec;
121
122 string substitute(string _str, const regex &expr)
123 {
124 smatch match;
125 while (regex_search(_str, match, expr, regex_constants::format_first_only))
126 {
127 const auto &len = match.length(0);
128 const auto &pos = match.position(0);
129 const auto &str = match.str(0);
130
131 const auto it = find(vec.cbegin(), vec.cend(), str);
132 const size_t id = it==vec.cend() ? vec.size() : it-vec.cbegin();
133
134 _str.replace(pos, len, "{"+to_string(id)+"}");
135
136 if (it==vec.cend())
137 vec.push_back(str);//.substr(1, str.size()-2));
138 }
139
140 return _str;
141 }
142
143 string substitute(const string &str, const string &expr)
144 {
145 return substitute(str, regex(expr));
146 }
147
148 vector<string> queries;
149
150 string resub(string str)
151 {
152 // search for "KEYWORD expression"
153 regex reg("\\{[0-9]+\\}");
154
155 //smatch match;
156 smatch match;
157 while (regex_search(str, match, reg, regex_constants::format_first_only))
158 {
159 const auto &len = match.length(0);
160 const auto &pos = match.position(0);
161 const auto &arg = match.str(0); // Argument
162
163 const auto idx = atoi(arg.c_str()+1);
164
165 str.replace(pos, len, resub(vec[idx]));
166 }
167
168 return str;
169 }
170
171 void expression(string expr, size_t indent=0)
172 {
173 if (expr[0]=='{')
174 {
175 const auto idx = atoi(expr.c_str()+1);
176
177 // This is a subquery
178 if (vec[idx].substr(0,3)=="(/*")
179 {
180 cout << setw(indent) << ' ' << "(\n";
181 find_tokens(vec[idx], indent+4);
182 cout << setw(indent) << ' ' << ") ";
183 }
184 else
185 // This is just something to substitute back
186 if (vec[idx].substr(0,2)=="({")
187 {
188 cout << setw(indent) << ' ' << "(" << resub(vec[idx]) << ") ";
189 }
190 else
191 {
192 if (indent>0)
193 cout << setw(indent) << ' ';
194 cout << resub(vec[idx]);
195 }
196 }
197 else
198 {
199 if (indent>0)
200 cout << setw(indent) << ' ';
201 cout << resub(expr);
202 }
203 }
204
205 void find_tokens(string str, size_t indent=0)
206 {
207 // ( COMMENT )?( TOKEN )?(( {NNN} | NNN )( AS|ON ( {NNN}) ))?(,)?)
208 //regex reg("(\\/\\*\\ select\\#[0-9]+\\ \\*\\/\\ *)?([a-zA-Z ]+)?((\\{[0-9]+\\}|[0-9]+)(\\ ?([Aa][Ss]|[Oo][Nn])\\ ?(\\{[0-9]+\\}))?(,)?)");
209
210 const string _com = "\\/\\*\\ select\\#[0-9]+\\ \\*\\/\\ *";
211
212 const string _tok = "[a-zA-Z_ ]+";
213
214 const string _nnn = "\\{[0-9]+\\}|[0-9]+";
215
216 const string _as = "\\ ?([Aa][Ss])\\ ?";
217
218 // ( _nnn ) ( _as ( _nnn ))?(,)? // can also match noting in between two {NNN}
219 const string _exp = "("+_nnn+")" + "("+_as+"("+_nnn+"))?(,)?";
220
221 // Matche: ( _com )? ( ( _tok )? ( _exp ) | ( _tok ) )
222 regex reg("("+_com+")?" + "(" + "("+_tok+")?"+"("+_exp+")" + "|" + "("+_tok+")" + ")");
223
224 smatch match;
225 while (regex_search(str, match, reg, regex_constants::format_first_only))
226 {
227
228 const auto &com = match.str(1); // comment
229 const auto &tok1 = Tools::Trim(match.str(3)); // token with expression
230 const auto &arg1 = match.str(5); // argument 1
231 const auto &as = match.str(7); // as
232 const auto &arg2 = match.str(8); // argument 2
233 const auto &comma = match.str(9); // comma
234 const auto &tok2 = Tools::Trim(match.str(10)); // token without expression
235
236 if (!com.empty())
237 cout << setw(indent) << ' ' << "\033[34m" << com << "\033[0m" << '\n';
238
239 if (!tok1.empty())
240 cout << setw(indent) << ' ' << "\033[32m" << tok1 << "\033[0m" << '\n';
241 if (!tok2.empty())
242 cout << setw(indent) << ' ' << "\033[32m" << tok2 << "\033[0m" << '\n';
243
244 if (!arg1.empty())
245 {
246 expression(arg1, indent+4);
247
248 if (!as.empty())
249 cout << " \033[33m" << as << "\033[0m ";
250
251 if (!arg2.empty())
252 expression(arg2);
253
254 if (!comma.empty())
255 cout << ',';
256
257 cout << '\n';
258 }
259
260 str = str.substr(match.position(0)+match.length(0));
261 }
262 }
263
264
265 ExplainParser(const string &_sql) : sql(_sql)
266 {
267 // substitute all strings
268 sql = substitute(sql, "'[^']*'");
269
270 // substitute all escaped sequences (`something`.`something-else`)
271 sql = substitute(sql, "`[^`]*`(\\.`[^`]*`)*");
272
273 // substitute all paranthesis
274 sql = substitute(sql, "[a-zA-Z0-9_]*\\([^\\(\\)]*\\)");
275
276 //cout << sql << "\n\n";
277 find_tokens(sql);
278 cout << endl;
279 }
280};
281
282// Remove queries...
283void format(string sql)
284{
285 ExplainParser p(sql);
286
287 /*
288
289 SELECT
290 [ALL | DISTINCT | DISTINCTROW ]
291 [HIGH_PRIORITY]
292 [STRAIGHT_JOIN]
293 [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
294 [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
295 select_expr [, select_expr ...]
296 [FROM table_references
297 [PARTITION partition_list]
298 [WHERE where_condition]
299 [GROUP BY {col_name | expr | position}, ... [WITH ROLLUP]]
300 [HAVING where_condition]
301 [WINDOW window_name AS (window_spec)
302 [, window_name AS (window_spec)] ...]
303 [ORDER BY {col_name | expr | position}
304 [ASC | DESC], ... [WITH ROLLUP]]
305 [LIMIT {[offset,] row_count | row_count OFFSET offset}]
306 [INTO OUTFILE 'file_name'
307 [CHARACTER SET charset_name]
308 export_options
309 | INTO DUMPFILE 'file_name'
310 | INTO var_name [, var_name]]
311 [FOR {UPDATE | SHARE} [OF tbl_name [, tbl_name] ...] [NOWAIT | SKIP LOCKED]
312 | LOCK IN SHARE MODE]]
313 */
314
315 /*
316table_references:
317 escaped_table_reference [, escaped_table_reference] ...
318
319escaped_table_reference:
320 table_reference
321 | { OJ table_reference }
322
323table_reference:
324 table_factor
325 | join_table
326
327table_factor:
328 tbl_name [PARTITION (partition_names)]
329 [[AS] alias] [index_hint_list]
330 | table_subquery [AS] alias [(col_list)]
331 | ( table_references )
332
333join_table:
334 table_reference [INNER | CROSS] JOIN table_factor [join_condition]
335 | table_reference STRAIGHT_JOIN table_factor
336 | table_reference STRAIGHT_JOIN table_factor ON conditional_expr
337 | table_reference {LEFT|RIGHT} [OUTER] JOIN table_reference join_condition
338 | table_reference NATURAL [INNER | {LEFT|RIGHT} [OUTER]] JOIN table_factor
339
340join_condition:
341 ON conditional_expr
342 | USING (column_list)
343
344index_hint_list:
345 index_hint [, index_hint] ...
346
347index_hint:
348 USE {INDEX|KEY}
349 [FOR {JOIN|ORDER BY|GROUP BY}] ([index_list])
350 | IGNORE {INDEX|KEY}
351 [FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
352 | FORCE {INDEX|KEY}
353 [FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
354
355index_list:
356 index_name [, index_name] ...
357 */
358
359}
360
361int finish(Database &connection, const uint16_t &verbose, const bool &profiling, const bool &print_connection)
362{
363 if (verbose>0)
364 {
365 try
366 {
367 const auto resw =
368 connection.query("SHOW WARNINGS").store();
369
370 if (resw.num_rows()>0)
371 cout << "\n" << resw.num_rows() << " Warning(s) issued:\n\n";
372
373 for (size_t i=0; i<resw.num_rows(); i++)
374 {
375 const mysqlpp::Row &roww = resw[i];
376
377 cout << roww["Level"] << '[' << roww["Code"] << "]: ";
378 cout << roww["Message"] << '\n';
379 }
380 cout << endl;
381
382 }
383 catch (const exception &e)
384 {
385 cerr << "\nSHOW WARNINGS\n\n";
386 cerr << "SQL query failed:\n" << e.what() << endl;
387 return 1;
388 }
389 }
390
391 if (profiling)
392 {
393 try
394 {
395 const auto N =
396 connection.query("SHOW PROFILES").store().num_rows();
397
398 const auto resp =
399 connection.query("SHOW PROFILE ALL FOR QUERY "+to_string(verbose?N-1:N)).store();
400
401 cout << '\n';
402 cout << left;
403 cout << setw(26) << "Status" << ' ';
404 cout << right;
405 cout << setw(11) << "Duration" << ' ';
406 cout << setw(11) << "CPU User" << ' ';
407 cout << setw(11) << "CPU System" << '\n';
408 cout << "--------------------------------------------------------------\n";
409 for (size_t i=0; i<resp.num_rows(); i++)
410 {
411 const mysqlpp::Row &rowp = resp[i];
412
413 cout << left;
414 cout << setw(26) << rowp["Status"] << ' ';
415 cout << right;
416 cout << setw(11) << rowp["Duration"] << ' ';
417 cout << setw(11) << rowp["CPU_user"] << ' ';
418 cout << setw(11) << rowp["CPU_system"] << '\n';
419 }
420 cout << "--------------------------------------------------------------\n";
421 cout << endl;
422 }
423 catch (const exception &e)
424 {
425 cerr << "\nSHOW PROFILE ALL\n\n";
426 cerr << "SQL query failed:\n" << e.what() << '\n' <<endl;
427 return 2;
428 }
429 }
430
431 if (print_connection)
432 {
433 try
434 {
435 // Exchange _send and _received as it is the view of the server
436 const auto &res1 = connection.query("SHOW STATUS LIKE 'Bytes_%'").store();
437 cout << left << setw(16) << res1[1]["Variable_name"] << ' ' << Tools::Scientific(res1[0]["Value"]) << endl;
438 cout << left << setw(16) << res1[0]["Variable_name"] << ' ' << Tools::Scientific(res1[1]["Value"]) << endl;
439 cout << endl;
440 }
441 catch (const exception &e)
442 {
443 cerr << "\nSHOW STATUS LIKE 'Bytes_%'\n\n";
444 cerr << "SQL query failed:\n" << e.what() << endl;
445 return 3;
446 }
447 }
448
449 return 0;
450
451}
452
453int main(int argc, const char* argv[])
454{
455 Time start;
456
457 gROOT->SetBatch();
458
459 Configuration conf(argv[0]);
460 conf.SetPrintUsage(PrintUsage);
461 SetupConfiguration(conf);
462
463 if (!conf.DoParse(argc, argv))
464 return 127;
465
466 // ----------------------------- Evaluate options --------------------------
467 const string uri = conf.Get<string>("uri");
468 const string out = conf.Get<string>("out");
469 const string file = conf.Get<string>("file");
470 const string tree = conf.Get<string>("tree");
471 const bool force = conf.Get<bool>("force");
472 const bool ignorenull = conf.Get<bool>("ignore-null");
473 const bool update = conf.Get<bool>("update");
474 const bool display = conf.Get<bool>("display");
475 const string write = conf.Get<string>("write");
476 const bool noout = conf.Get<bool>("null");
477 const bool nofill = conf.Get<bool>("no-fill");
478 const bool explain = conf.Get<bool>("explain");
479 const bool profiling = conf.Get<bool>("profiling");
480 const uint16_t verbose = conf.Get<uint16_t>("verbose");
481 const uint16_t compression = conf.Get<uint16_t>("compression");
482 const string delimiter = conf.Get<string>("delimiter");
483 const vector<string> _ignore = conf.Vec<string>("ignore");
484 const bool print_connection = conf.Get<bool>("print-connection");
485 //const vector<Map> mymap = conf.Vec<Map>("map");
486
487 // -------------------------------------------------------------------------
488
489 const auto vars = conf.GetWildcardOptions("var.*");
490
491 vector<string> variables;
492 for (const auto &var : vars)
493 variables.emplace_back('@'+var.substr(4)+":="+Tools::Trim(conf.Get<string>(var)));
494
495 // -------------------------------------------------------------------------
496
497 if (verbose>0)
498 {
499 cout << "\n------------------------ Rootify SQL -------------------------" << endl;
500 cout << "Start Time: " << Time::sql << Time(Time::local) << endl;
501 }
502
503 string query = conf.Get<string>("query");
504 if (query.empty())
505 {
506 if (verbose>0)
507 cout << "Reading query from file '" << file << "'." << endl;
508
509 ifstream fin(file);
510 if (!fin)
511 {
512 cerr << "Could not open query in '" << file << "': " << strerror(errno) << endl;
513 return 4;
514 }
515 getline(fin, query, (char)fin.eof());
516 }
517
518 if (query.empty())
519 {
520 cerr << "No query specified." << endl;
521 return 5;
522 }
523
524 // -------------------------------------------------------------------------
525
526 map<string, vector<string>> envs;
527
528 for (const auto &env : conf.GetWildcardOptions("env.*"))
529 envs[env.substr(4)] = conf.Vec<string>(env);
530
531 for (const auto &env : conf.GetWildcardOptions("list.*"))
532 {
533 const string fname = conf.Get<string>(env);
534 const string &ident = env.substr(5);
535
536 ifstream fin(fname);
537 if (!fin)
538 {
539 cerr << "Could not open environment in '" << fname << "' for ${" << ident << "}: " << strerror(errno) << endl;
540 return 6;
541 }
542
543 for (string line; getline(fin, line); )
544 {
545 const auto &l = Tools::Trim(line);
546 if (!l.empty() && l[0]!='#')
547 envs[ident].push_back(line);
548 }
549
550 if (verbose>0)
551 cout << "Found " << envs[ident].size() << " list element(s) for ${" << ident << "}" << endl;
552 }
553
554 for (const auto &env : envs)
555 {
556 regex rexpr("\\$(\\{"+env.first+"\\}|"+env.first+"\\b)");
557 query = regex_replace(query, rexpr, boost::join(env.second, ", "));
558 }
559
560 // -------------------------- Check for file permssion ---------------------
561 // Strictly speaking, checking for write permission and existance is not necessary,
562 // but it is convenient that the user does not find out that it failed after
563 // waiting long for the query result
564 //
565 // I am using root here instead of boost to be
566 // consistent with the access pattern by TFile
567 TString path(noout?"/dev/null":out.c_str());
568 gSystem->ExpandPathName(path);
569
570 if (!noout)
571 {
572 FileStat_t stat;
573 const Int_t exist = !gSystem->GetPathInfo(path, stat);
574 const Bool_t _write = !gSystem->AccessPathName(path, kWritePermission) && R_ISREG(stat.fMode);
575
576 if ((update && !exist) || (update && exist && !_write) || (force && exist && !_write))
577 {
578 cerr << "File '" << path << "' is not writable." << endl;
579 return 7;
580 }
581
582 if (!update && !force && exist)
583 {
584 cerr << "File '" << path << "' already exists." << endl;
585 return 8;
586 }
587 }
588
589 Time start2;
590
591 // --------------------------- Connect to database -------------------------------------------------
592
593 if (query.back()!='\n')
594 query += '\n';
595
596 if (verbose>0)
597 {
598 cout << "Connecting to database...\n";
599 cout << "Client Version: " << mysqlpp::Connection().client_version() << endl;
600 }
601
602 Database connection(uri); // Keep alive while fetching rows
603
604 if (verbose>0)
605 cout << "Server Version: " << connection.server_version() << endl;
606
607 if (print_connection)
608 {
609 try
610 {
611 const auto &res1 = connection.query("SHOW STATUS LIKE 'Compression'").store();
612 cout << "Compression of database connection is " << string(res1[0][1]) << endl;
613
614 const auto &res2 = connection.query("SHOW STATUS LIKE 'Ssl_cipher'").store();
615 cout << "Connection to databases is " << (string(res2[0][1]).empty()?"UNENCRYPTED":"ENCRYPTED ("+string(res2[0][1])+")") << endl;
616 }
617 catch (const exception &e)
618 {
619 cerr << "\nSHOW STATUS LIKE 'Compression'\n\n";
620 cerr << "SQL query failed:\n" << e.what() << endl;
621 return 9;
622 }
623 }
624
625 try
626 {
627 if (profiling)
628 connection.query("SET PROFILING=1").execute();
629 }
630 catch (const exception &e)
631 {
632 cerr << "\nSET profiling=1\n\n";
633 cerr << "SQL query failed:\n" << e.what() << endl;
634 return 10;
635 }
636
637 // -------------------------- Set user defined variables -------------------
638 if (variables.size()>0)
639 {
640 if (verbose>0)
641 cout << "Setting user defined variables..." << endl;
642
643 const string varset =
644 "SET\n "+boost::algorithm::join(variables, ",\n ");
645
646 try
647 {
648 connection.query(varset).execute();
649 }
650 catch (const exception &e)
651 {
652 cerr << '\n' << varset << "\n\n";
653 cerr << "SQL query failed:\n" << e.what() << endl;
654 return 11;
655 }
656
657 if (verbose>2)
658 cout << '\n' << varset << '\n' << endl;
659 }
660
661 // ------------------------- Explain query if requested --------------------
662
663 if (explain)
664 {
665 try
666 {
667 const auto res0 =
668 connection.query("EXPLAIN FORMAT=JSON "+query).store();
669
670 cout << res0[0][0] << endl;
671 cout << endl;
672
673 const mysqlpp::StoreQueryResult res1 =
674 connection.query("EXPLAIN "+query).store();
675
676 for (size_t i=0; i<res1.num_rows(); i++)
677 {
678 const mysqlpp::Row &row = res1[i];
679
680 cout << "\nid : " << row["id"];
681 cout << "\nselect type : " << row["select_type"];
682
683 if (!row["table"].is_null())
684 cout << "\ntable : " << row["table"];
685
686 if (!row["partitions"].is_null())
687 cout << "\npartitions : " << row["partitions"];
688
689 if (!row["key"].is_null())
690 cout << "\nselected key : " << row["key"] << " [len=" << row["key_len"] << "] out of (" << row["possible_keys"] << ")";
691
692 if (!row["type"].is_null())
693 cout << "\njoin type : " << row["type"];
694
695 //if (!row["possible_keys"].is_null())
696 // cout << "\npossible_keys: " << row["possible_keys"];
697
698 //if (!row["key_len"].is_null())
699 // cout << "\nkey_len : " << row["key_len"];
700
701 if (!row["ref"].is_null())
702 cout << "\nref : (" << row["ref"] << ") compared to the index";
703
704 if (!row["rows"].is_null())
705 cout << "\nrows : " << row["rows"];
706
707 if (!row["filtered"].is_null())
708 cout << "\nfiltered : " << row["filtered"];
709
710 if (!row["extra"].is_null())
711 cout << "\nExtra : " << row["extra"];
712
713 cout << endl;
714 }
715
716 cout << endl;
717
718 const mysqlpp::StoreQueryResult res2 =
719 connection.query("SHOW WARNINGS").store();
720
721 for (size_t i=0; i<res2.num_rows(); i++)
722 {
723 const mysqlpp::Row &row = res2[i];
724
725 // 1003 //
726 cout << row["Level"] << '[' << row["Code"] << "]:\n";
727 if (uint32_t(row["Code"])==1003)
728 format(row["Message"].c_str());
729 else
730 cout << row["Message"] << '\n' << endl;
731
732 }
733
734 }
735 catch (const exception &e)
736 {
737 cerr << '\n' << query << "\n\n";
738 cerr << "SQL query failed:\n" << e.what() << endl;
739 return 12;
740 }
741
742 return 0;
743 }
744
745 // -------------------------- Request data from database -------------------
746 if (verbose>0)
747 cout << "Requesting data..." << endl;
748
749 if (verbose>2)
750 cout << '\n' << query << endl;
751
752 const mysqlpp::UseQueryResult res =
753 connection.query(query).use();
754
755 // -------------------------------------------------------------------------
756
757 if (verbose>0)
758 {
759 cout << "Opening file '" << path << "' [compression=" << compression << "]...\n";
760 cout << "Writing data to tree '" << tree << "'" << endl;
761 }
762
763 // ----------------------------- Open output file --------------------------
764 TFile tfile(path, update?"UPDATE":(force?"RECREATE":"CREATE"), "Rootify SQL", compression);
765 if (tfile.IsZombie())
766 return 13;
767
768 // -------------------------------------------------------------------------
769
770 // get the first row to get the field description
771 mysqlpp::Row row = res.fetch_row();
772 if (!row)
773 {
774 cerr << "Empty set returned... nothing to write." << endl;
775 return finish(connection, verbose, profiling, print_connection)+20;
776 }
777
778 if (verbose>0)
779 cout << "Trying to setup " << row.size() << " branches..." << endl;
780
781 if (verbose>1)
782 cout << endl;
783
784 const mysqlpp::FieldNames &l = *row.field_list().list;
785
786 vector<double> buf(l.size());
787 vector<uint8_t> typ(l.size(),'n'); // n=number [double], d is used for DateTime
788
789 UInt_t cols = 0;
790
791
792 // -------------------- Configure branches of TTree ------------------------
793 TTree *ttree = new TTree(tree.c_str(), query.c_str());
794
795 size_t skipat = 0;
796 size_t skipreg = 0;
797 for (size_t i=0; i<l.size(); i++)
798 {
799 const string t = row[i].type().sql_name();
800
801 if (t.find("DATETIME")!=string::npos)
802 typ[i] = 'd';
803 else
804 if (t.find("DATE")!=string::npos)
805 typ[i] = 'D';
806 else
807 if (t.find("TIME")!=string::npos)
808 typ[i] = 'T';
809 else
810 if (t.find("VARCHAR")!=string::npos)
811 typ[i] = 'V';
812 else
813 if (t.find("CHAR")!=string::npos)
814 typ[i] = 'C';
815
816 bool found = false;
817 for (const auto &pattern: _ignore)
818 {
819 if (regex_match(l[i], regex(pattern)))
820 {
821 found = true;
822 typ[i] = '-';
823 skipreg++;
824 break;
825 }
826 }
827
828 if (l[i][0]=='@')
829 {
830 typ[i] = '@';
831 skipat++;
832 }
833
834 const bool use = l[i][0]!='@' && typ[i]!='V' && typ[i]!='C' && !found;
835
836 if (verbose>1)
837 cout << (use?" + ":" - ") << l[i].c_str() << " [" << t << "] {" << typ[i] << "}\n";
838
839 if (use)
840 {
841 // string name = l[i];
842 // for (const auto &m: mymap)
843 // name = boost::regex_replace(l[i], boost::regex(m.first), m.second);
844
845 ttree->Branch(l[i].c_str(), buf.data()+i);
846 cols++;
847 }
848 }
849 // -------------------------------------------------------------------------
850
851 if (verbose>1)
852 cout << endl;
853 if (verbose>0)
854 {
855 if (skipreg)
856 cout << skipreg << " branches skipped due to ignore list." << endl;
857 if (skipat)
858 cout << skipat << " branches skipped due to name starting with @." << endl;
859 cout << "Configured " << cols << " branches.\nFilling branches..." << endl;
860 }
861
862 ofstream fout(write);
863 if (!write.empty() && !fout)
864 cout << "WARNING: Writing to '" << write << "' failed: " << strerror(errno) << endl;
865
866 if (display)
867 {
868 cout << endl;
869 cout << "#";
870 for (size_t i=0; i<l.size(); i++)
871 cout << ' ' << l[i].c_str();
872 cout << endl;
873 }
874
875 if (!write.empty())
876 {
877 fout << "#";
878 for (size_t i=0; i<l.size(); i++)
879 fout << ' ' << l[i].c_str();
880 fout << endl;
881 }
882
883 // ---------------------- Fill TTree with DB data --------------------------
884 size_t count = 0;
885 size_t skip = 0;
886 do
887 {
888 count++;
889
890 ostringstream sout;
891
892 size_t idx=0;
893 for (auto col=row.begin(); col!=row.end(); col++, idx++)
894 {
895 if (display || !write.empty())
896 {
897 if (idx>0)
898 sout << (delimiter.empty()?"\t":delimiter);
899 sout << col->c_str();
900 }
901
902 if (!ignorenull && col->is_null())
903 {
904 skip++;
905 break;
906 }
907
908 switch (typ[idx])
909 {
910 case 'd':
911 buf[idx] = time_t((mysqlpp::DateTime)(*col));
912 break;
913
914 case 'D':
915 buf[idx] = time_t((mysqlpp::Date)(*col));
916 break;
917
918 case 'T':
919 buf[idx] = time_t((mysqlpp::Time)(*col));
920 break;
921
922 case 'V':
923 case 'C':
924 case '-':
925 case '@':
926 break;
927
928 default:
929 buf[idx] = atof(col->c_str());
930 }
931 }
932
933 if (idx==row.size())
934 {
935 if (!nofill)
936 ttree->Fill();
937
938 if (display)
939 cout << sout.str() << endl;
940 if (!write.empty())
941 fout << sout.str() << '\n';
942 }
943
944 row = res.fetch_row();
945
946
947 } while (row);
948
949 // -------------------------------------------------------------------------
950
951 if (display)
952 cout << '\n' << endl;
953
954 if (verbose>0)
955 {
956 cout << count << " rows fetched." << endl;
957 if (skip>0)
958 cout << skip << " rows skipped due to NULL field." << endl;
959
960 cout << ttree->GetEntries() << " rows filled into tree." << endl;
961 }
962
963 ttree->Write();
964 tfile.Close();
965
966 if (verbose>0)
967 {
968 const auto sec = Time().UnixTime()-start.UnixTime();
969
970 cout << Tools::Scientific(tfile.GetSize()) << "B written to disk.\n";
971 cout << "File closed.\n";
972 cout << "Execution time: " << sec << "s ";
973 cout << "(" << Tools::Fractional(sec/count) << "s/row)\n";
974 cout << "--------------------------------------------------------------" << endl;
975 }
976
977 return finish(connection, verbose, profiling, print_connection);
978}
Note: See TracBrowser for help on using the repository browser.