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

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