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

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