#include "capi_tester.hpp" #include using namespace duckdb; using namespace std; static void require_hugeint_eq(duckdb_hugeint left, duckdb_hugeint right) { REQUIRE(left.lower == right.lower); REQUIRE(left.upper == right.upper); } static void require_hugeint_eq(duckdb_hugeint left, uint64_t lower, int64_t upper) { duckdb_hugeint temp; temp.lower = lower; temp.upper = upper; require_hugeint_eq(left, temp); } static void require_uhugeint_eq(duckdb_uhugeint left, duckdb_uhugeint right) { REQUIRE(left.lower == right.lower); REQUIRE(left.upper == right.upper); } static void require_uhugeint_eq(duckdb_uhugeint left, uint64_t lower, uint64_t upper) { duckdb_uhugeint temp; temp.lower = lower; temp.upper = upper; require_uhugeint_eq(left, temp); } TEST_CASE("Basic test of C API", "[capi]") { CAPITester tester; duckdb::unique_ptr result; // open the database in in-memory mode REQUIRE(tester.OpenDatabase(nullptr)); REQUIRE_NO_FAIL(tester.Query("SET default_null_order='nulls_first'")); // select scalar value result = tester.Query("SELECT CAST(42 AS BIGINT)"); REQUIRE_NO_FAIL(*result); REQUIRE(result->ColumnType(0) == DUCKDB_TYPE_BIGINT); REQUIRE(result->ColumnData(0)[0] == 42); REQUIRE(result->ColumnCount() == 1); REQUIRE(result->row_count() == 1); REQUIRE(result->Fetch(0, 0) == 42); REQUIRE(!result->IsNull(0, 0)); // out of range fetch REQUIRE(result->Fetch(1, 0) == 0); REQUIRE(result->Fetch(0, 1) == 0); // cannot fetch data chunk after using the value API REQUIRE(result->FetchChunk(0) == nullptr); // select scalar NULL result = tester.Query("SELECT NULL"); REQUIRE_NO_FAIL(*result); REQUIRE(result->ColumnCount() == 1); REQUIRE(result->row_count() == 1); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->IsNull(0, 0)); // select scalar string result = tester.Query("SELECT 'hello'"); REQUIRE_NO_FAIL(*result); REQUIRE(result->ColumnCount() == 1); REQUIRE(result->row_count() == 1); REQUIRE(result->Fetch(0, 0) == "hello"); REQUIRE(!result->IsNull(0, 0)); result = tester.Query("SELECT 1=1"); REQUIRE_NO_FAIL(*result); REQUIRE(result->ColumnCount() == 1); REQUIRE(result->row_count() == 1); REQUIRE(result->Fetch(0, 0) == true); REQUIRE(!result->IsNull(0, 0)); result = tester.Query("SELECT 1=0"); REQUIRE_NO_FAIL(*result); REQUIRE(result->ColumnCount() == 1); REQUIRE(result->row_count() == 1); REQUIRE(result->Fetch(0, 0) == false); REQUIRE(!result->IsNull(0, 0)); result = tester.Query("SELECT i FROM (values (true), (false)) tbl(i) group by i order by i"); REQUIRE_NO_FAIL(*result); REQUIRE(result->ColumnCount() == 1); REQUIRE(result->row_count() == 2); REQUIRE(result->Fetch(0, 0) == false); REQUIRE(result->Fetch(0, 1) == true); REQUIRE(!result->IsNull(0, 0)); // multiple insertions REQUIRE_NO_FAIL(tester.Query("CREATE TABLE test (a INTEGER, b INTEGER);")); REQUIRE_NO_FAIL(tester.Query("INSERT INTO test VALUES (11, 22)")); REQUIRE_NO_FAIL(tester.Query("INSERT INTO test VALUES (NULL, 21)")); result = tester.Query("INSERT INTO test VALUES (13, 22)"); REQUIRE_NO_FAIL(*result); REQUIRE(result->rows_changed() == 1); // NULL selection result = tester.Query("SELECT a, b FROM test ORDER BY a"); REQUIRE_NO_FAIL(*result); REQUIRE(result->rows_changed() == 0); // NULL, 11, 13 REQUIRE(result->IsNull(0, 0)); REQUIRE(result->Fetch(0, 1) == 11); REQUIRE(result->Fetch(0, 2) == 13); // 21, 22, 22 REQUIRE(result->Fetch(1, 0) == 21); REQUIRE(result->Fetch(1, 1) == 22); REQUIRE(result->Fetch(1, 2) == 22); REQUIRE(result->ColumnName(0) == "a"); REQUIRE(result->ColumnName(1) == "b"); REQUIRE(result->ColumnName(2) == ""); result = tester.Query("UPDATE test SET a = 1 WHERE b=22"); REQUIRE_NO_FAIL(*result); REQUIRE(result->rows_changed() == 2); // several error conditions REQUIRE(duckdb_value_is_null(nullptr, 0, 0) == false); REQUIRE(duckdb_column_type(nullptr, 0) == DUCKDB_TYPE_INVALID); REQUIRE(duckdb_column_count(nullptr) == 0); REQUIRE(duckdb_row_count(nullptr) == 0); REQUIRE(duckdb_rows_changed(nullptr) == 0); REQUIRE(duckdb_result_error(nullptr) == nullptr); REQUIRE(duckdb_nullmask_data(nullptr, 0) == nullptr); REQUIRE(duckdb_column_data(nullptr, 0) == nullptr); REQUIRE(duckdb_result_error_type(nullptr) == DUCKDB_ERROR_INVALID); } TEST_CASE("Test different types of C API", "[capi]") { CAPITester tester; duckdb::unique_ptr result; // open the database in in-memory mode REQUIRE(tester.OpenDatabase(nullptr)); REQUIRE_NO_FAIL(tester.Query("SET default_null_order='nulls_first'")); // integer columns duckdb::vector types = {"TINYINT", "SMALLINT", "INTEGER", "BIGINT", "HUGEINT", "UTINYINT", "USMALLINT", "UINTEGER", "UBIGINT", "UHUGEINT"}; for (auto &type : types) { // create the table and insert values REQUIRE_NO_FAIL(tester.Query("BEGIN TRANSACTION")); REQUIRE_NO_FAIL(tester.Query("CREATE TABLE integers(i " + type + ")")); REQUIRE_NO_FAIL(tester.Query("INSERT INTO integers VALUES (1), (NULL)")); result = tester.Query("SELECT * FROM integers ORDER BY i"); REQUIRE_NO_FAIL(*result); REQUIRE(result->IsNull(0, 0)); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(duckdb_uhugeint_to_double(result->Fetch(0, 0)) == 0); REQUIRE(duckdb_hugeint_to_double(result->Fetch(0, 0)) == 0); REQUIRE(result->Fetch(0, 0) == ""); REQUIRE(ApproxEqual(result->Fetch(0, 0), 0.0f)); REQUIRE(ApproxEqual(result->Fetch(0, 0), 0.0)); REQUIRE(!result->IsNull(0, 1)); REQUIRE(result->Fetch(0, 1) == 1); REQUIRE(result->Fetch(0, 1) == 1); REQUIRE(result->Fetch(0, 1) == 1); REQUIRE(result->Fetch(0, 1) == 1); REQUIRE(result->Fetch(0, 1) == 1); REQUIRE(result->Fetch(0, 1) == 1); REQUIRE(result->Fetch(0, 1) == 1); REQUIRE(result->Fetch(0, 1) == 1); REQUIRE(duckdb_uhugeint_to_double(result->Fetch(0, 1)) == 1); REQUIRE(duckdb_hugeint_to_double(result->Fetch(0, 1)) == 1); REQUIRE(ApproxEqual(result->Fetch(0, 1), 1.0f)); REQUIRE(ApproxEqual(result->Fetch(0, 1), 1.0)); REQUIRE(result->Fetch(0, 1) == "1"); REQUIRE_NO_FAIL(tester.Query("ROLLBACK")); } // real/double columns types = {"REAL", "DOUBLE"}; for (auto &type : types) { // create the table and insert values REQUIRE_NO_FAIL(tester.Query("BEGIN TRANSACTION")); REQUIRE_NO_FAIL(tester.Query("CREATE TABLE doubles(i " + type + ")")); REQUIRE_NO_FAIL(tester.Query("INSERT INTO doubles VALUES (1), (NULL)")); result = tester.Query("SELECT * FROM doubles ORDER BY i"); REQUIRE_NO_FAIL(*result); REQUIRE(result->IsNull(0, 0)); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->Fetch(0, 0) == ""); REQUIRE(ApproxEqual(result->Fetch(0, 0), 0.0f)); REQUIRE(ApproxEqual(result->Fetch(0, 0), 0.0)); REQUIRE(!result->IsNull(0, 1)); REQUIRE(result->Fetch(0, 1) == 1); REQUIRE(result->Fetch(0, 1) == 1); REQUIRE(result->Fetch(0, 1) == 1); REQUIRE(result->Fetch(0, 1) == 1); REQUIRE(ApproxEqual(result->Fetch(0, 1), 1.0f)); REQUIRE(ApproxEqual(result->Fetch(0, 1), 1.0)); REQUIRE_NO_FAIL(tester.Query("ROLLBACK")); } // date columns REQUIRE_NO_FAIL(tester.Query("CREATE TABLE dates(d DATE)")); REQUIRE_NO_FAIL(tester.Query("INSERT INTO dates VALUES ('1992-09-20'), (NULL), ('30000-09-20')")); result = tester.Query("SELECT * FROM dates ORDER BY d"); REQUIRE_NO_FAIL(*result); REQUIRE(result->IsNull(0, 0)); duckdb_date_struct date = duckdb_from_date(result->Fetch(0, 1)); REQUIRE(date.year == 1992); REQUIRE(date.month == 9); REQUIRE(date.day == 20); REQUIRE(result->Fetch(0, 1) == Value::DATE(1992, 9, 20).ToString()); date = duckdb_from_date(result->Fetch(0, 2)); REQUIRE(date.year == 30000); REQUIRE(date.month == 9); REQUIRE(date.day == 20); REQUIRE(result->Fetch(0, 2) == Value::DATE(30000, 9, 20).ToString()); // time columns REQUIRE_NO_FAIL(tester.Query("CREATE TABLE times(d TIME)")); REQUIRE_NO_FAIL(tester.Query("INSERT INTO times VALUES ('12:00:30.1234'), (NULL), ('02:30:01')")); result = tester.Query("SELECT * FROM times ORDER BY d"); REQUIRE_NO_FAIL(*result); REQUIRE(result->IsNull(0, 0)); duckdb_time_struct time_val = duckdb_from_time(result->Fetch(0, 1)); REQUIRE(time_val.hour == 2); REQUIRE(time_val.min == 30); REQUIRE(time_val.sec == 1); REQUIRE(time_val.micros == 0); REQUIRE(result->Fetch(0, 1) == Value::TIME(2, 30, 1, 0).ToString()); time_val = duckdb_from_time(result->Fetch(0, 2)); REQUIRE(time_val.hour == 12); REQUIRE(time_val.min == 0); REQUIRE(time_val.sec == 30); REQUIRE(time_val.micros == 123400); REQUIRE(result->Fetch(0, 2) == Value::TIME(12, 0, 30, 123400).ToString()); // blob columns REQUIRE_NO_FAIL(tester.Query("CREATE TABLE blobs(b BLOB)")); REQUIRE_NO_FAIL(tester.Query("INSERT INTO blobs VALUES ('hello\\x12world'), ('\\x00'), (NULL)")); result = tester.Query("SELECT * FROM blobs"); REQUIRE_NO_FAIL(*result); REQUIRE(!result->IsNull(0, 0)); duckdb_blob blob = result->Fetch(0, 0); REQUIRE(blob.size == 11); REQUIRE(memcmp(blob.data, "hello\012world", 11)); REQUIRE(result->Fetch(0, 1) == "\\x00"); REQUIRE(result->IsNull(0, 2)); blob = result->Fetch(0, 2); REQUIRE(blob.data == nullptr); REQUIRE(blob.size == 0); // boolean columns REQUIRE_NO_FAIL(tester.Query("CREATE TABLE booleans(b BOOLEAN)")); REQUIRE_NO_FAIL(tester.Query("INSERT INTO booleans VALUES (42 > 60), (42 > 20), (42 > NULL)")); result = tester.Query("SELECT * FROM booleans ORDER BY b"); REQUIRE_NO_FAIL(*result); REQUIRE(result->IsNull(0, 0)); REQUIRE(!result->Fetch(0, 0)); REQUIRE(!result->Fetch(0, 1)); REQUIRE(result->Fetch(0, 2)); REQUIRE(result->Fetch(0, 2) == "true"); // decimal columns REQUIRE_NO_FAIL(tester.Query("CREATE TABLE decimals(dec DECIMAL(18, 4) NULL)")); REQUIRE_NO_FAIL(tester.Query("INSERT INTO decimals VALUES (NULL), (12.3)")); result = tester.Query("SELECT * FROM decimals ORDER BY dec"); REQUIRE_NO_FAIL(*result); REQUIRE(result->IsNull(0, 0)); duckdb_decimal decimal = result->Fetch(0, 1); REQUIRE(duckdb_decimal_to_double(decimal) == 12.3); // test more decimal physical types result = tester.Query("SELECT " "1.2::DECIMAL(4,1)," "100.3::DECIMAL(9,1)," "-320938.4298::DECIMAL(18,4)," "49082094824.904820482094::DECIMAL(30,12)," "NULL::DECIMAL"); REQUIRE_NO_FAIL(*result); REQUIRE(duckdb_decimal_to_double(result->Fetch(0, 0)) == 1.2); REQUIRE(duckdb_decimal_to_double(result->Fetch(1, 0)) == 100.3); REQUIRE(duckdb_decimal_to_double(result->Fetch(2, 0)) == -320938.4298); REQUIRE(duckdb_decimal_to_double(result->Fetch(3, 0)) == 49082094824.904820482094); REQUIRE(duckdb_decimal_to_double(result->Fetch(4, 0)) == 0.0); REQUIRE(!result->IsNull(0, 0)); REQUIRE(!result->IsNull(1, 0)); REQUIRE(!result->IsNull(2, 0)); REQUIRE(!result->IsNull(3, 0)); REQUIRE(result->IsNull(4, 0)); REQUIRE(result->Fetch(0, 0) == true); REQUIRE(result->Fetch(1, 0) == true); REQUIRE(result->Fetch(2, 0) == true); REQUIRE(result->Fetch(3, 0) == true); REQUIRE(result->Fetch(4, 0) == false); REQUIRE(result->Fetch(0, 0) == 1); REQUIRE(result->Fetch(1, 0) == 100); REQUIRE(result->Fetch(2, 0) == 0); // overflow REQUIRE(result->Fetch(3, 0) == 0); // overflow REQUIRE(result->Fetch(4, 0) == 0); REQUIRE(result->Fetch(0, 0) == 1); REQUIRE(result->Fetch(1, 0) == 100); REQUIRE(result->Fetch(2, 0) == 0); // overflow REQUIRE(result->Fetch(3, 0) == 0); // overflow REQUIRE(result->Fetch(4, 0) == 0); REQUIRE(result->Fetch(0, 0) == 1); REQUIRE(result->Fetch(1, 0) == 100); REQUIRE(result->Fetch(2, 0) == 0); // overflow REQUIRE(result->Fetch(3, 0) == 0); // overflow REQUIRE(result->Fetch(4, 0) == 0); REQUIRE(result->Fetch(0, 0) == 1); REQUIRE(result->Fetch(1, 0) == 100); REQUIRE(result->Fetch(2, 0) == 0); // overflow REQUIRE(result->Fetch(3, 0) == 0); // overflow REQUIRE(result->Fetch(4, 0) == 0); REQUIRE(result->Fetch(0, 0) == 1); REQUIRE(result->Fetch(1, 0) == 100); REQUIRE(result->Fetch(2, 0) == -320938); REQUIRE(result->Fetch(3, 0) == 0); // overflow REQUIRE(result->Fetch(4, 0) == 0); REQUIRE(result->Fetch(0, 0) == 1); REQUIRE(result->Fetch(1, 0) == 100); REQUIRE(result->Fetch(2, 0) == 0); // overflow REQUIRE(result->Fetch(3, 0) == 0); // overflow REQUIRE(result->Fetch(4, 0) == 0); REQUIRE(result->Fetch(0, 0) == 1); REQUIRE(result->Fetch(1, 0) == 100); REQUIRE(result->Fetch(2, 0) == -320938); REQUIRE(result->Fetch(3, 0) == 49082094825); // ceiling REQUIRE(result->Fetch(4, 0) == 0); REQUIRE(result->Fetch(0, 0) == 1); REQUIRE(result->Fetch(1, 0) == 100); REQUIRE(result->Fetch(2, 0) == 0); // overflow REQUIRE(result->Fetch(3, 0) == 49082094825); REQUIRE(result->Fetch(4, 0) == 0); require_hugeint_eq(result->Fetch(0, 0), 1, 0); require_hugeint_eq(result->Fetch(1, 0), 100, 0); require_hugeint_eq(result->Fetch(2, 0), 18446744073709230678ul, -1); require_hugeint_eq(result->Fetch(3, 0), 49082094825, 0); require_hugeint_eq(result->Fetch(4, 0), 0, 0); require_uhugeint_eq(result->Fetch(0, 0), 1, 0); require_uhugeint_eq(result->Fetch(1, 0), 100, 0); require_uhugeint_eq(result->Fetch(2, 0), 0, 0); // overflow require_uhugeint_eq(result->Fetch(3, 0), 49082094825, 0); require_uhugeint_eq(result->Fetch(4, 0), 0, 0); REQUIRE(result->Fetch(0, 0) == 1.2f); REQUIRE(result->Fetch(1, 0) == 100.3f); REQUIRE(floor(result->Fetch(2, 0)) == -320939); REQUIRE((int64_t)floor(result->Fetch(3, 0)) == 49082093568); REQUIRE(result->Fetch(4, 0) == 0.0); REQUIRE(result->Fetch(0, 0) == 1.2); REQUIRE(result->Fetch(1, 0) == 100.3); REQUIRE(result->Fetch(2, 0) == -320938.4298); REQUIRE(result->Fetch(3, 0) == 49082094824.904820482094); REQUIRE(result->Fetch(4, 0) == 0.0); REQUIRE(result->Fetch(0, 0) == "1.2"); REQUIRE(result->Fetch(1, 0) == "100.3"); REQUIRE(result->Fetch(2, 0) == "-320938.4298"); REQUIRE(result->Fetch(3, 0) == "49082094824.904820482094"); REQUIRE(result->Fetch(4, 0) == ""); result = tester.Query("SELECT -123.45::DECIMAL(5,2)"); REQUIRE_NO_FAIL(*result); REQUIRE(result->Fetch(0, 0) == true); REQUIRE(result->Fetch(0, 0) == -123); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->Fetch(0, 0) == -123); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->Fetch(0, 0) == -123); REQUIRE(result->Fetch(0, 0) == 0); REQUIRE(result->Fetch(0, 0) == -123); REQUIRE(result->Fetch(0, 0) == 0); hugeint_t expected_hugeint_val; Hugeint::TryConvert(-123, expected_hugeint_val); duckdb_hugeint expected_val; expected_val.lower = expected_hugeint_val.lower; expected_val.upper = expected_hugeint_val.upper; require_hugeint_eq(result->Fetch(0, 0), expected_val); REQUIRE(result->Fetch(0, 0) == -123.45f); REQUIRE(result->Fetch(0, 0) == -123.45); REQUIRE(result->Fetch(0, 0) == "-123.45"); } TEST_CASE("decompose timetz with duckdb_from_time_tz", "[capi]") { CAPITester tester; REQUIRE(tester.OpenDatabase(nullptr)); auto res = tester.Query("SELECT TIMETZ '11:30:00.123456-02:00'"); REQUIRE(res->success); auto chunk = res->FetchChunk(0); REQUIRE(chunk->ColumnCount() == 1); REQUIRE(res->ColumnType(0) == DUCKDB_TYPE_TIME_TZ); auto data = (duckdb_time_tz *)chunk->GetData(0); auto time_tz = duckdb_from_time_tz(data[0]); REQUIRE(time_tz.time.hour == 11); REQUIRE(time_tz.time.min == 30); REQUIRE(time_tz.time.sec == 0); REQUIRE(time_tz.time.micros == 123456); REQUIRE(time_tz.offset == -7200); } TEST_CASE("create time_tz value") { duckdb_time_struct time; time.hour = 4; time.min = 2; time.sec = 6; time.micros = 9; int offset = 8000; auto micros = duckdb_to_time(time); auto res = duckdb_create_time_tz(micros.micros, offset); // and back again auto inverse = duckdb_from_time_tz(res); REQUIRE(offset == inverse.offset); REQUIRE(inverse.time.hour == 4); REQUIRE(inverse.time.min == 2); REQUIRE(inverse.time.sec == 6); REQUIRE(inverse.time.micros == 9); } TEST_CASE("Test errors in C API", "[capi]") { CAPITester tester; duckdb::unique_ptr result; // cannot open database in random directory REQUIRE(!tester.OpenDatabase("/bla/this/directory/should/not/exist/hopefully/awerar333")); REQUIRE(tester.OpenDatabase(nullptr)); // syntax error in query REQUIRE_FAIL(tester.Query("SELEC * FROM TABLE")); // bind error REQUIRE_FAIL(tester.Query("SELECT * FROM TABLE")); duckdb_result res; duckdb_prepared_statement stmt = nullptr; // fail prepare API calls REQUIRE(duckdb_prepare(NULL, "SELECT 42", &stmt) == DuckDBError); REQUIRE(duckdb_prepare(tester.connection, NULL, &stmt) == DuckDBError); REQUIRE(stmt == nullptr); REQUIRE(duckdb_prepare(tester.connection, "SELECT * from INVALID_TABLE", &stmt) == DuckDBError); REQUIRE(duckdb_prepare_error(nullptr) == nullptr); REQUIRE(stmt != nullptr); REQUIRE(duckdb_prepare_error(stmt) != nullptr); duckdb_destroy_prepare(&stmt); REQUIRE(duckdb_bind_boolean(NULL, 0, true) == DuckDBError); REQUIRE(duckdb_execute_prepared(NULL, &res) == DuckDBError); duckdb_destroy_prepare(NULL); // fail to query arrow duckdb_arrow out_arrow; REQUIRE(duckdb_query_arrow(tester.connection, "SELECT * from INVALID_TABLE", &out_arrow) == DuckDBError); REQUIRE(duckdb_query_arrow_error(out_arrow) != nullptr); duckdb_destroy_arrow(&out_arrow); // various edge cases/nullptrs REQUIRE(duckdb_query_arrow_schema(out_arrow, nullptr) == DuckDBSuccess); REQUIRE(duckdb_query_arrow_array(out_arrow, nullptr) == DuckDBSuccess); // default duckdb_value_date on invalid date result = tester.Query("SELECT 1, true, 'a'"); REQUIRE_NO_FAIL(*result); duckdb_date_struct d = result->Fetch(0, 0); REQUIRE(d.year == 1970); REQUIRE(d.month == 1); REQUIRE(d.day == 1); d = result->Fetch(1, 0); REQUIRE(d.year == 1970); REQUIRE(d.month == 1); REQUIRE(d.day == 1); d = result->Fetch(2, 0); REQUIRE(d.year == 1970); REQUIRE(d.month == 1); REQUIRE(d.day == 1); } TEST_CASE("Test C API config", "[capi]") { duckdb_database db = nullptr; duckdb_connection con = nullptr; duckdb_config config = nullptr; duckdb_result result; // enumerate config options auto config_count = duckdb_config_count(); for (size_t i = 0; i < config_count; i++) { const char *name = nullptr; const char *description = nullptr; duckdb_get_config_flag(i, &name, &description); REQUIRE(strlen(name) > 0); REQUIRE(strlen(description) > 0); } // test config creation REQUIRE(duckdb_create_config(&config) == DuckDBSuccess); REQUIRE(duckdb_set_config(config, "access_mode", "invalid_access_mode") == DuckDBError); REQUIRE(duckdb_set_config(config, "access_mode", "read_only") == DuckDBSuccess); auto dbdir = TestCreatePath("capi_read_only_db"); // open the database & connection // cannot open an in-memory database in read-only mode char *error = nullptr; REQUIRE(duckdb_open_ext(":memory:", &db, config, &error) == DuckDBError); REQUIRE(strlen(error) > 0); duckdb_free(error); // now without the error REQUIRE(duckdb_open_ext(":memory:", &db, config, nullptr) == DuckDBError); // cannot open a database that does not exist REQUIRE(duckdb_open_ext(dbdir.c_str(), &db, config, &error) == DuckDBError); REQUIRE(strlen(error) > 0); duckdb_free(error); // we can create the database and add some tables { DuckDB cppdb(dbdir); Connection cppcon(cppdb); cppcon.Query("CREATE TABLE integers(i INTEGER)"); cppcon.Query("INSERT INTO integers VALUES (42)"); } // now we can connect REQUIRE(duckdb_open_ext(dbdir.c_str(), &db, config, &error) == DuckDBSuccess); // test unrecognized configuration REQUIRE(duckdb_set_config(config, "aaaa_invalidoption", "read_only") == DuckDBSuccess); REQUIRE(((DBConfig *)config)->options.unrecognized_options["aaaa_invalidoption"] == "read_only"); REQUIRE(duckdb_open_ext(dbdir.c_str(), &db, config, &error) == DuckDBError); REQUIRE_THAT(error, Catch::Matchers::Contains("The following options were not recognized")); duckdb_free(error); // we can destroy the config right after duckdb_open duckdb_destroy_config(&config); // we can spam this duckdb_destroy_config(&config); duckdb_destroy_config(&config); REQUIRE(duckdb_connect(db, nullptr) == DuckDBError); REQUIRE(duckdb_connect(nullptr, &con) == DuckDBError); REQUIRE(duckdb_connect(db, &con) == DuckDBSuccess); // we can query REQUIRE(duckdb_query(con, "SELECT 42::INT", &result) == DuckDBSuccess); REQUIRE(duckdb_value_int32(&result, 0, 0) == 42); duckdb_destroy_result(&result); REQUIRE(duckdb_query(con, "SELECT i::INT FROM integers", &result) == DuckDBSuccess); REQUIRE(duckdb_value_int32(&result, 0, 0) == 42); duckdb_destroy_result(&result); // but we cannot create new tables REQUIRE(duckdb_query(con, "CREATE TABLE new_table(i INTEGER)", nullptr) == DuckDBError); duckdb_disconnect(&con); duckdb_close(&db); // api abuse REQUIRE(duckdb_create_config(nullptr) == DuckDBError); REQUIRE(duckdb_get_config_flag(9999999, nullptr, nullptr) == DuckDBError); REQUIRE(duckdb_set_config(nullptr, nullptr, nullptr) == DuckDBError); REQUIRE(duckdb_create_config(nullptr) == DuckDBError); duckdb_destroy_config(nullptr); duckdb_destroy_config(nullptr); } TEST_CASE("Issue #2058: Cleanup after execution of invalid SQL statement causes segmentation fault", "[capi]") { duckdb_database db; duckdb_connection con; duckdb_result result; duckdb_result result_count; REQUIRE(duckdb_open(NULL, &db) != DuckDBError); REQUIRE(duckdb_connect(db, &con) != DuckDBError); REQUIRE(duckdb_query(con, "CREATE TABLE integers(i INTEGER, j INTEGER);", NULL) != DuckDBError); REQUIRE((duckdb_query(con, "SELECT count(*) FROM integers;", &result_count) != DuckDBError)); duckdb_destroy_result(&result_count); REQUIRE(duckdb_query(con, "non valid SQL", &result) == DuckDBError); duckdb_destroy_result(&result); // segmentation failure happens here duckdb_disconnect(&con); duckdb_close(&db); } TEST_CASE("Decimal -> Double casting issue", "[capi]") { CAPITester tester; duckdb::unique_ptr result; // open the database in in-memory mode REQUIRE(tester.OpenDatabase(nullptr)); result = tester.Query("select -0.5;"); REQUIRE_NO_FAIL(*result); REQUIRE(result->ColumnType(0) == DUCKDB_TYPE_DECIMAL); auto double_from_decimal = result->Fetch(0, 0); REQUIRE(double_from_decimal == (double)-0.5); auto string_from_decimal = result->Fetch(0, 0); REQUIRE(string_from_decimal == "-0.5"); } TEST_CASE("Test custom_user_agent config", "[capi]") { { duckdb_database db; duckdb_connection con; duckdb_result result; // Default custom_user_agent value REQUIRE(duckdb_open_ext(NULL, &db, nullptr, NULL) != DuckDBError); REQUIRE(duckdb_connect(db, &con) != DuckDBError); duckdb_query(con, "PRAGMA user_agent", &result); REQUIRE(duckdb_row_count(&result) == 1); char *user_agent_value = duckdb_value_varchar(&result, 0, 0); REQUIRE_THAT(user_agent_value, Catch::Matchers::Matches("duckdb/.*(.*) capi")); duckdb_free(user_agent_value); duckdb_destroy_result(&result); duckdb_disconnect(&con); duckdb_close(&db); } { // Custom custom_user_agent value duckdb_database db; duckdb_connection con; duckdb_result result_custom_user_agent; duckdb_result result_full_user_agent; duckdb_config config; REQUIRE(duckdb_create_config(&config) != DuckDBError); REQUIRE(duckdb_set_config(config, "custom_user_agent", "CUSTOM_STRING") != DuckDBError); REQUIRE(duckdb_open_ext(NULL, &db, config, NULL) != DuckDBError); REQUIRE(duckdb_connect(db, &con) != DuckDBError); duckdb_query(con, "SELECT current_setting('custom_user_agent')", &result_custom_user_agent); duckdb_query(con, "PRAGMA user_agent", &result_full_user_agent); REQUIRE(duckdb_row_count(&result_custom_user_agent) == 1); REQUIRE(duckdb_row_count(&result_full_user_agent) == 1); char *custom_user_agent_value = duckdb_value_varchar(&result_custom_user_agent, 0, 0); REQUIRE(string(custom_user_agent_value) == "CUSTOM_STRING"); char *full_user_agent_value = duckdb_value_varchar(&result_full_user_agent, 0, 0); REQUIRE_THAT(full_user_agent_value, Catch::Matchers::Matches("duckdb/.*(.*) capi CUSTOM_STRING")); duckdb_destroy_config(&config); duckdb_free(custom_user_agent_value); duckdb_free(full_user_agent_value); duckdb_destroy_result(&result_custom_user_agent); duckdb_destroy_result(&result_full_user_agent); duckdb_disconnect(&con); duckdb_close(&db); } } TEST_CASE("Test unsupported types in the deprecated C API", "[capi]") { CAPITester tester; REQUIRE(tester.OpenDatabase(nullptr)); string query_1 = R"EOF( CREATE TABLE test( id BIGINT, one DECIMAL(18,3)[] ); )EOF"; string query_2 = "INSERT INTO test VALUES (410, '[]');"; string query_3 = "INSERT INTO test VALUES (412, '[]');"; string query_4 = "SELECT id, one FROM test;"; REQUIRE_NO_FAIL(tester.Query(query_1)); REQUIRE_NO_FAIL(tester.Query(query_2)); REQUIRE_NO_FAIL(tester.Query(query_3)); // Passes, but does return invalid data for unsupported types. auto result = tester.Query(query_4); auto &result_c = result->InternalResult(); auto first_bigint_row = duckdb_value_string(&result_c, 0, 0).data; REQUIRE(!string(first_bigint_row).compare("410")); duckdb_free(first_bigint_row); REQUIRE(duckdb_value_string(&result_c, 1, 0).data == nullptr); auto second_bigint_row = duckdb_value_string(&result_c, 0, 1).data; REQUIRE(!string(second_bigint_row).compare("412")); duckdb_free(second_bigint_row); REQUIRE(duckdb_value_string(&result_c, 1, 1).data == nullptr); }