1#include <chrono>
2#include <iostream>
3
4#include <regex>
5
6#include <thread>
7#include <typeinfo>
8
9#include <thread>
10#include <typeinfo>
11
12#include "grammar.hh"
13#include "random.hh"
14#include "relmodel.hh"
15#include "schema.hh"
16
17#include "dump.hh"
18#include "dut.hh"
19#include "impedance.hh"
20#include "log.hh"
21
22#include "duckdb.hh"
23
24using namespace std;
25
26using namespace std::chrono;
27
28extern "C" {
29#include <signal.h>
30#include <stdlib.h>
31#include <unistd.h>
32}
33
34/* make the cerr logger globally accessible so we can emit one last
35 report on SIGINT */
36cerr_logger *global_cerr_logger;
37
38extern "C" void cerr_log_handler(int) {
39 if (global_cerr_logger)
40 global_cerr_logger->report();
41 exit(1);
42}
43
44int main(int argc, char *argv[]) {
45 map<string, string> options;
46 regex optregex("--(help|verbose|target|duckdb|monetdb|version|dump-"
47 "all-graphs|dump-all-queries|seed|dry-run|max-queries|rng-"
48 "state|exclude-catalog)(?:=((?:.|\n)*))?");
49
50 for (char **opt = argv + 1; opt < argv + argc; opt++) {
51 smatch match;
52 string s(*opt);
53 if (regex_match(s, match, optregex)) {
54 options[string(match[1])] = match[2];
55 } else {
56 cerr << "Cannot parse option: " << *opt << endl;
57 options["help"] = "";
58 }
59 }
60
61 if (options.count("help")) {
62 cerr << " --duckdb=URI SQLite database to send queries to" << endl
63 << " --seed=int seed RNG with specified int instead "
64 "of PID"
65 << endl
66 << " --dump-all-queries print queries as they are generated" << endl
67 << " --dump-all-graphs dump generated ASTs" << endl
68 << " --dry-run print queries instead of executing "
69 "them"
70 << endl
71 << " --exclude-catalog don't generate queries using catalog "
72 "relations"
73 << endl
74 << " --max-queries=long terminate after generating this many "
75 "queries"
76 << endl
77 << " --rng-state=string deserialize dumped rng state" << endl
78 << " --verbose emit progress output" << endl
79 << " --version print version information and exit" << endl
80 << " --help print available command line options "
81 "and exit"
82 << endl;
83 return 0;
84 } else if (options.count("version")) {
85 return 0;
86 }
87
88 try {
89 shared_ptr<schema> schema;
90 if (options.count("duckdb")) {
91 schema = make_shared<schema_duckdb>(options["duckdb"], options.count("exclude-catalog"));
92 } else {
93 cerr << "No DuckDB database specified!" << endl;
94 return 1;
95 }
96
97 scope scope;
98 long queries_generated = 0;
99 schema->fill_scope(scope);
100
101 if (options.count("rng-state")) {
102 istringstream(options["rng-state"]) >> smith::rng;
103 } else {
104 smith::rng.seed(options.count("seed") ? stoi(options["seed"]) : getpid());
105 }
106
107 vector<shared_ptr<logger>> loggers;
108
109 loggers.push_back(make_shared<impedance_feedback>());
110
111 if (options.count("verbose")) {
112 auto l = make_shared<cerr_logger>();
113 global_cerr_logger = &*l;
114 loggers.push_back(l);
115 signal(SIGINT, cerr_log_handler);
116 }
117
118 if (options.count("dump-all-graphs"))
119 loggers.push_back(make_shared<ast_logger>());
120
121 if (options.count("dump-all-queries"))
122 loggers.push_back(make_shared<query_dumper>());
123
124 if (options.count("dry-run")) {
125 while (1) {
126 shared_ptr<prod> gen = statement_factory(&scope);
127 gen->out(cout);
128 for (auto l : loggers)
129 l->generated(*gen);
130 cout << ";" << endl;
131 queries_generated++;
132
133 if (options.count("max-queries") && (queries_generated >= stol(options["max-queries"])))
134 return 0;
135 }
136 }
137
138 shared_ptr<dut_base> dut;
139
140 if (options.count("duckdb")) {
141 dut = make_shared<dut_duckdb>(options["duckdb"]);
142 } else {
143 cerr << "No DuckDB database specified!" << endl;
144 return 1;
145 }
146
147 cerr << "Running queries..." << endl;
148
149 ofstream complete_log;
150 complete_log.open("sqlsmith.complete.log");
151 while (1) /* Loop to recover connection loss */
152 {
153 try {
154 while (1) { /* Main loop */
155
156 if (options.count("max-queries") && (++queries_generated > stol(options["max-queries"]))) {
157 if (global_cerr_logger)
158 global_cerr_logger->report();
159 return 0;
160 }
161
162 /* Invoke top-level production to generate AST */
163 shared_ptr<prod> gen = statement_factory(&scope);
164
165 for (auto l : loggers)
166 l->generated(*gen);
167
168 /* Generate SQL from AST */
169 ostringstream s;
170 gen->out(s);
171
172 // write the query to the complete log that has all the
173 // queries
174 complete_log << s.str() << endl;
175 complete_log.flush();
176
177 // write the last-executed query to a separate log file
178 ofstream out_file;
179 out_file.open("sqlsmith.log");
180 out_file << s.str() << endl;
181 out_file.close();
182
183 /* Try to execute it */
184 try {
185 dut->test(s.str());
186 for (auto l : loggers)
187 l->executed(*gen);
188 } catch (const dut::failure &e) {
189 for (auto l : loggers)
190 try {
191 l->error(*gen, e);
192 } catch (runtime_error &e) {
193 cerr << endl << "log failed: " << typeid(*l).name() << ": " << e.what() << endl;
194 }
195 if ((dynamic_cast<const dut::broken *>(&e))) {
196 /* re-throw to outer loop to recover session. */
197 throw;
198 }
199 }
200 }
201 } catch (const dut::broken &e) {
202 /* Give server some time to recover. */
203 this_thread::sleep_for(milliseconds(1000));
204 }
205 }
206 } catch (const exception &e) {
207 cerr << e.what() << endl;
208 return 1;
209 }
210}
211