1 | #include "catch.hpp" |
2 | #include "test_helpers.hpp" |
3 | #include "duckdb/common/file_system.hpp" |
4 | |
5 | using namespace duckdb; |
6 | using namespace std; |
7 | |
8 | TEST_CASE("Test simple relation API" , "[relation_api]" ) { |
9 | DuckDB db(nullptr); |
10 | Connection con(db); |
11 | con.EnableQueryVerification(); |
12 | unique_ptr<QueryResult> result; |
13 | shared_ptr<Relation> tbl, filter, proj, proj2, v1, v2, v3; |
14 | |
15 | // create some tables |
16 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)" )); |
17 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)" )); |
18 | |
19 | // simple projection |
20 | REQUIRE_NOTHROW(tbl = con.Table("integers" )); |
21 | REQUIRE_NOTHROW(result = tbl->Project("i + 1" )->Execute()); |
22 | REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4})); |
23 | |
24 | // we support * expressions |
25 | REQUIRE_NOTHROW(result = tbl->Project("*" )->Execute()); |
26 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
27 | |
28 | // we can also read the table directly |
29 | REQUIRE_NOTHROW(result = tbl->Execute()); |
30 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
31 | |
32 | // we can stack projections |
33 | REQUIRE_NOTHROW( |
34 | result = |
35 | tbl->Project("i + 1 AS i" )->Project("i + 1 AS i" )->Project("i + 1 AS i" )->Project("i + 1 AS i" )->Execute()); |
36 | REQUIRE(CHECK_COLUMN(result, 0, {5, 6, 7})); |
37 | |
38 | // we can execute the same projection multiple times |
39 | REQUIRE_NOTHROW(proj = tbl->Project("i + 1" )); |
40 | for (idx_t i = 0; i < 10; i++) { |
41 | REQUIRE_NOTHROW(result = proj->Execute()); |
42 | REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4})); |
43 | } |
44 | |
45 | result = con.Query("SELECT i+1 FROM (SELECT * FROM integers WHERE i <> 2) relation" ); |
46 | REQUIRE(CHECK_COLUMN(result, 0, {2, 4})); |
47 | // filter and projection |
48 | REQUIRE_NOTHROW(filter = tbl->Filter("i <> 2" )); |
49 | REQUIRE_NOTHROW(proj = filter->Project("i + 1" )); |
50 | REQUIRE_NOTHROW(result = proj->Execute()); |
51 | REQUIRE(CHECK_COLUMN(result, 0, {2, 4})); |
52 | |
53 | // we can reuse the same filter again and perform a different projection |
54 | REQUIRE_NOTHROW(proj = filter->Project("i * 10" )); |
55 | REQUIRE_NOTHROW(result = proj->Execute()); |
56 | REQUIRE(CHECK_COLUMN(result, 0, {10, 30})); |
57 | |
58 | // add a limit |
59 | REQUIRE_NOTHROW(result = proj->Limit(1)->Execute()); |
60 | REQUIRE(CHECK_COLUMN(result, 0, {10})); |
61 | |
62 | // and an offset |
63 | REQUIRE_NOTHROW(result = proj->Limit(1, 1)->Execute()); |
64 | REQUIRE(CHECK_COLUMN(result, 0, {30})); |
65 | |
66 | // lets add some aliases |
67 | REQUIRE_NOTHROW(proj = filter->Project("i + 1 AS a" )); |
68 | REQUIRE_NOTHROW(result = proj->Execute()); |
69 | REQUIRE(CHECK_COLUMN(result, 0, {2, 4})); |
70 | // we can check the column names |
71 | REQUIRE(proj->Columns()[0].name == "a" ); |
72 | REQUIRE(proj->Columns()[0].type == SQLType::INTEGER); |
73 | |
74 | // we can also alias like this |
75 | REQUIRE_NOTHROW(proj = filter->Project("i + 1" , "a" )); |
76 | REQUIRE_NOTHROW(result = proj->Execute()); |
77 | REQUIRE(CHECK_COLUMN(result, 0, {2, 4})); |
78 | // we can check the column names |
79 | REQUIRE(proj->Columns()[0].name == "a" ); |
80 | REQUIRE(proj->Columns()[0].type == SQLType::INTEGER); |
81 | |
82 | // now we can use that column to perform additional projections |
83 | REQUIRE_NOTHROW(result = proj->Project("a + 1" )->Execute()); |
84 | REQUIRE(CHECK_COLUMN(result, 0, {3, 5})); |
85 | |
86 | // we can also filter on this again |
87 | REQUIRE_NOTHROW(result = proj->Filter("a=2" )->Execute()); |
88 | REQUIRE(CHECK_COLUMN(result, 0, {2})); |
89 | |
90 | // filters can also contain conjunctions |
91 | REQUIRE_NOTHROW(result = proj->Filter("a=2 OR a=4" )->Execute()); |
92 | REQUIRE(CHECK_COLUMN(result, 0, {2, 4})); |
93 | |
94 | // alias |
95 | REQUIRE_NOTHROW(result = proj->Project("a + 1" )->Alias("bla" )->Execute()); |
96 | REQUIRE(CHECK_COLUMN(result, 0, {3, 5})); |
97 | |
98 | // now test ordering |
99 | REQUIRE_NOTHROW(result = proj->Order("a DESC" )->Execute()); |
100 | REQUIRE(CHECK_COLUMN(result, 0, {4, 2})); |
101 | |
102 | // top n |
103 | REQUIRE_NOTHROW(result = proj->Order("a" )->Limit(1)->Execute()); |
104 | REQUIRE(CHECK_COLUMN(result, 0, {2})); |
105 | REQUIRE_NOTHROW(result = proj->Order("a DESC" )->Limit(1)->Execute()); |
106 | REQUIRE(CHECK_COLUMN(result, 0, {4})); |
107 | |
108 | // test set operations |
109 | REQUIRE_NOTHROW(result = tbl->Union(tbl)->Order("i" )->Execute()); |
110 | REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 2, 2, 3, 3})); |
111 | REQUIRE_NOTHROW(result = tbl->Except(tbl)->Order("i" )->Execute()); |
112 | REQUIRE(CHECK_COLUMN(result, 0, {})); |
113 | REQUIRE_NOTHROW(result = tbl->Intersect(tbl)->Order("i" )->Execute()); |
114 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
115 | REQUIRE_NOTHROW(result = tbl->Except(tbl->Filter("i=2" ))->Order("i" )->Execute()); |
116 | REQUIRE(CHECK_COLUMN(result, 0, {1, 3})); |
117 | REQUIRE_NOTHROW(result = tbl->Intersect(tbl->Filter("i=2" ))->Order("i" )->Execute()); |
118 | REQUIRE(CHECK_COLUMN(result, 0, {2})); |
119 | |
120 | // set operations with projections |
121 | REQUIRE_NOTHROW(proj = tbl->Project("i::TINYINT AS i, i::SMALLINT, i::BIGINT, i::VARCHAR" )); |
122 | REQUIRE_NOTHROW(proj2 = tbl->Project("(i+10)::TINYINT, (i+10)::SMALLINT, (i+10)::BIGINT, (i+10)::VARCHAR" )); |
123 | REQUIRE_NOTHROW(result = proj->Union(proj2)->Order("i" )->Execute()); |
124 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3, 11, 12, 13})); |
125 | REQUIRE(CHECK_COLUMN(result, 1, {1, 2, 3, 11, 12, 13})); |
126 | REQUIRE(CHECK_COLUMN(result, 2, {1, 2, 3, 11, 12, 13})); |
127 | REQUIRE(CHECK_COLUMN(result, 3, {"1" , "2" , "3" , "11" , "12" , "13" })); |
128 | |
129 | // distinct |
130 | REQUIRE_NOTHROW(result = tbl->Union(tbl)->Union(tbl)->Distinct()->Order("1" )->Execute()); |
131 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
132 | |
133 | // join |
134 | REQUIRE_NOTHROW(v1 = con.Values({{1, 10}, {2, 5}, {3, 4}}, {"id" , "j" }, "v1" )); |
135 | REQUIRE_NOTHROW(v2 = con.Values({{1, 27}, {2, 8}, {3, 20}}, {"id" , "k" }, "v2" )); |
136 | REQUIRE_NOTHROW(v3 = con.Values({{1, 2}, {2, 6}, {3, 10}}, {"id" , "k" }, "v3" )); |
137 | REQUIRE_NOTHROW(result = v1->Join(v2, "v1.id=v2.id" )->Execute()); |
138 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
139 | REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4})); |
140 | REQUIRE(CHECK_COLUMN(result, 2, {1, 2, 3})); |
141 | REQUIRE(CHECK_COLUMN(result, 3, {27, 8, 20})); |
142 | |
143 | // projection after a join |
144 | REQUIRE_NOTHROW(result = v1->Join(v2, "v1.id=v2.id" )->Project("v1.id+v2.id, j+k" )->Execute()); |
145 | REQUIRE(CHECK_COLUMN(result, 0, {2, 4, 6})); |
146 | REQUIRE(CHECK_COLUMN(result, 1, {37, 13, 24})); |
147 | |
148 | // chain multiple joins |
149 | auto multi_join = v1->Join(v2, "v1.id=v2.id" )->Join(v3, "v1.id=v3.id" ); |
150 | REQUIRE_NOTHROW(result = multi_join->Project("v1.id+v2.id+v3.id" )->Execute()); |
151 | REQUIRE(CHECK_COLUMN(result, 0, {3, 6, 9})); |
152 | |
153 | // multiple joins followed by a filter and a projection |
154 | REQUIRE_NOTHROW(result = multi_join->Filter("v1.id=1" )->Project("v1.id+v2.id+v3.id" )->Execute()); |
155 | REQUIRE(CHECK_COLUMN(result, 0, {3})); |
156 | // multiple joins followed by multiple filters |
157 | REQUIRE_NOTHROW(result = multi_join->Filter("v1.id>0" ) |
158 | ->Filter("v2.id < 3" ) |
159 | ->Filter("v3.id=2" ) |
160 | ->Project("v1.id+v2.id+v3.id" ) |
161 | ->Execute()); |
162 | REQUIRE(CHECK_COLUMN(result, 0, {6})); |
163 | |
164 | // test explain |
165 | REQUIRE_NO_FAIL(multi_join->Explain()); |
166 | } |
167 | |
168 | TEST_CASE("Test combinations of set operations" , "[relation_api]" ) { |
169 | DuckDB db(nullptr); |
170 | Connection con(db); |
171 | con.EnableQueryVerification(); |
172 | unique_ptr<QueryResult> result; |
173 | shared_ptr<Relation> values, v1, v2, v3; |
174 | |
175 | REQUIRE_NOTHROW(values = con.Values({{1, 10}, {2, 5}, {3, 4}}, {"i" , "j" })); |
176 | |
177 | // union between values |
178 | auto vunion = values->Union(values); |
179 | REQUIRE_NOTHROW(result = vunion->Order("i" )->Execute()); |
180 | REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 2, 2, 3, 3})); |
181 | REQUIRE(CHECK_COLUMN(result, 1, {10, 10, 5, 5, 4, 4})); |
182 | |
183 | // different ops after a union |
184 | // order and limit |
185 | REQUIRE_NOTHROW(result = vunion->Order("i" )->Limit(1)->Execute()); |
186 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
187 | REQUIRE(CHECK_COLUMN(result, 1, {10})); |
188 | // multiple orders and limits |
189 | REQUIRE_NOTHROW(result = vunion->Order("i" )->Limit(4)->Order("j" )->Limit(2)->Execute()); |
190 | REQUIRE(CHECK_COLUMN(result, 0, {2, 2})); |
191 | REQUIRE(CHECK_COLUMN(result, 1, {5, 5})); |
192 | // filter |
193 | REQUIRE_NOTHROW(result = vunion->Filter("i=1" )->Execute()); |
194 | REQUIRE(CHECK_COLUMN(result, 0, {1, 1})); |
195 | REQUIRE(CHECK_COLUMN(result, 1, {10, 10})); |
196 | // multiple filters |
197 | REQUIRE_NOTHROW(result = vunion->Filter("i<3" )->Filter("j=5" )->Execute()); |
198 | REQUIRE(CHECK_COLUMN(result, 0, {2, 2})); |
199 | REQUIRE(CHECK_COLUMN(result, 1, {5, 5})); |
200 | // distinct |
201 | REQUIRE_NOTHROW(result = vunion->Distinct()->Order("j DESC" )->Execute()); |
202 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
203 | REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4})); |
204 | // multiple distincts followed by a top-n |
205 | REQUIRE_NOTHROW(result = vunion->Distinct()->Distinct()->Distinct()->Order("j DESC" )->Limit(2)->Execute()); |
206 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2})); |
207 | REQUIRE(CHECK_COLUMN(result, 1, {10, 5})); |
208 | // top-n followed by multiple distincts |
209 | REQUIRE_NOTHROW(result = vunion->Order("j DESC" )->Limit(2)->Distinct()->Distinct()->Distinct()->Execute()); |
210 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
211 | REQUIRE(CHECK_COLUMN(result, 1, {10})); |
212 | |
213 | // multiple set ops |
214 | REQUIRE_NOTHROW(result = vunion->Union(vunion)->Distinct()->Order("1" )->Execute()); |
215 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
216 | REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4})); |
217 | REQUIRE_NOTHROW(result = vunion->Intersect(vunion)->Order("1" )->Execute()); |
218 | REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 2, 2, 3, 3})); |
219 | REQUIRE(CHECK_COLUMN(result, 1, {10, 10, 5, 5, 4, 4})); |
220 | REQUIRE_NOTHROW(result = vunion->Except(vunion)->Execute()); |
221 | REQUIRE(CHECK_COLUMN(result, 0, {})); |
222 | REQUIRE(CHECK_COLUMN(result, 1, {})); |
223 | |
224 | // setops require the same amount of columns on both sides |
225 | REQUIRE_NOTHROW(v1 = con.Values("(1, 2), (3, 4)" )); |
226 | REQUIRE_NOTHROW(v2 = con.Values("(1)" )); |
227 | REQUIRE_THROWS(v1->Union(v2)->Execute()); |
228 | |
229 | // setops require the same types on both sides |
230 | REQUIRE_NOTHROW(v1 = con.Values("(DATE '1992-01-01', 2)" )); |
231 | REQUIRE_NOTHROW(v2 = con.Values("(3.0, 'hello')" )); |
232 | REQUIRE_FAIL(v1->Union(v2)->Execute()); |
233 | } |
234 | |
235 | TEST_CASE("Test combinations of joins" , "[relation_api]" ) { |
236 | DuckDB db(nullptr); |
237 | Connection con(db); |
238 | con.EnableQueryVerification(); |
239 | unique_ptr<QueryResult> result; |
240 | shared_ptr<Relation> values, vjoin; |
241 | |
242 | REQUIRE_NOTHROW(values = con.Values({{1, 10}, {2, 5}, {3, 4}}, {"i" , "j" })); |
243 | |
244 | auto v1 = values->Alias("v1" ); |
245 | auto v2 = values->Alias("v2" ); |
246 | |
247 | // join on explicit join condition |
248 | vjoin = v1->Join(v2, "v1.i=v2.i" ); |
249 | REQUIRE_NOTHROW(result = vjoin->Order("v1.i" )->Execute()); |
250 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
251 | REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4})); |
252 | REQUIRE(CHECK_COLUMN(result, 2, {1, 2, 3})); |
253 | REQUIRE(CHECK_COLUMN(result, 3, {10, 5, 4})); |
254 | |
255 | // aggregate on EXPLICIT join |
256 | REQUIRE_NOTHROW(result = vjoin->Aggregate("SUM(v1.i) + SUM(v2.i), SUM(v1.j) + SUM(v2.j)" )->Execute()); |
257 | REQUIRE(CHECK_COLUMN(result, 0, {12})); |
258 | REQUIRE(CHECK_COLUMN(result, 1, {38})); |
259 | |
260 | // implicit join |
261 | vjoin = v1->Join(v2, "i" ); |
262 | REQUIRE_NOTHROW(result = vjoin->Order("i" )->Execute()); |
263 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
264 | REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4})); |
265 | REQUIRE(CHECK_COLUMN(result, 2, {10, 5, 4})); |
266 | |
267 | // implicit join on multiple columns |
268 | vjoin = v1->Join(v2, "i, j" ); |
269 | REQUIRE_NOTHROW(result = vjoin->Order("i" )->Execute()); |
270 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
271 | REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4})); |
272 | |
273 | // aggregate on USING join |
274 | REQUIRE_NOTHROW(result = vjoin->Aggregate("SUM(i), SUM(j)" )->Execute()); |
275 | REQUIRE(CHECK_COLUMN(result, 0, {6})); |
276 | REQUIRE(CHECK_COLUMN(result, 1, {19})); |
277 | |
278 | // joining on a column that doesn't exist results in an error |
279 | REQUIRE_THROWS(v1->Join(v2, "blabla" )); |
280 | // also with explicit join condition |
281 | REQUIRE_THROWS(v1->Join(v2, "v1.i=v2.blabla" )); |
282 | |
283 | // set ops involving joins |
284 | REQUIRE_NOTHROW(result = vjoin->Union(vjoin)->Distinct()->Order("i" )->Execute()); |
285 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
286 | REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4})); |
287 | |
288 | // do a bunch of joins in a loop |
289 | auto v1tmp = v1; |
290 | auto v2tmp = v2; |
291 | for (idx_t i = 0; i < 4; i++) { |
292 | REQUIRE_NOTHROW(v1tmp = v1tmp->Join(v2tmp->Alias(to_string(i)), "i, j" )); |
293 | } |
294 | REQUIRE_NOTHROW(result = v1tmp->Order("i" )->Execute()); |
295 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
296 | REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4})); |
297 | |
298 | // now add on some projections and such |
299 | auto complex_join = v1tmp->Order("i" )->Limit(2)->Order("i DESC" )->Limit(1)->Project("i+1, j+1" ); |
300 | for (idx_t i = 0; i < 3; i++) { |
301 | REQUIRE_NOTHROW(result = complex_join->Execute()); |
302 | REQUIRE(CHECK_COLUMN(result, 0, {3})); |
303 | REQUIRE(CHECK_COLUMN(result, 1, {6})); |
304 | } |
305 | |
306 | // create and query a view |
307 | REQUIRE_NOTHROW(complex_join->CreateView("test123" )); |
308 | result = con.Query("SELECT * FROM test123" ); |
309 | REQUIRE(CHECK_COLUMN(result, 0, {3})); |
310 | REQUIRE(CHECK_COLUMN(result, 1, {6})); |
311 | |
312 | // joins of tables that have output modifiers attached to them (limit, order by, distinct) |
313 | auto v1_modified = v1->Limit(100)->Order("1" )->Distinct()->Filter("i<1000" ); |
314 | auto v2_modified = v2->Limit(100)->Order("1" )->Distinct()->Filter("i<1000" ); |
315 | vjoin = v1_modified->Join(v2_modified, "i, j" ); |
316 | REQUIRE_NOTHROW(result = vjoin->Order("i" )->Execute()); |
317 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
318 | REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4})); |
319 | } |
320 | |
321 | TEST_CASE("Test view creation of relations" , "[relation_api]" ) { |
322 | DuckDB db(nullptr); |
323 | Connection con(db); |
324 | con.EnableQueryVerification(); |
325 | unique_ptr<QueryResult> result; |
326 | shared_ptr<Relation> tbl, filter, proj, proj2; |
327 | |
328 | // create some tables |
329 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)" )); |
330 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)" )); |
331 | |
332 | // simple view creation |
333 | REQUIRE_NOTHROW(tbl = con.Table("integers" )); |
334 | REQUIRE_NOTHROW(result = tbl->Query("test" , "SELECT * FROM test" )); |
335 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
336 | |
337 | // add a projection |
338 | REQUIRE_NOTHROW(result = tbl->Project("i + 1" )->Query("test" , "SELECT * FROM test" )); |
339 | REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4})); |
340 | |
341 | // multiple projections |
342 | proj = tbl->Project("i + 1" , "i" ); |
343 | for (idx_t i = 0; i < 10; i++) { |
344 | proj = proj->Project("i + 1" , "i" ); |
345 | } |
346 | REQUIRE_NOTHROW(result = proj->Execute()); |
347 | REQUIRE(CHECK_COLUMN(result, 0, {12, 13, 14})); |
348 | REQUIRE_NOTHROW(result = proj->Query("test" , "SELECT * FROM test" )); |
349 | REQUIRE(CHECK_COLUMN(result, 0, {12, 13, 14})); |
350 | |
351 | // we can also use more complex SQL |
352 | REQUIRE_NOTHROW(result = proj->Query("test" , "SELECT SUM(t1.i) FROM test t1 JOIN test t2 ON t1.i=t2.i" )); |
353 | REQUIRE(CHECK_COLUMN(result, 0, {39})); |
354 | |
355 | // limit |
356 | REQUIRE_NOTHROW(result = tbl->Limit(1)->Query("test" , "SELECT * FROM test" )); |
357 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
358 | // order |
359 | REQUIRE_NOTHROW(result = tbl->Order("i DESC" )->Limit(1)->Query("test" , "SELECT * FROM test" )); |
360 | REQUIRE(CHECK_COLUMN(result, 0, {3})); |
361 | // union |
362 | auto node = tbl->Order("i DESC" )->Limit(1); |
363 | REQUIRE_NOTHROW(result = node->Union(node)->Query("test" , "SELECT * FROM test" )); |
364 | REQUIRE(CHECK_COLUMN(result, 0, {3, 3})); |
365 | // distinct |
366 | REQUIRE_NOTHROW(result = node->Union(node)->Distinct()->Query("test" , "SELECT * FROM test" )); |
367 | REQUIRE(CHECK_COLUMN(result, 0, {3})); |
368 | |
369 | // manually create views and query from them |
370 | result = con.Query("SELECT i+1 FROM integers UNION SELECT i+10 FROM integers ORDER BY 1" ); |
371 | REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4, 11, 12, 13})); |
372 | |
373 | tbl->Project("i + 1" )->CreateView("test1" ); |
374 | tbl->Project("i + 10" )->CreateView("test2" ); |
375 | result = con.Query("SELECT * FROM test1 UNION SELECT * FROM test2 ORDER BY 1" ); |
376 | REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4, 11, 12, 13})); |
377 | } |
378 | |
379 | TEST_CASE("Test table creations using the relation API" , "[relation_api]" ) { |
380 | DuckDB db(nullptr); |
381 | Connection con(db); |
382 | con.EnableQueryVerification(); |
383 | unique_ptr<QueryResult> result; |
384 | shared_ptr<Relation> values; |
385 | |
386 | // create a table from a Values statement |
387 | REQUIRE_NOTHROW(values = con.Values({{1, 10}, {2, 5}, {3, 4}}, {"i" , "j" })); |
388 | REQUIRE_NOTHROW(values->Create("integers" )); |
389 | |
390 | result = con.Query("SELECT * FROM integers ORDER BY i" ); |
391 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); |
392 | REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4})); |
393 | |
394 | // insert from a set of values |
395 | REQUIRE_NOTHROW(con.Values({{4, 7}, {5, 8}})->Insert("integers" )); |
396 | |
397 | result = con.Query("SELECT * FROM integers ORDER BY i" ); |
398 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3, 4, 5})); |
399 | REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4, 7, 8})); |
400 | |
401 | // create a table from a query |
402 | REQUIRE_NOTHROW( |
403 | con.Table("integers" )->Filter("i BETWEEN 3 AND 4" )->Project("i + 1 AS k, 'hello' AS l" )->Create("new_values" )); |
404 | |
405 | result = con.Query("SELECT * FROM new_values ORDER BY k" ); |
406 | REQUIRE(CHECK_COLUMN(result, 0, {4, 5})); |
407 | REQUIRE(CHECK_COLUMN(result, 1, {"hello" , "hello" })); |
408 | } |
409 | |
410 | TEST_CASE("Test table deletions and updates" , "[relation_api]" ) { |
411 | DuckDB db(nullptr); |
412 | Connection con(db); |
413 | con.EnableQueryVerification(); |
414 | unique_ptr<QueryResult> result; |
415 | |
416 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)" )); |
417 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)" )); |
418 | |
419 | auto tbl = con.Table("integers" ); |
420 | |
421 | // update |
422 | tbl->Update("i=i+10" , "i=2" ); |
423 | |
424 | result = con.Query("SELECT * FROM integers ORDER BY 1" ); |
425 | REQUIRE(CHECK_COLUMN(result, 0, {1, 3, 12})); |
426 | |
427 | // we can only have a single expression in the condition liset |
428 | REQUIRE_THROWS(tbl->Update("i=1" , "i=3,i<100" )); |
429 | |
430 | tbl->Delete("i=3" ); |
431 | |
432 | result = con.Query("SELECT * FROM integers ORDER BY 1" ); |
433 | REQUIRE(CHECK_COLUMN(result, 0, {1, 12})); |
434 | |
435 | // delete without condition |
436 | tbl->Delete(); |
437 | |
438 | result = con.Query("SELECT * FROM integers ORDER BY 1" ); |
439 | REQUIRE(CHECK_COLUMN(result, 0, {})); |
440 | |
441 | // we cannot run update/delete on anything but base table relations |
442 | REQUIRE_THROWS(tbl->Limit(1)->Delete()); |
443 | REQUIRE_THROWS(tbl->Limit(1)->Update("i=1" )); |
444 | } |
445 | |
446 | TEST_CASE("Test aggregates in relation API" , "[relation_api]" ) { |
447 | DuckDB db(nullptr); |
448 | Connection con(db); |
449 | unique_ptr<QueryResult> result; |
450 | |
451 | // create a table |
452 | REQUIRE_NOTHROW(con.Values("(1, 5), (2, 6), (1, 7)" , {"i" , "j" })->Create("integers" )); |
453 | |
454 | // perform some aggregates |
455 | auto tbl = con.Table("integers" ); |
456 | |
457 | // ungrouped aggregate |
458 | REQUIRE_NOTHROW(result = tbl->Aggregate("SUM(i), SUM(j)" )->Execute()); |
459 | REQUIRE(CHECK_COLUMN(result, 0, {4})); |
460 | REQUIRE(CHECK_COLUMN(result, 1, {18})); |
461 | // we cannot put aggregates in a Project clause |
462 | REQUIRE_THROWS(result = tbl->Project("SUM(i), SUM(j)" )->Execute()); |
463 | REQUIRE_THROWS(result = tbl->Project("i, SUM(j)" )->Execute()); |
464 | // implicitly grouped aggregate |
465 | REQUIRE_NOTHROW(result = tbl->Aggregate("i, SUM(j)" )->Order("1" )->Execute()); |
466 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2})); |
467 | REQUIRE(CHECK_COLUMN(result, 1, {12, 6})); |
468 | REQUIRE_NOTHROW(result = tbl->Aggregate("SUM(j), i" )->Order("2" )->Execute()); |
469 | REQUIRE(CHECK_COLUMN(result, 0, {12, 6})); |
470 | REQUIRE(CHECK_COLUMN(result, 1, {1, 2})); |
471 | // grouped aggregates can be expressions |
472 | REQUIRE_NOTHROW(result = tbl->Aggregate("i+1 AS i, SUM(j)" )->Order("1" )->Execute()); |
473 | REQUIRE(CHECK_COLUMN(result, 0, {2, 3})); |
474 | REQUIRE(CHECK_COLUMN(result, 1, {12, 6})); |
475 | // they can also involve multiple columns |
476 | REQUIRE_NOTHROW(result = tbl->Aggregate("i+i AS i, SUM(j)" )->Order("1" )->Execute()); |
477 | REQUIRE(CHECK_COLUMN(result, 0, {2, 4})); |
478 | REQUIRE(CHECK_COLUMN(result, 1, {12, 6})); |
479 | // we cannot combine non-aggregates with aggregates |
480 | REQUIRE_THROWS(result = tbl->Aggregate("i + SUM(j) AS i" )->Order("1" )->Execute()); |
481 | REQUIRE_THROWS(result = tbl->Aggregate("i, i + SUM(j)" )->Order("1" )->Execute()); |
482 | // group by multiple columns |
483 | REQUIRE_NOTHROW(result = tbl->Aggregate("i, j, SUM(i + j)" )->Order("1, 2" )->Execute()); |
484 | REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 2})); |
485 | REQUIRE(CHECK_COLUMN(result, 1, {5, 7, 6})); |
486 | REQUIRE(CHECK_COLUMN(result, 2, {6, 8, 8})); |
487 | // subqueries as groups |
488 | REQUIRE_NOTHROW(result = tbl->Aggregate("(SELECT i), SUM(i + j)" )->Order("1" )->Execute()); |
489 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2})); |
490 | REQUIRE(CHECK_COLUMN(result, 1, {14, 8})); |
491 | // subqueries as aggregates |
492 | REQUIRE_NOTHROW(result = tbl->Aggregate("(SELECT i), (SELECT SUM(i + j))" )->Order("1" )->Execute()); |
493 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2})); |
494 | REQUIRE(CHECK_COLUMN(result, 1, {14, 8})); |
495 | // constants without a grouping column |
496 | REQUIRE_NOTHROW(result = tbl->Aggregate("'hello', SUM(i + j)" )->Order("1" )->Execute()); |
497 | REQUIRE(CHECK_COLUMN(result, 0, {"hello" })); |
498 | REQUIRE(CHECK_COLUMN(result, 1, {22})); |
499 | // constants with a grouping column |
500 | REQUIRE_NOTHROW(result = tbl->Aggregate("i, 'hello', SUM(i + j)" )->Order("1" )->Execute()); |
501 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2})); |
502 | REQUIRE(CHECK_COLUMN(result, 1, {"hello" , "hello" })); |
503 | REQUIRE(CHECK_COLUMN(result, 2, {14, 8})); |
504 | // aggregate with only non-aggregate columns becomes a distinct |
505 | REQUIRE_NOTHROW(result = tbl->Aggregate("i" )->Order("1" )->Execute()); |
506 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2})); |
507 | REQUIRE_NOTHROW(result = tbl->Aggregate("i, j" )->Order("1, 2" )->Execute()); |
508 | REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 2})); |
509 | REQUIRE(CHECK_COLUMN(result, 1, {5, 7, 6})); |
510 | |
511 | // now test aggregates with explicit groups |
512 | REQUIRE_NOTHROW(result = tbl->Aggregate("i" , "i" )->Order("1" )->Execute()); |
513 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2})); |
514 | REQUIRE_NOTHROW(result = tbl->Aggregate("i, SUM(j)" , "i" )->Order("1" )->Execute()); |
515 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2})); |
516 | REQUIRE(CHECK_COLUMN(result, 1, {12, 6})); |
517 | // explicit groups can be combined with aggregates |
518 | REQUIRE_NOTHROW(result = tbl->Aggregate("i, i+SUM(j)" , "i" )->Order("1" )->Execute()); |
519 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2})); |
520 | REQUIRE(CHECK_COLUMN(result, 1, {13, 8})); |
521 | // when using explicit groups, we cannot have non-explicit groups |
522 | REQUIRE_THROWS(tbl->Aggregate("j, i+SUM(j)" , "i" )->Order("1" )->Execute()); |
523 | |
524 | // project -> aggregate -> project -> aggregate |
525 | // SUM(j) = 18 -> 18 + 1 = 19 -> 19 * 2 = 38 |
526 | result = tbl->Aggregate("SUM(j) AS k" )->Project("k+1 AS l" )->Aggregate("SUM(l) AS m" )->Project("m*2" )->Execute(); |
527 | REQUIRE(CHECK_COLUMN(result, 0, {38})); |
528 | |
529 | // aggregate after output modifiers |
530 | result = tbl->Order("i" )->Limit(100)->Aggregate("SUM(j) AS k" )->Execute(); |
531 | REQUIRE(CHECK_COLUMN(result, 0, {18})); |
532 | } |
533 | |
534 | TEST_CASE("Test interaction of relations with transactions" , "[relation_api]" ) { |
535 | DuckDB db(nullptr); |
536 | Connection con1(db), con2(db); |
537 | unique_ptr<QueryResult> result; |
538 | |
539 | con1.BeginTransaction(); |
540 | con2.BeginTransaction(); |
541 | |
542 | // create a table in con1 |
543 | REQUIRE_NOTHROW(con1.Values("(1), (2), (3)" )->Create("integers" )); |
544 | |
545 | // con1 can see it, but con2 can't see it yet |
546 | REQUIRE_NOTHROW(con1.Table("integers" )); |
547 | REQUIRE_THROWS(con2.Table("integers" )); |
548 | |
549 | // we can also rollback |
550 | con1.Rollback(); |
551 | |
552 | REQUIRE_THROWS(con1.Table("integers" )); |
553 | REQUIRE_THROWS(con2.Table("integers" )); |
554 | |
555 | // recreate the table, this time in auto-commit mode |
556 | REQUIRE_NOTHROW(con1.Values("(1), (2), (3)" )->Create("integers" )); |
557 | |
558 | // con2 still can't see it, because it is in its own transaction |
559 | REQUIRE_NOTHROW(con1.Table("integers" )); |
560 | REQUIRE_THROWS(con2.Table("integers" )); |
561 | |
562 | // after con2 commits, both can see the table |
563 | con2.Commit(); |
564 | |
565 | REQUIRE_NOTHROW(con1.Table("integers" )); |
566 | REQUIRE_NOTHROW(con2.Table("integers" )); |
567 | } |
568 | |
569 | TEST_CASE("Test interaction of relations with schema changes" , "[relation_api]" ) { |
570 | DuckDB db(nullptr); |
571 | Connection con(db); |
572 | unique_ptr<QueryResult> result; |
573 | |
574 | // create some tables |
575 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)" )); |
576 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)" )); |
577 | |
578 | // create a table scan of the integers table |
579 | auto tbl_scan = con.Table("integers" )->Project("i+1" )->Order("1" ); |
580 | REQUIRE_NOTHROW(result = tbl_scan->Execute()); |
581 | REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4})); |
582 | |
583 | // now drop the table |
584 | REQUIRE_NO_FAIL(con.Query("DROP TABLE integers" )); |
585 | |
586 | // the scan now fails, because the table is dropped! |
587 | REQUIRE_FAIL(tbl_scan->Execute()); |
588 | |
589 | // if we recreate the table, it works again |
590 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)" )); |
591 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)" )); |
592 | |
593 | REQUIRE_NOTHROW(result = tbl_scan->Execute()); |
594 | REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4})); |
595 | |
596 | // but what if we recreate an incompatible table? |
597 | REQUIRE_NO_FAIL(con.Query("DROP TABLE integers" )); |
598 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i VARCHAR)" )); |
599 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES ('hello')" )); |
600 | |
601 | // this results in a binding error! |
602 | REQUIRE_FAIL(tbl_scan->Execute()); |
603 | |
604 | // now what if we run a query that still binds successfully, but changes result? |
605 | auto tbl = con.Table("integers" ); |
606 | REQUIRE_NO_FAIL(tbl->Execute()); |
607 | |
608 | // add extra columns |
609 | REQUIRE_NO_FAIL(con.Query("DROP TABLE integers" )); |
610 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i VARCHAR, j VARCHAR)" )); |
611 | REQUIRE_FAIL(tbl->Execute()); |
612 | |
613 | // change type of column |
614 | REQUIRE_NO_FAIL(con.Query("DROP TABLE integers" )); |
615 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i DATE)" )); |
616 | REQUIRE_FAIL(tbl->Execute()); |
617 | |
618 | // different name also results in an error |
619 | REQUIRE_NO_FAIL(con.Query("DROP TABLE integers" )); |
620 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(k VARCHAR)" )); |
621 | REQUIRE_FAIL(tbl->Execute()); |
622 | |
623 | // but once we go back to the original table it works again! |
624 | REQUIRE_NO_FAIL(con.Query("DROP TABLE integers" )); |
625 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i VARCHAR)" )); |
626 | REQUIRE_NO_FAIL(tbl->Execute()); |
627 | } |
628 | |
629 | TEST_CASE("Test junk SQL in expressions" , "[relation_api]" ) { |
630 | DuckDB db(nullptr); |
631 | Connection con(db); |
632 | unique_ptr<QueryResult> result; |
633 | |
634 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)" )); |
635 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)" )); |
636 | |
637 | auto tbl = con.Table("integers" ); |
638 | REQUIRE_THROWS(tbl->Filter("1=1; DELETE FROM tables;" )); |
639 | REQUIRE_THROWS(tbl->Filter("1=1 UNION SELECT 42" )); |
640 | REQUIRE_THROWS(tbl->Order("1 DESC; DELETE FROM tables;" )); |
641 | REQUIRE_THROWS(tbl->Update("i=1; DELETE FROM TABLES" )); |
642 | REQUIRE_THROWS(con.Values("(1, 1); SELECT 42" )); |
643 | REQUIRE_THROWS(con.Values("(1, 1) UNION SELECT 42" )); |
644 | } |
645 | |
646 | TEST_CASE("We cannot mix statements from multiple databases" , "[relation_api]" ) { |
647 | DuckDB db(nullptr), db2(nullptr); |
648 | Connection con(db), con2(db2); |
649 | unique_ptr<QueryResult> result; |
650 | |
651 | // create some tables |
652 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)" )); |
653 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)" )); |
654 | REQUIRE_NO_FAIL(con2.Query("CREATE TABLE integers(i INTEGER)" )); |
655 | REQUIRE_NO_FAIL(con2.Query("INSERT INTO integers VALUES (4)" )); |
656 | |
657 | auto i1 = con.Table("integers" ); |
658 | auto i2 = con2.Table("integers" ); |
659 | |
660 | // we cannot mix statements from different connections without a wrapper! |
661 | REQUIRE_THROWS(i2->Union(i1)); |
662 | REQUIRE_THROWS(i2->Except(i1)); |
663 | REQUIRE_THROWS(i2->Intersect(i1)); |
664 | REQUIRE_THROWS(i2->Join(i1, "i" )); |
665 | |
666 | // FIXME: what about a wrapper to scan data from other databases/connections? |
667 | } |
668 | |
669 | TEST_CASE("Test view relations" , "[relation_api]" ) { |
670 | DuckDB db(nullptr); |
671 | Connection con(db); |
672 | unique_ptr<QueryResult> result; |
673 | |
674 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)" )); |
675 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)" )); |
676 | REQUIRE_NO_FAIL(con.Query("CREATE VIEW v1 AS SELECT i+1 AS i, i+2 FROM integers" )); |
677 | REQUIRE_NO_FAIL(con.Query("CREATE VIEW v2(a,b) AS SELECT i+1, i+2 FROM integers" )); |
678 | |
679 | auto i1 = con.View("v1" ); |
680 | result = i1->Execute(); |
681 | REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4})); |
682 | REQUIRE(CHECK_COLUMN(result, 1, {3, 4, 5})); |
683 | |
684 | result = con.View("v2" )->Project("a+b" )->Execute(); |
685 | REQUIRE(CHECK_COLUMN(result, 0, {5, 7, 9})); |
686 | |
687 | // non-existant view |
688 | REQUIRE_THROWS(con.View("blabla" )); |
689 | |
690 | // combining views |
691 | result = con.View("v1" )->Join(con.View("v2" ), "v1.i=v2.a" )->Execute(); |
692 | REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4})); |
693 | REQUIRE(CHECK_COLUMN(result, 1, {3, 4, 5})); |
694 | REQUIRE(CHECK_COLUMN(result, 2, {2, 3, 4})); |
695 | REQUIRE(CHECK_COLUMN(result, 3, {3, 4, 5})); |
696 | } |
697 | |
698 | TEST_CASE("Test table function relations" , "[relation_api]" ) { |
699 | DuckDB db(nullptr); |
700 | Connection con(db); |
701 | unique_ptr<QueryResult> result; |
702 | |
703 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)" )); |
704 | |
705 | auto i1 = con.TableFunction("sqlite_master" ); |
706 | result = i1->Execute(); |
707 | REQUIRE(CHECK_COLUMN(result, 0, {"table" })); |
708 | REQUIRE(CHECK_COLUMN(result, 1, {"integers" })); |
709 | |
710 | // function with parameters |
711 | auto i2 = con.TableFunction("pragma_table_info" , {"integers" }); |
712 | result = i2->Execute(); |
713 | REQUIRE(CHECK_COLUMN(result, 0, {0})); |
714 | REQUIRE(CHECK_COLUMN(result, 1, {"i" })); |
715 | REQUIRE(CHECK_COLUMN(result, 2, {"INTEGER" })); |
716 | |
717 | // we can do ops on table functions |
718 | result = i2->Filter("cid=0" )->Project("concat(name, ' ', type), length(name), reverse(lower(type))" )->Execute(); |
719 | REQUIRE(CHECK_COLUMN(result, 0, {"i INTEGER" })); |
720 | REQUIRE(CHECK_COLUMN(result, 1, {1})); |
721 | REQUIRE(CHECK_COLUMN(result, 2, {"regetni" })); |
722 | |
723 | // non-existant table function |
724 | REQUIRE_THROWS(con.TableFunction("blabla" )); |
725 | } |
726 | |