Files
email-tracker/external/duckdb/test/api/test_relation_api.cpp
2025-10-24 19:21:19 -05:00

1147 lines
45 KiB
C++

#include "catch.hpp"
#include "duckdb/common/file_system.hpp"
#include "duckdb/common/enums/joinref_type.hpp"
#include "duckdb/parser/expression/constant_expression.hpp"
#include "duckdb/main/relation/value_relation.hpp"
#include "iostream"
#include "test_helpers.hpp"
#include "duckdb/main/relation/materialized_relation.hpp"
using namespace duckdb;
using namespace std;
TEST_CASE("Test simple relation API", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
duckdb::shared_ptr<Relation> tbl, filter, proj, proj2, v1, v2, v3;
// create some tables
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3), (1), (2), (3)"));
// simple projection
REQUIRE_NOTHROW(tbl = con.Table("integers"));
REQUIRE_NOTHROW(result = tbl->Project("i + 1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4}));
REQUIRE_NOTHROW(result = tbl->Project(duckdb::vector<string> {"i + 1", "i + 2"})->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4}));
REQUIRE(CHECK_COLUMN(result, 1, {3, 4, 5}));
REQUIRE_NOTHROW(result = tbl->Project(duckdb::vector<string> {"i + 1"}, {"i"})->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4}));
// we support * expressions
REQUIRE_NOTHROW(result = tbl->Project("*")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
// we can also read the table directly
REQUIRE_NOTHROW(result = tbl->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
// we can stack projections
REQUIRE_NOTHROW(
result =
tbl->Project("i + 1 AS i")->Project("i + 1 AS i")->Project("i + 1 AS i")->Project("i + 1 AS i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {5, 6, 7}));
// we can execute the same projection multiple times
REQUIRE_NOTHROW(proj = tbl->Project("i + 1"));
for (idx_t i = 0; i < 10; i++) {
REQUIRE_NOTHROW(result = proj->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4}));
}
result = con.Query("SELECT i+1 FROM (SELECT * FROM integers WHERE i <> 2) relation");
REQUIRE(CHECK_COLUMN(result, 0, {2, 4}));
// filter and projection
REQUIRE_NOTHROW(filter = tbl->Filter("i <> 2"));
REQUIRE_NOTHROW(proj = filter->Project("i + 1"));
REQUIRE_NOTHROW(result = proj->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 4}));
// multi filter
REQUIRE_NOTHROW(result = tbl->Filter(duckdb::vector<string> {"i <> 2", "i <> 3"})->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1}));
// we can reuse the same filter again and perform a different projection
REQUIRE_NOTHROW(proj = filter->Project("i * 10"));
REQUIRE_NOTHROW(result = proj->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {10, 30}));
// add a limit
REQUIRE_NOTHROW(result = proj->Limit(1)->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {10}));
// and an offset
REQUIRE_NOTHROW(result = proj->Limit(1, 1)->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {30}));
// lets add some aliases
REQUIRE_NOTHROW(proj = filter->Project("i + 1 AS a"));
REQUIRE_NOTHROW(result = proj->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 4}));
// we can check the column names
REQUIRE(proj->Columns()[0].Name() == "a");
REQUIRE(proj->Columns()[0].Type() == LogicalType::INTEGER);
// we can also alias like this
REQUIRE_NOTHROW(proj = filter->Project("i + 1", "a"));
REQUIRE_NOTHROW(result = proj->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 4}));
// we can check the column names
REQUIRE(proj->Columns()[0].Name() == "a");
REQUIRE(proj->Columns()[0].Type() == LogicalType::INTEGER);
// now we can use that column to perform additional projections
REQUIRE_NOTHROW(result = proj->Project("a + 1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {3, 5}));
// we can also filter on this again
REQUIRE_NOTHROW(result = proj->Filter("a=2")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2}));
// filters can also contain conjunctions
REQUIRE_NOTHROW(result = proj->Filter("a=2 OR a=4")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 2, 4, 4}));
// alias
REQUIRE_NOTHROW(result = proj->Project("a + 1")->Alias("bla")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {3, 5}));
// now test ordering
REQUIRE_NOTHROW(result = proj->Order("a DESC")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {4, 4, 2, 2}));
REQUIRE_NOTHROW(result = proj->Order(duckdb::vector<string> {"a DESC", "a ASC"})->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {4, 4, 2, 2}));
// top n
REQUIRE_NOTHROW(result = proj->Order("a")->Limit(1)->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2}));
REQUIRE_NOTHROW(result = proj->Order("a DESC")->Limit(1)->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {4}));
// test set operations
REQUIRE_NOTHROW(result = tbl->Union(tbl)->Order("i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3}));
REQUIRE_NOTHROW(result = tbl->Except(tbl)->Order("i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {}));
REQUIRE_NOTHROW(result = tbl->Intersect(tbl)->Order("i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 2, 2, 3, 3}));
REQUIRE_NOTHROW(result = tbl->Except(tbl->Filter("i=2"))->Order("i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 3, 3}));
REQUIRE_NOTHROW(result = tbl->Intersect(tbl->Filter("i=2"))->Order("i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 2}));
// set operations with projections
REQUIRE_NOTHROW(proj = tbl->Project("i::TINYINT AS i, i::SMALLINT, i::BIGINT, i::VARCHAR"));
REQUIRE_NOTHROW(proj2 = tbl->Project("(i+10)::TINYINT, (i+10)::SMALLINT, (i+10)::BIGINT, (i+10)::VARCHAR"));
REQUIRE_NOTHROW(result = proj->Union(proj2)->Order("i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 2, 2, 3, 3, 11, 11, 12, 12, 13, 13}));
REQUIRE(CHECK_COLUMN(result, 1, {1, 1, 2, 2, 3, 3, 11, 11, 12, 12, 13, 13}));
REQUIRE(CHECK_COLUMN(result, 2, {1, 1, 2, 2, 3, 3, 11, 11, 12, 12, 13, 13}));
REQUIRE(CHECK_COLUMN(result, 3, {"1", "1", "2", "2", "3", "3", "11", "11", "12", "12", "13", "13"}));
// distinct
REQUIRE_NOTHROW(result = tbl->Union(tbl)->Union(tbl)->Distinct()->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
// join
REQUIRE_NOTHROW(v1 = con.Values({{1, 10}, {2, 5}, {3, 4}}, {"id", "j"}, "v1"));
REQUIRE_NOTHROW(v2 = con.Values({{1, 27}, {2, 8}, {3, 20}}, {"id", "k"}, "v2"));
REQUIRE_NOTHROW(v3 = con.Values({{1, 2}, {2, 6}, {3, 10}}, {"id", "k"}, "v3"));
REQUIRE_NOTHROW(result = v1->Join(v2, "v1.id=v2.id")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
REQUIRE(CHECK_COLUMN(result, 2, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 3, {27, 8, 20}));
// asof join
REQUIRE_NOTHROW(v1 = con.Values({{1, 10}, {6, 5}, {8, 4}, {10, 23}, {12, 12}, {15, 14}}, {"id", "j"}, "v1"));
REQUIRE_NOTHROW(v2 = con.Values({{4, 27}, {8, 8}, {14, 20}}, {"id", "k"}, "v2"));
REQUIRE_NOTHROW(result = v1->Join(v2, "v1.id>=v2.id", JoinType::INNER, JoinRefType::ASOF)->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {6, 8, 10, 12, 15}));
REQUIRE(CHECK_COLUMN(result, 1, {5, 4, 23, 12, 14}));
REQUIRE(CHECK_COLUMN(result, 2, {4, 8, 8, 8, 14}));
REQUIRE(CHECK_COLUMN(result, 3, {27, 8, 8, 8, 20}));
REQUIRE_NOTHROW(v1 = con.Values({{1, 10}, {2, 5}, {3, 4}}, {"id", "j"}, "v1"));
REQUIRE_NOTHROW(v2 = con.Values({{1, 27}, {2, 8}, {3, 20}}, {"id", "k"}, "v2"));
REQUIRE_NOTHROW(v3 = con.Values({{1, 2}, {2, 6}, {3, 10}}, {"id", "k"}, "v3"));
// projection after a join
REQUIRE_NOTHROW(result = v1->Join(v2, "v1.id=v2.id")->Project("v1.id+v2.id, j+k")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 4, 6}));
REQUIRE(CHECK_COLUMN(result, 1, {37, 13, 24}));
// chain multiple joins
auto multi_join = v1->Join(v2, "v1.id=v2.id")->Join(v3, "v1.id=v3.id");
REQUIRE_NOTHROW(result = multi_join->Project("v1.id+v2.id+v3.id")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {3, 6, 9}));
// multiple joins followed by a filter and a projection
REQUIRE_NOTHROW(result = multi_join->Filter("v1.id=1")->Project("v1.id+v2.id+v3.id")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {3}));
// multiple joins followed by multiple filters
REQUIRE_NOTHROW(result = multi_join->Filter("v1.id>0")
->Filter("v2.id < 3")
->Filter("v3.id=2")
->Project("v1.id+v2.id+v3.id")
->Order("1")
->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {6}));
// test explain
REQUIRE_NO_FAIL(multi_join->Explain());
// incorrect API usage
REQUIRE_THROWS(tbl->Project(duckdb::vector<string> {})->Execute());
REQUIRE_THROWS(tbl->Project(duckdb::vector<string> {"1, 2, 3"})->Execute());
REQUIRE_THROWS(tbl->Project(duckdb::vector<string> {""})->Execute());
REQUIRE_THROWS(tbl->Filter("i=1, i=2")->Execute());
REQUIRE_THROWS(tbl->Filter("")->Execute());
REQUIRE_THROWS(tbl->Filter(duckdb::vector<string> {})->Execute());
REQUIRE_THROWS(tbl->Filter(duckdb::vector<string> {"1, 2, 3"})->Execute());
REQUIRE_THROWS(tbl->Filter(duckdb::vector<string> {""})->Execute());
REQUIRE_THROWS(tbl->Order(duckdb::vector<string> {})->Execute());
REQUIRE_THROWS(tbl->Order(duckdb::vector<string> {"1, 2, 3"})->Execute());
REQUIRE_THROWS(tbl->Order("1 LIMIT 3")->Execute());
REQUIRE_THROWS(tbl->Order("1; SELECT 42")->Execute());
REQUIRE_THROWS(tbl->Join(tbl, "")->Execute());
REQUIRE_THROWS(tbl->Join(tbl, "a, a+1")->Execute());
REQUIRE_THROWS(tbl->Join(tbl, "a, bla.bla")->Execute());
}
TEST_CASE("Test combinations of set operations", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
duckdb::shared_ptr<Relation> values, v1, v2, v3;
REQUIRE_NOTHROW(values = con.Values({{1, 10}, {2, 5}, {3, 4}}, {"i", "j"}));
// union between values
auto vunion = values->Union(values);
REQUIRE_NOTHROW(result = vunion->Order("i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 2, 2, 3, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 10, 5, 5, 4, 4}));
// different ops after a union
// order and limit
REQUIRE_NOTHROW(result = vunion->Order("i")->Limit(1)->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1}));
REQUIRE(CHECK_COLUMN(result, 1, {10}));
// multiple orders and limits
REQUIRE_NOTHROW(result = vunion->Order("i")->Limit(4)->Order("j")->Limit(2)->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 2}));
REQUIRE(CHECK_COLUMN(result, 1, {5, 5}));
// filter
REQUIRE_NOTHROW(result = vunion->Filter("i=1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 1}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 10}));
// multiple filters
REQUIRE_NOTHROW(result = vunion->Filter("i<3")->Filter("j=5")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 2}));
REQUIRE(CHECK_COLUMN(result, 1, {5, 5}));
// distinct
REQUIRE_NOTHROW(result = vunion->Distinct()->Order("j DESC")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
// multiple distincts followed by a top-n
REQUIRE_NOTHROW(result = vunion->Distinct()->Distinct()->Distinct()->Order("j DESC")->Limit(2)->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5}));
// top-n followed by multiple distincts
REQUIRE_NOTHROW(result = vunion->Order("j DESC")->Limit(2)->Distinct()->Distinct()->Distinct()->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1}));
REQUIRE(CHECK_COLUMN(result, 1, {10}));
// multiple set ops
REQUIRE_NOTHROW(result = vunion->Union(vunion)->Distinct()->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
REQUIRE_NOTHROW(result = vunion->Intersect(vunion)->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 2, 2, 3, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 10, 5, 5, 4, 4}));
REQUIRE_NOTHROW(result = vunion->Except(vunion)->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {}));
REQUIRE(CHECK_COLUMN(result, 1, {}));
// setops require the same amount of columns on both sides
REQUIRE_NOTHROW(v1 = con.Values("(1, 2), (3, 4)"));
REQUIRE_NOTHROW(v2 = con.Values("(1)"));
REQUIRE_THROWS(v1->Union(v2)->Execute());
// setops require the same types on both sides
REQUIRE_NOTHROW(v1 = con.Values("(DATE '1992-01-01', 2)"));
REQUIRE_NOTHROW(v2 = con.Values("(3.0, 'hello')"));
REQUIRE_FAIL(v1->Union(v2)->Execute());
}
TEST_CASE("Test combinations of joins", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
duckdb::shared_ptr<Relation> values, vjoin;
REQUIRE_NOTHROW(values = con.Values({{1, 10}, {2, 5}, {3, 4}}, {"i", "j"}));
auto v1 = values->Alias("v1");
auto v2 = values->Alias("v2");
// join on explicit join condition
vjoin = v1->Join(v2, "v1.i=v2.i");
REQUIRE_NOTHROW(result = vjoin->Order("v1.i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
REQUIRE(CHECK_COLUMN(result, 2, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 3, {10, 5, 4}));
// aggregate on EXPLICIT join
REQUIRE_NOTHROW(result = vjoin->Aggregate("SUM(v1.i) + SUM(v2.i), SUM(v1.j) + SUM(v2.j)")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {12}));
REQUIRE(CHECK_COLUMN(result, 1, {38}));
// implicit join
vjoin = v1->Join(v2, "i");
REQUIRE_NOTHROW(result = vjoin->Order("i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
REQUIRE(CHECK_COLUMN(result, 2, {10, 5, 4}));
// implicit join on multiple columns
vjoin = v1->Join(v2, "i, j");
REQUIRE_NOTHROW(result = vjoin->Order("i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
// aggregate on USING join
REQUIRE_NOTHROW(result = vjoin->Aggregate("SUM(i), SUM(j)")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {6}));
REQUIRE(CHECK_COLUMN(result, 1, {19}));
// joining on a column that doesn't exist results in an error
REQUIRE_THROWS(v1->Join(v2, "blabla"));
// also with explicit join condition
REQUIRE_THROWS(v1->Join(v2, "v1.i=v2.blabla"));
// set ops involving joins
REQUIRE_NOTHROW(result = vjoin->Union(vjoin)->Distinct()->Order("i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
// do a bunch of joins in a loop
auto v1tmp = v1;
auto v2tmp = v2;
for (idx_t i = 0; i < 4; i++) {
REQUIRE_NOTHROW(v1tmp = v1tmp->Join(v2tmp->Alias(to_string(i)), "i, j"));
}
REQUIRE_NOTHROW(result = v1tmp->Order("i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
// now add on some projections and such
auto complex_join = v1tmp->Order("i")->Limit(2)->Order("i DESC")->Limit(1)->Project("i+1, j+1");
for (idx_t i = 0; i < 3; i++) {
REQUIRE_NOTHROW(result = complex_join->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {3}));
REQUIRE(CHECK_COLUMN(result, 1, {6}));
}
// create and query a view
REQUIRE_NOTHROW(complex_join->CreateView("test123"));
result = con.Query("SELECT * FROM test123");
REQUIRE(CHECK_COLUMN(result, 0, {3}));
REQUIRE(CHECK_COLUMN(result, 1, {6}));
// joins of tables that have output modifiers attached to them (limit, order by, distinct)
auto v1_modified = v1->Limit(100)->Order("1")->Distinct()->Filter("i<1000");
auto v2_modified = v2->Limit(100)->Order("1")->Distinct()->Filter("i<1000");
vjoin = v1_modified->Join(v2_modified, "i, j");
REQUIRE_NOTHROW(result = vjoin->Order("i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
REQUIRE_NO_FAIL(con.Query("SELECT * FROM sqlite_master"));
}
TEST_CASE("Test crossproduct relation", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
duckdb::shared_ptr<Relation> values, vcross;
REQUIRE_NOTHROW(values = con.Values({{1, 10}, {2, 5}, {3, 4}}, {"i", "j"}), "v1");
REQUIRE_NOTHROW(values = con.Values({{1, 10}, {2, 5}, {3, 4}}, {"i", "j"}), "v2");
auto v1 = values->Alias("v1");
auto v2 = values->Alias("v2");
// run cross product
vcross = v1->CrossProduct(v2);
REQUIRE_NOTHROW(result = vcross->Order("v1.i, v2.i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 1, 2, 2, 2, 3, 3, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 10, 10, 5, 5, 5, 4, 4, 4}));
REQUIRE(CHECK_COLUMN(result, 2, {1, 2, 3, 1, 2, 3, 1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 3, {10, 5, 4, 10, 5, 4, 10, 5, 4}));
// run a positional cross product
auto join_ref_type = JoinRefType::POSITIONAL;
vcross = v1->CrossProduct(v2, join_ref_type);
REQUIRE_NOTHROW(result = vcross->Order("v1.i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
REQUIRE(CHECK_COLUMN(result, 2, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 3, {10, 5, 4}));
}
TEST_CASE("Test view creation of relations", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
duckdb::shared_ptr<Relation> tbl, filter, proj, proj2;
// create some tables
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
REQUIRE_NOTHROW(con.Table("integers")->Insert({{Value::INTEGER(1)}, {Value::INTEGER(2)}, {Value::INTEGER(3)}}));
// insertion failure because of primary key
REQUIRE_NO_FAIL(con.Query("CREATE TABLE tbl_pk(i INTEGER PRIMARY KEY)"));
REQUIRE_NOTHROW(con.Table("tbl_pk")->Insert({{Value::INTEGER(1)}, {Value::INTEGER(2)}, {Value::INTEGER(3)}}));
REQUIRE_THROWS(con.Table("tbl_pk")->Insert({{Value::INTEGER(1)}, {Value::INTEGER(2)}, {Value::INTEGER(3)}}));
// simple view creation
REQUIRE_NOTHROW(tbl = con.Table("integers"));
REQUIRE_NOTHROW(result = tbl->Query("test", "SELECT * FROM test"));
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
duckdb::vector<duckdb::unique_ptr<ParsedExpression>> expressions;
expressions.push_back(duckdb::make_uniq<duckdb::ColumnRefExpression>("i"));
duckdb::vector<duckdb::string> aliases;
aliases.push_back("j");
REQUIRE_NOTHROW(result = tbl->Project(std::move(expressions), aliases)->Query("test", "SELECT * FROM test"));
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
// add a projection
REQUIRE_NOTHROW(result = tbl->Project("i + 1")->Query("test", "SELECT * FROM test"));
REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4}));
// multiple projections
proj = tbl->Project("i + 1", "i");
for (idx_t i = 0; i < 10; i++) {
proj = proj->Project("i + 1", "i");
}
REQUIRE_NOTHROW(result = proj->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {12, 13, 14}));
REQUIRE_NOTHROW(result = proj->Query("test", "SELECT * FROM test"));
REQUIRE(CHECK_COLUMN(result, 0, {12, 13, 14}));
// we can also use more complex SQL
REQUIRE_NOTHROW(result = proj->Query("test", "SELECT SUM(t1.i) FROM test t1 JOIN test t2 ON t1.i=t2.i"));
REQUIRE(CHECK_COLUMN(result, 0, {39}));
// limit
REQUIRE_NOTHROW(result = tbl->Limit(1)->Query("test", "SELECT * FROM test"));
REQUIRE(CHECK_COLUMN(result, 0, {1}));
// order
REQUIRE_NOTHROW(result = tbl->Order("i DESC")->Limit(1)->Query("test", "SELECT * FROM test"));
REQUIRE(CHECK_COLUMN(result, 0, {3}));
// union
auto node = tbl->Order("i DESC")->Limit(1);
REQUIRE_NOTHROW(result = node->Union(node)->Query("test", "SELECT * FROM test"));
REQUIRE(CHECK_COLUMN(result, 0, {3, 3}));
// distinct
REQUIRE_NOTHROW(result = node->Union(node)->Distinct()->Query("test", "SELECT * FROM test"));
REQUIRE(CHECK_COLUMN(result, 0, {3}));
// manually create views and query from them
result = con.Query("SELECT i+1 FROM integers UNION SELECT i+10 FROM integers ORDER BY 1");
REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4, 11, 12, 13}));
REQUIRE_NOTHROW(tbl->Project("i + 1")->CreateView("test1"));
REQUIRE_NOTHROW(tbl->Project("i + 10")->CreateView("test2"));
result = con.Query("SELECT * FROM test1 UNION SELECT * FROM test2 ORDER BY 1");
REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4, 11, 12, 13}));
// project <> alias column count mismatch
REQUIRE_THROWS(proj->Project("i + 1, i + 2", "i"));
// view already exists
REQUIRE_THROWS(tbl->Project("i + 10")->CreateView("test2", false));
// table already exists
REQUIRE_THROWS(tbl->Project("i + 10")->Create("test2"));
}
TEST_CASE("Test table creations using the relation API", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
duckdb::shared_ptr<Relation> values;
// create a table from a Values statement
REQUIRE_NOTHROW(values = con.Values({{1, 10}, {2, 5}, {3, 4}}, {"i", "j"}));
REQUIRE_NOTHROW(values->Create("integers"));
result = con.Query("SELECT * FROM integers ORDER BY i");
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
// insert from a set of values
REQUIRE_NOTHROW(con.Values({{4, 7}, {5, 8}})->Insert("integers"));
result = con.Query("SELECT * FROM integers ORDER BY i");
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3, 4, 5}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4, 7, 8}));
// create a table from a query
REQUIRE_NOTHROW(
con.Table("integers")->Filter("i BETWEEN 3 AND 4")->Project("i + 1 AS k, 'hello' AS l")->Create("new_values"));
result = con.Query("SELECT * FROM new_values ORDER BY k");
REQUIRE(CHECK_COLUMN(result, 0, {4, 5}));
REQUIRE(CHECK_COLUMN(result, 1, {"hello", "hello"}));
}
TEST_CASE("Test table creations with on_create_conflict using the relation API", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
duckdb::shared_ptr<Relation> values, values1, proj;
// create a table from a Values statement
REQUIRE_NOTHROW(values = con.Values({{1, 10}, {2, 5}, {3, 4}}, {"i", "j"}));
REQUIRE_NOTHROW(values->Create("integers"));
result = con.Query("SELECT * FROM integers ORDER BY i");
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
REQUIRE_NOTHROW(values1 = con.Values({{4, 14}, {5, 15}, {6, 16}}, {"i", "j"}));
REQUIRE_THROWS(values1->Create("integers"));
result = con.Query("SELECT * FROM integers ORDER BY i");
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
REQUIRE_THROWS(values1->Create("integers", false, OnCreateConflict::ERROR_ON_CONFLICT));
result = con.Query("SELECT * FROM integers ORDER BY i");
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
REQUIRE_NOTHROW(values1->Create("integers", false, OnCreateConflict::IGNORE_ON_CONFLICT));
result = con.Query("SELECT * FROM integers ORDER BY i");
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 5, 4}));
REQUIRE_NOTHROW(values1->Create("integers", false, OnCreateConflict::REPLACE_ON_CONFLICT));
result = con.Query("SELECT * FROM integers ORDER BY i");
REQUIRE(CHECK_COLUMN(result, 0, {4, 5, 6}));
REQUIRE(CHECK_COLUMN(result, 1, {14, 15, 16}));
}
TEST_CASE("Test table create from query with on_create_conflict using the relation API", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
duckdb::shared_ptr<Relation> values, values1, proj;
REQUIRE_NOTHROW(values = con.Values({{1, 10}, {2, 20}, {3, 30}, {4, 40}}, {"i", "j"}));
REQUIRE_NOTHROW(values->Create("integers"));
result = con.Query("SELECT * FROM integers ORDER BY i");
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3, 4}));
REQUIRE(CHECK_COLUMN(result, 1, {10, 20, 30, 40}));
REQUIRE_NOTHROW(con.Table("integers")
->Filter("i BETWEEN 2 AND 3")
->Project("i + 100 AS k, 'hello' AS l")
->Create("new_values"));
result = con.Query("SELECT * FROM new_values ORDER BY k");
REQUIRE(CHECK_COLUMN(result, 0, {102, 103}));
REQUIRE(CHECK_COLUMN(result, 1, {"hello", "hello"}));
REQUIRE_NOTHROW(proj = con.Table("integers")->Filter("i BETWEEN 1 AND 2")->Project("i + 200 AS k, 'hi' AS l"));
REQUIRE_THROWS(proj->Create("new_values"));
REQUIRE_THROWS(proj->Create("new_values", false, OnCreateConflict::ERROR_ON_CONFLICT));
REQUIRE_NOTHROW(proj->Create("new_values", false, OnCreateConflict::IGNORE_ON_CONFLICT));
result = con.Query("SELECT * FROM new_values ORDER BY k");
REQUIRE(CHECK_COLUMN(result, 0, {102, 103}));
REQUIRE(CHECK_COLUMN(result, 1, {"hello", "hello"}));
REQUIRE_NOTHROW(proj->Create("new_values", false, OnCreateConflict::REPLACE_ON_CONFLICT));
result = con.Query("SELECT * FROM new_values ORDER BY k");
REQUIRE(CHECK_COLUMN(result, 0, {201, 202}));
REQUIRE(CHECK_COLUMN(result, 1, {"hi", "hi"}));
}
TEST_CASE("Test table deletions and updates", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)"));
auto tbl = con.Table("integers");
// update
tbl->Update("i=i+10", "i=2");
result = con.Query("SELECT * FROM integers ORDER BY 1");
REQUIRE(CHECK_COLUMN(result, 0, {1, 3, 12}));
// we can only have a single expression in the condition liset
REQUIRE_THROWS(tbl->Update("i=1", "i=3,i<100"));
tbl->Delete("i=3");
result = con.Query("SELECT * FROM integers ORDER BY 1");
REQUIRE(CHECK_COLUMN(result, 0, {1, 12}));
// delete without condition
tbl->Delete();
result = con.Query("SELECT * FROM integers ORDER BY 1");
REQUIRE(CHECK_COLUMN(result, 0, {}));
// we cannot run update/delete on anything but base table relations
REQUIRE_THROWS(tbl->Limit(1)->Delete());
REQUIRE_THROWS(tbl->Limit(1)->Update("i=1"));
}
TEST_CASE("Test aggregates in relation API", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
// create a table
REQUIRE_NOTHROW(con.Values("(1, 5), (2, 6), (1, 7)", {"i", "j"})->Create("integers"));
// perform some aggregates
auto tbl = con.Table("integers");
// ungrouped aggregate
REQUIRE_NOTHROW(result = tbl->Aggregate("SUM(i), SUM(j)")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {4}));
REQUIRE(CHECK_COLUMN(result, 1, {18}));
REQUIRE_NOTHROW(result = tbl->Aggregate(duckdb::vector<string> {"SUM(i)", "SUM(j)"})->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {4}));
REQUIRE(CHECK_COLUMN(result, 1, {18}));
// we cannot put aggregates in a Project clause
REQUIRE_THROWS(result = tbl->Project("SUM(i), SUM(j)")->Execute());
REQUIRE_THROWS(result = tbl->Project("i, SUM(j)")->Execute());
// implicitly grouped aggregate
REQUIRE_NOTHROW(result = tbl->Aggregate("i, SUM(j)")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2}));
REQUIRE(CHECK_COLUMN(result, 1, {12, 6}));
REQUIRE_NOTHROW(result = tbl->Aggregate("SUM(j), i")->Order("2")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {12, 6}));
REQUIRE(CHECK_COLUMN(result, 1, {1, 2}));
// explicitly grouped aggregate
REQUIRE_NOTHROW(
result =
tbl->Aggregate(duckdb::vector<string> {"SUM(j)"}, duckdb::vector<string> {"i"})->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {6, 12}));
// grouped aggregates can be expressions
REQUIRE_NOTHROW(result = tbl->Aggregate("i+1 AS i, SUM(j)")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 3}));
REQUIRE(CHECK_COLUMN(result, 1, {12, 6}));
// they can also involve multiple columns
REQUIRE_NOTHROW(result = tbl->Aggregate("i+i AS i, SUM(j)")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 4}));
REQUIRE(CHECK_COLUMN(result, 1, {12, 6}));
// we cannot combine non-aggregates with aggregates
REQUIRE_THROWS(result = tbl->Aggregate("i + SUM(j) AS i")->Order("1")->Execute());
REQUIRE_THROWS(result = tbl->Aggregate("i, i + SUM(j)")->Order("1")->Execute());
// group by multiple columns
REQUIRE_NOTHROW(result = tbl->Aggregate("i, j, SUM(i + j)")->Order("1, 2")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 2}));
REQUIRE(CHECK_COLUMN(result, 1, {5, 7, 6}));
REQUIRE(CHECK_COLUMN(result, 2, {6, 8, 8}));
// subqueries as groups
REQUIRE_NOTHROW(result = tbl->Aggregate("(SELECT i), SUM(i + j)")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2}));
REQUIRE(CHECK_COLUMN(result, 1, {14, 8}));
// subqueries as aggregates
REQUIRE_NOTHROW(result = tbl->Aggregate("(SELECT i), (SELECT SUM(i + j))")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2}));
REQUIRE(CHECK_COLUMN(result, 1, {14, 8}));
// constants without a grouping column
REQUIRE_NOTHROW(result = tbl->Aggregate("'hello', SUM(i + j)")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {"hello"}));
REQUIRE(CHECK_COLUMN(result, 1, {22}));
// constants with a grouping column
REQUIRE_NOTHROW(result = tbl->Aggregate("i, 'hello', SUM(i + j)")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2}));
REQUIRE(CHECK_COLUMN(result, 1, {"hello", "hello"}));
REQUIRE(CHECK_COLUMN(result, 2, {14, 8}));
// aggregate with only non-aggregate columns becomes a distinct
REQUIRE_NOTHROW(result = tbl->Aggregate("i")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2}));
REQUIRE_NOTHROW(result = tbl->Aggregate("i, j")->Order("1, 2")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 2}));
REQUIRE(CHECK_COLUMN(result, 1, {5, 7, 6}));
// now test aggregates with explicit groups
REQUIRE_NOTHROW(result = tbl->Aggregate("i", "i")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2}));
REQUIRE_NOTHROW(result = tbl->Aggregate("i, SUM(j)", "i")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2}));
REQUIRE(CHECK_COLUMN(result, 1, {12, 6}));
// explicit groups can be combined with aggregates
REQUIRE_NOTHROW(result = tbl->Aggregate("i, i+SUM(j)", "i")->Order("1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1, 2}));
REQUIRE(CHECK_COLUMN(result, 1, {13, 8}));
// when using explicit groups, we cannot have non-explicit groups
REQUIRE_THROWS(tbl->Aggregate("j, i+SUM(j)", "i")->Order("1")->Execute());
// Coverage: Groups expressions can not create multiple statements
REQUIRE_THROWS(tbl->Aggregate("i", "i; select 42")->Execute());
// project -> aggregate -> project -> aggregate
// SUM(j) = 18 -> 18 + 1 = 19 -> 19 * 2 = 38
result = tbl->Aggregate("SUM(j) AS k")->Project("k+1 AS l")->Aggregate("SUM(l) AS m")->Project("m*2")->Execute();
REQUIRE(CHECK_COLUMN(result, 0, {38}));
// aggregate after output modifiers
result = tbl->Order("i")->Limit(100)->Aggregate("SUM(j) AS k")->Execute();
REQUIRE(CHECK_COLUMN(result, 0, {18}));
}
TEST_CASE("Test interaction of relations with transactions", "[relation_api]") {
DuckDB db(nullptr);
Connection con1(db), con2(db);
duckdb::unique_ptr<QueryResult> result;
con1.BeginTransaction();
con2.BeginTransaction();
// create a table in con1
REQUIRE_NOTHROW(con1.Values("(1), (2), (3)")->Create("integers"));
// con1 can see it, but con2 can't see it yet
REQUIRE_NOTHROW(con1.Table("integers"));
REQUIRE_THROWS(con2.Table("integers"));
// we can also rollback
con1.Rollback();
REQUIRE_THROWS(con1.Table("integers"));
REQUIRE_THROWS(con2.Table("integers"));
// recreate the table, this time in auto-commit mode
REQUIRE_NOTHROW(con1.Values("(1), (2), (3)")->Create("integers"));
// con2 still can't see it, because it is in its own transaction
REQUIRE_NOTHROW(con1.Table("integers"));
REQUIRE_THROWS(con2.Table("integers"));
// after con2 commits, both can see the table
con2.Commit();
REQUIRE_NOTHROW(con1.Table("integers"));
REQUIRE_NOTHROW(con2.Table("integers"));
}
TEST_CASE("Test interaction of relations with schema changes", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
duckdb::unique_ptr<QueryResult> result;
// create some tables
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)"));
// create a table scan of the integers table
auto tbl_scan = con.Table("integers")->Project("i+1")->Order("1");
REQUIRE_NOTHROW(result = tbl_scan->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4}));
// now drop the table
REQUIRE_NO_FAIL(con.Query("DROP TABLE integers"));
// the scan now fails, because the table is dropped!
REQUIRE_FAIL(tbl_scan->Execute());
// if we recreate the table, it works again
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)"));
REQUIRE_NOTHROW(result = tbl_scan->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4}));
// but what if we recreate an incompatible table?
REQUIRE_NO_FAIL(con.Query("DROP TABLE integers"));
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i VARCHAR, j INTEGER)"));
REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES ('hello', 3)"));
// this results in a binding error!
REQUIRE_FAIL(tbl_scan->Execute());
// now what if we run a query that still binds successfully, but changes result?
auto tbl = con.Table("integers");
REQUIRE_NO_FAIL(tbl->Execute());
// add extra columns
REQUIRE_NO_FAIL(con.Query("DROP TABLE integers"));
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i VARCHAR, j VARCHAR)"));
REQUIRE_FAIL(tbl->Execute());
// change type of column
REQUIRE_NO_FAIL(con.Query("DROP TABLE integers"));
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i DATE, j INTEGER)"));
REQUIRE_FAIL(tbl->Execute());
// different name also results in an error
REQUIRE_NO_FAIL(con.Query("DROP TABLE integers"));
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(k VARCHAR, j INTEGER)"));
REQUIRE_FAIL(tbl->Execute());
// but once we go back to the original table it works again!
REQUIRE_NO_FAIL(con.Query("DROP TABLE integers"));
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i VARCHAR, j INTEGER)"));
REQUIRE_NO_FAIL(tbl->Execute());
}
TEST_CASE("Test junk SQL in expressions", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
duckdb::unique_ptr<QueryResult> result;
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)"));
auto tbl = con.Table("integers");
REQUIRE_THROWS(tbl->Filter("1=1; DELETE FROM tables;"));
REQUIRE_THROWS(tbl->Filter("1=1 UNION SELECT 42"));
REQUIRE_THROWS(tbl->Order("1 DESC; DELETE FROM tables;"));
REQUIRE_THROWS(tbl->Update("i=1; DELETE FROM TABLES"));
REQUIRE_THROWS(con.Values("(1, 1); SELECT 42"));
REQUIRE_THROWS(con.Values("(1, 1) UNION SELECT 42"));
}
TEST_CASE("We cannot mix statements from multiple databases", "[relation_api]") {
DuckDB db(nullptr), db2(nullptr);
Connection con(db), con2(db2);
duckdb::unique_ptr<QueryResult> result;
// create some tables
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)"));
REQUIRE_NO_FAIL(con2.Query("CREATE TABLE integers(i INTEGER)"));
REQUIRE_NO_FAIL(con2.Query("INSERT INTO integers VALUES (4)"));
auto i1 = con.Table("integers");
auto i2 = con2.Table("integers");
// we cannot mix statements from different connections without a wrapper!
REQUIRE_THROWS(i2->Union(i1));
REQUIRE_THROWS(i2->Except(i1));
REQUIRE_THROWS(i2->Intersect(i1));
REQUIRE_THROWS(i2->Join(i1, "i"));
REQUIRE_THROWS(i2->Join(i1, "i1.i=i2.i"));
// FIXME: what about a wrapper to scan data from other databases/connections?
}
TEST_CASE("Test view relations", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)"));
REQUIRE_NO_FAIL(con.Query("CREATE VIEW v1 AS SELECT i+1 AS i, i+2 FROM integers"));
REQUIRE_NO_FAIL(con.Query("CREATE VIEW v2(a,b) AS SELECT i+1, i+2 FROM integers"));
auto i1 = con.View("v1");
result = i1->Execute();
REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4}));
REQUIRE(CHECK_COLUMN(result, 1, {3, 4, 5}));
result = con.View("v2")->Project("a+b")->Execute();
REQUIRE(CHECK_COLUMN(result, 0, {5, 7, 9}));
// non-existant view
REQUIRE_THROWS(con.View("blabla"));
// combining views
result = con.View("v1")->Join(con.View("v2"), "v1.i=v2.a")->Execute();
REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 4}));
REQUIRE(CHECK_COLUMN(result, 1, {3, 4, 5}));
REQUIRE(CHECK_COLUMN(result, 2, {2, 3, 4}));
REQUIRE(CHECK_COLUMN(result, 3, {3, 4, 5}));
}
TEST_CASE("Test table function relations", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
auto i1 = con.TableFunction("duckdb_tables");
result = i1->Execute();
REQUIRE(CHECK_COLUMN(result, 2, {"main"}));
// function with parameters
auto i2 = con.TableFunction("pragma_table_info", {"integers"});
result = i2->Execute();
REQUIRE(CHECK_COLUMN(result, 0, {0}));
REQUIRE(CHECK_COLUMN(result, 1, {"i"}));
REQUIRE(CHECK_COLUMN(result, 2, {"INTEGER"}));
// we can do ops on table functions
result = i2->Filter("cid=0")->Project("concat(name, ' ', type), length(name), reverse(lower(type))")->Execute();
REQUIRE(CHECK_COLUMN(result, 0, {"i INTEGER"}));
REQUIRE(CHECK_COLUMN(result, 1, {1}));
REQUIRE(CHECK_COLUMN(result, 2, {"regetni"}));
// table function that takes a relation as input
auto values = con.Values("(42)", {"i"});
auto summary = values->TableFunction("summary", duckdb::vector<Value> {});
result = summary->Execute();
REQUIRE(CHECK_COLUMN(result, 0, {"[42]"}));
REQUIRE(CHECK_COLUMN(result, 1, {"42"}));
// non-existant table function
REQUIRE_THROWS(con.TableFunction("blabla"));
}
TEST_CASE("Test CSV Relation with union by name", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
named_parameter_map_t options;
options["union_by_name"] = Value(true);
duckdb::vector<string> paths {"data/csv/sample-0.csv", "data/csv/sample-0.csv"};
auto csv_scan = con.ReadCSV(paths, std::move(options));
auto result = csv_scan->Execute();
REQUIRE(!result->HasError());
REQUIRE(CHECK_COLUMN(result, 0, {Value::DATE(2024, 1, 2), Value::DATE(2024, 1, 2), Value::DATE(2024, 1, 2)}));
options["union_by_name"] = Value(true);
paths = {"data/csv/union-by-name/ubn1.csv", "data/csv/union-by-name/ubn2.csv"};
csv_scan = con.ReadCSV(paths, std::move(options));
result = csv_scan->Execute();
REQUIRE(!result->HasError());
}
TEST_CASE("Test CSV reading/writing from relations", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
// write a bunch of values to a CSV
auto csv_file = TestCreatePath("relationtest.csv");
case_insensitive_map_t<duckdb::vector<Value>> options;
options["header"] = {duckdb::Value(0)};
con.Values("(1), (2), (3)", {"i"})->WriteCSV(csv_file, options);
REQUIRE_THROWS(con.Values("(1), (2), (3)", {"i"})->WriteCSV("//fef//gw/g/bla/bla", options));
// now scan the CSV file
auto csv_scan = con.ReadCSV(csv_file, {"i INTEGER"});
result = csv_scan->Execute();
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
// with auto detect
auto auto_csv_scan = con.ReadCSV(csv_file);
result = auto_csv_scan->Execute();
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
REQUIRE_THROWS(con.ReadCSV(csv_file, {"i INTEGER); SELECT 42;--"}));
REQUIRE_THROWS(con.ReadCSV(csv_file, {"i INTEGER, j INTEGER"}));
}
TEST_CASE("Test CSV reading from weather.csv", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
auto auto_csv_scan = con.ReadCSV("data/csv/weather.csv")->Limit(1);
result = auto_csv_scan->Execute();
REQUIRE(CHECK_COLUMN(result, 0, {"2016-01-01"}));
}
TEST_CASE("Test query relation", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
duckdb::shared_ptr<Relation> tbl;
// create some tables
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1), (2), (3)"));
REQUIRE_NOTHROW(tbl = con.RelationFromQuery("SELECT * FROM integers WHERE i >= 2"));
REQUIRE_NOTHROW(result = tbl->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2, 3}));
REQUIRE_NOTHROW(result = tbl->Project("i + 1")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {3, 4}));
REQUIRE_NOTHROW(result = tbl->Alias("q1")->Join(tbl->Alias("q2")->Filter("i=3"), "q1.i=q2.i")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {3}));
REQUIRE_THROWS(tbl->Project("k+1"));
// multiple queries
REQUIRE_THROWS(con.RelationFromQuery("SELECT 42; SELECT 84"));
// not a select statement
REQUIRE_THROWS(con.RelationFromQuery("DELETE FROM tbl"));
}
TEST_CASE("Test TopK relation", "[relation_api]") {
DuckDB db(nullptr);
Connection con(db);
con.EnableQueryVerification();
duckdb::unique_ptr<QueryResult> result;
duckdb::shared_ptr<Relation> tbl;
REQUIRE_NO_FAIL(con.Query("CREATE TABLE test (i integer,j VARCHAR, k varchar )"));
REQUIRE_NO_FAIL(con.Query("insert into test values (10,'a','a'), (20,'a','b')"));
REQUIRE_NOTHROW(tbl = con.Table("test"));
REQUIRE_NOTHROW(tbl = tbl->Filter("k < 'f' and k is not null")
->Project("#3,#2,#1")
->Project("#2,#3")
->Order("(#2-10)::UTINYINT ASC")
->Limit(1));
}
TEST_CASE("Test Relation Pending Query API", "[relation_api]") {
DuckDB db;
Connection con(db);
SECTION("Materialized result") {
auto tbl = con.TableFunction("range", {Value(1000000)});
auto aggr = tbl->Aggregate("SUM(range)");
auto pending_query = con.context->PendingQuery(aggr, false);
REQUIRE(!pending_query->HasError());
auto result = pending_query->Execute();
REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(499999500000)}));
// cannot fetch twice from the same pending query
REQUIRE_THROWS(pending_query->Execute());
REQUIRE_THROWS(pending_query->Execute());
// query the connection as normal after
result = con.Query("SELECT 42");
REQUIRE(CHECK_COLUMN(result, 0, {42}));
}
SECTION("Runtime error in pending query (materialized)") {
auto tbl = con.TableFunction("range", {Value(1000000)});
auto aggr = tbl->Aggregate("SUM(range) AS s")->Project("concat(s::varchar, 'hello')::int");
// this succeeds initially
auto pending_query = con.context->PendingQuery(aggr, false);
REQUIRE(!pending_query->HasError());
// we only encounter the failure later on as we are executing the query
auto result = pending_query->Execute();
REQUIRE_FAIL(result);
// query the connection as normal after
result = con.Query("SELECT 42");
REQUIRE(CHECK_COLUMN(result, 0, {42}));
}
}
TEST_CASE("Test Relation Query setting query", "[relation_api]") {
DuckDB db;
Connection con(db);
auto query = con.RelationFromQuery("SELECT current_query()");
auto result = query->Limit(1)->Execute();
REQUIRE(!result->Fetch()->GetValue(0, 0).ToString().empty());
}
TEST_CASE("Construct ValueRelation with RelationContextWrapper and operate on it", "[relation_api][txn][wrapper]") {
DuckDB db;
Connection con(db);
con.EnableQueryVerification();
// Build expressions to force the "expressions" overload (not the constants path)
duckdb::vector<duckdb::vector<duckdb::unique_ptr<duckdb::ParsedExpression>>> expressions;
{
duckdb::vector<duckdb::unique_ptr<duckdb::ParsedExpression>> row;
{
duckdb::ConstantExpression ce1(duckdb::Value::INTEGER(1));
row.push_back(ce1.Copy());
}
{
duckdb::ConstantExpression ce2(duckdb::Value::INTEGER(2));
row.push_back(ce2.Copy());
}
expressions.push_back(std::move(row));
}
// Explicitly create a RelationContextWrapper from the client's context
auto rcw = duckdb::make_shared_ptr<duckdb::RelationContextWrapper>(con.context);
// Manually construct a ValueRelation using the RelationContextWrapper + expressions ctor
duckdb::shared_ptr<duckdb::Relation> rel;
REQUIRE_NOTHROW(rel = duckdb::make_shared_ptr<duckdb::ValueRelation>(rcw, std::move(expressions),
duckdb::vector<std::string> {}, "vr"));
// Base relation should execute (1 row, 2 columns)
duckdb::unique_ptr<QueryResult> result;
REQUIRE_NOTHROW(result = rel->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1}));
REQUIRE(CHECK_COLUMN(result, 1, {2}));
// Project on top — should bind & execute under a valid transaction
REQUIRE_NOTHROW(result = rel->Project("1+1, 2+2")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {2}));
REQUIRE(CHECK_COLUMN(result, 1, {4}));
// Aggregate on top — also must bind & execute cleanly
REQUIRE_NOTHROW(result = rel->Aggregate("count(*)")->Execute());
REQUIRE(CHECK_COLUMN(result, 0, {1}));
}
TEST_CASE("Test materialized relations", "[relation_api]") {
auto db_path = TestCreatePath("relational_api_materialized_view.db");
{
DuckDB db(db_path);
Connection con(db);
con.EnableQueryVerification();
REQUIRE_NO_FAIL(con.Query("create table tbl(a varchar);"));
auto result = con.Query("insert into tbl values ('test') returning *");
auto &materialized_result = result->Cast<MaterializedQueryResult>();
auto materialized_relation = make_shared_ptr<MaterializedRelation>(
con.context, materialized_result.TakeCollection(), result->names, "vw");
materialized_relation->CreateView("vw");
materialized_relation.reset();
result = con.Query("SELECT * FROM vw");
REQUIRE(CHECK_COLUMN(result, 0, {"test"}));
}
// reset and query again
{
DuckDB db(db_path);
Connection con(db);
auto result = con.Query("SELECT * FROM vw");
REQUIRE(CHECK_COLUMN(result, 0, {"test"}));
}
}