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

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