#include "catch.hpp" #include "test_helpers.hpp" #include "duckdb/main/appender.hpp" #include "duckdb/common/types/hugeint.hpp" using namespace duckdb; using namespace std; template void TestAppendingSingleDecimalValue(SRC value, Value expected_result, uint8_t width, uint8_t scale) { auto db = make_uniq(nullptr); auto conn = make_uniq(*db); duckdb::unique_ptr appender; duckdb::unique_ptr result; REQUIRE_NO_FAIL(conn->Query(StringUtil::Format("CREATE TABLE decimals(i DECIMAL(%d,%d))", width, scale))); appender = make_uniq(*conn, "decimals"); appender->BeginRow(); appender->Append(value); appender->EndRow(); appender->Flush(); result = conn->Query("SELECT * FROM decimals"); REQUIRE(CHECK_COLUMN(result, 0, {expected_result})); } TEST_CASE("Test appending to a decimal column", "[api]") { TestAppendingSingleDecimalValue(1, Value::DECIMAL(1000, 4, 3), 4, 3); TestAppendingSingleDecimalValue(-9999, Value::DECIMAL(-9999, 4, 0), 4, 0); TestAppendingSingleDecimalValue(9999, Value::DECIMAL(9999, 4, 0), 4, 0); TestAppendingSingleDecimalValue(99999999, Value::DECIMAL(99999999, 8, 0), 8, 0); TestAppendingSingleDecimalValue("1.234", Value::DECIMAL(1234, 4, 3), 4, 3); TestAppendingSingleDecimalValue("123.4", Value::DECIMAL(1234, 4, 1), 4, 1); hugeint_t hugeint_value; bool result; result = Hugeint::TryConvert("3245234123123", hugeint_value); REQUIRE(result); TestAppendingSingleDecimalValue("3245234.123123", Value::DECIMAL(hugeint_value, 19, 6), 19, 6); int64_t bigint_reference_value = 3245234123123; TestAppendingSingleDecimalValue("3245234.123123", Value::DECIMAL(bigint_reference_value, 13, 6), 13, 6); // Precision loss TestAppendingSingleDecimalValue(12.3124324f, Value::DECIMAL(123124320, 9, 7), 9, 7); // Precision loss result = Hugeint::TryConvert("12345234234312432287744000", hugeint_value); REQUIRE(result); TestAppendingSingleDecimalValue(12345234234.31243244234324, Value::DECIMAL(hugeint_value, 26, 15), 26, 15); } TEST_CASE("Test using appender after connection is gone", "[api]") { auto db = make_uniq(nullptr); auto conn = make_uniq(*db); duckdb::unique_ptr appender; duckdb::unique_ptr result; // create an appender for a non-existing table fails REQUIRE_THROWS(make_uniq(*conn, "integers")); // now create the table and create the appender REQUIRE_NO_FAIL(conn->Query("CREATE TABLE integers(i INTEGER)")); appender = make_uniq(*conn, "integers"); // we can use the appender appender->BeginRow(); appender->Append(1); appender->EndRow(); appender->Flush(); result = conn->Query("SELECT * FROM integers"); REQUIRE(CHECK_COLUMN(result, 0, {1})); // removing the connection invalidates the appender conn.reset(); appender->BeginRow(); appender->Append(2); appender->EndRow(); // the connection is gone REQUIRE_THROWS(appender->Flush()); } TEST_CASE("Test appender and connection destruction order", "[api]") { for (idx_t i = 0; i < 6; i++) { auto db = make_uniq(nullptr); auto con = make_uniq(*db); REQUIRE_NO_FAIL(con->Query("CREATE TABLE integers(i INTEGER)")); auto appender = make_uniq(*con, "integers"); switch (i) { case 0: // db - con - appender db.reset(); con.reset(); appender.reset(); break; case 1: // db - appender - con db.reset(); appender.reset(); con.reset(); break; case 2: // con - db - appender con.reset(); db.reset(); appender.reset(); break; case 3: // con - appender - db con.reset(); appender.reset(); db.reset(); break; case 4: // appender - con - db appender.reset(); con.reset(); db.reset(); break; default: // appender - db - con appender.reset(); db.reset(); con.reset(); break; } } } TEST_CASE("Test using appender after table is dropped", "[api]") { DuckDB db(nullptr); Connection con(db); // create the table REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)")); // now create the appender Appender appender(con, "integers"); // appending works initially appender.BeginRow(); appender.Append(1); appender.EndRow(); appender.Flush(); // now drop the table REQUIRE_NO_FAIL(con.Query("DROP TABLE integers")); // now appending fails appender.BeginRow(); appender.Append(1); appender.EndRow(); REQUIRE_THROWS(appender.Flush()); } TEST_CASE("Test using appender after table is altered", "[api]") { DuckDB db(nullptr); Connection con(db); // create the table REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)")); // now create the appender Appender appender(con, "integers"); // appending works initially appender.BeginRow(); appender.Append(1); appender.EndRow(); // now create a new table with the same name but with different types REQUIRE_NO_FAIL(con.Query("DROP TABLE integers")); REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i VARCHAR, j INTEGER)")); // now appending fails appender.BeginRow(); appender.Append(1); appender.EndRow(); REQUIRE_THROWS(appender.Flush()); } TEST_CASE("Test appenders and transactions", "[api]") { DuckDB db(nullptr); Connection con(db); duckdb::unique_ptr result; // create the table REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)")); // now create the appender Appender appender(con, "integers"); // rollback an append REQUIRE_NO_FAIL(con.Query("BEGIN TRANSACTION")); appender.BeginRow(); appender.Append(1); appender.EndRow(); appender.Flush(); result = con.Query("SELECT * FROM integers"); REQUIRE(CHECK_COLUMN(result, 0, {1})); REQUIRE_NO_FAIL(con.Query("ROLLBACK")); result = con.Query("SELECT * FROM integers"); REQUIRE(CHECK_COLUMN(result, 0, {})); // we can still use the appender in auto commit mode appender.BeginRow(); appender.Append(1); appender.EndRow(); appender.Flush(); result = con.Query("SELECT * FROM integers"); REQUIRE(CHECK_COLUMN(result, 0, {1})); } TEST_CASE("Test using multiple appenders", "[api]") { DuckDB db(nullptr); Connection con(db); duckdb::unique_ptr result; // create the table REQUIRE_NO_FAIL(con.Query("CREATE TABLE t1(i INTEGER)")); REQUIRE_NO_FAIL(con.Query("CREATE TABLE t2(i VARCHAR, j DATE)")); // now create the appender Appender a1(con, "t1"); Appender a2(con, "t2"); // begin appending from both REQUIRE_NO_FAIL(con.Query("BEGIN TRANSACTION")); a1.BeginRow(); a1.Append(1); a1.EndRow(); a1.Flush(); a2.BeginRow(); a2.Append("hello"); a2.Append(Value::DATE(1992, 1, 1)); a2.EndRow(); a2.Flush(); result = con.Query("SELECT * FROM t1"); REQUIRE(CHECK_COLUMN(result, 0, {1})); result = con.Query("SELECT * FROM t2"); REQUIRE(CHECK_COLUMN(result, 0, {"hello"})); REQUIRE(CHECK_COLUMN(result, 1, {Value::DATE(1992, 1, 1)})); REQUIRE_NO_FAIL(con.Query("ROLLBACK")); result = con.Query("SELECT * FROM t1"); REQUIRE(CHECK_COLUMN(result, 0, {})); } TEST_CASE("Test usage of appender interleaved with connection usage", "[api]") { DuckDB db(nullptr); Connection con(db); duckdb::unique_ptr result; // create the table REQUIRE_NO_FAIL(con.Query("CREATE TABLE t1(i INTEGER)")); Appender appender(con, "t1"); appender.AppendRow(1); appender.Flush(); result = con.Query("SELECT * FROM t1"); REQUIRE(CHECK_COLUMN(result, 0, {1})); appender.AppendRow(2); appender.Flush(); result = con.Query("SELECT * FROM t1"); REQUIRE(CHECK_COLUMN(result, 0, {1, 2})); } TEST_CASE("Test appender during stack unwinding", "[api]") { // test appender exception DuckDB db; Connection con(db); REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)")); { Appender appender(con, "integers"); appender.AppendRow(1); // closing the apender throws an exception, because we changed the table's columns REQUIRE_NO_FAIL(con.Query("ALTER TABLE integers ADD COLUMN j VARCHAR")); REQUIRE_THROWS(appender.Close()); } REQUIRE_NO_FAIL(con.Query("DROP TABLE integers")); REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)")); try { // now we do the same, but we trigger the destructor of the appender during stack unwinding Appender appender(con, "integers"); appender.AppendRow(1); REQUIRE_NO_FAIL(con.Query("ALTER TABLE integers ADD COLUMN j VARCHAR")); { throw std::runtime_error("Hello"); } } catch (...) { } }