| 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 | |