1 | #include "duckdb.hh" |
2 | #include "dbgen.hpp" |
3 | |
4 | #include <cassert> |
5 | #include <cstring> |
6 | #include <iostream> |
7 | #include <stdexcept> |
8 | #include <thread> |
9 | #include <chrono> |
10 | |
11 | #include <regex> |
12 | |
13 | using namespace duckdb; |
14 | using namespace std; |
15 | |
16 | static regex e_syntax("Query Error: syntax error at or near .*" ); |
17 | |
18 | duckdb_connection::duckdb_connection(string &conninfo) { |
19 | // in-memory database |
20 | database = make_unique<DuckDB>(nullptr); |
21 | connection = make_unique<Connection>(*database); |
22 | } |
23 | |
24 | void duckdb_connection::q(const char *query) { |
25 | auto result = connection->Query(query); |
26 | if (!result->success) { |
27 | throw runtime_error(result->error); |
28 | } |
29 | } |
30 | |
31 | schema_duckdb::schema_duckdb(std::string &conninfo, bool no_catalog) : duckdb_connection(conninfo) { |
32 | // generate empty TPC-H schema |
33 | tpch::dbgen(0, *database); |
34 | |
35 | cerr << "Loading tables..." ; |
36 | auto result = connection->Query("SELECT * FROM sqlite_master() WHERE type IN ('table', 'view')" ); |
37 | if (!result->success) { |
38 | throw runtime_error(result->error); |
39 | } |
40 | for (size_t i = 0; i < result->collection.count; i++) { |
41 | auto type = result->collection.GetValue(0, i).str_value; |
42 | auto name = result->collection.GetValue(2, i).str_value; |
43 | bool view = type == "view" ; |
44 | table tab(name, "main" , !view, !view); |
45 | tables.push_back(tab); |
46 | } |
47 | cerr << "done." << endl; |
48 | |
49 | if (tables.size() == 0) { |
50 | throw std::runtime_error("No tables available in catalog!" ); |
51 | } |
52 | |
53 | cerr << "Loading columns and constraints..." ; |
54 | |
55 | for (auto t = tables.begin(); t != tables.end(); ++t) { |
56 | result = connection->Query("PRAGMA table_info('" + t->name + "')" ); |
57 | if (!result->success) { |
58 | throw runtime_error(result->error); |
59 | } |
60 | for (size_t i = 0; i < result->collection.count; i++) { |
61 | auto name = result->collection.GetValue(1, i).str_value; |
62 | auto type = result->collection.GetValue(2, i).str_value; |
63 | column c(name, sqltype::get(type)); |
64 | t->columns().push_back(c); |
65 | } |
66 | } |
67 | |
68 | cerr << "done." << endl; |
69 | |
70 | #define BINOP(n, t) \ |
71 | do { \ |
72 | op o(#n, sqltype::get(#t), sqltype::get(#t), sqltype::get(#t)); \ |
73 | register_operator(o); \ |
74 | } while (0) |
75 | |
76 | // BINOP(||, TEXT); |
77 | BINOP(*, INTEGER); |
78 | BINOP(/, INTEGER); |
79 | |
80 | BINOP(+, INTEGER); |
81 | BINOP(-, INTEGER); |
82 | |
83 | // BINOP(>>, INTEGER); |
84 | // BINOP(<<, INTEGER); |
85 | |
86 | // BINOP(&, INTEGER); |
87 | // BINOP(|, INTEGER); |
88 | |
89 | BINOP(<, INTEGER); |
90 | BINOP(<=, INTEGER); |
91 | BINOP(>, INTEGER); |
92 | BINOP(>=, INTEGER); |
93 | |
94 | BINOP(=, INTEGER); |
95 | BINOP(<>, INTEGER); |
96 | BINOP(IS, INTEGER); |
97 | BINOP(IS NOT, INTEGER); |
98 | |
99 | BINOP(AND, INTEGER); |
100 | BINOP(OR, INTEGER); |
101 | |
102 | #define FUNC(n, r) \ |
103 | do { \ |
104 | routine proc("", "", sqltype::get(#r), #n); \ |
105 | register_routine(proc); \ |
106 | } while (0) |
107 | |
108 | #define FUNC1(n, r, a) \ |
109 | do { \ |
110 | routine proc("", "", sqltype::get(#r), #n); \ |
111 | proc.argtypes.push_back(sqltype::get(#a)); \ |
112 | register_routine(proc); \ |
113 | } while (0) |
114 | |
115 | #define FUNC2(n, r, a, b) \ |
116 | do { \ |
117 | routine proc("", "", sqltype::get(#r), #n); \ |
118 | proc.argtypes.push_back(sqltype::get(#a)); \ |
119 | proc.argtypes.push_back(sqltype::get(#b)); \ |
120 | register_routine(proc); \ |
121 | } while (0) |
122 | |
123 | #define FUNC3(n, r, a, b, c) \ |
124 | do { \ |
125 | routine proc("", "", sqltype::get(#r), #n); \ |
126 | proc.argtypes.push_back(sqltype::get(#a)); \ |
127 | proc.argtypes.push_back(sqltype::get(#b)); \ |
128 | proc.argtypes.push_back(sqltype::get(#c)); \ |
129 | register_routine(proc); \ |
130 | } while (0) |
131 | |
132 | // FUNC(last_insert_rowid, INTEGER); |
133 | // FUNC(random, INTEGER); |
134 | // FUNC(sqlite_source_id, TEXT); |
135 | // FUNC(sqlite_version, TEXT); |
136 | // FUNC(total_changes, INTEGER); |
137 | |
138 | FUNC1(abs, INTEGER, REAL); |
139 | // FUNC1(hex, TEXT, TEXT); |
140 | // FUNC1(length, INTEGER, TEXT); |
141 | // FUNC1(lower, TEXT, TEXT); |
142 | // FUNC1(ltrim, TEXT, TEXT); |
143 | // FUNC1(quote, TEXT, TEXT); |
144 | // FUNC1(randomblob, TEXT, INTEGER); |
145 | // FUNC1(round, INTEGER, REAL); |
146 | // FUNC1(rtrim, TEXT, TEXT); |
147 | // FUNC1(soundex, TEXT, TEXT); |
148 | // FUNC1(sqlite_compileoption_get, TEXT, INTEGER); |
149 | // FUNC1(sqlite_compileoption_used, INTEGER, TEXT); |
150 | // FUNC1(trim, TEXT, TEXT); |
151 | // FUNC1(typeof, TEXT, INTEGER); |
152 | // FUNC1(typeof, TEXT, NUMERIC); |
153 | // FUNC1(typeof, TEXT, REAL); |
154 | // FUNC1(typeof, TEXT, TEXT); |
155 | // FUNC1(unicode, INTEGER, TEXT); |
156 | // FUNC1(upper, TEXT, TEXT); |
157 | // FUNC1(zeroblob, TEXT, INTEGER); |
158 | |
159 | // FUNC2(glob, INTEGER, TEXT, TEXT); |
160 | // FUNC2(instr, INTEGER, TEXT, TEXT); |
161 | // FUNC2(like, INTEGER, TEXT, TEXT); |
162 | // FUNC2(ltrim, TEXT, TEXT, TEXT); |
163 | // FUNC2(rtrim, TEXT, TEXT, TEXT); |
164 | // FUNC2(trim, TEXT, TEXT, TEXT); |
165 | // FUNC2(round, INTEGER, REAL, INTEGER); |
166 | // FUNC2(substr, TEXT, TEXT, INTEGER); |
167 | |
168 | // FUNC3(substr, TEXT, TEXT, INTEGER, INTEGER); |
169 | // FUNC3(replace, TEXT, TEXT, TEXT, TEXT); |
170 | |
171 | #define AGG(n, r, a) \ |
172 | do { \ |
173 | routine proc("", "", sqltype::get(#r), #n); \ |
174 | proc.argtypes.push_back(sqltype::get(#a)); \ |
175 | register_aggregate(proc); \ |
176 | } while (0) |
177 | |
178 | AGG(avg, INTEGER, INTEGER); |
179 | AGG(avg, REAL, REAL); |
180 | AGG(count, INTEGER, REAL); |
181 | AGG(count, INTEGER, TEXT); |
182 | AGG(count, INTEGER, INTEGER); |
183 | // AGG(group_concat, TEXT, TEXT); |
184 | AGG(max, REAL, REAL); |
185 | AGG(max, INTEGER, INTEGER); |
186 | AGG(min, REAL, REAL); |
187 | AGG(min, INTEGER, INTEGER); |
188 | AGG(sum, REAL, REAL); |
189 | AGG(sum, INTEGER, INTEGER); |
190 | // AGG(total, REAL, INTEGER); |
191 | // AGG(total, REAL, REAL); |
192 | |
193 | booltype = sqltype::get("INTEGER" ); |
194 | inttype = sqltype::get("INTEGER" ); |
195 | |
196 | internaltype = sqltype::get("internal" ); |
197 | arraytype = sqltype::get("ARRAY" ); |
198 | |
199 | true_literal = "1" ; |
200 | false_literal = "0" ; |
201 | |
202 | generate_indexes(); |
203 | } |
204 | |
205 | dut_duckdb::dut_duckdb(std::string &conninfo) : duckdb_connection(conninfo) { |
206 | cerr << "Generating TPC-H..." ; |
207 | tpch::dbgen(0.1, *database); |
208 | cerr << "done." << endl; |
209 | // q("PRAGMA main.auto_vacuum = 2"); |
210 | } |
211 | |
212 | volatile bool is_active = false; |
213 | // timeout is 10ms * TIMEOUT_TICKS |
214 | #define TIMEOUT_TICKS 50 |
215 | |
216 | void sleep_thread(Connection *connection) { |
217 | for (size_t i = 0; i < TIMEOUT_TICKS && is_active; i++) { |
218 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); |
219 | } |
220 | if (is_active) { |
221 | connection->Interrupt(); |
222 | } |
223 | } |
224 | |
225 | void dut_duckdb::test(const std::string &stmt) { |
226 | is_active = true; |
227 | thread interrupt_thread(sleep_thread, connection.get()); |
228 | auto result = connection->Query(stmt); |
229 | is_active = false; |
230 | interrupt_thread.join(); |
231 | |
232 | if (!result->success) { |
233 | auto error = result->error.c_str(); |
234 | try { |
235 | if (regex_match(error, e_syntax)) |
236 | throw dut::syntax(error); |
237 | else |
238 | throw dut::failure(error); |
239 | } catch (dut::failure &e) { |
240 | throw; |
241 | } |
242 | } |
243 | } |
244 | |