should be it

This commit is contained in:
2025-10-24 19:21:19 -05:00
parent a4b23fc57c
commit f09560c7b1
14047 changed files with 3161551 additions and 1 deletions

View File

@@ -0,0 +1,609 @@
#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
TEST_CASE("Test prepared statements in C API", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
duckdb_result res;
duckdb_prepared_statement stmt = nullptr;
duckdb_state status;
// open the database in in-memory mode
REQUIRE(tester.OpenDatabase(nullptr));
status = duckdb_prepare(tester.connection, "SELECT CAST($1 AS BIGINT)", &stmt);
REQUIRE(status == DuckDBSuccess);
REQUIRE(stmt != nullptr);
REQUIRE(duckdb_prepared_statement_column_count(stmt) == 1);
REQUIRE(duckdb_prepared_statement_column_type(stmt, 0) == DUCKDB_TYPE_BIGINT);
auto logical_type = duckdb_prepared_statement_column_logical_type(stmt, 0);
REQUIRE(logical_type);
REQUIRE(duckdb_get_type_id(logical_type) == DUCKDB_TYPE_BIGINT);
duckdb_destroy_logical_type(&logical_type);
status = duckdb_bind_boolean(stmt, 1, true);
REQUIRE(status == DuckDBSuccess);
// Parameter index 2 is out of bounds
status = duckdb_bind_boolean(stmt, 2, true);
REQUIRE(status == DuckDBError);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_int64(&res, 0, 0) == 1);
duckdb_destroy_result(&res);
duckdb_bind_int8(stmt, 1, 8);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_int64(&res, 0, 0) == 8);
duckdb_destroy_result(&res);
duckdb_bind_int16(stmt, 1, 16);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_int64(&res, 0, 0) == 16);
duckdb_destroy_result(&res);
duckdb_bind_int32(stmt, 1, 32);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_int64(&res, 0, 0) == 32);
duckdb_destroy_result(&res);
duckdb_bind_int64(stmt, 1, 64);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_int64(&res, 0, 0) == 64);
duckdb_destroy_result(&res);
duckdb_bind_hugeint(stmt, 1, duckdb_double_to_hugeint(64));
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_hugeint_to_double(duckdb_value_hugeint(&res, 0, 0)) == 64.0);
duckdb_destroy_result(&res);
duckdb_bind_uhugeint(stmt, 1, duckdb_double_to_uhugeint(64));
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_uhugeint_to_double(duckdb_value_uhugeint(&res, 0, 0)) == 64.0);
duckdb_destroy_result(&res);
// Fetching a DECIMAL from a non-DECIMAL result returns 0
duckdb_decimal decimal = duckdb_double_to_decimal(634.3453, 7, 4);
duckdb_bind_decimal(stmt, 1, decimal);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
duckdb_decimal result_decimal = duckdb_value_decimal(&res, 0, 0);
REQUIRE(result_decimal.scale == 0);
REQUIRE(result_decimal.width == 0);
REQUIRE(result_decimal.value.upper == 0);
REQUIRE(result_decimal.value.lower == 0);
duckdb_destroy_result(&res);
duckdb_bind_uint8(stmt, 1, 8);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_uint8(&res, 0, 0) == 8);
duckdb_destroy_result(&res);
duckdb_bind_uint16(stmt, 1, 8);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_uint16(&res, 0, 0) == 8);
duckdb_destroy_result(&res);
duckdb_bind_uint32(stmt, 1, 8);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_uint32(&res, 0, 0) == 8);
duckdb_destroy_result(&res);
duckdb_bind_uint64(stmt, 1, 8);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_uint64(&res, 0, 0) == 8);
duckdb_destroy_result(&res);
duckdb_bind_float(stmt, 1, 42.0);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_int64(&res, 0, 0) == 42);
duckdb_destroy_result(&res);
duckdb_bind_double(stmt, 1, 43.0);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_int64(&res, 0, 0) == 43);
duckdb_destroy_result(&res);
REQUIRE(duckdb_bind_float(stmt, 1, NAN) == DuckDBSuccess);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBError);
duckdb_destroy_result(&res);
REQUIRE(duckdb_bind_double(stmt, 1, NAN) == DuckDBSuccess);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBError);
duckdb_destroy_result(&res);
REQUIRE(duckdb_bind_varchar(stmt, 1, "\x80\x40\x41") == DuckDBError);
duckdb_bind_varchar(stmt, 1, "44");
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_int64(&res, 0, 0) == 44);
duckdb_destroy_result(&res);
duckdb_bind_null(stmt, 1);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_nullmask_data(&res, 0)[0] == true);
duckdb_destroy_result(&res);
duckdb_destroy_prepare(&stmt);
// again to make sure it does not crash
duckdb_destroy_result(&res);
duckdb_destroy_prepare(&stmt);
status = duckdb_prepare(tester.connection, "SELECT CAST($1 AS VARCHAR)", &stmt);
REQUIRE(status == DuckDBSuccess);
REQUIRE(stmt != nullptr);
// invalid unicode
REQUIRE(duckdb_bind_varchar_length(stmt, 1, "\x80", 1) == DuckDBError);
// we can bind null values, though!
REQUIRE(duckdb_bind_varchar_length(stmt, 1, "\x00\x40\x41", 3) == DuckDBSuccess);
duckdb_bind_varchar_length(stmt, 1, "hello world", 5);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
auto value = duckdb_value_varchar(&res, 0, 0);
REQUIRE(string(value) == "hello");
REQUIRE(duckdb_value_int8(&res, 0, 0) == 0);
duckdb_free(value);
duckdb_destroy_result(&res);
duckdb_bind_blob(stmt, 1, "hello\0world", 11);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
value = duckdb_value_varchar(&res, 0, 0);
REQUIRE(string(value) == "hello\\x00world");
REQUIRE(duckdb_value_int8(&res, 0, 0) == 0);
duckdb_free(value);
duckdb_destroy_result(&res);
duckdb_date_struct date_struct;
date_struct.year = 1992;
date_struct.month = 9;
date_struct.day = 3;
duckdb_bind_date(stmt, 1, duckdb_to_date(date_struct));
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
value = duckdb_value_varchar(&res, 0, 0);
REQUIRE(string(value) == "1992-09-03");
duckdb_free(value);
duckdb_destroy_result(&res);
duckdb_time_struct time_struct;
time_struct.hour = 12;
time_struct.min = 22;
time_struct.sec = 33;
time_struct.micros = 123400;
duckdb_bind_time(stmt, 1, duckdb_to_time(time_struct));
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
value = duckdb_value_varchar(&res, 0, 0);
REQUIRE(string(value) == "12:22:33.1234");
duckdb_free(value);
duckdb_destroy_result(&res);
duckdb_timestamp_struct ts;
ts.date = date_struct;
ts.time = time_struct;
duckdb_bind_timestamp(stmt, 1, duckdb_to_timestamp(ts));
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
value = duckdb_value_varchar(&res, 0, 0);
REQUIRE(string(value) == "1992-09-03 12:22:33.1234");
duckdb_free(value);
duckdb_destroy_result(&res);
duckdb_bind_timestamp_tz(stmt, 1, duckdb_to_timestamp(ts));
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
value = duckdb_value_varchar(&res, 0, 0);
REQUIRE(StringUtil::Contains(string(value), "1992-09"));
duckdb_free(value);
duckdb_destroy_result(&res);
duckdb_interval interval;
interval.months = 3;
interval.days = 0;
interval.micros = 0;
duckdb_bind_interval(stmt, 1, interval);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
value = duckdb_value_varchar(&res, 0, 0);
REQUIRE(string(value) == "3 months");
duckdb_free(value);
duckdb_destroy_result(&res);
duckdb_destroy_prepare(&stmt);
status = duckdb_query(tester.connection, "CREATE TABLE a (i INTEGER)", NULL);
REQUIRE(status == DuckDBSuccess);
status = duckdb_prepare(tester.connection, "INSERT INTO a VALUES (?)", &stmt);
REQUIRE(status == DuckDBSuccess);
REQUIRE(stmt != nullptr);
REQUIRE(duckdb_nparams(nullptr) == 0);
REQUIRE(duckdb_nparams(stmt) == 1);
REQUIRE(duckdb_param_type(nullptr, 0) == DUCKDB_TYPE_INVALID);
REQUIRE(duckdb_param_type(stmt, 0) == DUCKDB_TYPE_INVALID);
REQUIRE(duckdb_param_type(stmt, 1) == DUCKDB_TYPE_INTEGER);
REQUIRE(duckdb_param_type(stmt, 2) == DUCKDB_TYPE_INVALID);
for (int32_t i = 1; i <= 1000; i++) {
duckdb_bind_int32(stmt, 1, i);
status = duckdb_execute_prepared(stmt, nullptr);
REQUIRE(status == DuckDBSuccess);
}
duckdb_destroy_prepare(&stmt);
status = duckdb_prepare(tester.connection, "SELECT SUM(i)*$1-$2 FROM a", &stmt);
REQUIRE(status == DuckDBSuccess);
REQUIRE(stmt != nullptr);
// clear bindings
duckdb_bind_int32(stmt, 1, 2);
REQUIRE(duckdb_clear_bindings(stmt) == DuckDBSuccess);
// bind again will succeed
duckdb_bind_int32(stmt, 1, 2);
duckdb_bind_int32(stmt, 2, 1000);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_int32(&res, 0, 0) == 1000000);
duckdb_destroy_result(&res);
duckdb_destroy_prepare(&stmt);
// not-so-happy path
status = duckdb_prepare(tester.connection, "SELECT XXXXX", &stmt);
REQUIRE(status == DuckDBError);
duckdb_destroy_prepare(&stmt);
status = duckdb_prepare(tester.connection, "SELECT CAST($1 AS INTEGER)", &stmt);
REQUIRE(status == DuckDBSuccess);
REQUIRE(stmt != nullptr);
REQUIRE(duckdb_prepared_statement_column_count(stmt) == 1);
REQUIRE(duckdb_prepared_statement_column_type(stmt, 0) == DUCKDB_TYPE_INTEGER);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBError);
duckdb_destroy_result(&res);
duckdb_destroy_prepare(&stmt);
// test duckdb_malloc explicitly
auto malloced_data = duckdb_malloc(100);
memcpy(malloced_data, "hello\0", 6);
REQUIRE(string((char *)malloced_data) == "hello");
duckdb_free(malloced_data);
status = duckdb_prepare(tester.connection, "SELECT sum(i) FROM a WHERE i > ?", &stmt);
REQUIRE(status == DuckDBSuccess);
REQUIRE(stmt != nullptr);
REQUIRE(duckdb_nparams(stmt) == 1);
REQUIRE(duckdb_param_type(nullptr, 0) == DUCKDB_TYPE_INVALID);
REQUIRE(duckdb_param_type(stmt, 1) == DUCKDB_TYPE_INTEGER);
REQUIRE(duckdb_prepared_statement_column_count(stmt) == 1);
REQUIRE(duckdb_prepared_statement_column_type(stmt, 0) == DUCKDB_TYPE_HUGEINT);
duckdb_destroy_prepare(&stmt);
}
TEST_CASE("Test duckdb_prepared_statement return value APIs", "[capi]") {
duckdb_database db;
duckdb_connection conn;
duckdb_prepared_statement stmt;
REQUIRE(duckdb_open("", &db) == DuckDBSuccess);
REQUIRE(duckdb_connect(db, &conn) == DuckDBSuccess);
// Unambiguous return column types
REQUIRE(duckdb_prepare(conn, "select $1::TEXT, $2::integer, $3::BOOLEAN, $4::FLOAT, $5::DOUBLE", &stmt) ==
DuckDBSuccess);
REQUIRE(duckdb_prepared_statement_column_count(stmt) == 5);
auto expected_types = {DUCKDB_TYPE_VARCHAR, DUCKDB_TYPE_INTEGER, DUCKDB_TYPE_BOOLEAN, DUCKDB_TYPE_FLOAT,
DUCKDB_TYPE_DOUBLE};
for (idx_t i = 0; i < 5; i++) {
REQUIRE(duckdb_prepared_statement_column_type(stmt, i) == *next(expected_types.begin(), i));
auto logical_type = duckdb_prepared_statement_column_logical_type(stmt, i);
REQUIRE(logical_type);
REQUIRE(duckdb_get_type_id(logical_type) == *next(expected_types.begin(), i));
duckdb_destroy_logical_type(&logical_type);
}
auto column_name = duckdb_prepared_statement_column_name(stmt, 0);
std::string col_name_str = column_name;
duckdb_free((void *)column_name);
REQUIRE(col_name_str == "CAST($1 AS VARCHAR)");
duckdb_destroy_prepare(&stmt);
// Return columns contain ambiguous types
REQUIRE(duckdb_prepare(conn, "select $1::TEXT, $2::integer, $3, $4::BOOLEAN, $5::FLOAT, $6::DOUBLE", &stmt) ==
DuckDBSuccess);
REQUIRE(duckdb_prepared_statement_column_count(stmt) == 1);
REQUIRE(duckdb_prepared_statement_column_type(stmt, 0) == DUCKDB_TYPE_INVALID);
auto logical_type = duckdb_prepared_statement_column_logical_type(stmt, 0);
REQUIRE(logical_type);
REQUIRE(duckdb_get_type_id(logical_type) == DUCKDB_TYPE_INVALID);
duckdb_destroy_logical_type(&logical_type);
auto col_name_ptr = duckdb_prepared_statement_column_name(stmt, 0);
col_name_str = col_name_ptr;
duckdb_free((void *)col_name_ptr);
REQUIRE(col_name_str == "unknown");
REQUIRE(duckdb_prepared_statement_column_name(stmt, 1) == nullptr);
REQUIRE(duckdb_prepared_statement_column_name(stmt, 5) == nullptr);
duckdb_destroy_prepare(&stmt);
duckdb_disconnect(&conn);
duckdb_close(&db);
}
TEST_CASE("Test duckdb_param_type and duckdb_param_logical_type", "[capi]") {
duckdb_database db;
duckdb_connection conn;
duckdb_prepared_statement stmt;
REQUIRE(duckdb_open("", &db) == DuckDBSuccess);
REQUIRE(duckdb_connect(db, &conn) == DuckDBSuccess);
REQUIRE(duckdb_prepare(conn, "select $1::integer, $2::integer", &stmt) == DuckDBSuccess);
auto logical_type = duckdb_param_logical_type(stmt, 2);
REQUIRE(logical_type);
REQUIRE(duckdb_get_type_id(logical_type) == DUCKDB_TYPE_INTEGER);
duckdb_destroy_logical_type(&logical_type);
REQUIRE(duckdb_param_type(stmt, 2) == DUCKDB_TYPE_INTEGER);
REQUIRE(duckdb_bind_null(stmt, 1) == DuckDBSuccess);
REQUIRE(duckdb_bind_int32(stmt, 2, 10) == DuckDBSuccess);
REQUIRE(!duckdb_param_logical_type(nullptr, 2));
REQUIRE(duckdb_param_type(nullptr, 2) == DUCKDB_TYPE_INVALID);
REQUIRE(!duckdb_param_logical_type(stmt, 2000));
REQUIRE(duckdb_param_type(stmt, 2000) == DUCKDB_TYPE_INVALID);
duckdb_result result;
REQUIRE(duckdb_execute_prepared(stmt, &result) == DuckDBSuccess);
REQUIRE(duckdb_param_type(stmt, 2) == DUCKDB_TYPE_INTEGER);
duckdb_clear_bindings(stmt);
duckdb_destroy_result(&result);
duckdb_destroy_prepare(&stmt);
duckdb_disconnect(&conn);
duckdb_close(&db);
}
TEST_CASE("Test prepared statements with named parameters in C API", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
duckdb_result res;
duckdb_prepared_statement stmt = nullptr;
duckdb_state status;
// open the database in in-memory mode
REQUIRE(tester.OpenDatabase(nullptr));
status = duckdb_prepare(tester.connection, "SELECT CAST($my_val AS BIGINT)", &stmt);
REQUIRE(status == DuckDBSuccess);
REQUIRE(stmt != nullptr);
idx_t parameter_index;
// test invalid name
status = duckdb_bind_parameter_index(stmt, &parameter_index, "invalid");
REQUIRE(status == DuckDBError);
status = duckdb_bind_parameter_index(stmt, &parameter_index, "my_val");
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_param_type(stmt, 1) == DUCKDB_TYPE_BIGINT);
auto logical_type = duckdb_param_logical_type(stmt, 1);
REQUIRE(logical_type);
REQUIRE(duckdb_get_type_id(logical_type) == DUCKDB_TYPE_BIGINT);
duckdb_destroy_logical_type(&logical_type);
idx_t param_count = duckdb_nparams(stmt);
duckdb::vector<string> names;
for (idx_t i = 0; i < param_count; i++) {
auto name = duckdb_parameter_name(stmt, i + 1);
names.push_back(std::string(name));
duckdb_free((void *)name);
}
REQUIRE(duckdb_parameter_name(stmt, 0) == (const char *)NULL);
REQUIRE(duckdb_parameter_name(stmt, 2) == (const char *)NULL);
REQUIRE(duckdb_prepared_statement_column_count(stmt) == 1);
REQUIRE(duckdb_prepared_statement_column_type(stmt, 0) == DUCKDB_TYPE_BIGINT);
duckdb::vector<string> expected_names = {"my_val"};
REQUIRE(names.size() == expected_names.size());
for (idx_t i = 0; i < expected_names.size(); i++) {
auto &name = names[i];
auto &expected_name = expected_names[i];
REQUIRE(name == expected_name);
}
status = duckdb_bind_boolean(stmt, parameter_index, 1);
REQUIRE(status == DuckDBSuccess);
status = duckdb_bind_boolean(stmt, parameter_index + 1, 1);
REQUIRE(status == DuckDBError);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_int64(&res, 0, 0) == 1);
duckdb_destroy_result(&res);
// Clear the bindings, don't rebind the parameter index
status = duckdb_clear_bindings(stmt);
REQUIRE(status == DuckDBSuccess);
status = duckdb_bind_boolean(stmt, parameter_index, 1);
REQUIRE(status == DuckDBSuccess);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_int64(&res, 0, 0) == 1);
duckdb_destroy_result(&res);
duckdb_destroy_prepare(&stmt);
}
TEST_CASE("Maintain prepared statement types", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
duckdb_result res;
duckdb_prepared_statement stmt = nullptr;
duckdb_state status;
// open the database in in-memory mode
REQUIRE(tester.OpenDatabase(nullptr));
status = duckdb_prepare(tester.connection, "select cast(111 as short) * $1", &stmt);
REQUIRE(status == DuckDBSuccess);
REQUIRE(stmt != nullptr);
status = duckdb_bind_int64(stmt, 1, 1665);
REQUIRE(status == DuckDBSuccess);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_int64(&res, 0, 0) == 184815);
duckdb_destroy_result(&res);
duckdb_destroy_prepare(&stmt);
}
TEST_CASE("Prepared streaming result", "[capi]") {
CAPITester tester;
// open the database in in-memory mode
REQUIRE(tester.OpenDatabase(nullptr));
SECTION("non streaming result") {
REQUIRE(tester.Query("CREATE TABLE t2 (i INTEGER, j INTEGER);"));
duckdb_prepared_statement stmt;
REQUIRE(duckdb_prepare(tester.connection,
"INSERT INTO t2 SELECT 2 AS i, 3 AS j RETURNING *, i * j AS i_times_j",
&stmt) == DuckDBSuccess);
duckdb_result res;
REQUIRE(duckdb_execute_prepared_streaming(stmt, &res) == DuckDBSuccess);
REQUIRE(!duckdb_result_is_streaming(res));
duckdb_destroy_result(&res);
duckdb_destroy_prepare(&stmt);
}
SECTION("streaming result") {
duckdb_prepared_statement stmt;
REQUIRE(duckdb_prepare(tester.connection, "FROM RANGE(0, 10)", &stmt) == DuckDBSuccess);
duckdb_result res;
REQUIRE(duckdb_execute_prepared_streaming(stmt, &res) == DuckDBSuccess);
REQUIRE(duckdb_result_is_streaming(res));
duckdb_data_chunk chunk;
idx_t index = 0;
while (true) {
chunk = duckdb_stream_fetch_chunk(res);
if (!chunk) {
break;
}
auto chunk_size = duckdb_data_chunk_get_size(chunk);
REQUIRE(chunk_size > 0);
auto vec = duckdb_data_chunk_get_vector(chunk, 0);
auto column_type = duckdb_vector_get_column_type(vec);
REQUIRE(duckdb_get_type_id(column_type) == DUCKDB_TYPE_BIGINT);
duckdb_destroy_logical_type(&column_type);
auto data = reinterpret_cast<int64_t *>(duckdb_vector_get_data(vec));
for (idx_t i = 0; i < chunk_size; i++) {
REQUIRE(data[i] == int64_t(index + i));
}
index += chunk_size;
duckdb_destroy_data_chunk(&chunk);
}
REQUIRE(duckdb_stream_fetch_chunk(res) == nullptr);
duckdb_destroy_result(&res);
duckdb_destroy_prepare(&stmt);
}
SECTION("streaming extracted statements") {
duckdb_extracted_statements stmts;
auto n_statements = duckdb_extract_statements(tester.connection, "Select 1; Select 2;", &stmts);
REQUIRE(n_statements == 2);
for (idx_t i = 0; i < n_statements; i++) {
duckdb_prepared_statement stmt;
REQUIRE(duckdb_prepare_extracted_statement(tester.connection, stmts, i, &stmt) == DuckDBSuccess);
duckdb_result res;
REQUIRE(duckdb_execute_prepared_streaming(stmt, &res) == DuckDBSuccess);
REQUIRE(duckdb_result_is_streaming(res));
duckdb_data_chunk chunk;
chunk = duckdb_stream_fetch_chunk(res);
REQUIRE(chunk != nullptr);
REQUIRE(duckdb_data_chunk_get_size(chunk) == 1);
auto vec = duckdb_data_chunk_get_vector(chunk, 0);
auto type = duckdb_vector_get_column_type(vec);
REQUIRE(duckdb_get_type_id(type) == DUCKDB_TYPE_INTEGER);
duckdb_destroy_logical_type(&type);
auto data = (int32_t *)duckdb_vector_get_data(vec);
REQUIRE(data[0] == (int32_t)(i + 1));
REQUIRE(duckdb_stream_fetch_chunk(res) == nullptr);
duckdb_destroy_data_chunk(&chunk);
duckdb_destroy_result(&res);
duckdb_destroy_prepare(&stmt);
}
duckdb_destroy_extracted(&stmts);
}
}
TEST_CASE("Test STRING LITERAL parameter type", "[capi]") {
duckdb_database db;
duckdb_connection conn;
duckdb_prepared_statement stmt;
REQUIRE(duckdb_open("", &db) == DuckDBSuccess);
REQUIRE(duckdb_connect(db, &conn) == DuckDBSuccess);
REQUIRE(duckdb_prepare(conn, "SELECT ?", &stmt) == DuckDBSuccess);
REQUIRE(duckdb_bind_varchar(stmt, 1, "a") == DuckDBSuccess);
REQUIRE(duckdb_param_type(stmt, 1) == DUCKDB_TYPE_STRING_LITERAL);
duckdb_destroy_prepare(&stmt);
duckdb_disconnect(&conn);
duckdb_close(&db);
}