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,32 @@
add_library_unity(
test_sql_capi
OBJECT
capi_aggregate_functions.cpp
capi_custom_type.cpp
capi_file_system.cpp
capi_scalar_functions.cpp
capi_table_functions.cpp
test_capi.cpp
test_capi_any_invalid_type.cpp
test_capi_append_data_chunk.cpp
test_starting_database.cpp
test_capi_appender.cpp
test_capi_arrow.cpp
test_capi_data_chunk.cpp
test_capi_extract.cpp
test_capi_instance_cache.cpp
test_capi_pending.cpp
test_capi_prepared.cpp
test_capi_profiling.cpp
test_capi_website.cpp
test_capi_complex_types.cpp
test_capi_to_decimal.cpp
test_capi_values.cpp
test_capi_vector.cpp
test_capi_replacement_scan.cpp
test_capi_streaming.cpp
test_capi_table_description.cpp
test_without_disabled_functions.cpp)
set(ALL_OBJECT_FILES
${ALL_OBJECT_FILES} $<TARGET_OBJECTS:test_sql_capi>
PARENT_SCOPE)

View File

@@ -0,0 +1,440 @@
#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
struct WeightedSumState {
int64_t sum;
uint64_t count;
};
idx_t WeightedSumSize(duckdb_function_info info) {
return sizeof(WeightedSumState);
}
void WeightedSumInit(duckdb_function_info info, duckdb_aggregate_state state_p) {
auto state = reinterpret_cast<WeightedSumState *>(state_p);
state->sum = 0;
state->count = 0;
}
void WeightedSumUpdate(duckdb_function_info info, duckdb_data_chunk input, duckdb_aggregate_state *states) {
auto state = reinterpret_cast<WeightedSumState **>(states);
auto row_count = duckdb_data_chunk_get_size(input);
auto input_vector = duckdb_data_chunk_get_vector(input, 0);
auto input_data = static_cast<int64_t *>(duckdb_vector_get_data(input_vector));
auto input_validity = duckdb_vector_get_validity(input_vector);
if (duckdb_data_chunk_get_column_count(input) == 1) {
// single argument
for (idx_t i = 0; i < row_count; i++) {
if (duckdb_validity_row_is_valid(input_validity, i)) {
state[i]->sum += input_data[i];
state[i]->count++;
}
}
} else {
// two arguments
auto weight_vector = duckdb_data_chunk_get_vector(input, 1);
auto weight_data = static_cast<int64_t *>(duckdb_vector_get_data(weight_vector));
auto weight_validity = duckdb_vector_get_validity(weight_vector);
for (idx_t i = 0; i < row_count; i++) {
if (duckdb_validity_row_is_valid(input_validity, i) && duckdb_validity_row_is_valid(weight_validity, i)) {
state[i]->sum += input_data[i] * weight_data[i];
state[i]->count++;
}
}
}
}
void WeightedSumCombine(duckdb_function_info info, duckdb_aggregate_state *source_p, duckdb_aggregate_state *target_p,
idx_t count) {
auto source = reinterpret_cast<WeightedSumState **>(source_p);
auto target = reinterpret_cast<WeightedSumState **>(target_p);
for (idx_t i = 0; i < count; i++) {
target[i]->sum += source[i]->sum;
target[i]->count += source[i]->count;
}
}
void WeightedSumFinalize(duckdb_function_info info, duckdb_aggregate_state *source_p, duckdb_vector result, idx_t count,
idx_t offset) {
auto source = reinterpret_cast<WeightedSumState **>(source_p);
auto result_data = static_cast<int64_t *>(duckdb_vector_get_data(result));
duckdb_vector_ensure_validity_writable(result);
auto result_validity = duckdb_vector_get_validity(result);
for (idx_t i = 0; i < count; i++) {
if (source[i]->count == 0) {
duckdb_validity_set_row_invalid(result_validity, offset + i);
} else {
result_data[offset + i] = source[i]->sum;
}
}
}
static duckdb_aggregate_function CAPIGetAggregateFunction(duckdb_connection connection, const char *name,
idx_t parameter_count = 2) {
// create an aggregate function
auto function = duckdb_create_aggregate_function();
duckdb_aggregate_function_set_name(nullptr, name);
duckdb_aggregate_function_set_name(function, nullptr);
duckdb_aggregate_function_set_name(function, name);
duckdb_aggregate_function_set_name(function, name);
// add a two bigint parameters
auto type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
duckdb_aggregate_function_add_parameter(nullptr, type);
duckdb_aggregate_function_add_parameter(function, nullptr);
for (idx_t idx = 0; idx < parameter_count; idx++) {
duckdb_aggregate_function_add_parameter(function, type);
}
// set the return type to bigint
duckdb_aggregate_function_set_return_type(nullptr, type);
duckdb_aggregate_function_set_return_type(function, nullptr);
duckdb_aggregate_function_set_return_type(function, type);
duckdb_destroy_logical_type(&type);
// set up the function
duckdb_aggregate_function_set_functions(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
duckdb_aggregate_function_set_functions(function, nullptr, nullptr, nullptr, nullptr, nullptr);
duckdb_aggregate_function_set_functions(function, WeightedSumSize, WeightedSumInit, WeightedSumUpdate,
WeightedSumCombine, WeightedSumFinalize);
return function;
}
static void CAPIRegisterWeightedSum(duckdb_connection connection, const char *name, duckdb_state expected_outcome) {
duckdb_state status;
// create an aggregate function
auto function = CAPIGetAggregateFunction(connection, name);
// register and cleanup
status = duckdb_register_aggregate_function(connection, function);
REQUIRE(status == expected_outcome);
duckdb_destroy_aggregate_function(&function);
duckdb_destroy_aggregate_function(&function);
duckdb_destroy_aggregate_function(nullptr);
}
struct CAPICallbacks {
duckdb_aggregate_state_size state_size;
duckdb_aggregate_init_t init;
duckdb_aggregate_update_t update;
duckdb_aggregate_combine_t combine;
duckdb_aggregate_finalize_t finalize;
};
idx_t CallbackSize(duckdb_function_info info) {
auto callbacks = (CAPICallbacks *)duckdb_aggregate_function_get_extra_info(info);
return callbacks->state_size(info);
}
void CallbackInit(duckdb_function_info info, duckdb_aggregate_state state_p) {
auto callbacks = (CAPICallbacks *)duckdb_aggregate_function_get_extra_info(info);
callbacks->init(info, state_p);
}
void CallbackUpdate(duckdb_function_info info, duckdb_data_chunk input, duckdb_aggregate_state *states) {
auto callbacks = (CAPICallbacks *)duckdb_aggregate_function_get_extra_info(info);
callbacks->update(info, input, states);
}
void CallbackCombine(duckdb_function_info info, duckdb_aggregate_state *source_p, duckdb_aggregate_state *target_p,
idx_t count) {
auto callbacks = (CAPICallbacks *)duckdb_aggregate_function_get_extra_info(info);
callbacks->combine(info, source_p, target_p, count);
}
void CallbackFinalize(duckdb_function_info info, duckdb_aggregate_state *source_p, duckdb_vector result, idx_t count,
idx_t offset) {
auto callbacks = (CAPICallbacks *)duckdb_aggregate_function_get_extra_info(info);
callbacks->finalize(info, source_p, result, count, offset);
}
static void CAPIRegisterWeightedSumExtraInfo(duckdb_connection connection, const char *name,
duckdb_state expected_outcome) {
duckdb_state status;
// create an aggregate function
auto function = duckdb_create_aggregate_function();
duckdb_aggregate_function_set_name(function, name);
// add a two bigint parameters
auto type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
duckdb_aggregate_function_add_parameter(function, type);
duckdb_aggregate_function_add_parameter(function, type);
// set the return type to bigint
duckdb_aggregate_function_set_return_type(function, type);
duckdb_destroy_logical_type(&type);
auto callback_ptr = malloc(sizeof(CAPICallbacks));
auto callback_struct = (CAPICallbacks *)callback_ptr;
callback_struct->state_size = WeightedSumSize;
callback_struct->init = WeightedSumInit;
callback_struct->update = WeightedSumUpdate;
callback_struct->combine = WeightedSumCombine;
callback_struct->finalize = WeightedSumFinalize;
duckdb_aggregate_function_set_extra_info(function, callback_ptr, free);
// set up the function
duckdb_aggregate_function_set_functions(function, CallbackSize, CallbackInit, CallbackUpdate, CallbackCombine,
CallbackFinalize);
// register and cleanup
status = duckdb_register_aggregate_function(connection, function);
REQUIRE(status == expected_outcome);
duckdb_destroy_aggregate_function(&function);
}
TEST_CASE("Test Aggregate Functions C API", "[capi]") {
typedef void (*register_function_t)(duckdb_connection, const char *, duckdb_state);
duckdb::vector<register_function_t> register_functions {CAPIRegisterWeightedSum, CAPIRegisterWeightedSumExtraInfo};
for (auto &register_function : register_functions) {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
register_function(tester.connection, "my_weighted_sum", DuckDBSuccess);
// try to register it again - this should be an error
register_function(tester.connection, "my_weighted_sum", DuckDBError);
// now call it
result = tester.Query("SELECT my_weighted_sum(40, 2)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 80);
result = tester.Query("SELECT my_weighted_sum(40, NULL)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->IsNull(0, 0));
result = tester.Query("SELECT my_weighted_sum(NULL, 2)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->IsNull(0, 0));
result = tester.Query("SELECT my_weighted_sum(i, 2) FROM range(100) t(i)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 9900);
result = tester.Query("SELECT i % 2 AS gr, my_weighted_sum(i, 2) FROM range(100) t(i) GROUP BY gr ORDER BY gr");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 0);
REQUIRE(result->Fetch<int64_t>(1, 0) == 4900);
REQUIRE(result->Fetch<int64_t>(0, 1) == 1);
REQUIRE(result->Fetch<int64_t>(1, 1) == 5000);
}
}
struct RepeatedStringAggState {
char *data;
idx_t size;
};
idx_t RepeatedStringAggSize(duckdb_function_info info) {
return sizeof(RepeatedStringAggState);
}
void RepeatedStringAggInit(duckdb_function_info info, duckdb_aggregate_state state_p) {
auto state = reinterpret_cast<RepeatedStringAggState *>(state_p);
state->data = nullptr;
state->size = 0;
}
void RepeatedStringAggUpdate(duckdb_function_info info, duckdb_data_chunk input, duckdb_aggregate_state *states) {
auto state = reinterpret_cast<RepeatedStringAggState **>(states);
auto row_count = duckdb_data_chunk_get_size(input);
auto input_vector = duckdb_data_chunk_get_vector(input, 0);
auto input_data = static_cast<duckdb_string_t *>(duckdb_vector_get_data(input_vector));
auto input_validity = duckdb_vector_get_validity(input_vector);
auto weight_vector = duckdb_data_chunk_get_vector(input, 1);
auto weight_data = static_cast<int64_t *>(duckdb_vector_get_data(weight_vector));
auto weight_validity = duckdb_vector_get_validity(weight_vector);
for (idx_t i = 0; i < row_count; i++) {
if (!duckdb_validity_row_is_valid(input_validity, i) || !duckdb_validity_row_is_valid(weight_validity, i)) {
continue;
}
auto length = duckdb_string_t_length(input_data[i]);
auto data = duckdb_string_t_data(input_data + i);
auto weight = weight_data[i];
if (weight < 0) {
duckdb_aggregate_function_set_error(info, "Weight must be >= 0");
return;
}
auto new_data = (char *)malloc(state[i]->size + length * weight + 1);
if (state[i]->size > 0) {
memcpy((void *)(new_data), state[i]->data, state[i]->size);
}
if (state[i]->data) {
free((void *)(state[i]->data));
}
idx_t offset = state[i]->size;
for (idx_t rep_idx = 0; rep_idx < static_cast<idx_t>(weight); rep_idx++) {
memcpy((void *)(new_data + offset), data, length);
offset += length;
}
state[i]->data = new_data;
state[i]->size = offset;
state[i]->data[state[i]->size] = '\0';
}
}
void RepeatedStringAggCombine(duckdb_function_info info, duckdb_aggregate_state *source_p,
duckdb_aggregate_state *target_p, idx_t count) {
auto source = reinterpret_cast<RepeatedStringAggState **>(source_p);
auto target = reinterpret_cast<RepeatedStringAggState **>(target_p);
for (idx_t i = 0; i < count; i++) {
if (source[i]->size == 0) {
continue;
}
auto new_data = (char *)malloc(target[i]->size + source[i]->size + 1);
if (target[i]->size > 0) {
memcpy((void *)new_data, target[i]->data, target[i]->size);
}
if (target[i]->data) {
free((void *)target[i]->data);
}
memcpy((void *)(new_data + target[i]->size), source[i]->data, source[i]->size);
target[i]->data = new_data;
target[i]->size += source[i]->size;
target[i]->data[target[i]->size] = '\0';
}
}
void RepeatedStringAggFinalize(duckdb_function_info info, duckdb_aggregate_state *source_p, duckdb_vector result,
idx_t count, idx_t offset) {
auto source = reinterpret_cast<RepeatedStringAggState **>(source_p);
duckdb_vector_ensure_validity_writable(result);
auto result_validity = duckdb_vector_get_validity(result);
for (idx_t i = 0; i < count; i++) {
if (!source[i]->data) {
duckdb_validity_set_row_invalid(result_validity, offset + i);
} else {
duckdb_vector_assign_string_element_len(result, offset + i, reinterpret_cast<const char *>(source[i]->data),
source[i]->size);
}
}
}
void RepeatedStringAggDestructor(duckdb_aggregate_state *states, idx_t count) {
auto source = reinterpret_cast<RepeatedStringAggState **>(states);
for (idx_t i = 0; i < count; i++) {
if (source[i]->data) {
free(source[i]->data);
}
}
}
static void CAPIRegisterRepeatedStringAgg(duckdb_connection connection) {
duckdb_state status;
// create an aggregate function
auto function = duckdb_create_aggregate_function();
duckdb_aggregate_function_set_name(function, "repeated_string_agg");
// add a varchar/bigint parameter
auto varchar_type = duckdb_create_logical_type(DUCKDB_TYPE_VARCHAR);
auto bigint_type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
duckdb_aggregate_function_add_parameter(function, varchar_type);
duckdb_aggregate_function_add_parameter(function, bigint_type);
// set the return type to varchar
duckdb_aggregate_function_set_return_type(function, varchar_type);
duckdb_destroy_logical_type(&varchar_type);
duckdb_destroy_logical_type(&bigint_type);
// set up the function
duckdb_aggregate_function_set_functions(function, RepeatedStringAggSize, RepeatedStringAggInit,
RepeatedStringAggUpdate, RepeatedStringAggCombine,
RepeatedStringAggFinalize);
duckdb_aggregate_function_set_destructor(function, RepeatedStringAggDestructor);
// register and cleanup
status = duckdb_register_aggregate_function(connection, function);
REQUIRE(status == DuckDBSuccess);
duckdb_destroy_aggregate_function(&function);
}
TEST_CASE("Test String Aggregate Function", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
CAPIRegisterRepeatedStringAgg(tester.connection);
// now call it
result = tester.Query("SELECT repeated_string_agg('x', 2)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<string>(0, 0) == "xx");
result = tester.Query("SELECT repeated_string_agg('', 2)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<string>(0, 0) == "");
result = tester.Query("SELECT repeated_string_agg('abcdefgh', 3)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<string>(0, 0) == "abcdefghabcdefghabcdefgh");
result = tester.Query("SELECT repeated_string_agg(NULL, 2)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->IsNull(0, 0));
REQUIRE_FAIL(tester.Query("SELECT repeated_string_agg('x', -1)"));
result = tester.Query(
"SELECT repeated_string_agg(CASE WHEN i%10=0 THEN i::VARCHAR ELSE '' END, 2) FROM range(100) t(i)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<string>(0, 0) == "00101020203030404050506060707080809090");
}
static void CAPIRegisterWeightedSumOverloads(duckdb_connection connection, const char *name,
duckdb_state expected_outcome) {
duckdb_state status;
auto function_set = duckdb_create_aggregate_function_set(name);
// create an aggregate function with 2 parameters
auto function = CAPIGetAggregateFunction(connection, name, 1);
duckdb_add_aggregate_function_to_set(function_set, function);
duckdb_destroy_aggregate_function(&function);
// create an aggregate function with 3 parameters
function = CAPIGetAggregateFunction(connection, name, 2);
duckdb_add_aggregate_function_to_set(function_set, function);
duckdb_destroy_aggregate_function(&function);
// register and cleanup
status = duckdb_register_aggregate_function_set(connection, function_set);
REQUIRE(status == expected_outcome);
duckdb_destroy_aggregate_function_set(&function_set);
duckdb_destroy_aggregate_function_set(&function_set);
duckdb_destroy_aggregate_function_set(nullptr);
}
TEST_CASE("Test Aggregate Function Overloads C API", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
CAPIRegisterWeightedSumOverloads(tester.connection, "my_weighted_sum", DuckDBSuccess);
// try to register it again - this should be an error
CAPIRegisterWeightedSumOverloads(tester.connection, "my_weighted_sum", DuckDBError);
// now call it
result = tester.Query("SELECT my_weighted_sum(40)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 40);
result = tester.Query("SELECT my_weighted_sum(40, 2)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 80);
}

View File

@@ -0,0 +1,260 @@
#include "capi_tester.hpp"
#include <cstdio>
using namespace duckdb;
using namespace std;
static void CAPIRegisterCustomType(duckdb_connection connection, const char *name, duckdb_type duckdb_type,
duckdb_state expected_outcome) {
duckdb_state status;
auto base_type = duckdb_create_logical_type(duckdb_type);
duckdb_logical_type_set_alias(base_type, name);
status = duckdb_register_logical_type(connection, base_type, nullptr);
REQUIRE(status == expected_outcome);
duckdb_destroy_logical_type(&base_type);
duckdb_destroy_logical_type(&base_type);
duckdb_destroy_logical_type(nullptr);
}
static void Vec3DAddFunction(duckdb_function_info info, duckdb_data_chunk input, duckdb_vector output) {
const auto count = duckdb_data_chunk_get_size(input);
const auto left_vector = duckdb_data_chunk_get_vector(input, 0);
const auto right_vector = duckdb_data_chunk_get_vector(input, 1);
const auto left_data = static_cast<float *>(duckdb_vector_get_data(duckdb_array_vector_get_child(left_vector)));
const auto right_data = static_cast<float *>(duckdb_vector_get_data(duckdb_array_vector_get_child(right_vector)));
const auto result_data = static_cast<float *>(duckdb_vector_get_data(duckdb_array_vector_get_child(output)));
for (idx_t i = 0; i < count; i++) {
for (idx_t j = 0; j < 3; j++) {
const auto idx = i * 3 + j;
result_data[idx] = left_data[idx] + right_data[idx];
}
}
}
static bool Vec3DToVarcharCastFunction(duckdb_function_info info, idx_t count, duckdb_vector input,
duckdb_vector output) {
const auto input_data = static_cast<float *>(duckdb_vector_get_data(duckdb_array_vector_get_child(input)));
for (idx_t i = 0; i < count; i++) {
const auto x = input_data[i * 3];
const auto y = input_data[i * 3 + 1];
const auto z = input_data[i * 3 + 2];
const auto res = StringUtil::Format("<%f, %f, %f>", x, y, z);
duckdb_vector_assign_string_element_len(output, i, res.c_str(), res.size());
}
return true;
}
static bool TryParseVec3D(char *str, idx_t len, float &x, float &y, float &z) {
char *end = str + len;
while (str < end && *str != '<') {
str++;
}
str++;
if (str >= end) {
return false;
}
x = std::strtof(str, &str);
if (str >= end || *str != ',') {
return false;
}
str++;
y = std::strtof(str, &str);
if (str >= end || *str != ',') {
return false;
}
str++;
z = std::strtof(str, &str);
if (str >= end || *str != '>') {
return false;
}
str++;
return str == end;
}
static bool Vec3DFromVarcharCastFunction(duckdb_function_info info, idx_t count, duckdb_vector input,
duckdb_vector output) {
const auto cast_mode = duckdb_cast_function_get_cast_mode(info);
// For testing purposes, check that we got the custom data
auto custom_data = reinterpret_cast<string *>(duckdb_cast_function_get_extra_info(info));
REQUIRE(*custom_data == "foobar");
const auto input_data = static_cast<duckdb_string_t *>(duckdb_vector_get_data(input));
const auto output_data = static_cast<float *>(duckdb_vector_get_data(duckdb_array_vector_get_child(output)));
bool success = true;
for (idx_t i = 0; i < count; i++) {
auto x = 0.0f, y = 0.0f, z = 0.0f;
auto str = input_data[i];
auto str_len = duckdb_string_t_length(str);
char *str_ptr = duckdb_string_is_inlined(str) ? str.value.inlined.inlined : str.value.pointer.ptr;
// yes, sscanf is not safe, but this is just a test
if (TryParseVec3D(str_ptr, str_len, x, y, z)) {
// Success
output_data[i * 3] = x;
output_data[i * 3 + 1] = y;
output_data[i * 3 + 2] = z;
} else {
// Error
duckdb_cast_function_set_row_error(info, "Failed to parse VEC3D", i, output);
if (cast_mode == DUCKDB_CAST_TRY) {
// Try cast, continue with the next row
success = false;
} else {
// Strict cast, short-circuit and return false
return false;
}
}
}
return success;
}
TEST_CASE("Test Custom Type Registration", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
// try to register a custom type with an invalid base type
CAPIRegisterCustomType(tester.connection, "NUMBER", DUCKDB_TYPE_INVALID, DuckDBError);
// try to register a custom type with a valid base type
CAPIRegisterCustomType(tester.connection, "NUMBER", DUCKDB_TYPE_INTEGER, DuckDBSuccess);
// try to register it again - this should be an error
CAPIRegisterCustomType(tester.connection, "NUMBER", DUCKDB_TYPE_INTEGER, DuckDBError);
// check that it is in the catalog
result = tester.Query("SELECT type_name FROM duckdb_types WHERE type_name = 'NUMBER'");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->row_count() == 1);
REQUIRE(result->Fetch<string>(0, 0) == "NUMBER");
}
TEST_CASE("Test Custom Type Function", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
// Register a custom type (VEC3D) that is between 0 and 1
auto element_type = duckdb_create_logical_type(DUCKDB_TYPE_FLOAT);
auto vector_type = duckdb_create_array_type(element_type, 3);
duckdb_logical_type_set_alias(vector_type, "VEC3D");
REQUIRE(duckdb_register_logical_type(tester.connection, vector_type, nullptr) == DuckDBSuccess);
// Register a scalar function that adds two vectors
auto function = duckdb_create_scalar_function();
duckdb_scalar_function_set_name(function, "vec3d_add");
duckdb_scalar_function_set_return_type(function, vector_type);
duckdb_scalar_function_add_parameter(function, vector_type);
duckdb_scalar_function_add_parameter(function, vector_type);
duckdb_scalar_function_set_function(function, Vec3DAddFunction);
REQUIRE(duckdb_register_scalar_function(tester.connection, function) == DuckDBSuccess);
// Also add a cast function to convert VEC3D to VARCHAR
auto varchar_type = duckdb_create_logical_type(DUCKDB_TYPE_VARCHAR);
auto to_varchar_cast_function = duckdb_create_cast_function();
duckdb_cast_function_set_implicit_cast_cost(to_varchar_cast_function, 0);
duckdb_cast_function_set_source_type(to_varchar_cast_function, vector_type);
duckdb_cast_function_set_target_type(to_varchar_cast_function, varchar_type);
duckdb_cast_function_set_function(to_varchar_cast_function, Vec3DToVarcharCastFunction);
REQUIRE(duckdb_register_cast_function(tester.connection, to_varchar_cast_function) == DuckDBSuccess);
auto from_varchar_cast_function = duckdb_create_cast_function();
duckdb_cast_function_set_implicit_cast_cost(from_varchar_cast_function, 0);
duckdb_cast_function_set_source_type(from_varchar_cast_function, varchar_type);
duckdb_cast_function_set_target_type(from_varchar_cast_function, vector_type);
duckdb_cast_function_set_function(from_varchar_cast_function, Vec3DFromVarcharCastFunction);
auto cast_custom_data = new string("foobar");
auto cast_custom_data_delete = [](void *data) {
delete reinterpret_cast<string *>(data);
};
duckdb_cast_function_set_extra_info(from_varchar_cast_function, cast_custom_data, cast_custom_data_delete);
REQUIRE(duckdb_register_cast_function(tester.connection, from_varchar_cast_function) == DuckDBSuccess);
// Cleanup the custom type and functions
duckdb_destroy_scalar_function(&function);
duckdb_destroy_cast_function(&to_varchar_cast_function);
duckdb_destroy_cast_function(&from_varchar_cast_function);
duckdb_destroy_logical_type(&varchar_type);
duckdb_destroy_logical_type(&element_type);
duckdb_destroy_logical_type(&vector_type);
// Ensure that we can free the casts multiple times without issue
duckdb_destroy_cast_function(&from_varchar_cast_function);
duckdb_destroy_cast_function(nullptr);
// Create a table with the custom type
REQUIRE_NO_FAIL(tester.Query("CREATE TABLE vec3d_table (a VEC3D)"));
REQUIRE_NO_FAIL(tester.Query("INSERT INTO vec3d_table VALUES ([1.0, 2.0, 3.0]::FLOAT[3])"));
REQUIRE_NO_FAIL(tester.Query("INSERT INTO vec3d_table VALUES ([4.0, 5.0, 6.0]::FLOAT[3])"));
// Query the table
result = tester.Query("SELECT vec3d_add(a, a) FROM vec3d_table");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->row_count() == 2);
// Check that the result is correct
auto chunk = result->FetchChunk(0);
auto data = static_cast<float *>(duckdb_vector_get_data(duckdb_array_vector_get_child(chunk->GetVector(0))));
REQUIRE(data[0] == 2.0f);
REQUIRE(data[1] == 4.0f);
REQUIRE(data[2] == 6.0f);
REQUIRE(data[3] == 8.0f);
REQUIRE(data[4] == 10.0f);
REQUIRE(data[5] == 12.0f);
// But we cant execute the function with a non-VEC3D type
result = tester.Query("SELECT vec3d_add([0,0,0]::FLOAT[3], [1,1,1]::FLOAT[3])");
REQUIRE_FAIL(result);
REQUIRE(result->ErrorType() == DUCKDB_ERROR_BINDER);
// But we can cast the base type to the custom type
result = tester.Query("SELECT vec3d_add(CAST([0,0,0] AS VEC3D), CAST([1,1,1] AS VEC3D))");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->row_count() == 1);
// Speaking of casts, let's test the VEC3D to VARCHAR cast
result = tester.Query("SELECT CAST(a AS VARCHAR) FROM vec3d_table");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->row_count() == 2);
// Check that the result is correct
REQUIRE(result->Fetch<string>(0, 0) == "<1.000000, 2.000000, 3.000000>");
REQUIRE(result->Fetch<string>(0, 1) == "<4.000000, 5.000000, 6.000000>");
// Now cast from varchar to VEC3D
result = tester.Query("SELECT CAST('<1.0, 3.0, 4.0>' AS VEC3D)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->row_count() == 1);
// Check that the result is correct
chunk = result->FetchChunk(0);
data = static_cast<float *>(duckdb_vector_get_data(duckdb_array_vector_get_child(chunk->GetVector(0))));
REQUIRE(data[0] == 1.0f);
REQUIRE(data[1] == 3.0f);
REQUIRE(data[2] == 4.0f);
// Try a faulty cast
result = tester.Query("SELECT CAST('<1.0, 3.0, abc' AS VEC3D)");
REQUIRE_FAIL(result);
REQUIRE(result->ErrorType() == DUCKDB_ERROR_CONVERSION);
REQUIRE_THAT(result->ErrorMessage(), Catch::Matchers::StartsWith("Conversion Error: Failed to parse VEC3D"));
// Try a faulty cast with TRY_CAST
result = tester.Query("SELECT TRY_CAST('<1.0, 3.0, abc' AS FLOAT[3]) IS NULL");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->row_count() == 1);
REQUIRE(result->Fetch<bool>(0, 0) == true);
}

View File

@@ -0,0 +1,159 @@
#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
static void test_file_system(duckdb_file_system fs, string file_name) {
REQUIRE(fs != nullptr);
duckdb_state state = DuckDBSuccess;
duckdb_file_handle file;
auto file_path = TestDirectoryPath() + "/" + file_name;
auto options = duckdb_create_file_open_options();
state = duckdb_file_open_options_set_flag(options, DUCKDB_FILE_FLAG_WRITE, true);
REQUIRE(state == DuckDBSuccess);
state = duckdb_file_open_options_set_flag(options, DUCKDB_FILE_FLAG_READ, true);
REQUIRE(state == DuckDBSuccess);
// Try to open non-existing file without create flag
state = duckdb_file_system_open(fs, file_path.c_str(), options, &file);
REQUIRE(state != DuckDBSuccess);
auto error_data = duckdb_file_system_error_data(fs);
auto error_type = duckdb_error_data_error_type(error_data);
REQUIRE(error_type == DUCKDB_ERROR_IO);
duckdb_destroy_error_data(&error_data);
// Try to write to a null file handle
auto failed_bytes_written = duckdb_file_handle_write(file, "data", 4);
REQUIRE(failed_bytes_written == -1);
auto file_error_data = duckdb_file_handle_error_data(file);
auto has_error = duckdb_error_data_has_error(file_error_data);
REQUIRE(has_error == false);
duckdb_destroy_error_data(&file_error_data);
// Set create flag
state = duckdb_file_open_options_set_flag(options, DUCKDB_FILE_FLAG_CREATE, true);
REQUIRE(state == DuckDBSuccess);
// Create and open a file
state = duckdb_file_system_open(fs, file_path.c_str(), options, &file);
REQUIRE(state == DuckDBSuccess);
REQUIRE(file != nullptr);
// Write to the file
const char *data = "Hello, DuckDB File System!";
auto bytes_written = duckdb_file_handle_write(file, data, strlen(data));
REQUIRE(bytes_written == (int64_t)strlen(data));
auto position = duckdb_file_handle_tell(file);
REQUIRE(position == bytes_written);
auto size = duckdb_file_handle_size(file);
REQUIRE(size == bytes_written);
// Sync
state = duckdb_file_handle_sync(file);
// Seek to the beginning
state = duckdb_file_handle_seek(file, 0);
REQUIRE(state == DuckDBSuccess);
position = duckdb_file_handle_tell(file);
REQUIRE(position == 0);
// Read from the file
char buffer[30];
memset(buffer, 0, sizeof(buffer));
auto bytes_read = duckdb_file_handle_read(file, buffer, sizeof(buffer) - 1);
REQUIRE(bytes_read == bytes_written);
REQUIRE(strcmp(buffer, data) == 0);
position = duckdb_file_handle_tell(file);
REQUIRE(position == bytes_read);
size = duckdb_file_handle_size(file);
REQUIRE(size == bytes_written);
// Seek to the end
state = duckdb_file_handle_seek(file, bytes_written);
REQUIRE(state == DuckDBSuccess);
position = duckdb_file_handle_tell(file);
REQUIRE(position == bytes_written);
size = duckdb_file_handle_size(file);
REQUIRE(size == bytes_written);
// Try to read from the end of the file
memset(buffer, 0, sizeof(buffer));
bytes_read = duckdb_file_handle_read(file, buffer, sizeof(buffer) - 1);
REQUIRE(bytes_read == 0); // EOF
position = duckdb_file_handle_tell(file);
REQUIRE(position == bytes_written);
size = duckdb_file_handle_size(file);
REQUIRE(size == bytes_written);
// Seek back to the beginning
state = duckdb_file_handle_seek(file, 0);
REQUIRE(state == DuckDBSuccess);
position = duckdb_file_handle_tell(file);
REQUIRE(position == 0);
size = duckdb_file_handle_size(file);
REQUIRE(size == bytes_written);
// Close the file
duckdb_file_handle_close(file);
duckdb_destroy_file_handle(&file);
// Open file again for reading
state = duckdb_file_system_open(fs, file_path.c_str(), options, &file);
REQUIRE(state == DuckDBSuccess);
REQUIRE(file != nullptr);
size = duckdb_file_handle_size(file);
REQUIRE(size == bytes_written);
// Check that the data is still there
memset(buffer, 0, sizeof(buffer));
bytes_read = duckdb_file_handle_read(file, buffer, sizeof(buffer) - 1);
REQUIRE(bytes_read == bytes_written);
REQUIRE(strcmp(buffer, data) == 0);
position = duckdb_file_handle_tell(file);
REQUIRE(position == bytes_read);
size = duckdb_file_handle_size(file);
REQUIRE(size == bytes_written);
// Close the file again
duckdb_file_handle_close(file);
duckdb_destroy_file_handle(&file);
duckdb_destroy_file_open_options(&options);
REQUIRE(file == nullptr);
REQUIRE(options == nullptr);
// Try destroy again for good measure
duckdb_destroy_file_handle(&file);
duckdb_destroy_file_open_options(&options);
REQUIRE(file == nullptr);
REQUIRE(options == nullptr);
}
TEST_CASE("Test File System in C API", "[capi]") {
CAPITester tester;
duckdb_client_context context;
duckdb_file_system fs;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
// get a file system from the client context
duckdb_connection_get_client_context(tester.connection, &context);
fs = duckdb_client_context_get_file_system(context);
REQUIRE(fs != nullptr);
test_file_system(fs, "test_file_capi_1.txt");
duckdb_destroy_file_system(&fs);
REQUIRE(fs == nullptr);
duckdb_destroy_client_context(&context);
// Try to destory fs again
duckdb_destroy_file_system(&fs);
REQUIRE(fs == nullptr);
}

View File

@@ -0,0 +1,646 @@
#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
void AddVariadicNumbersTogether(duckdb_function_info, duckdb_data_chunk input, duckdb_vector output) {
// get the total number of rows in this chunk
auto input_size = duckdb_data_chunk_get_size(input);
// extract the input vectors
auto column_count = duckdb_data_chunk_get_column_count(input);
std::vector<duckdb_vector> inputs;
std::vector<int64_t *> data_ptrs;
std::vector<uint64_t *> validity_masks;
auto result_data = (int64_t *)duckdb_vector_get_data(output);
duckdb_vector_ensure_validity_writable(output);
auto result_validity = duckdb_vector_get_validity(output);
// early-out by setting each row to NULL
if (column_count == 0) {
for (idx_t row_idx = 0; row_idx < input_size; row_idx++) {
duckdb_validity_set_row_invalid(result_validity, row_idx);
}
return;
}
// setup
for (idx_t col_idx = 0; col_idx < column_count; col_idx++) {
inputs.push_back(duckdb_data_chunk_get_vector(input, col_idx));
auto data_ptr = (int64_t *)duckdb_vector_get_data(inputs.back());
data_ptrs.push_back(data_ptr);
auto validity_mask = duckdb_vector_get_validity(inputs.back());
validity_masks.push_back(validity_mask);
}
// execution
for (idx_t row_idx = 0; row_idx < input_size; row_idx++) {
// validity check
auto invalid = false;
for (idx_t col_idx = 0; col_idx < column_count; col_idx++) {
if (!duckdb_validity_row_is_valid(validity_masks[col_idx], row_idx)) {
// not valid, set to NULL
duckdb_validity_set_row_invalid(result_validity, row_idx);
invalid = true;
break;
}
}
if (invalid) {
continue;
}
result_data[row_idx] = 0;
for (idx_t col_idx = 0; col_idx < column_count; col_idx++) {
auto data = data_ptrs[col_idx][row_idx];
result_data[row_idx] += data;
}
}
}
static duckdb_scalar_function CAPIGetScalarFunction(duckdb_connection connection, const char *name,
idx_t parameter_count = 2) {
auto function = duckdb_create_scalar_function();
duckdb_scalar_function_set_name(nullptr, name);
duckdb_scalar_function_set_name(function, nullptr);
duckdb_scalar_function_set_name(function, name);
duckdb_scalar_function_set_name(function, name);
// add a two bigint parameters
auto type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
duckdb_scalar_function_add_parameter(nullptr, type);
duckdb_scalar_function_add_parameter(function, nullptr);
for (idx_t idx = 0; idx < parameter_count; idx++) {
duckdb_scalar_function_add_parameter(function, type);
}
// set the return type to bigint
duckdb_scalar_function_set_return_type(nullptr, type);
duckdb_scalar_function_set_return_type(function, nullptr);
duckdb_scalar_function_set_return_type(function, type);
duckdb_destroy_logical_type(&type);
// set up the function
duckdb_scalar_function_set_function(nullptr, AddVariadicNumbersTogether);
duckdb_scalar_function_set_function(function, nullptr);
duckdb_scalar_function_set_function(function, AddVariadicNumbersTogether);
return function;
}
static void CAPIRegisterAddition(duckdb_connection connection, const char *name, duckdb_state expected_outcome) {
duckdb_state status;
// create a scalar function
auto function = CAPIGetScalarFunction(connection, name);
// register and cleanup
status = duckdb_register_scalar_function(connection, function);
REQUIRE(status == expected_outcome);
duckdb_destroy_scalar_function(&function);
duckdb_destroy_scalar_function(&function);
duckdb_destroy_scalar_function(nullptr);
}
TEST_CASE("Test Scalar Functions C API", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
CAPIRegisterAddition(tester.connection, "my_addition", DuckDBSuccess);
// try to register it again - this should not be an error
CAPIRegisterAddition(tester.connection, "my_addition", DuckDBSuccess);
// now call it
result = tester.Query("SELECT my_addition(40, 2)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 42);
result = tester.Query("SELECT my_addition(40, NULL)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->IsNull(0, 0));
result = tester.Query("SELECT my_addition(NULL, 2)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->IsNull(0, 0));
// call it over a vector of values
result = tester.Query("SELECT my_addition(1000000, i) FROM range(10000) t(i)");
REQUIRE_NO_FAIL(*result);
for (idx_t row = 0; row < 10000; row++) {
REQUIRE(result->Fetch<int64_t>(0, row) == static_cast<int64_t>(1000000 + row));
}
}
void ReturnStringInfo(duckdb_function_info info, duckdb_data_chunk input, duckdb_vector output) {
auto extra_info = string((const char *)duckdb_scalar_function_get_extra_info(info));
// get the total number of rows in this chunk
auto input_size = duckdb_data_chunk_get_size(input);
// extract the two input vectors
auto input_vector = duckdb_data_chunk_get_vector(input, 0);
// get the data pointers for the input vectors (both int64 as specified by the parameter types)
auto input_data = (duckdb_string_t *)duckdb_vector_get_data(input_vector);
// get the validity vectors
auto input_validity = duckdb_vector_get_validity(input_vector);
duckdb_vector_ensure_validity_writable(output);
auto result_validity = duckdb_vector_get_validity(output);
for (idx_t row = 0; row < input_size; row++) {
if (duckdb_validity_row_is_valid(input_validity, row)) {
// not null - do the operation
auto input_string = input_data[row];
string result = extra_info + "_";
if (duckdb_string_is_inlined(input_string)) {
result += string(input_string.value.inlined.inlined, input_string.value.inlined.length);
} else {
result += string(input_string.value.pointer.ptr, input_string.value.pointer.length);
}
duckdb_vector_assign_string_element_len(output, row, result.c_str(), result.size());
} else {
// either a or b is NULL - set the result row to NULL
duckdb_validity_set_row_invalid(result_validity, row);
}
}
}
static void CAPIRegisterStringInfo(duckdb_connection connection, const char *name, duckdb_function_info info,
duckdb_delete_callback_t destroy_func) {
duckdb_state status;
// create a scalar function
auto function = duckdb_create_scalar_function();
duckdb_scalar_function_set_name(function, name);
// add a single varchar parameter
auto type = duckdb_create_logical_type(DUCKDB_TYPE_VARCHAR);
duckdb_scalar_function_add_parameter(function, type);
// set the return type to varchar
duckdb_scalar_function_set_return_type(function, type);
duckdb_destroy_logical_type(&type);
// set up the function
duckdb_scalar_function_set_function(function, ReturnStringInfo);
// set the extra info
duckdb_scalar_function_set_extra_info(function, info, destroy_func);
// register and cleanup
status = duckdb_register_scalar_function(connection, function);
REQUIRE(status == DuckDBSuccess);
duckdb_destroy_scalar_function(&function);
}
TEST_CASE("Test Scalar Functions - strings & extra_info", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
auto string_data = reinterpret_cast<char *>(malloc(100));
strcpy(string_data, "my_prefix");
auto extra_info = reinterpret_cast<duckdb_function_info>(string_data);
REQUIRE(tester.OpenDatabase(nullptr));
CAPIRegisterStringInfo(tester.connection, "my_prefix", extra_info, free);
// now call it
result = tester.Query("SELECT my_prefix('hello_world')");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<string>(0, 0) == "my_prefix_hello_world");
result = tester.Query("SELECT my_prefix(NULL)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->IsNull(0, 0));
}
static void CAPIRegisterVarargsFun(duckdb_connection connection, const char *name, duckdb_state expected_outcome) {
duckdb_state status;
// create a scalar function
auto function = duckdb_create_scalar_function();
duckdb_scalar_function_set_name(function, name);
// set the variable arguments
auto type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
duckdb_scalar_function_set_varargs(function, type);
// set the return type to bigint
duckdb_scalar_function_set_return_type(function, type);
duckdb_destroy_logical_type(&type);
// set up the function
duckdb_scalar_function_set_function(function, AddVariadicNumbersTogether);
// register and cleanup
status = duckdb_register_scalar_function(connection, function);
REQUIRE(status == expected_outcome);
duckdb_destroy_scalar_function(&function);
}
TEST_CASE("Test Scalar Functions - variadic number of input parameters", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
CAPIRegisterVarargsFun(tester.connection, "my_addition", DuckDBSuccess);
result = tester.Query("SELECT my_addition(40, 2, 100, 3)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 145);
result = tester.Query("SELECT my_addition(40, 42, NULL)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->IsNull(0, 0));
result = tester.Query("SELECT my_addition(NULL, 2)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->IsNull(0, 0));
result = tester.Query("SELECT my_addition()");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->IsNull(0, 0));
result = tester.Query("SELECT my_addition('hello', [1])");
REQUIRE_FAIL(result);
}
void CountNULLValues(duckdb_function_info, duckdb_data_chunk input, duckdb_vector output) {
// Get the total number of rows and columns in this chunk.
auto input_size = duckdb_data_chunk_get_size(input);
auto column_count = duckdb_data_chunk_get_column_count(input);
// Extract the validity masks.
std::vector<uint64_t *> validity_masks;
for (idx_t col_idx = 0; col_idx < column_count; col_idx++) {
auto col = duckdb_data_chunk_get_vector(input, col_idx);
auto validity_mask = duckdb_vector_get_validity(col);
validity_masks.push_back(validity_mask);
}
// Execute the function.
auto result_data = (uint64_t *)duckdb_vector_get_data(output);
for (idx_t row_idx = 0; row_idx < input_size; row_idx++) {
idx_t null_count = 0;
idx_t other_null_count = 0;
for (idx_t col_idx = 0; col_idx < column_count; col_idx++) {
if (!duckdb_validity_row_is_valid(validity_masks[col_idx], row_idx)) {
null_count++;
}
// Alternative code path using SQLNULL.
auto duckdb_vector = duckdb_data_chunk_get_vector(input, col_idx);
auto logical_type = duckdb_vector_get_column_type(duckdb_vector);
auto type_id = duckdb_get_type_id(logical_type);
if (type_id == DUCKDB_TYPE_SQLNULL) {
other_null_count++;
}
duckdb_destroy_logical_type(&logical_type);
}
REQUIRE(null_count == other_null_count);
result_data[row_idx] = null_count;
}
}
static void CAPIRegisterANYFun(duckdb_connection connection, const char *name, duckdb_state expected_outcome) {
duckdb_state status;
// create a scalar function
auto function = duckdb_create_scalar_function();
duckdb_scalar_function_set_name(function, name);
// set the variable arguments
auto any_type = duckdb_create_logical_type(DUCKDB_TYPE_ANY);
duckdb_scalar_function_set_varargs(function, any_type);
duckdb_destroy_logical_type(&any_type);
// Set special null handling.
duckdb_scalar_function_set_special_handling(function);
duckdb_scalar_function_set_volatile(function);
duckdb_scalar_function_set_special_handling(nullptr);
duckdb_scalar_function_set_volatile(nullptr);
// set the return type uto bigint
auto return_type = duckdb_create_logical_type(DUCKDB_TYPE_UBIGINT);
duckdb_scalar_function_set_return_type(function, return_type);
duckdb_destroy_logical_type(&return_type);
// set up the function
duckdb_scalar_function_set_function(function, CountNULLValues);
// register and cleanup
status = duckdb_register_scalar_function(connection, function);
REQUIRE(status == expected_outcome);
duckdb_destroy_scalar_function(&function);
}
TEST_CASE("Test Scalar Functions - variadic number of ANY parameters", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
CAPIRegisterANYFun(tester.connection, "my_null_count", DuckDBSuccess);
result = tester.Query("SELECT my_null_count(40, [1], 'hello', 3)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<uint64_t>(0, 0) == 0);
result = tester.Query("SELECT my_null_count([1], 42, NULL)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<uint64_t>(0, 0) == 1);
result = tester.Query("SELECT my_null_count(NULL, NULL, NULL)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<uint64_t>(0, 0) == 3);
result = tester.Query("SELECT my_null_count()");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<uint64_t>(0, 0) == 0);
}
static void CAPIRegisterAdditionOverloads(duckdb_connection connection, const char *name,
duckdb_state expected_outcome) {
duckdb_state status;
auto function_set = duckdb_create_scalar_function_set(name);
// create a scalar function with 2 parameters
auto function = CAPIGetScalarFunction(connection, name, 2);
duckdb_add_scalar_function_to_set(function_set, function);
duckdb_destroy_scalar_function(&function);
// create a scalar function with 3 parameters
function = CAPIGetScalarFunction(connection, name, 3);
duckdb_add_scalar_function_to_set(function_set, function);
duckdb_destroy_scalar_function(&function);
// register and cleanup
status = duckdb_register_scalar_function_set(connection, function_set);
REQUIRE(status == expected_outcome);
duckdb_destroy_scalar_function_set(&function_set);
duckdb_destroy_scalar_function_set(&function_set);
duckdb_destroy_scalar_function_set(nullptr);
}
TEST_CASE("Test Scalar Function Overloads C API", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
CAPIRegisterAdditionOverloads(tester.connection, "my_addition", DuckDBSuccess);
// try to register it again - this should not be an error
CAPIRegisterAdditionOverloads(tester.connection, "my_addition", DuckDBSuccess);
// now call it
result = tester.Query("SELECT my_addition(40, 2)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 42);
result = tester.Query("SELECT my_addition(40, 2, 2)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 44);
// call it over a vector of values
result = tester.Query("SELECT my_addition(1000000, i, i) FROM range(10000) t(i)");
REQUIRE_NO_FAIL(*result);
for (idx_t row = 0; row < 10000; row++) {
REQUIRE(result->Fetch<int64_t>(0, row) == static_cast<int64_t>(1000000 + row + row));
}
}
struct ConnectionIdStruct {
idx_t connection_id;
idx_t folded_value;
};
void *CopyConnectionIdStruct(void *in_data_ptr) {
auto in_data = reinterpret_cast<ConnectionIdStruct *>(in_data_ptr);
auto out_data = reinterpret_cast<ConnectionIdStruct *>(malloc(sizeof(ConnectionIdStruct)));
out_data->connection_id = in_data->connection_id;
out_data->folded_value = in_data->folded_value;
return out_data;
}
void GetConnectionIdBind(duckdb_bind_info info) {
// Get the extra info.
auto extra_info_ptr = duckdb_scalar_function_bind_get_extra_info(info);
auto extra_info = string(reinterpret_cast<const char *>(extra_info_ptr));
if (extra_info.empty()) {
return;
}
// Get the connection ID.
duckdb_client_context context;
duckdb_scalar_function_get_client_context(info, &context);
auto connection_id = duckdb_client_context_get_connection_id(context);
// Get the expression.
auto argument_count = duckdb_scalar_function_bind_get_argument_count(info);
REQUIRE(argument_count == 1);
auto expr = duckdb_scalar_function_bind_get_argument(info, 0);
auto foldable = duckdb_expression_is_foldable(expr);
if (!foldable) {
duckdb_scalar_function_bind_set_error(info, "input argument must be foldable");
duckdb_destroy_expression(&expr);
duckdb_destroy_client_context(&context);
return;
}
// Fold the expression.
duckdb_value value;
auto error_data = duckdb_expression_fold(context, expr, &value);
auto has_error = duckdb_error_data_has_error(error_data);
if (has_error) {
auto error_msg = duckdb_error_data_message(error_data);
duckdb_scalar_function_bind_set_error(info, error_msg);
duckdb_destroy_expression(&expr);
duckdb_destroy_client_context(&context);
duckdb_destroy_error_data(&error_data);
return;
}
auto value_type = duckdb_get_value_type(value);
auto value_type_id = duckdb_get_type_id(value_type);
REQUIRE(value_type_id == DUCKDB_TYPE_UBIGINT);
auto uint64_value = duckdb_get_uint64(value);
duckdb_destroy_value(&value);
duckdb_destroy_expression(&expr);
duckdb_destroy_client_context(&context);
// Set the connection id.
auto bind_data = reinterpret_cast<ConnectionIdStruct *>(malloc(sizeof(ConnectionIdStruct)));
bind_data->connection_id = connection_id;
bind_data->folded_value = uint64_value;
duckdb_scalar_function_set_bind_data(info, bind_data, free);
duckdb_scalar_function_set_bind_data_copy(info, CopyConnectionIdStruct);
}
void GetConnectionId(duckdb_function_info info, duckdb_data_chunk input, duckdb_vector output) {
auto bind_data_ptr = duckdb_scalar_function_get_bind_data(info);
if (bind_data_ptr == nullptr) {
duckdb_scalar_function_set_error(info, "empty bind data");
return;
}
auto bind_data = reinterpret_cast<ConnectionIdStruct *>(bind_data_ptr);
auto input_size = duckdb_data_chunk_get_size(input);
auto result_data = reinterpret_cast<uint64_t *>(duckdb_vector_get_data(output));
for (idx_t row_idx = 0; row_idx < input_size; row_idx++) {
result_data[row_idx] = bind_data->connection_id + bind_data->folded_value;
}
}
static void CAPIRegisterGetConnectionId(duckdb_connection connection, bool is_volatile, string name) {
duckdb_state status;
auto function = duckdb_create_scalar_function();
duckdb_scalar_function_set_name(function, name.c_str());
// Set the return type to UBIGINT.
auto type = duckdb_create_logical_type(DUCKDB_TYPE_UBIGINT);
duckdb_scalar_function_add_parameter(function, type);
duckdb_scalar_function_set_return_type(function, type);
duckdb_destroy_logical_type(&type);
if (is_volatile) {
duckdb_scalar_function_set_volatile(function);
}
// Set up the bind and function callbacks.
duckdb_scalar_function_set_bind(function, GetConnectionIdBind);
duckdb_scalar_function_set_function(function, GetConnectionId);
// Set some extra info to retrieve during binding.
auto string_data = reinterpret_cast<char *>(malloc(100));
strcpy(string_data, "my_prefix");
auto extra_info = reinterpret_cast<duckdb_function_info>(string_data);
duckdb_scalar_function_set_extra_info(function, extra_info, free);
// Register and cleanup.
status = duckdb_register_scalar_function(connection, function);
REQUIRE(status == DuckDBSuccess);
duckdb_destroy_scalar_function(&function);
}
TEST_CASE("Test Scalar Function with Bind Info", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
CAPIRegisterGetConnectionId(tester.connection, false, "get_connection_id");
duckdb_client_context context;
duckdb_connection_get_client_context(tester.connection, &context);
auto first_conn_id = duckdb_client_context_get_connection_id(context);
duckdb_destroy_client_context(&context);
result = tester.Query("SELECT get_connection_id((40 + 2)::UBIGINT)");
REQUIRE_NO_FAIL(*result);
auto first_result = result->Fetch<uint64_t>(0, 0);
REQUIRE(first_result == first_conn_id + 42);
tester.ChangeConnection();
duckdb_connection_get_client_context(tester.connection, &context);
auto second_conn_id = duckdb_client_context_get_connection_id(context);
duckdb_destroy_client_context(&context);
result = tester.Query("SELECT get_connection_id((44 - 2)::UBIGINT)");
REQUIRE_NO_FAIL(*result);
auto second_result = result->Fetch<uint64_t>(0, 0);
REQUIRE(second_conn_id + 42 == second_result);
REQUIRE(first_result != second_result);
result = tester.Query("SELECT get_connection_id(random()::UBIGINT)");
REQUIRE_FAIL(result);
REQUIRE(StringUtil::Contains(result->ErrorMessage(), "input argument must be foldable"));
result = tester.Query("SELECT get_connection_id(200::UTINYINT + 200::UTINYINT)");
REQUIRE_FAIL(result);
REQUIRE(StringUtil::Contains(result->ErrorMessage(), "Overflow in addition of"));
}
TEST_CASE("Test volatile scalar function with bind in WHERE clause", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
CAPIRegisterGetConnectionId(tester.connection, true, "my_volatile_fun");
result = tester.Query("SELECT true WHERE my_volatile_fun((40 + 2)::UBIGINT) != 0");
REQUIRE(!result->HasError());
REQUIRE(result->Fetch<bool>(0, 0));
}
void ListSum(duckdb_function_info, duckdb_data_chunk input, duckdb_vector output) {
auto input_vector = duckdb_data_chunk_get_vector(input, 0);
auto input_size = duckdb_data_chunk_get_size(input);
auto input_validity = duckdb_vector_get_validity(input_vector);
auto list_entry = reinterpret_cast<duckdb_list_entry *>(duckdb_vector_get_data(input_vector));
auto list_child = duckdb_list_vector_get_child(input_vector);
auto child_validity = duckdb_vector_get_validity(list_child);
auto child_data = reinterpret_cast<uint64_t *>(duckdb_vector_get_data(list_child));
auto result_data = reinterpret_cast<uint64_t *>(duckdb_vector_get_data(output));
duckdb_vector_ensure_validity_writable(output);
auto result_validity = duckdb_vector_get_validity(output);
for (idx_t row = 0; row < input_size; row++) {
if (!duckdb_validity_row_is_valid(input_validity, row)) {
duckdb_validity_set_row_invalid(result_validity, row);
continue;
}
auto entry = list_entry[row];
auto offset = entry.offset;
auto length = entry.length;
uint64_t sum = 0;
for (idx_t idx = offset; idx < offset + length; idx++) {
if (duckdb_validity_row_is_valid(child_validity, idx)) {
sum += child_data[idx];
}
}
result_data[row] = sum;
}
}
static void CAPIRegisterListSum(duckdb_connection connection, const char *name) {
duckdb_state status;
auto function = duckdb_create_scalar_function();
duckdb_scalar_function_set_name(function, name);
auto ubigint_type = duckdb_create_logical_type(DUCKDB_TYPE_UBIGINT);
auto list_type = duckdb_create_list_type(ubigint_type);
duckdb_scalar_function_add_parameter(function, list_type);
duckdb_scalar_function_set_return_type(function, ubigint_type);
duckdb_destroy_logical_type(&list_type);
duckdb_destroy_logical_type(&ubigint_type);
duckdb_scalar_function_set_function(function, ListSum);
status = duckdb_register_scalar_function(connection, function);
REQUIRE(status == DuckDBSuccess);
duckdb_destroy_scalar_function(&function);
}
TEST_CASE("Test Scalar Functions - LIST", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
CAPIRegisterListSum(tester.connection, "my_list_sum");
result = tester.Query("SELECT my_list_sum([1::uint64])");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<uint64_t>(0, 0) == 1);
result = tester.Query("SELECT my_list_sum(NULL)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->IsNull(0, 0));
result = tester.Query("SELECT my_list_sum([])");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<uint64_t>(0, 0) == 0);
}

View File

@@ -0,0 +1,322 @@
#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
struct my_bind_data_struct {
int64_t size;
};
void my_bind(duckdb_bind_info info) {
REQUIRE(duckdb_bind_get_parameter_count(info) == 1);
duckdb_logical_type type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
duckdb_bind_add_result_column(info, "forty_two", type);
duckdb_destroy_logical_type(&type);
auto my_bind_data = (my_bind_data_struct *)malloc(sizeof(my_bind_data_struct));
auto param = duckdb_bind_get_parameter(info, 0);
my_bind_data->size = duckdb_get_int64(param);
duckdb_destroy_value(&param);
duckdb_bind_set_bind_data(info, my_bind_data, free);
}
struct my_init_data_struct {
int64_t pos;
};
void my_init(duckdb_init_info info) {
REQUIRE(duckdb_init_get_bind_data(info) != nullptr);
REQUIRE(duckdb_init_get_bind_data(nullptr) == nullptr);
auto my_init_data = (my_init_data_struct *)malloc(sizeof(my_init_data_struct));
my_init_data->pos = 0;
duckdb_init_set_init_data(info, my_init_data, free);
}
void my_function(duckdb_function_info info, duckdb_data_chunk output) {
auto bind_data = (my_bind_data_struct *)duckdb_function_get_bind_data(info);
auto init_data = (my_init_data_struct *)duckdb_function_get_init_data(info);
auto ptr = (int64_t *)duckdb_vector_get_data(duckdb_data_chunk_get_vector(output, 0));
idx_t i;
for (i = 0; i < STANDARD_VECTOR_SIZE; i++) {
if (init_data->pos >= bind_data->size) {
break;
}
ptr[i] = init_data->pos % 2 == 0 ? 42 : 84;
init_data->pos++;
}
duckdb_data_chunk_set_size(output, i);
}
static void capi_register_table_function(duckdb_connection connection, const char *name,
duckdb_table_function_bind_t bind, duckdb_table_function_init_t init,
duckdb_table_function_t f, duckdb_state expected_state = DuckDBSuccess) {
duckdb_state status;
// create a table function
auto function = duckdb_create_table_function();
duckdb_table_function_set_name(nullptr, name);
duckdb_table_function_set_name(function, nullptr);
duckdb_table_function_set_name(function, name);
duckdb_table_function_set_name(function, name);
// add a string parameter
duckdb_logical_type type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
duckdb_table_function_add_parameter(function, type);
duckdb_destroy_logical_type(&type);
// add a named parameter
duckdb_logical_type itype = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
duckdb_table_function_add_named_parameter(function, "my_parameter", itype);
duckdb_destroy_logical_type(&itype);
// set up the function pointers
duckdb_table_function_set_bind(function, bind);
duckdb_table_function_set_init(function, init);
duckdb_table_function_set_function(function, f);
// register and cleanup
status = duckdb_register_table_function(connection, function);
duckdb_destroy_table_function(&function);
duckdb_destroy_table_function(&function);
duckdb_destroy_table_function(nullptr);
REQUIRE(status == expected_state);
}
TEST_CASE("Test Table Functions C API", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
capi_register_table_function(tester.connection, "my_function", my_bind, my_init, my_function);
// registering again does not cause error, because we overload
capi_register_table_function(tester.connection, "my_function", my_bind, my_init, my_function);
// now call it
result = tester.Query("SELECT * FROM my_function(1)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 42);
result = tester.Query("SELECT * FROM my_function(1, my_parameter=3)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 42);
result = tester.Query("SELECT * FROM my_function(1, my_parameter=\"val\")");
REQUIRE(result->HasError());
result = tester.Query("SELECT * FROM my_function(1, nota_parameter=\"val\")");
REQUIRE(result->HasError());
result = tester.Query("SELECT * FROM my_function(3)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 42);
REQUIRE(result->Fetch<int64_t>(0, 1) == 84);
REQUIRE(result->Fetch<int64_t>(0, 2) == 42);
result = tester.Query("SELECT forty_two, COUNT(*) FROM my_function(10000) GROUP BY 1 ORDER BY 1");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 42);
REQUIRE(result->Fetch<int64_t>(0, 1) == 84);
REQUIRE(result->Fetch<int64_t>(1, 0) == 5000);
REQUIRE(result->Fetch<int64_t>(1, 1) == 5000);
}
void my_error_bind(duckdb_bind_info info) {
duckdb_bind_set_error(nullptr, nullptr);
duckdb_bind_set_error(info, "My error message");
}
void my_error_init(duckdb_init_info info) {
duckdb_init_set_error(nullptr, nullptr);
duckdb_init_set_error(info, "My error message");
}
void my_error_function(duckdb_function_info info, duckdb_data_chunk output) {
duckdb_function_set_error(nullptr, nullptr);
duckdb_function_set_error(info, "My error message");
}
TEST_CASE("Test Table Function errors in C API", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
capi_register_table_function(tester.connection, "my_error_bind", my_error_bind, my_init, my_function);
capi_register_table_function(tester.connection, "my_error_init", my_bind, my_error_init, my_function);
capi_register_table_function(tester.connection, "my_error_function", my_bind, my_init, my_error_function);
result = tester.Query("SELECT * FROM my_error_bind(1)");
REQUIRE(result->HasError());
result = tester.Query("SELECT * FROM my_error_init(1)");
REQUIRE(result->HasError());
result = tester.Query("SELECT * FROM my_error_function(1)");
REQUIRE(result->HasError());
}
TEST_CASE("Test Table Function register errors in C API", "[capi]") {
CAPITester tester;
REQUIRE(tester.OpenDatabase(nullptr));
capi_register_table_function(tester.connection, "x", my_error_bind, my_init, my_function, DuckDBSuccess);
// Try to register it again with the same name, is ok (because of overloading)
capi_register_table_function(tester.connection, "x", my_error_bind, my_init, my_function, DuckDBSuccess);
}
struct my_named_bind_data_struct {
int64_t size;
int64_t multiplier;
};
void my_named_bind(duckdb_bind_info info) {
REQUIRE(duckdb_bind_get_parameter_count(info) == 1);
duckdb_logical_type type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
duckdb_bind_add_result_column(info, "forty_two", type);
duckdb_destroy_logical_type(&type);
auto my_bind_data = (my_named_bind_data_struct *)malloc(sizeof(my_named_bind_data_struct));
auto param = duckdb_bind_get_parameter(info, 0);
my_bind_data->size = duckdb_get_int64(param);
duckdb_destroy_value(&param);
auto nparam = duckdb_bind_get_named_parameter(info, "my_parameter");
if (nparam) {
my_bind_data->multiplier = duckdb_get_int64(nparam);
} else {
my_bind_data->multiplier = 1;
}
duckdb_destroy_value(&nparam);
duckdb_bind_set_bind_data(info, my_bind_data, free);
}
void my_named_init(duckdb_init_info info) {
REQUIRE(duckdb_init_get_bind_data(info) != nullptr);
REQUIRE(duckdb_init_get_bind_data(nullptr) == nullptr);
auto my_init_data = (my_init_data_struct *)malloc(sizeof(my_init_data_struct));
my_init_data->pos = 0;
duckdb_init_set_init_data(info, my_init_data, free);
}
void my_named_function(duckdb_function_info info, duckdb_data_chunk output) {
auto bind_data = (my_named_bind_data_struct *)duckdb_function_get_bind_data(info);
auto init_data = (my_init_data_struct *)duckdb_function_get_init_data(info);
auto ptr = (int64_t *)duckdb_vector_get_data(duckdb_data_chunk_get_vector(output, 0));
idx_t i;
for (i = 0; i < STANDARD_VECTOR_SIZE; i++) {
if (init_data->pos >= bind_data->size) {
break;
}
ptr[i] = init_data->pos % 2 == 0 ? (42 * bind_data->multiplier) : (84 * bind_data->multiplier);
init_data->pos++;
}
duckdb_data_chunk_set_size(output, i);
}
TEST_CASE("Test Table Function named parameters in C API", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
capi_register_table_function(tester.connection, "my_multiplier_function", my_named_bind, my_named_init,
my_named_function);
result = tester.Query("SELECT * FROM my_multiplier_function(3)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 42);
REQUIRE(result->Fetch<int64_t>(0, 1) == 84);
REQUIRE(result->Fetch<int64_t>(0, 2) == 42);
result = tester.Query("SELECT * FROM my_multiplier_function(2, my_parameter=2)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 84);
REQUIRE(result->Fetch<int64_t>(0, 1) == 168);
result = tester.Query("SELECT * FROM my_multiplier_function(2, my_parameter=3)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 126);
REQUIRE(result->Fetch<int64_t>(0, 1) == 252);
}
struct my_bind_connection_id_data {
idx_t connection_id;
idx_t rows_requested;
};
void my_bind_connection_id(duckdb_bind_info info) {
REQUIRE(duckdb_bind_get_parameter_count(info) == 1);
duckdb_logical_type type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
duckdb_bind_add_result_column(info, "connection_id", type);
duckdb_destroy_logical_type(&type);
type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
duckdb_bind_add_result_column(info, "forty_two", type);
duckdb_destroy_logical_type(&type);
auto bind_data = (my_bind_connection_id_data *)malloc(sizeof(my_bind_connection_id_data));
auto param = duckdb_bind_get_parameter(info, 0);
auto rows_requested = duckdb_get_int64(param);
duckdb_destroy_value(&param);
duckdb_client_context context;
duckdb_table_function_get_client_context(info, &context);
auto connection_id = duckdb_client_context_get_connection_id(context);
duckdb_destroy_client_context(&context);
bind_data->rows_requested = rows_requested;
bind_data->connection_id = connection_id;
duckdb_bind_set_bind_data(info, bind_data, free);
}
void my_init_connection_id(duckdb_init_info info) {
REQUIRE(duckdb_init_get_bind_data(info) != nullptr);
REQUIRE(duckdb_init_get_bind_data(nullptr) == nullptr);
auto init_data = (my_init_data_struct *)malloc(sizeof(my_init_data_struct));
init_data->pos = 0;
duckdb_init_set_init_data(info, init_data, free);
}
void my_function_connection_id(duckdb_function_info info, duckdb_data_chunk output) {
auto bind_data = (my_bind_connection_id_data *)duckdb_function_get_bind_data(info);
auto init_data = (my_init_data_struct *)duckdb_function_get_init_data(info);
auto ptr = (int64_t *)duckdb_vector_get_data(duckdb_data_chunk_get_vector(output, 0));
auto ptr2 = (int64_t *)duckdb_vector_get_data(duckdb_data_chunk_get_vector(output, 1));
idx_t i;
for (i = 0; i < STANDARD_VECTOR_SIZE; i++) {
if (init_data->pos >= bind_data->rows_requested) {
break;
}
ptr[i] = bind_data->connection_id;
ptr2[i] = 42;
init_data->pos++;
}
duckdb_data_chunk_set_size(output, i);
}
TEST_CASE("Table function client context return") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
capi_register_table_function(tester.connection, "my_connection_id_function", my_bind_connection_id,
my_init_connection_id, my_function_connection_id);
duckdb_client_context context;
duckdb_connection_get_client_context(tester.connection, &context);
auto first_conn_id = duckdb_client_context_get_connection_id(context);
duckdb_destroy_client_context(&context);
result = tester.Query("SELECT * FROM my_connection_id_function(3)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == first_conn_id);
REQUIRE(result->Fetch<int64_t>(0, 1) == first_conn_id);
REQUIRE(result->Fetch<int64_t>(0, 2) == first_conn_id);
REQUIRE(result->Fetch<int64_t>(1, 0) == 42);
REQUIRE(result->Fetch<int64_t>(1, 1) == 42);
REQUIRE(result->Fetch<int64_t>(1, 2) == 42);
}

View File

@@ -0,0 +1,737 @@
#include "capi_tester.hpp"
#include <regex>
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<CAPIResult> 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<int64_t>(0)[0] == 42);
REQUIRE(result->ColumnCount() == 1);
REQUIRE(result->row_count() == 1);
REQUIRE(result->Fetch<int64_t>(0, 0) == 42);
REQUIRE(!result->IsNull(0, 0));
// out of range fetch
REQUIRE(result->Fetch<int64_t>(1, 0) == 0);
REQUIRE(result->Fetch<int64_t>(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<int64_t>(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<string>(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<bool>(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<bool>(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<bool>(0, 0) == false);
REQUIRE(result->Fetch<bool>(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<int32_t>(0, 1) == 11);
REQUIRE(result->Fetch<int32_t>(0, 2) == 13);
// 21, 22, 22
REQUIRE(result->Fetch<int32_t>(1, 0) == 21);
REQUIRE(result->Fetch<int32_t>(1, 1) == 22);
REQUIRE(result->Fetch<int32_t>(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<CAPIResult> 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<string> 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<int8_t>(0, 0) == 0);
REQUIRE(result->Fetch<int16_t>(0, 0) == 0);
REQUIRE(result->Fetch<int32_t>(0, 0) == 0);
REQUIRE(result->Fetch<int64_t>(0, 0) == 0);
REQUIRE(result->Fetch<uint8_t>(0, 0) == 0);
REQUIRE(result->Fetch<uint16_t>(0, 0) == 0);
REQUIRE(result->Fetch<uint32_t>(0, 0) == 0);
REQUIRE(result->Fetch<uint64_t>(0, 0) == 0);
REQUIRE(duckdb_uhugeint_to_double(result->Fetch<duckdb_uhugeint>(0, 0)) == 0);
REQUIRE(duckdb_hugeint_to_double(result->Fetch<duckdb_hugeint>(0, 0)) == 0);
REQUIRE(result->Fetch<string>(0, 0) == "");
REQUIRE(ApproxEqual(result->Fetch<float>(0, 0), 0.0f));
REQUIRE(ApproxEqual(result->Fetch<double>(0, 0), 0.0));
REQUIRE(!result->IsNull(0, 1));
REQUIRE(result->Fetch<int8_t>(0, 1) == 1);
REQUIRE(result->Fetch<int16_t>(0, 1) == 1);
REQUIRE(result->Fetch<int32_t>(0, 1) == 1);
REQUIRE(result->Fetch<int64_t>(0, 1) == 1);
REQUIRE(result->Fetch<uint8_t>(0, 1) == 1);
REQUIRE(result->Fetch<uint16_t>(0, 1) == 1);
REQUIRE(result->Fetch<uint32_t>(0, 1) == 1);
REQUIRE(result->Fetch<uint64_t>(0, 1) == 1);
REQUIRE(duckdb_uhugeint_to_double(result->Fetch<duckdb_uhugeint>(0, 1)) == 1);
REQUIRE(duckdb_hugeint_to_double(result->Fetch<duckdb_hugeint>(0, 1)) == 1);
REQUIRE(ApproxEqual(result->Fetch<float>(0, 1), 1.0f));
REQUIRE(ApproxEqual(result->Fetch<double>(0, 1), 1.0));
REQUIRE(result->Fetch<string>(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<int8_t>(0, 0) == 0);
REQUIRE(result->Fetch<int16_t>(0, 0) == 0);
REQUIRE(result->Fetch<int32_t>(0, 0) == 0);
REQUIRE(result->Fetch<int64_t>(0, 0) == 0);
REQUIRE(result->Fetch<string>(0, 0) == "");
REQUIRE(ApproxEqual(result->Fetch<float>(0, 0), 0.0f));
REQUIRE(ApproxEqual(result->Fetch<double>(0, 0), 0.0));
REQUIRE(!result->IsNull(0, 1));
REQUIRE(result->Fetch<int8_t>(0, 1) == 1);
REQUIRE(result->Fetch<int16_t>(0, 1) == 1);
REQUIRE(result->Fetch<int32_t>(0, 1) == 1);
REQUIRE(result->Fetch<int64_t>(0, 1) == 1);
REQUIRE(ApproxEqual(result->Fetch<float>(0, 1), 1.0f));
REQUIRE(ApproxEqual(result->Fetch<double>(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<duckdb_date>(0, 1));
REQUIRE(date.year == 1992);
REQUIRE(date.month == 9);
REQUIRE(date.day == 20);
REQUIRE(result->Fetch<string>(0, 1) == Value::DATE(1992, 9, 20).ToString());
date = duckdb_from_date(result->Fetch<duckdb_date>(0, 2));
REQUIRE(date.year == 30000);
REQUIRE(date.month == 9);
REQUIRE(date.day == 20);
REQUIRE(result->Fetch<string>(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<duckdb_time>(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<string>(0, 1) == Value::TIME(2, 30, 1, 0).ToString());
time_val = duckdb_from_time(result->Fetch<duckdb_time>(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<string>(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<duckdb_blob>(0, 0);
REQUIRE(blob.size == 11);
REQUIRE(memcmp(blob.data, "hello\012world", 11));
REQUIRE(result->Fetch<string>(0, 1) == "\\x00");
REQUIRE(result->IsNull(0, 2));
blob = result->Fetch<duckdb_blob>(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<bool>(0, 0));
REQUIRE(!result->Fetch<bool>(0, 1));
REQUIRE(result->Fetch<bool>(0, 2));
REQUIRE(result->Fetch<string>(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<duckdb_decimal>(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<duckdb_decimal>(0, 0)) == 1.2);
REQUIRE(duckdb_decimal_to_double(result->Fetch<duckdb_decimal>(1, 0)) == 100.3);
REQUIRE(duckdb_decimal_to_double(result->Fetch<duckdb_decimal>(2, 0)) == -320938.4298);
REQUIRE(duckdb_decimal_to_double(result->Fetch<duckdb_decimal>(3, 0)) == 49082094824.904820482094);
REQUIRE(duckdb_decimal_to_double(result->Fetch<duckdb_decimal>(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<bool>(0, 0) == true);
REQUIRE(result->Fetch<bool>(1, 0) == true);
REQUIRE(result->Fetch<bool>(2, 0) == true);
REQUIRE(result->Fetch<bool>(3, 0) == true);
REQUIRE(result->Fetch<bool>(4, 0) == false);
REQUIRE(result->Fetch<int8_t>(0, 0) == 1);
REQUIRE(result->Fetch<int8_t>(1, 0) == 100);
REQUIRE(result->Fetch<int8_t>(2, 0) == 0); // overflow
REQUIRE(result->Fetch<int8_t>(3, 0) == 0); // overflow
REQUIRE(result->Fetch<int8_t>(4, 0) == 0);
REQUIRE(result->Fetch<uint8_t>(0, 0) == 1);
REQUIRE(result->Fetch<uint8_t>(1, 0) == 100);
REQUIRE(result->Fetch<uint8_t>(2, 0) == 0); // overflow
REQUIRE(result->Fetch<uint8_t>(3, 0) == 0); // overflow
REQUIRE(result->Fetch<uint8_t>(4, 0) == 0);
REQUIRE(result->Fetch<int16_t>(0, 0) == 1);
REQUIRE(result->Fetch<int16_t>(1, 0) == 100);
REQUIRE(result->Fetch<int16_t>(2, 0) == 0); // overflow
REQUIRE(result->Fetch<int16_t>(3, 0) == 0); // overflow
REQUIRE(result->Fetch<int16_t>(4, 0) == 0);
REQUIRE(result->Fetch<uint16_t>(0, 0) == 1);
REQUIRE(result->Fetch<uint16_t>(1, 0) == 100);
REQUIRE(result->Fetch<uint16_t>(2, 0) == 0); // overflow
REQUIRE(result->Fetch<uint16_t>(3, 0) == 0); // overflow
REQUIRE(result->Fetch<uint16_t>(4, 0) == 0);
REQUIRE(result->Fetch<int32_t>(0, 0) == 1);
REQUIRE(result->Fetch<int32_t>(1, 0) == 100);
REQUIRE(result->Fetch<int32_t>(2, 0) == -320938);
REQUIRE(result->Fetch<int32_t>(3, 0) == 0); // overflow
REQUIRE(result->Fetch<int32_t>(4, 0) == 0);
REQUIRE(result->Fetch<uint32_t>(0, 0) == 1);
REQUIRE(result->Fetch<uint32_t>(1, 0) == 100);
REQUIRE(result->Fetch<uint32_t>(2, 0) == 0); // overflow
REQUIRE(result->Fetch<uint32_t>(3, 0) == 0); // overflow
REQUIRE(result->Fetch<uint32_t>(4, 0) == 0);
REQUIRE(result->Fetch<int64_t>(0, 0) == 1);
REQUIRE(result->Fetch<int64_t>(1, 0) == 100);
REQUIRE(result->Fetch<int64_t>(2, 0) == -320938);
REQUIRE(result->Fetch<int64_t>(3, 0) == 49082094825); // ceiling
REQUIRE(result->Fetch<int64_t>(4, 0) == 0);
REQUIRE(result->Fetch<uint64_t>(0, 0) == 1);
REQUIRE(result->Fetch<uint64_t>(1, 0) == 100);
REQUIRE(result->Fetch<uint64_t>(2, 0) == 0); // overflow
REQUIRE(result->Fetch<uint64_t>(3, 0) == 49082094825);
REQUIRE(result->Fetch<uint64_t>(4, 0) == 0);
require_hugeint_eq(result->Fetch<duckdb_hugeint>(0, 0), 1, 0);
require_hugeint_eq(result->Fetch<duckdb_hugeint>(1, 0), 100, 0);
require_hugeint_eq(result->Fetch<duckdb_hugeint>(2, 0), 18446744073709230678ul, -1);
require_hugeint_eq(result->Fetch<duckdb_hugeint>(3, 0), 49082094825, 0);
require_hugeint_eq(result->Fetch<duckdb_hugeint>(4, 0), 0, 0);
require_uhugeint_eq(result->Fetch<duckdb_uhugeint>(0, 0), 1, 0);
require_uhugeint_eq(result->Fetch<duckdb_uhugeint>(1, 0), 100, 0);
require_uhugeint_eq(result->Fetch<duckdb_uhugeint>(2, 0), 0, 0); // overflow
require_uhugeint_eq(result->Fetch<duckdb_uhugeint>(3, 0), 49082094825, 0);
require_uhugeint_eq(result->Fetch<duckdb_uhugeint>(4, 0), 0, 0);
REQUIRE(result->Fetch<float>(0, 0) == 1.2f);
REQUIRE(result->Fetch<float>(1, 0) == 100.3f);
REQUIRE(floor(result->Fetch<float>(2, 0)) == -320939);
REQUIRE((int64_t)floor(result->Fetch<float>(3, 0)) == 49082093568);
REQUIRE(result->Fetch<float>(4, 0) == 0.0);
REQUIRE(result->Fetch<double>(0, 0) == 1.2);
REQUIRE(result->Fetch<double>(1, 0) == 100.3);
REQUIRE(result->Fetch<double>(2, 0) == -320938.4298);
REQUIRE(result->Fetch<double>(3, 0) == 49082094824.904820482094);
REQUIRE(result->Fetch<double>(4, 0) == 0.0);
REQUIRE(result->Fetch<string>(0, 0) == "1.2");
REQUIRE(result->Fetch<string>(1, 0) == "100.3");
REQUIRE(result->Fetch<string>(2, 0) == "-320938.4298");
REQUIRE(result->Fetch<string>(3, 0) == "49082094824.904820482094");
REQUIRE(result->Fetch<string>(4, 0) == "");
result = tester.Query("SELECT -123.45::DECIMAL(5,2)");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<bool>(0, 0) == true);
REQUIRE(result->Fetch<int8_t>(0, 0) == -123);
REQUIRE(result->Fetch<uint8_t>(0, 0) == 0);
REQUIRE(result->Fetch<int16_t>(0, 0) == -123);
REQUIRE(result->Fetch<uint16_t>(0, 0) == 0);
REQUIRE(result->Fetch<int32_t>(0, 0) == -123);
REQUIRE(result->Fetch<uint32_t>(0, 0) == 0);
REQUIRE(result->Fetch<int64_t>(0, 0) == -123);
REQUIRE(result->Fetch<uint64_t>(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<duckdb_hugeint>(0, 0), expected_val);
REQUIRE(result->Fetch<float>(0, 0) == -123.45f);
REQUIRE(result->Fetch<double>(0, 0) == -123.45);
REQUIRE(result->Fetch<string>(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<CAPIResult> 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<duckdb_date_struct>(0, 0);
REQUIRE(d.year == 1970);
REQUIRE(d.month == 1);
REQUIRE(d.day == 1);
d = result->Fetch<duckdb_date_struct>(1, 0);
REQUIRE(d.year == 1970);
REQUIRE(d.month == 1);
REQUIRE(d.day == 1);
d = result->Fetch<duckdb_date_struct>(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<CAPIResult> 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<double>(0, 0);
REQUIRE(double_from_decimal == (double)-0.5);
auto string_from_decimal = result->Fetch<string>(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);
}

View File

@@ -0,0 +1,262 @@
#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
TEST_CASE("Test logical type creation with unsupported types", "[capi]") {
// Test duckdb_create_logical_type with unsupported types.
duckdb::vector<duckdb_type> unsupported_types = {
DUCKDB_TYPE_INVALID, DUCKDB_TYPE_DECIMAL, DUCKDB_TYPE_ENUM, DUCKDB_TYPE_LIST,
DUCKDB_TYPE_STRUCT, DUCKDB_TYPE_MAP, DUCKDB_TYPE_ARRAY, DUCKDB_TYPE_UNION,
};
for (const auto unsupported_type : unsupported_types) {
auto logical_type = duckdb_create_logical_type(unsupported_type);
REQUIRE(DUCKDB_TYPE_INVALID == duckdb_get_type_id(logical_type));
duckdb_destroy_logical_type(&logical_type);
}
}
TEST_CASE("Test INVALID, ANY and SQLNULL", "[capi]") {
auto sql_null_type = duckdb_create_logical_type(DUCKDB_TYPE_SQLNULL);
duckdb_destroy_logical_type(&sql_null_type);
auto any_type = duckdb_create_logical_type(DUCKDB_TYPE_ANY);
auto invalid_type = duckdb_create_logical_type(DUCKDB_TYPE_INVALID);
auto result_type_id = duckdb_get_type_id(any_type);
REQUIRE(result_type_id == DUCKDB_TYPE_ANY);
result_type_id = duckdb_get_type_id(invalid_type);
REQUIRE(result_type_id == DUCKDB_TYPE_INVALID);
// LIST with ANY
auto list = duckdb_create_list_type(any_type);
result_type_id = duckdb_get_type_id(list);
REQUIRE(result_type_id == DUCKDB_TYPE_LIST);
duckdb_destroy_logical_type(&list);
// LIST with INVALID
list = duckdb_create_list_type(invalid_type);
result_type_id = duckdb_get_type_id(list);
REQUIRE(result_type_id == DUCKDB_TYPE_LIST);
duckdb_destroy_logical_type(&list);
// ARRAY with ANY
auto array = duckdb_create_array_type(any_type, 2);
result_type_id = duckdb_get_type_id(array);
REQUIRE(result_type_id == DUCKDB_TYPE_ARRAY);
duckdb_destroy_logical_type(&array);
// ARRAY with INVALID
array = duckdb_create_array_type(invalid_type, 2);
result_type_id = duckdb_get_type_id(array);
REQUIRE(result_type_id == DUCKDB_TYPE_ARRAY);
duckdb_destroy_logical_type(&array);
// MAP with ANY
auto map = duckdb_create_map_type(any_type, any_type);
result_type_id = duckdb_get_type_id(map);
REQUIRE(result_type_id == DUCKDB_TYPE_MAP);
duckdb_destroy_logical_type(&map);
// MAP with INVALID
map = duckdb_create_map_type(any_type, any_type);
result_type_id = duckdb_get_type_id(map);
REQUIRE(result_type_id == DUCKDB_TYPE_MAP);
duckdb_destroy_logical_type(&map);
// UNION with ANY and INVALID
std::vector<const char *> member_names {"any", "invalid"};
duckdb::vector<duckdb_logical_type> types = {any_type, invalid_type};
auto union_type = duckdb_create_union_type(types.data(), member_names.data(), member_names.size());
result_type_id = duckdb_get_type_id(union_type);
REQUIRE(result_type_id == DUCKDB_TYPE_UNION);
duckdb_destroy_logical_type(&union_type);
// Clean-up.
duckdb_destroy_logical_type(&any_type);
duckdb_destroy_logical_type(&invalid_type);
}
TEST_CASE("Test LIST and ARRAY with INVALID and ANY", "[capi]") {
auto int_type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
auto any_type = duckdb_create_logical_type(DUCKDB_TYPE_ANY);
auto invalid_type = duckdb_create_logical_type(DUCKDB_TYPE_INVALID);
auto value = duckdb_create_int64(42);
duckdb::vector<duckdb_value> list_values {value, value};
auto int_list = duckdb_create_list_value(int_type, list_values.data(), list_values.size());
auto result = duckdb_get_varchar(int_list);
REQUIRE(string(result).compare("[42, 42]") == 0);
duckdb_free(result);
duckdb_destroy_value(&int_list);
auto int_array = duckdb_create_array_value(int_type, list_values.data(), list_values.size());
result = duckdb_get_varchar(int_array);
REQUIRE(string(result).compare("[42, 42]") == 0);
duckdb_free(result);
duckdb_destroy_value(&int_array);
auto invalid_list = duckdb_create_list_value(any_type, list_values.data(), list_values.size());
REQUIRE(invalid_list == nullptr);
auto invalid_array = duckdb_create_array_value(any_type, list_values.data(), list_values.size());
REQUIRE(invalid_array == nullptr);
auto any_list = duckdb_create_list_value(any_type, list_values.data(), list_values.size());
REQUIRE(any_list == nullptr);
auto any_array = duckdb_create_array_value(any_type, list_values.data(), list_values.size());
REQUIRE(any_array == nullptr);
// Clean-up.
duckdb_destroy_value(&value);
duckdb_destroy_logical_type(&int_type);
duckdb_destroy_logical_type(&any_type);
duckdb_destroy_logical_type(&invalid_type);
}
TEST_CASE("Test STRUCT with INVALID and ANY", "[capi]") {
auto int_type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
auto any_type = duckdb_create_logical_type(DUCKDB_TYPE_ANY);
auto invalid_type = duckdb_create_logical_type(DUCKDB_TYPE_INVALID);
auto value = duckdb_create_int64(42);
duckdb::vector<duckdb_value> struct_values {value, value};
// Test duckdb_create_struct_type with ANY.
std::vector<const char *> member_names {"int", "other"};
duckdb::vector<duckdb_logical_type> types = {int_type, any_type};
auto struct_type = duckdb_create_struct_type(types.data(), member_names.data(), member_names.size());
REQUIRE(struct_type != nullptr);
// Test duckdb_create_struct_value with ANY.
auto struct_value = duckdb_create_struct_value(struct_type, struct_values.data());
REQUIRE(struct_value == nullptr);
duckdb_destroy_logical_type(&struct_type);
// Test duckdb_create_struct_type with INVALID.
types = {int_type, invalid_type};
struct_type = duckdb_create_struct_type(types.data(), member_names.data(), member_names.size());
REQUIRE(struct_type != nullptr);
// Test duckdb_create_struct_value with INVALID.
struct_value = duckdb_create_struct_value(struct_type, struct_values.data());
REQUIRE(struct_value == nullptr);
duckdb_destroy_logical_type(&struct_type);
// Clean-up.
duckdb_destroy_value(&value);
duckdb_destroy_logical_type(&int_type);
duckdb_destroy_logical_type(&any_type);
duckdb_destroy_logical_type(&invalid_type);
}
TEST_CASE("Test data chunk creation with INVALID and ANY types", "[capi]") {
auto any_type = duckdb_create_logical_type(DUCKDB_TYPE_ANY);
auto invalid_type = duckdb_create_logical_type(DUCKDB_TYPE_INVALID);
auto list_type = duckdb_create_list_type(any_type);
// For each type, try to create a data chunk with that type.
std::vector<duckdb_logical_type> test_types = {any_type, invalid_type, list_type};
for (idx_t i = 0; i < test_types.size(); i++) {
duckdb_logical_type types[1];
types[0] = test_types[i];
auto data_chunk = duckdb_create_data_chunk(types, 1);
REQUIRE(data_chunk == nullptr);
}
// Clean-up.
duckdb_destroy_logical_type(&list_type);
duckdb_destroy_logical_type(&any_type);
duckdb_destroy_logical_type(&invalid_type);
}
void DummyScalar(duckdb_function_info, duckdb_data_chunk, duckdb_vector) {
}
static duckdb_scalar_function DummyScalarFunction() {
auto function = duckdb_create_scalar_function();
duckdb_scalar_function_set_name(function, "hello");
duckdb_scalar_function_set_function(function, DummyScalar);
return function;
}
static void TestScalarFunction(duckdb_scalar_function function, duckdb_connection connection) {
auto status = duckdb_register_scalar_function(connection, function);
REQUIRE(status == DuckDBError);
duckdb_destroy_scalar_function(&function);
}
TEST_CASE("Test scalar functions with INVALID and ANY types", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
auto int_type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
auto any_type = duckdb_create_logical_type(DUCKDB_TYPE_ANY);
auto invalid_type = duckdb_create_logical_type(DUCKDB_TYPE_INVALID);
// Set INVALID as a parameter.
auto function = DummyScalarFunction();
duckdb_scalar_function_add_parameter(function, invalid_type);
duckdb_scalar_function_set_return_type(function, int_type);
TestScalarFunction(function, tester.connection);
// Set INVALID as the return type.
function = DummyScalarFunction();
duckdb_scalar_function_set_return_type(function, invalid_type);
TestScalarFunction(function, tester.connection);
// Set ANY as the return type.
function = DummyScalarFunction();
duckdb_scalar_function_set_return_type(function, any_type);
TestScalarFunction(function, tester.connection);
// Clean-up.
duckdb_destroy_logical_type(&int_type);
duckdb_destroy_logical_type(&any_type);
duckdb_destroy_logical_type(&invalid_type);
}
void my_dummy_bind(duckdb_bind_info) {
}
void my_dummy_init(duckdb_init_info) {
}
void my_dummy_function(duckdb_function_info, duckdb_data_chunk) {
}
static duckdb_table_function DummyTableFunction() {
auto function = duckdb_create_table_function();
duckdb_table_function_set_name(function, "hello");
duckdb_table_function_set_bind(function, my_dummy_bind);
duckdb_table_function_set_init(function, my_dummy_init);
duckdb_table_function_set_function(function, my_dummy_function);
return function;
}
static void TestTableFunction(duckdb_table_function function, duckdb_connection connection) {
auto status = duckdb_register_table_function(connection, function);
REQUIRE(status == DuckDBError);
duckdb_destroy_table_function(&function);
}
TEST_CASE("Test table functions with INVALID and ANY types", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
auto invalid_type = duckdb_create_logical_type(DUCKDB_TYPE_INVALID);
// Set INVALID as a parameter.
auto function = DummyTableFunction();
duckdb_table_function_add_parameter(function, invalid_type);
TestTableFunction(function, tester.connection);
// Set INVALID as a named parameter.
function = DummyTableFunction();
duckdb_table_function_add_named_parameter(function, "my_parameter", invalid_type);
TestTableFunction(function, tester.connection);
duckdb_destroy_logical_type(&invalid_type);
}

View File

@@ -0,0 +1,140 @@
#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
TEST_CASE("Test casting columns in AppendDataChunk in C API", "[capi]") {
duckdb::vector<string> tables;
tables.push_back("CREATE TABLE test(i BIGINT, j VARCHAR);");
tables.push_back("CREATE TABLE test(i BIGINT, j BOOLEAN);");
for (idx_t i = 0; i < tables.size(); i++) {
CAPITester tester;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE(duckdb_vector_size() == STANDARD_VECTOR_SIZE);
tester.Query(tables[i]);
duckdb_logical_type types[2];
types[0] = duckdb_create_logical_type(DUCKDB_TYPE_SMALLINT);
types[1] = duckdb_create_logical_type(DUCKDB_TYPE_BOOLEAN);
auto data_chunk = duckdb_create_data_chunk(types, 2);
REQUIRE(data_chunk);
auto smallint_col = duckdb_data_chunk_get_vector(data_chunk, 0);
auto boolean_col = duckdb_data_chunk_get_vector(data_chunk, 1);
auto smallint_data = reinterpret_cast<int16_t *>(duckdb_vector_get_data(smallint_col));
smallint_data[0] = 15;
smallint_data[1] = -15;
auto boolean_data = reinterpret_cast<bool *>(duckdb_vector_get_data(boolean_col));
boolean_data[0] = false;
boolean_data[1] = true;
duckdb_data_chunk_set_size(data_chunk, 2);
duckdb_appender appender;
auto status = duckdb_appender_create(tester.connection, nullptr, "test", &appender);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_append_data_chunk(appender, data_chunk) == DuckDBSuccess);
duckdb_appender_close(appender);
auto result = tester.Query("SELECT i, j FROM test;");
REQUIRE(result->Fetch<int64_t>(0, 0) == 15);
REQUIRE(result->Fetch<int64_t>(0, 1) == -15);
auto str = result->Fetch<string>(1, 0);
REQUIRE(str.compare("false") == 0);
str = result->Fetch<string>(1, 1);
REQUIRE(str.compare("true") == 0);
duckdb_appender_destroy(&appender);
duckdb_destroy_data_chunk(&data_chunk);
duckdb_destroy_logical_type(&types[0]);
duckdb_destroy_logical_type(&types[1]);
}
}
TEST_CASE("Test casting error in AppendDataChunk in C API", "[capi]") {
CAPITester tester;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE(duckdb_vector_size() == STANDARD_VECTOR_SIZE);
tester.Query("CREATE TABLE test(i BIGINT, j BOOLEAN[]);");
duckdb_logical_type types[2];
types[0] = duckdb_create_logical_type(DUCKDB_TYPE_SMALLINT);
types[1] = duckdb_create_logical_type(DUCKDB_TYPE_BOOLEAN);
auto data_chunk = duckdb_create_data_chunk(types, 2);
REQUIRE(data_chunk);
auto smallint_col = duckdb_data_chunk_get_vector(data_chunk, 0);
auto boolean_col = duckdb_data_chunk_get_vector(data_chunk, 1);
auto smallint_data = reinterpret_cast<int16_t *>(duckdb_vector_get_data(smallint_col));
smallint_data[0] = 15;
smallint_data[1] = -15;
auto boolean_data = reinterpret_cast<bool *>(duckdb_vector_get_data(boolean_col));
boolean_data[0] = false;
boolean_data[1] = true;
duckdb_data_chunk_set_size(data_chunk, 2);
duckdb_appender appender;
auto status = duckdb_appender_create(tester.connection, nullptr, "test", &appender);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_append_data_chunk(appender, data_chunk) == DuckDBError);
auto error_msg = duckdb_appender_error(appender);
REQUIRE(string(error_msg) == "type mismatch in AppendDataChunk, expected BOOLEAN[], got BOOLEAN for column 1");
duckdb_appender_close(appender);
duckdb_appender_destroy(&appender);
duckdb_destroy_data_chunk(&data_chunk);
duckdb_destroy_logical_type(&types[0]);
duckdb_destroy_logical_type(&types[1]);
}
TEST_CASE("Test casting timestamps in AppendDataChunk in C API", "[capi]") {
CAPITester tester;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE(duckdb_vector_size() == STANDARD_VECTOR_SIZE);
tester.Query("CREATE TABLE test(i TIMESTAMP, j DATE);");
duckdb_logical_type types[2];
types[0] = duckdb_create_logical_type(DUCKDB_TYPE_VARCHAR);
types[1] = duckdb_create_logical_type(DUCKDB_TYPE_VARCHAR);
auto data_chunk = duckdb_create_data_chunk(types, 2);
REQUIRE(data_chunk);
auto ts_column = duckdb_data_chunk_get_vector(data_chunk, 0);
auto date_column = duckdb_data_chunk_get_vector(data_chunk, 1);
duckdb_vector_assign_string_element(ts_column, 0, "2017-07-23 13:10:11");
duckdb_vector_assign_string_element(date_column, 0, "1993-08-14");
duckdb_data_chunk_set_size(data_chunk, 1);
duckdb_appender appender;
auto status = duckdb_appender_create(tester.connection, nullptr, "test", &appender);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_append_data_chunk(appender, data_chunk) == DuckDBSuccess);
duckdb_appender_close(appender);
auto result = tester.Query("SELECT i::VARCHAR, j::VARCHAR FROM test;");
auto str = result->Fetch<string>(0, 0);
REQUIRE(str.compare("2017-07-23 13:10:11") == 0);
str = result->Fetch<string>(1, 0);
REQUIRE(str.compare("1993-08-14") == 0);
duckdb_appender_destroy(&appender);
duckdb_destroy_data_chunk(&data_chunk);
duckdb_destroy_logical_type(&types[0]);
duckdb_destroy_logical_type(&types[1]);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,463 @@
#include "capi_tester.hpp"
#include "duckdb/common/arrow/arrow_appender.hpp"
#include "duckdb/common/arrow/arrow_converter.hpp"
using namespace duckdb;
using namespace std;
TEST_CASE("Test arrow in C API", "[capi][arrow]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
duckdb_prepared_statement stmt = nullptr;
duckdb_arrow arrow_result = nullptr;
// open the database in in-memory mode
REQUIRE(tester.OpenDatabase(nullptr));
SECTION("test rows changed") {
REQUIRE_NO_FAIL(tester.Query("CREATE TABLE test(a INTEGER);"));
auto state = duckdb_query_arrow(tester.connection, "INSERT INTO test VALUES (1), (2);", &arrow_result);
REQUIRE(state == DuckDBSuccess);
REQUIRE(duckdb_arrow_rows_changed(arrow_result) == 2);
duckdb_destroy_arrow(&arrow_result);
REQUIRE_NO_FAIL(tester.Query("DROP TABLE test;"));
}
SECTION("test query arrow") {
auto state = duckdb_query_arrow(tester.connection, "SELECT 42 AS VALUE, [1,2,3,4,5] AS LST;", &arrow_result);
REQUIRE(state == DuckDBSuccess);
REQUIRE(duckdb_arrow_row_count(arrow_result) == 1);
REQUIRE(duckdb_arrow_column_count(arrow_result) == 2);
REQUIRE(duckdb_arrow_rows_changed(arrow_result) == 0);
// query the arrow schema
ArrowSchema arrow_schema;
arrow_schema.Init();
auto arrow_schema_ptr = &arrow_schema;
state = duckdb_query_arrow_schema(arrow_result, reinterpret_cast<duckdb_arrow_schema *>(&arrow_schema_ptr));
REQUIRE(state == DuckDBSuccess);
REQUIRE(string(arrow_schema.name) == "duckdb_query_result");
if (arrow_schema.release) {
arrow_schema.release(arrow_schema_ptr);
}
// query array data
ArrowArray arrow_array;
arrow_array.Init();
auto arrow_array_ptr = &arrow_array;
state = duckdb_query_arrow_array(arrow_result, reinterpret_cast<duckdb_arrow_array *>(&arrow_array_ptr));
REQUIRE(state == DuckDBSuccess);
REQUIRE(arrow_array.length == 1);
REQUIRE(arrow_array.release != nullptr);
arrow_array.release(arrow_array_ptr);
duckdb_arrow_array null_array = nullptr;
state = duckdb_query_arrow_array(arrow_result, &null_array);
REQUIRE(state == DuckDBSuccess);
REQUIRE(null_array == nullptr);
// destroy the arrow result
duckdb_destroy_arrow(&arrow_result);
}
SECTION("test multiple chunks") {
// create a table that consists of multiple chunks
REQUIRE_NO_FAIL(tester.Query("CREATE TABLE test(a INTEGER);"));
REQUIRE_NO_FAIL(
tester.Query("INSERT INTO test SELECT i FROM (VALUES (1), (2), (3), (4), (5)) t(i), range(500);"));
auto state = duckdb_query_arrow(tester.connection, "SELECT CAST(a AS INTEGER) AS a FROM test ORDER BY a;",
&arrow_result);
REQUIRE(state == DuckDBSuccess);
// query the arrow schema
ArrowSchema arrow_schema;
arrow_schema.Init();
auto arrow_schema_ptr = &arrow_schema;
state = duckdb_query_arrow_schema(arrow_result, reinterpret_cast<duckdb_arrow_schema *>(&arrow_schema_ptr));
REQUIRE(state == DuckDBSuccess);
REQUIRE(arrow_schema.release != nullptr);
arrow_schema.release(arrow_schema_ptr);
int total_count = 0;
while (true) {
// query array data
ArrowArray arrow_array;
arrow_array.Init();
auto arrow_array_ptr = &arrow_array;
state = duckdb_query_arrow_array(arrow_result, reinterpret_cast<duckdb_arrow_array *>(&arrow_array_ptr));
REQUIRE(state == DuckDBSuccess);
if (arrow_array.length == 0) {
// nothing to release
REQUIRE(total_count == 2500);
break;
}
REQUIRE(arrow_array.length > 0);
total_count += arrow_array.length;
REQUIRE(arrow_array.release != nullptr);
arrow_array.release(arrow_array_ptr);
}
// destroy the arrow result
duckdb_destroy_arrow(&arrow_result);
REQUIRE_NO_FAIL(tester.Query("DROP TABLE test;"));
}
SECTION("test prepare query arrow") {
auto state = duckdb_prepare(tester.connection, "SELECT CAST($1 AS BIGINT)", &stmt);
REQUIRE(state == DuckDBSuccess);
REQUIRE(stmt != nullptr);
REQUIRE(duckdb_bind_int64(stmt, 1, 42) == DuckDBSuccess);
// prepare and execute the arrow schema
ArrowSchema prepared_schema;
prepared_schema.Init();
auto prepared_schema_ptr = &prepared_schema;
state = duckdb_prepared_arrow_schema(stmt, reinterpret_cast<duckdb_arrow_schema *>(&prepared_schema_ptr));
REQUIRE(state == DuckDBSuccess);
REQUIRE(string(prepared_schema.format) == "+s");
REQUIRE(duckdb_execute_prepared_arrow(stmt, nullptr) == DuckDBError);
REQUIRE(duckdb_execute_prepared_arrow(stmt, &arrow_result) == DuckDBSuccess);
REQUIRE(prepared_schema.release != nullptr);
prepared_schema.release(prepared_schema_ptr);
// query the arrow schema
ArrowSchema arrow_schema;
arrow_schema.Init();
auto arrow_schema_ptr = &arrow_schema;
state = duckdb_query_arrow_schema(arrow_result, reinterpret_cast<duckdb_arrow_schema *>(&arrow_schema_ptr));
REQUIRE(state == DuckDBSuccess);
REQUIRE(string(arrow_schema.format) == "+s");
REQUIRE(arrow_schema.release != nullptr);
arrow_schema.release(arrow_schema_ptr);
ArrowArray arrow_array;
arrow_array.Init();
auto arrow_array_ptr = &arrow_array;
state = duckdb_query_arrow_array(arrow_result, reinterpret_cast<duckdb_arrow_array *>(&arrow_array_ptr));
REQUIRE(state == DuckDBSuccess);
REQUIRE(arrow_array.length == 1);
REQUIRE(arrow_array.release);
arrow_array.release(arrow_array_ptr);
duckdb_destroy_arrow(&arrow_result);
duckdb_destroy_prepare(&stmt);
}
SECTION("test scan") {
const auto logical_types = duckdb::vector<LogicalType> {LogicalType(LogicalTypeId::INTEGER)};
const auto column_names = duckdb::vector<string> {"value"};
// arrow schema, release after use
ArrowSchema arrow_schema;
arrow_schema.Init();
auto arrow_schema_ptr = &arrow_schema;
ClientProperties options = (reinterpret_cast<Connection *>(tester.connection)->context->GetClientProperties());
duckdb::ArrowConverter::ToArrowSchema(arrow_schema_ptr, logical_types, column_names, options);
ArrowArray arrow_array;
arrow_array.Init();
auto arrow_array_ptr = &arrow_array;
SECTION("empty array") {
// Create an empty view with a `value` column.
string view_name = "foo_empty_table";
// arrow array scan, destroy out_stream after use
ArrowArrayStream *arrow_array_stream;
auto out_stream = reinterpret_cast<duckdb_arrow_stream *>(&arrow_array_stream);
auto state = duckdb_arrow_array_scan(tester.connection, view_name.c_str(),
reinterpret_cast<duckdb_arrow_schema>(arrow_schema_ptr),
reinterpret_cast<duckdb_arrow_array>(arrow_array_ptr), out_stream);
REQUIRE(state == DuckDBSuccess);
// get the created view from the DB
auto get_query = "SELECT * FROM " + view_name + ";";
state = duckdb_prepare(tester.connection, get_query.c_str(), &stmt);
REQUIRE(state == DuckDBSuccess);
REQUIRE(stmt != nullptr);
state = duckdb_execute_prepared_arrow(stmt, &arrow_result);
REQUIRE(state == DuckDBSuccess);
// recover the arrow array from the arrow result
ArrowArray out_array;
out_array.Init();
auto out_array_ptr = &out_array;
state = duckdb_query_arrow_array(arrow_result, reinterpret_cast<duckdb_arrow_array *>(&out_array_ptr));
REQUIRE(state == DuckDBSuccess);
REQUIRE(out_array.length == 0);
REQUIRE(out_array.release == nullptr);
duckdb_destroy_arrow_stream(out_stream);
REQUIRE(arrow_array.release == nullptr);
}
SECTION("big array") {
// Create a view with a `value` column containing 4096 values.
int num_buffers = 2, size = STANDARD_VECTOR_SIZE * num_buffers;
unordered_map<idx_t, const duckdb::shared_ptr<ArrowTypeExtensionData>> extension_type_cast;
ArrowAppender appender(logical_types, size, options, extension_type_cast);
Allocator allocator;
auto data_chunks = std::vector<DataChunk>(num_buffers);
for (int i = 0; i < num_buffers; i++) {
auto data_chunk = &data_chunks[i];
data_chunk->Initialize(allocator, logical_types, STANDARD_VECTOR_SIZE);
data_chunk->SetCardinality(STANDARD_VECTOR_SIZE);
for (idx_t row = 0; row < STANDARD_VECTOR_SIZE; row++) {
data_chunk->SetValue(0, row, duckdb::Value(i));
}
appender.Append(*data_chunk, 0, data_chunk->size(), data_chunk->size());
}
*arrow_array_ptr = appender.Finalize();
// Create the view.
string view_name = "foo_table";
// arrow array scan, destroy out_stream after use
ArrowArrayStream *arrow_array_stream;
auto out_stream = reinterpret_cast<duckdb_arrow_stream *>(&arrow_array_stream);
auto state = duckdb_arrow_array_scan(tester.connection, view_name.c_str(),
reinterpret_cast<duckdb_arrow_schema>(arrow_schema_ptr),
reinterpret_cast<duckdb_arrow_array>(arrow_array_ptr), out_stream);
REQUIRE(state == DuckDBSuccess);
// Get the created view from the DB.
auto get_query = "SELECT * FROM " + view_name + ";";
state = duckdb_prepare(tester.connection, get_query.c_str(), &stmt);
REQUIRE(state == DuckDBSuccess);
REQUIRE(stmt != nullptr);
state = duckdb_execute_prepared_arrow(stmt, &arrow_result);
REQUIRE(state == DuckDBSuccess);
// Recover the arrow array from the arrow result.
ArrowArray out_array;
out_array.Init();
auto out_array_ptr = &out_array;
state = duckdb_query_arrow_array(arrow_result, reinterpret_cast<duckdb_arrow_array *>(&out_array_ptr));
REQUIRE(state == DuckDBSuccess);
REQUIRE(out_array.length == STANDARD_VECTOR_SIZE);
REQUIRE(out_array.release != nullptr);
out_array.release(out_array_ptr);
out_array.Init();
state = duckdb_query_arrow_array(arrow_result, reinterpret_cast<duckdb_arrow_array *>(&out_array_ptr));
REQUIRE(state == DuckDBSuccess);
REQUIRE(out_array.length == STANDARD_VECTOR_SIZE);
REQUIRE(out_array.release != nullptr);
out_array.release(out_array_ptr);
out_array.Init();
state = duckdb_query_arrow_array(arrow_result, reinterpret_cast<duckdb_arrow_array *>(&out_array_ptr));
REQUIRE(state == DuckDBSuccess);
REQUIRE(out_array.length == 0);
REQUIRE(out_array.release == nullptr);
duckdb_destroy_arrow_stream(out_stream);
REQUIRE(arrow_array.release != nullptr);
}
SECTION("null schema") {
// Creating a view with a null schema should fail gracefully.
string view_name = "foo_empty_table_null_schema";
// arrow array scan, destroy out_stream after use
ArrowArrayStream *arrow_array_stream;
auto out_stream = reinterpret_cast<duckdb_arrow_stream *>(&arrow_array_stream);
auto state = duckdb_arrow_array_scan(tester.connection, view_name.c_str(), nullptr,
reinterpret_cast<duckdb_arrow_array>(arrow_array_ptr), out_stream);
REQUIRE(state == DuckDBError);
duckdb_destroy_arrow_stream(out_stream);
}
if (arrow_schema.release) {
arrow_schema.release(arrow_schema_ptr);
}
if (arrow_array.release) {
arrow_array.release(arrow_array_ptr);
}
duckdb_destroy_arrow(&arrow_result);
duckdb_destroy_prepare(&stmt);
}
// FIXME: needs test for scanning a fixed size list
// this likely requires nanoarrow to create the array to scan
}
TEST_CASE("Test C-API Arrow conversion functions", "[capi][arrow]") {
CAPITester tester;
REQUIRE(tester.OpenDatabase(nullptr));
SECTION("roundtrip: duckdb table -> arrow -> duckdb chunk, validate correctness") {
// 1. Create and populate table
REQUIRE_NO_FAIL(tester.Query("CREATE TABLE big_table(i INTEGER);"));
REQUIRE_NO_FAIL(tester.Query("INSERT INTO big_table SELECT i FROM range(10000) tbl(i);"));
// 2. Query the table and fetch all results as data chunks
duckdb_result result;
REQUIRE(duckdb_query(tester.connection, "SELECT i FROM big_table ORDER BY i", &result) == DuckDBSuccess);
idx_t chunk_count = duckdb_result_chunk_count(result);
idx_t total_rows = 0;
std::vector<ArrowArray> arrow_arrays;
std::vector<duckdb_data_chunk> duckdb_chunks;
std::vector<int32_t> all_duckdb_values;
std::vector<int32_t> all_arrow_values;
// 3. For each chunk, convert to Arrow Array and collect values
for (idx_t chunk_idx = 0; chunk_idx < chunk_count; chunk_idx++) {
duckdb_data_chunk chunk = duckdb_result_get_chunk(result, chunk_idx);
duckdb_chunks.push_back(chunk); // for later roundtrip
idx_t chunk_size = duckdb_data_chunk_get_size(chunk);
total_rows += chunk_size;
auto vec = duckdb_data_chunk_get_vector(chunk, 0);
auto data = static_cast<int32_t *>(duckdb_vector_get_data(vec));
for (idx_t i = 0; i < chunk_size; i++) {
all_duckdb_values.push_back(data[i]);
}
ArrowArray duckdb_arrow_array;
duckdb_arrow_options arrow_options;
duckdb_connection_get_arrow_options(tester.connection, &arrow_options);
duckdb_error_data err = duckdb_data_chunk_to_arrow(arrow_options, chunk, &duckdb_arrow_array);
duckdb_destroy_arrow_options(&arrow_options);
REQUIRE(err == nullptr);
arrow_arrays.push_back(duckdb_arrow_array);
}
REQUIRE(total_rows == 10000);
REQUIRE(all_duckdb_values.size() == 10000);
// 4. Prepare Arrow schema for roundtrip
duckdb_logical_type type = duckdb_create_logical_type(DUCKDB_TYPE_INTEGER);
duckdb_logical_type types[1] = {type};
const char *names[1] = {strdup("i")};
ArrowSchemaWrapper arrow_schema_wrapper;
duckdb_arrow_options arrow_options;
duckdb_connection_get_arrow_options(tester.connection, &arrow_options);
duckdb_error_data err =
duckdb_to_arrow_schema(arrow_options, types, names, 1, &arrow_schema_wrapper.arrow_schema);
duckdb_destroy_arrow_options(&arrow_options);
REQUIRE(err == nullptr);
duckdb_arrow_converted_schema converted_schema = nullptr;
// Convert schema (simulate real use)
err = duckdb_schema_from_arrow(tester.connection, &arrow_schema_wrapper.arrow_schema, &converted_schema);
REQUIRE(err == nullptr);
// 5. For each Arrow array, convert back to DuckDB chunk and validate
for (size_t idx = 0, offset = 0; idx < arrow_arrays.size(); idx++) {
ArrowArray *duckdb_arrow_array = &arrow_arrays[idx];
// Prepare output chunk
duckdb_data_chunk out_chunk;
// Convert Arrow array to DuckDB chunk
err = duckdb_data_chunk_from_arrow(tester.connection, duckdb_arrow_array, converted_schema, &out_chunk);
REQUIRE(err == nullptr);
idx_t chunk_size = duckdb_data_chunk_get_size(out_chunk);
auto vec = duckdb_data_chunk_get_vector(out_chunk, 0);
auto data = static_cast<int32_t *>(duckdb_vector_get_data(vec));
for (idx_t i = 0; i < chunk_size; i++, offset++) {
REQUIRE(data[i] == all_duckdb_values[offset]);
all_arrow_values.push_back(data[i]);
}
duckdb_destroy_data_chunk(&out_chunk);
}
REQUIRE(all_arrow_values.size() == 10000);
REQUIRE(all_arrow_values == all_duckdb_values);
// 6. Cleanup
free((void *)names[0]);
duckdb_destroy_arrow_converted_schema(&converted_schema);
for (auto arrow_array : arrow_arrays) {
if (arrow_array.release) {
arrow_array.release(&arrow_array);
}
}
for (auto chunk : duckdb_chunks) {
duckdb_destroy_data_chunk(&chunk);
}
duckdb_destroy_logical_type(&type);
duckdb_destroy_result(&result);
}
SECTION("C-API Arrow Tess Null pointer inputs") {
duckdb_error_data err;
duckdb_logical_type type = duckdb_create_logical_type(DUCKDB_TYPE_INTEGER);
const char *names[1] = {strdup("i")};
// Test duckdb_to_arrow_schema
ArrowSchema duckdb_arrow_schema;
err = duckdb_to_arrow_schema(nullptr, &type, names, 1, &duckdb_arrow_schema);
duckdb_arrow_options arrow_options;
duckdb_connection_get_arrow_options(tester.connection, &arrow_options);
REQUIRE(err != nullptr);
duckdb_destroy_error_data(&err);
err = duckdb_to_arrow_schema(arrow_options, nullptr, names, 1, &duckdb_arrow_schema);
REQUIRE(err != nullptr);
duckdb_destroy_error_data(&err);
err = duckdb_to_arrow_schema(arrow_options, &type, nullptr, 1, &duckdb_arrow_schema);
REQUIRE(err != nullptr);
duckdb_destroy_error_data(&err);
// Zero columns
err = duckdb_to_arrow_schema(arrow_options, &type, names, 0, &duckdb_arrow_schema);
REQUIRE(err == nullptr); // zero columns is allowed, but produces an empty schema
if (duckdb_arrow_schema.release) {
duckdb_arrow_schema.release(&duckdb_arrow_schema);
}
duckdb_destroy_logical_type(&type);
// Test duckdb_data_chunk_to_arrow
ArrowArray duckdb_arrow_array;
duckdb_destroy_error_data(&err);
err = duckdb_data_chunk_to_arrow(arrow_options, nullptr, &duckdb_arrow_array);
REQUIRE(err != nullptr);
duckdb_destroy_error_data(&err);
err = duckdb_data_chunk_to_arrow(nullptr, nullptr, &duckdb_arrow_array);
REQUIRE(err != nullptr);
duckdb_destroy_error_data(&err);
// Test duckdb_schema_from_arrow
ArrowSchema schema;
duckdb_arrow_converted_schema converted_schema = nullptr;
err = duckdb_schema_from_arrow(nullptr, &schema, &converted_schema);
REQUIRE(err != nullptr);
duckdb_destroy_error_data(&err);
err = duckdb_schema_from_arrow(tester.connection, &schema, nullptr);
REQUIRE(err != nullptr);
duckdb_destroy_error_data(&err);
// Test duckdb_data_chunk_from_arrow
ArrowArray arr;
duckdb_data_chunk out_chunk = nullptr;
err = duckdb_data_chunk_from_arrow(nullptr, &arr, converted_schema, &out_chunk);
REQUIRE(err != nullptr);
duckdb_destroy_error_data(&err);
err = duckdb_data_chunk_from_arrow(tester.connection, &arr, nullptr, &out_chunk);
REQUIRE(err != nullptr);
duckdb_destroy_error_data(&err);
err = duckdb_data_chunk_from_arrow(tester.connection, &arr, converted_schema, nullptr);
REQUIRE(err != nullptr);
duckdb_destroy_error_data(&err);
duckdb_destroy_arrow_options(&arrow_options);
free((void *)names[0]);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,549 @@
#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
TEST_CASE("Test table_info incorrect 'is_valid' value for 'dflt_value' column", "[capi]") {
duckdb_database db;
duckdb_connection con;
duckdb_result result;
REQUIRE(duckdb_open(NULL, &db) != DuckDBError);
REQUIRE(duckdb_connect(db, &con) != DuckDBError);
//! Create a table with 40 columns
REQUIRE(duckdb_query(con,
"CREATE TABLE foo (c00 varchar, c01 varchar, c02 varchar, c03 varchar, c04 varchar, c05 "
"varchar, c06 varchar, c07 varchar, c08 varchar, c09 varchar, c10 varchar, c11 varchar, c12 "
"varchar, c13 varchar, c14 varchar, c15 varchar, c16 varchar, c17 varchar, c18 varchar, c19 "
"varchar, c20 varchar, c21 varchar, c22 varchar, c23 varchar, c24 varchar, c25 varchar, c26 "
"varchar, c27 varchar, c28 varchar, c29 varchar, c30 varchar, c31 varchar, c32 varchar, c33 "
"varchar, c34 varchar, c35 varchar, c36 varchar, c37 varchar, c38 varchar, c39 varchar);",
NULL) != DuckDBError);
//! Get table info for the created table
REQUIRE(duckdb_query(con, "PRAGMA table_info(foo);", &result) != DuckDBError);
//! Columns ({cid, name, type, notnull, dflt_value, pk}}
idx_t col_count = duckdb_column_count(&result);
REQUIRE(col_count == 6);
idx_t chunk_count = duckdb_result_chunk_count(result);
// Loop over the produced chunks
for (idx_t chunk_idx = 0; chunk_idx < chunk_count; chunk_idx++) {
duckdb_data_chunk chunk = duckdb_result_get_chunk(result, chunk_idx);
idx_t row_count = duckdb_data_chunk_get_size(chunk);
for (idx_t row_idx = 0; row_idx < row_count; row_idx++) {
for (idx_t col_idx = 0; col_idx < col_count; col_idx++) {
//! Get the column
duckdb_vector vector = duckdb_data_chunk_get_vector(chunk, col_idx);
uint64_t *validity = duckdb_vector_get_validity(vector);
bool is_valid = duckdb_validity_row_is_valid(validity, row_idx);
if (col_idx == 4) {
//'dflt_value' column
REQUIRE(is_valid == false);
}
}
}
duckdb_destroy_data_chunk(&chunk);
}
duckdb_destroy_result(&result);
duckdb_disconnect(&con);
duckdb_close(&db);
}
TEST_CASE("Test Logical Types C API", "[capi]") {
duckdb_logical_type type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
REQUIRE(type);
REQUIRE(duckdb_get_type_id(type) == DUCKDB_TYPE_BIGINT);
duckdb_destroy_logical_type(&type);
duckdb_destroy_logical_type(&type);
// list type
duckdb_logical_type elem_type = duckdb_create_logical_type(DUCKDB_TYPE_INTEGER);
duckdb_logical_type list_type = duckdb_create_list_type(elem_type);
REQUIRE(duckdb_get_type_id(list_type) == DUCKDB_TYPE_LIST);
duckdb_logical_type elem_type_dup = duckdb_list_type_child_type(list_type);
REQUIRE(elem_type_dup != elem_type);
REQUIRE(duckdb_get_type_id(elem_type_dup) == duckdb_get_type_id(elem_type));
duckdb_destroy_logical_type(&elem_type);
duckdb_destroy_logical_type(&list_type);
duckdb_destroy_logical_type(&elem_type_dup);
// map type
duckdb_logical_type key_type = duckdb_create_logical_type(DUCKDB_TYPE_SMALLINT);
duckdb_logical_type value_type = duckdb_create_logical_type(DUCKDB_TYPE_DOUBLE);
duckdb_logical_type map_type = duckdb_create_map_type(key_type, value_type);
REQUIRE(duckdb_get_type_id(map_type) == DUCKDB_TYPE_MAP);
duckdb_logical_type key_type_dup = duckdb_map_type_key_type(map_type);
duckdb_logical_type value_type_dup = duckdb_map_type_value_type(map_type);
REQUIRE(key_type_dup != key_type);
REQUIRE(value_type_dup != value_type);
REQUIRE(duckdb_get_type_id(key_type_dup) == duckdb_get_type_id(key_type));
REQUIRE(duckdb_get_type_id(value_type_dup) == duckdb_get_type_id(value_type));
duckdb_destroy_logical_type(&key_type);
duckdb_destroy_logical_type(&value_type);
duckdb_destroy_logical_type(&map_type);
duckdb_destroy_logical_type(&key_type_dup);
duckdb_destroy_logical_type(&value_type_dup);
duckdb_destroy_logical_type(nullptr);
}
TEST_CASE("Test DataChunk C API", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
duckdb_state status;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE(duckdb_vector_size() == STANDARD_VECTOR_SIZE);
// create column types
const idx_t COLUMN_COUNT = 3;
duckdb_type duckdbTypes[COLUMN_COUNT];
duckdbTypes[0] = DUCKDB_TYPE_BIGINT;
duckdbTypes[1] = DUCKDB_TYPE_SMALLINT;
duckdbTypes[2] = DUCKDB_TYPE_BLOB;
duckdb_logical_type types[COLUMN_COUNT];
for (idx_t i = 0; i < COLUMN_COUNT; i++) {
types[i] = duckdb_create_logical_type(duckdbTypes[i]);
}
// create data chunk
auto data_chunk = duckdb_create_data_chunk(types, COLUMN_COUNT);
REQUIRE(data_chunk);
REQUIRE(duckdb_data_chunk_get_column_count(data_chunk) == COLUMN_COUNT);
// test duckdb_vector_get_column_type
for (idx_t i = 0; i < COLUMN_COUNT; i++) {
auto vector = duckdb_data_chunk_get_vector(data_chunk, i);
auto type = duckdb_vector_get_column_type(vector);
REQUIRE(duckdb_get_type_id(type) == duckdbTypes[i]);
duckdb_destroy_logical_type(&type);
}
REQUIRE(duckdb_data_chunk_get_vector(data_chunk, 999) == nullptr);
REQUIRE(duckdb_data_chunk_get_vector(nullptr, 0) == nullptr);
REQUIRE(duckdb_vector_get_column_type(nullptr) == nullptr);
REQUIRE(duckdb_data_chunk_get_size(data_chunk) == 0);
REQUIRE(duckdb_data_chunk_get_size(nullptr) == 0);
// create table
tester.Query("CREATE TABLE test(i BIGINT, j SMALLINT, k BLOB)");
// use the appender to insert values using the data chunk API
duckdb_appender appender;
status = duckdb_appender_create(tester.connection, nullptr, "test", &appender);
REQUIRE(status == DuckDBSuccess);
// get the column types from the appender
REQUIRE(duckdb_appender_column_count(appender) == COLUMN_COUNT);
// test duckdb_appender_column_type
for (idx_t i = 0; i < COLUMN_COUNT; i++) {
auto type = duckdb_appender_column_type(appender, i);
REQUIRE(duckdb_get_type_id(type) == duckdbTypes[i]);
duckdb_destroy_logical_type(&type);
}
// append BIGINT
auto bigint_vector = duckdb_data_chunk_get_vector(data_chunk, 0);
auto int64_ptr = (int64_t *)duckdb_vector_get_data(bigint_vector);
*int64_ptr = 42;
// append SMALLINT
auto smallint_vector = duckdb_data_chunk_get_vector(data_chunk, 1);
auto int16_ptr = (int16_t *)duckdb_vector_get_data(smallint_vector);
*int16_ptr = 84;
// append BLOB
string s = "this is my blob";
auto blob_vector = duckdb_data_chunk_get_vector(data_chunk, 2);
duckdb_vector_assign_string_element_len(blob_vector, 0, s.c_str(), s.length());
REQUIRE(duckdb_vector_get_data(nullptr) == nullptr);
duckdb_data_chunk_set_size(data_chunk, 1);
REQUIRE(duckdb_data_chunk_get_size(data_chunk) == 1);
REQUIRE(duckdb_append_data_chunk(appender, data_chunk) == DuckDBSuccess);
REQUIRE(duckdb_append_data_chunk(appender, nullptr) == DuckDBError);
REQUIRE(duckdb_append_data_chunk(nullptr, data_chunk) == DuckDBError);
// append nulls
duckdb_data_chunk_reset(data_chunk);
REQUIRE(duckdb_data_chunk_get_size(data_chunk) == 0);
for (idx_t i = 0; i < COLUMN_COUNT; i++) {
auto vector = duckdb_data_chunk_get_vector(data_chunk, i);
duckdb_vector_ensure_validity_writable(vector);
auto validity = duckdb_vector_get_validity(vector);
REQUIRE(duckdb_validity_row_is_valid(validity, 0));
duckdb_validity_set_row_validity(validity, 0, false);
REQUIRE(!duckdb_validity_row_is_valid(validity, 0));
}
duckdb_data_chunk_set_size(data_chunk, 1);
REQUIRE(duckdb_data_chunk_get_size(data_chunk) == 1);
REQUIRE(duckdb_append_data_chunk(appender, data_chunk) == DuckDBSuccess);
REQUIRE(duckdb_vector_get_validity(nullptr) == nullptr);
duckdb_appender_destroy(&appender);
result = tester.Query("SELECT * FROM test");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->Fetch<int64_t>(0, 0) == 42);
REQUIRE(result->Fetch<int16_t>(1, 0) == 84);
REQUIRE(result->Fetch<string>(2, 0) == "this is my blob");
REQUIRE(result->IsNull(0, 1));
REQUIRE(result->IsNull(1, 1));
REQUIRE(result->IsNull(2, 1));
duckdb_data_chunk_reset(data_chunk);
duckdb_data_chunk_reset(nullptr);
REQUIRE(duckdb_data_chunk_get_size(data_chunk) == 0);
duckdb_destroy_data_chunk(&data_chunk);
duckdb_destroy_data_chunk(&data_chunk);
duckdb_destroy_data_chunk(nullptr);
for (idx_t i = 0; i < COLUMN_COUNT; i++) {
duckdb_destroy_logical_type(&types[i]);
}
}
TEST_CASE("Test DataChunk varchar result fetch in C API", "[capi]") {
if (duckdb_vector_size() < 64) {
return;
}
duckdb_database database;
duckdb_connection connection;
duckdb_state state;
state = duckdb_open(nullptr, &database);
REQUIRE(state == DuckDBSuccess);
state = duckdb_connect(database, &connection);
REQUIRE(state == DuckDBSuccess);
constexpr const char *VARCHAR_TEST_QUERY = "select case when i != 0 and i % 42 = 0 then NULL else repeat(chr((65 + "
"(i % 26))::INTEGER), (4 + (i % 12))) end from range(5000) tbl(i);";
// fetch a small result set
duckdb_result result;
state = duckdb_query(connection, VARCHAR_TEST_QUERY, &result);
REQUIRE(state == DuckDBSuccess);
REQUIRE(duckdb_column_count(&result) == 1);
REQUIRE(duckdb_row_count(&result) == 5000);
REQUIRE(duckdb_result_error(&result) == nullptr);
idx_t expected_chunk_count = (5000 / STANDARD_VECTOR_SIZE) + (5000 % STANDARD_VECTOR_SIZE != 0);
REQUIRE(duckdb_result_chunk_count(result) == expected_chunk_count);
auto chunk = duckdb_result_get_chunk(result, 0);
REQUIRE(duckdb_data_chunk_get_column_count(chunk) == 1);
REQUIRE(STANDARD_VECTOR_SIZE < 5000);
REQUIRE(duckdb_data_chunk_get_size(chunk) == STANDARD_VECTOR_SIZE);
duckdb_destroy_data_chunk(&chunk);
idx_t tuple_index = 0;
auto chunk_amount = duckdb_result_chunk_count(result);
for (idx_t chunk_index = 0; chunk_index < chunk_amount; chunk_index++) {
chunk = duckdb_result_get_chunk(result, chunk_index);
// Our result only has one column
auto vector = duckdb_data_chunk_get_vector(chunk, 0);
auto validity = duckdb_vector_get_validity(vector);
auto string_data = (duckdb_string_t *)duckdb_vector_get_data(vector);
auto tuples_in_chunk = duckdb_data_chunk_get_size(chunk);
for (idx_t i = 0; i < tuples_in_chunk; i++, tuple_index++) {
if (!duckdb_validity_row_is_valid(validity, i)) {
// This entry is NULL
REQUIRE((tuple_index != 0 && tuple_index % 42 == 0));
continue;
}
idx_t expected_length = (tuple_index % 12) + 4;
char expected_character = (tuple_index % 26) + 'A';
// TODO: how does the c-api handle non-flat vectors?
auto tuple = string_data[i];
auto length = tuple.value.inlined.length;
REQUIRE(length == expected_length);
if (duckdb_string_is_inlined(tuple)) {
// The data is small enough to fit in the string_t, it does not have a separate allocation
for (idx_t string_index = 0; string_index < length; string_index++) {
REQUIRE(tuple.value.inlined.inlined[string_index] == expected_character);
}
} else {
for (idx_t string_index = 0; string_index < length; string_index++) {
REQUIRE(tuple.value.pointer.ptr[string_index] == expected_character);
}
}
}
duckdb_destroy_data_chunk(&chunk);
}
duckdb_destroy_result(&result);
duckdb_disconnect(&connection);
duckdb_close(&database);
}
TEST_CASE("Test DataChunk result fetch in C API", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
if (duckdb_vector_size() < 64) {
return;
}
REQUIRE(tester.OpenDatabase(nullptr));
// fetch a small result set
result = tester.Query("SELECT CASE WHEN i=1 THEN NULL ELSE i::INTEGER END i FROM range(3) tbl(i)");
REQUIRE(NO_FAIL(*result));
REQUIRE(result->ColumnCount() == 1);
REQUIRE(result->row_count() == 3);
REQUIRE(result->ErrorMessage() == nullptr);
// fetch the first chunk
REQUIRE(result->ChunkCount() == 1);
auto chunk = result->FetchChunk(0);
REQUIRE(chunk);
REQUIRE(chunk->ColumnCount() == 1);
REQUIRE(chunk->size() == 3);
auto data = (int32_t *)chunk->GetData(0);
auto validity = chunk->GetValidity(0);
REQUIRE(data[0] == 0);
REQUIRE(data[2] == 2);
REQUIRE(duckdb_validity_row_is_valid(validity, 0));
REQUIRE(!duckdb_validity_row_is_valid(validity, 1));
REQUIRE(duckdb_validity_row_is_valid(validity, 2));
// after fetching a chunk, we cannot use the old API anymore
REQUIRE(result->ColumnData<int32_t>(0) == nullptr);
REQUIRE(result->Fetch<int32_t>(0, 1) == 0);
// result set is exhausted!
chunk = result->FetchChunk(1);
REQUIRE(!chunk);
}
TEST_CASE("Test duckdb_result_return_type", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
result = tester.Query("CREATE TABLE t (id INT)");
REQUIRE(duckdb_result_return_type(result->InternalResult()) == DUCKDB_RESULT_TYPE_NOTHING);
result = tester.Query("INSERT INTO t VALUES (42)");
REQUIRE(duckdb_result_return_type(result->InternalResult()) == DUCKDB_RESULT_TYPE_CHANGED_ROWS);
result = tester.Query("FROM t");
REQUIRE(duckdb_result_return_type(result->InternalResult()) == DUCKDB_RESULT_TYPE_QUERY_RESULT);
}
TEST_CASE("Test DataChunk populate ListVector in C API", "[capi]") {
if (duckdb_vector_size() < 3) {
return;
}
REQUIRE(duckdb_list_vector_reserve(nullptr, 100) == duckdb_state::DuckDBError);
REQUIRE(duckdb_list_vector_set_size(nullptr, 200) == duckdb_state::DuckDBError);
auto elem_type = duckdb_create_logical_type(duckdb_type::DUCKDB_TYPE_INTEGER);
auto list_type = duckdb_create_list_type(elem_type);
duckdb_logical_type schema[] = {list_type};
auto chunk = duckdb_create_data_chunk(schema, 1);
auto list_vector = duckdb_data_chunk_get_vector(chunk, 0);
duckdb_data_chunk_set_size(chunk, 3);
REQUIRE(duckdb_list_vector_reserve(list_vector, 123) == duckdb_state::DuckDBSuccess);
REQUIRE(duckdb_list_vector_get_size(list_vector) == 0);
auto child = duckdb_list_vector_get_child(list_vector);
for (int i = 0; i < 123; i++) {
((int *)duckdb_vector_get_data(child))[i] = i;
}
REQUIRE(duckdb_list_vector_set_size(list_vector, 123) == duckdb_state::DuckDBSuccess);
REQUIRE(duckdb_list_vector_get_size(list_vector) == 123);
auto entries = (duckdb_list_entry *)duckdb_vector_get_data(list_vector);
entries[0].offset = 0;
entries[0].length = 20;
entries[1].offset = 20;
entries[1].length = 80;
entries[2].offset = 100;
entries[2].length = 23;
auto child_data = (int *)duckdb_vector_get_data(child);
int count = 0;
for (idx_t i = 0; i < duckdb_data_chunk_get_size(chunk); i++) {
for (idx_t j = 0; j < entries[i].length; j++) {
REQUIRE(child_data[entries[i].offset + j] == count);
count++;
}
}
auto &vector = (Vector &)(*list_vector);
for (int i = 0; i < 123; i++) {
REQUIRE(ListVector::GetEntry(vector).GetValue(i) == i);
}
duckdb_destroy_data_chunk(&chunk);
duckdb_destroy_logical_type(&list_type);
duckdb_destroy_logical_type(&elem_type);
}
TEST_CASE("Test DataChunk populate ArrayVector in C API", "[capi]") {
auto elem_type = duckdb_create_logical_type(duckdb_type::DUCKDB_TYPE_INTEGER);
auto array_type = duckdb_create_array_type(elem_type, 3);
duckdb_logical_type schema[] = {array_type};
auto chunk = duckdb_create_data_chunk(schema, 1);
duckdb_data_chunk_set_size(chunk, 2);
auto array_vector = duckdb_data_chunk_get_vector(chunk, 0);
auto child = duckdb_array_vector_get_child(array_vector);
for (int i = 0; i < 6; i++) {
((int *)duckdb_vector_get_data(child))[i] = i;
}
auto vec = (Vector &)(*array_vector);
for (int i = 0; i < 2; i++) {
auto child_vals = ArrayValue::GetChildren(vec.GetValue(i));
for (int j = 0; j < 3; j++) {
REQUIRE(child_vals[j].GetValue<int>() == i * 3 + j);
}
}
duckdb_destroy_data_chunk(&chunk);
duckdb_destroy_logical_type(&array_type);
duckdb_destroy_logical_type(&elem_type);
}
TEST_CASE("Test PK violation in the C API appender", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE(duckdb_vector_size() == STANDARD_VECTOR_SIZE);
// Create column types.
const idx_t COLUMN_COUNT = 1;
duckdb_logical_type types[COLUMN_COUNT];
types[0] = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
// Create data chunk.
auto data_chunk = duckdb_create_data_chunk(types, COLUMN_COUNT);
auto bigint_vector = duckdb_data_chunk_get_vector(data_chunk, 0);
auto int64_ptr = reinterpret_cast<int64_t *>(duckdb_vector_get_data(bigint_vector));
int64_ptr[0] = 42;
int64_ptr[1] = 42;
duckdb_data_chunk_set_size(data_chunk, 2);
// Use the appender to append the data chunk.
tester.Query("CREATE TABLE test(i BIGINT PRIMARY KEY)");
duckdb_appender appender;
REQUIRE(duckdb_appender_create(tester.connection, nullptr, "test", &appender) == DuckDBSuccess);
// We only flush when destroying the appender. Thus, we expect this to succeed, as we only
// detect constraint violations when flushing the results.
REQUIRE(duckdb_append_data_chunk(appender, data_chunk) == DuckDBSuccess);
// duckdb_appender_close attempts to flush the data and fails.
auto state = duckdb_appender_close(appender);
REQUIRE(state == DuckDBError);
auto error = duckdb_appender_error(appender);
REQUIRE(duckdb::StringUtil::Contains(error, "PRIMARY KEY or UNIQUE constraint violation"));
// Destroy the appender despite the error to avoid leaks.
state = duckdb_appender_destroy(&appender);
REQUIRE(state == DuckDBError);
// Clean-up.
duckdb_destroy_data_chunk(&data_chunk);
for (idx_t i = 0; i < COLUMN_COUNT; i++) {
duckdb_destroy_logical_type(&types[i]);
}
// Ensure that no rows were appended.
result = tester.Query("SELECT * FROM test;");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->row_count() == 0);
// Try again by appending rows and flushing.
REQUIRE(duckdb_appender_create(tester.connection, nullptr, "test", &appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_begin_row(appender) == DuckDBSuccess);
REQUIRE(duckdb_append_int64(appender, 42) == DuckDBSuccess);
REQUIRE(duckdb_appender_end_row(appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_begin_row(appender) == DuckDBSuccess);
REQUIRE(duckdb_append_int64(appender, 42) == DuckDBSuccess);
REQUIRE(duckdb_appender_end_row(appender) == DuckDBSuccess);
state = duckdb_appender_flush(appender);
REQUIRE(state == DuckDBError);
error = duckdb_appender_error(appender);
REQUIRE(duckdb::StringUtil::Contains(error, "PRIMARY KEY or UNIQUE constraint violation"));
REQUIRE(duckdb_appender_destroy(&appender) == DuckDBError);
// Ensure that only the last row was appended.
result = tester.Query("SELECT * FROM test;");
REQUIRE_NO_FAIL(*result);
REQUIRE(result->row_count() == 0);
}
TEST_CASE("Test DataChunk write BLOB", "[capi]") {
duckdb_logical_type type = duckdb_create_logical_type(DUCKDB_TYPE_BLOB);
REQUIRE(type);
REQUIRE(duckdb_get_type_id(type) == DUCKDB_TYPE_BLOB);
duckdb_logical_type types[] = {type};
auto chunk = duckdb_create_data_chunk(types, 1);
duckdb_data_chunk_set_size(chunk, 1);
duckdb_vector vector = duckdb_data_chunk_get_vector(chunk, 0);
auto column_type = duckdb_vector_get_column_type(vector);
REQUIRE(duckdb_get_type_id(column_type) == DUCKDB_TYPE_BLOB);
duckdb_destroy_logical_type(&column_type);
uint8_t bytes[] = {0x80, 0x00, 0x01, 0x2a};
duckdb_vector_assign_string_element_len(vector, 0, (const char *)bytes, 4);
auto string_data = static_cast<duckdb_string_t *>(duckdb_vector_get_data(vector));
auto string_value = duckdb_string_t_data(string_data);
REQUIRE(duckdb_string_t_length(*string_data) == 4);
REQUIRE(string_value[0] == (char)0x80);
REQUIRE(string_value[1] == (char)0x00);
REQUIRE(string_value[2] == (char)0x01);
REQUIRE(string_value[3] == (char)0x2a);
duckdb_destroy_data_chunk(&chunk);
duckdb_destroy_logical_type(&type);
}
TEST_CASE("Test DataChunk write BIGNUM", "[capi]") {
duckdb_logical_type type = duckdb_create_logical_type(DUCKDB_TYPE_BIGNUM);
REQUIRE(type);
REQUIRE(duckdb_get_type_id(type) == DUCKDB_TYPE_BIGNUM);
duckdb_logical_type types[] = {type};
auto chunk = duckdb_create_data_chunk(types, 1);
duckdb_data_chunk_set_size(chunk, 1);
duckdb_vector vector = duckdb_data_chunk_get_vector(chunk, 0);
auto column_type = duckdb_vector_get_column_type(vector);
REQUIRE(duckdb_get_type_id(column_type) == DUCKDB_TYPE_BIGNUM);
duckdb_destroy_logical_type(&column_type);
uint8_t bytes[] = {0x80, 0x00, 0x01, 0x2a}; // BIGNUM 42
duckdb_vector_assign_string_element_len(vector, 0, (const char *)bytes, 4);
auto string_data = static_cast<duckdb_string_t *>(duckdb_vector_get_data(vector));
auto string_value = duckdb_string_t_data(string_data);
REQUIRE(duckdb_string_t_length(*string_data) == 4);
REQUIRE(string_value[0] == (char)0x80);
REQUIRE(string_value[1] == (char)0x00);
REQUIRE(string_value[2] == (char)0x01);
REQUIRE(string_value[3] == (char)0x2a);
duckdb_destroy_data_chunk(&chunk);
duckdb_destroy_logical_type(&type);
}

View File

@@ -0,0 +1,86 @@
#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
TEST_CASE("Test extract statements in C API", "[capi]") {
CAPITester tester;
duckdb_result res;
duckdb_extracted_statements stmts = nullptr;
duckdb_state status;
const char *error;
duckdb_prepared_statement prepared = nullptr;
REQUIRE(tester.OpenDatabase(nullptr));
idx_t size = duckdb_extract_statements(tester.connection,
"CREATE TABLE tbl (col INT); INSERT INTO tbl VALUES (1), (2), (3), (4); "
"SELECT COUNT(col) FROM tbl WHERE col > $1",
&stmts);
REQUIRE(size == 3);
REQUIRE(stmts != nullptr);
for (idx_t i = 0; i + 1 < size; i++) {
status = duckdb_prepare_extracted_statement(tester.connection, stmts, i, &prepared);
REQUIRE(status == DuckDBSuccess);
status = duckdb_execute_prepared(prepared, &res);
REQUIRE(status == DuckDBSuccess);
duckdb_destroy_prepare(&prepared);
duckdb_destroy_result(&res);
}
duckdb_prepared_statement stmt = nullptr;
status = duckdb_prepare_extracted_statement(tester.connection, stmts, size - 1, &stmt);
REQUIRE(status == DuckDBSuccess);
duckdb_bind_int32(stmt, 1, 1);
status = duckdb_execute_prepared(stmt, &res);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_value_int64(&res, 0, 0) == 3);
duckdb_destroy_prepare(&stmt);
duckdb_destroy_result(&res);
duckdb_destroy_extracted(&stmts);
// test empty statement is not an error
size = duckdb_extract_statements(tester.connection, "", &stmts);
REQUIRE(size == 0);
error = duckdb_extract_statements_error(stmts);
REQUIRE(error == nullptr);
duckdb_destroy_extracted(&stmts);
// test incorrect statement cannot be extracted
size = duckdb_extract_statements(tester.connection, "This is not valid SQL", &stmts);
REQUIRE(size == 0);
error = duckdb_extract_statements_error(stmts);
REQUIRE(error != nullptr);
duckdb_destroy_extracted(&stmts);
// test out of bounds
size = duckdb_extract_statements(tester.connection, "SELECT CAST($1 AS BIGINT)", &stmts);
REQUIRE(size == 1);
status = duckdb_prepare_extracted_statement(tester.connection, stmts, 2, &prepared);
REQUIRE(status == DuckDBError);
duckdb_destroy_extracted(&stmts);
}
TEST_CASE("Test invalid PRAGMA in C API", "[capi]") {
duckdb_database db;
duckdb_connection con;
const char *err_msg;
REQUIRE(duckdb_open(nullptr, &db) == DuckDBSuccess);
REQUIRE(duckdb_connect(db, &con) == DuckDBSuccess);
duckdb_extracted_statements stmts;
auto size = duckdb_extract_statements(con, "PRAGMA something;", &stmts);
REQUIRE(size == 0);
err_msg = duckdb_extract_statements_error(stmts);
REQUIRE(err_msg != nullptr);
REQUIRE(string(err_msg).find("Catalog Error") != std::string::npos);
duckdb_destroy_extracted(&stmts);
duckdb_disconnect(&con);
duckdb_close(&db);
}

View File

@@ -0,0 +1,67 @@
#include "capi_tester.hpp"
#include "duckdb.h"
#include <chrono>
#include <thread>
using namespace duckdb;
using namespace std;
static void background_thread_connect(duckdb_instance_cache instance_cache, const char *path) {
try {
duckdb_database out_database;
auto state = duckdb_get_or_create_from_cache(instance_cache, path, &out_database, nullptr, nullptr);
REQUIRE(state == DuckDBSuccess);
duckdb_close(&out_database);
} catch (std::exception &ex) {
FAIL(ex.what());
}
}
TEST_CASE("Test the database instance cache in the C API", "[api][.]") {
auto instance_cache = duckdb_create_instance_cache();
for (idx_t i = 0; i < 30; i++) {
auto path = TestCreatePath("shared_db.db");
duckdb_database shared_out_database;
auto state =
duckdb_get_or_create_from_cache(instance_cache, path.c_str(), &shared_out_database, nullptr, nullptr);
REQUIRE(state == DuckDBSuccess);
thread background_thread(background_thread_connect, instance_cache, path.c_str());
duckdb_close(&shared_out_database);
background_thread.join();
TestDeleteFile(path);
REQUIRE(1);
}
duckdb_destroy_instance_cache(&instance_cache);
}
TEST_CASE("Test the database instance cache in the C API with a null path", "[capi]") {
auto instance_cache = duckdb_create_instance_cache();
duckdb_database db;
auto state = duckdb_get_or_create_from_cache(instance_cache, nullptr, &db, nullptr, nullptr);
REQUIRE(state == DuckDBSuccess);
duckdb_close(&db);
duckdb_destroy_instance_cache(&instance_cache);
}
TEST_CASE("Test the database instance cache in the C API with an empty path", "[capi]") {
auto instance_cache = duckdb_create_instance_cache();
duckdb_database db;
auto state = duckdb_get_or_create_from_cache(instance_cache, "", &db, nullptr, nullptr);
REQUIRE(state == DuckDBSuccess);
duckdb_close(&db);
duckdb_destroy_instance_cache(&instance_cache);
}
TEST_CASE("Test the database instance cache in the C API with a memory path", "[capi]") {
auto instance_cache = duckdb_create_instance_cache();
duckdb_database db;
auto state = duckdb_get_or_create_from_cache(instance_cache, ":memory:", &db, nullptr, nullptr);
REQUIRE(state == DuckDBSuccess);
duckdb_close(&db);
duckdb_destroy_instance_cache(&instance_cache);
}

View File

@@ -0,0 +1,30 @@
#include "capi_tester.hpp"
#include "duckdb.h"
using namespace duckdb;
using namespace std;
TEST_CASE("Test pending statements in C API", "[capi]") {
CAPITester tester;
CAPIPrepared prepared;
CAPIPending pending;
duckdb::unique_ptr<CAPIResult> result;
// open the database in in-memory mode
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE(prepared.Prepare(tester, "SELECT SUM(i) FROM range(1000000) tbl(i)"));
REQUIRE(pending.Pending(prepared));
while (true) {
auto state = pending.ExecuteTask();
REQUIRE(state != DUCKDB_PENDING_ERROR);
if (duckdb_pending_execution_is_finished(state)) {
break;
}
}
result = pending.Execute();
REQUIRE(result);
REQUIRE(!result->HasError());
REQUIRE(result->Fetch<int64_t>(0, 0) == 499999500000LL);
}

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);
}

View File

@@ -0,0 +1,416 @@
#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
string BuildSettingsString(const duckdb::vector<string> &settings) {
string result = "'{";
for (idx_t i = 0; i < settings.size(); i++) {
result += "\"" + settings[i] + "\": \"true\"";
if (i < settings.size() - 1) {
result += ", ";
}
}
result += "}'";
return result;
}
void RetrieveMetrics(duckdb_profiling_info info, duckdb::map<string, double> &cumulative_counter,
duckdb::map<string, double> &cumulative_result, const idx_t depth) {
auto map = duckdb_profiling_info_get_metrics(info);
REQUIRE(map);
auto count = duckdb_get_map_size(map);
REQUIRE(count != 0);
// Test index out of bounds for MAP value.
if (depth == 0) {
auto invalid_key = duckdb_get_map_key(map, 10000000);
REQUIRE(!invalid_key);
auto invalid_value = duckdb_get_map_value(map, 10000000);
REQUIRE(!invalid_value);
}
for (idx_t i = 0; i < count; i++) {
auto key = duckdb_get_map_key(map, i);
REQUIRE(key);
auto value = duckdb_get_map_value(map, i);
REQUIRE(value);
auto key_c_str = duckdb_get_varchar(key);
auto value_c_str = duckdb_get_varchar(value);
auto key_str = duckdb::string(key_c_str);
auto value_str = duckdb::string(value_c_str);
if (depth == 0) {
REQUIRE(key_str != EnumUtil::ToString(MetricsType::OPERATOR_CARDINALITY));
REQUIRE(key_str != EnumUtil::ToString(MetricsType::OPERATOR_ROWS_SCANNED));
REQUIRE(key_str != EnumUtil::ToString(MetricsType::OPERATOR_TIMING));
REQUIRE(key_str != EnumUtil::ToString(MetricsType::OPERATOR_NAME));
REQUIRE(key_str != EnumUtil::ToString(MetricsType::OPERATOR_TYPE));
} else {
REQUIRE(key_str != EnumUtil::ToString(MetricsType::QUERY_NAME));
REQUIRE(key_str != EnumUtil::ToString(MetricsType::BLOCKED_THREAD_TIME));
REQUIRE(key_str != EnumUtil::ToString(MetricsType::LATENCY));
REQUIRE(key_str != EnumUtil::ToString(MetricsType::ROWS_RETURNED));
}
if (key_str == EnumUtil::ToString(MetricsType::QUERY_NAME) ||
key_str == EnumUtil::ToString(MetricsType::OPERATOR_NAME) ||
key_str == EnumUtil::ToString(MetricsType::OPERATOR_TYPE) ||
key_str == EnumUtil::ToString(MetricsType::EXTRA_INFO)) {
REQUIRE(!value_str.empty());
} else {
double result = 0;
try {
result = std::stod(value_str);
} catch (std::invalid_argument &e) {
REQUIRE(false);
}
if (cumulative_counter.find(key_str) != cumulative_counter.end()) {
cumulative_counter[key_str] += result;
}
if (cumulative_result.find(key_str) != cumulative_result.end() && cumulative_result[key_str] == 0) {
cumulative_result[key_str] = result;
}
}
duckdb_destroy_value(&key);
duckdb_destroy_value(&value);
duckdb_free(key_c_str);
duckdb_free(value_c_str);
}
duckdb_destroy_value(&map);
}
void TraverseTree(duckdb_profiling_info profiling_info, duckdb::map<string, double> &cumulative_counter,
duckdb::map<string, double> &cumulative_result, const idx_t depth) {
RetrieveMetrics(profiling_info, cumulative_counter, cumulative_result, depth);
// Recurse into the child node.
auto child_count = duckdb_profiling_info_get_child_count(profiling_info);
if (depth == 0) {
REQUIRE(child_count != 0);
}
for (idx_t i = 0; i < child_count; i++) {
auto child = duckdb_profiling_info_get_child(profiling_info, i);
TraverseTree(child, cumulative_counter, cumulative_result, depth + 1);
}
}
idx_t ConvertToInt(double value) {
return idx_t(value * 1000);
}
TEST_CASE("Test profiling with a single metric and get_value", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
// test only CPU_TIME profiling
duckdb::vector<string> settings = {"CPU_TIME"};
REQUIRE_NO_FAIL(tester.Query("PRAGMA custom_profiling_settings=" + BuildSettingsString(settings)));
REQUIRE_NO_FAIL(tester.Query("SELECT 42"));
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info != nullptr);
// Retrieve a metric that is not enabled.
REQUIRE(duckdb_profiling_info_get_value(info, "EXTRA_INFO") == nullptr);
duckdb::map<string, double> cumulative_counter;
duckdb::map<string, double> cumulative_result;
TraverseTree(info, cumulative_counter, cumulative_result, 0);
tester.Cleanup();
}
TEST_CASE("Test profiling with cumulative metrics", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
// test all profiling metrics
duckdb::vector<string> settings = {"BLOCKED_THREAD_TIME", "CPU_TIME", "CUMULATIVE_CARDINALITY", "EXTRA_INFO",
"OPERATOR_CARDINALITY", "OPERATOR_TIMING"};
REQUIRE_NO_FAIL(tester.Query("PRAGMA custom_profiling_settings=" + BuildSettingsString(settings)));
REQUIRE_NO_FAIL(tester.Query("SELECT 42"));
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info != nullptr);
duckdb::map<string, double> cumulative_counter = {{"OPERATOR_TIMING", 0}, {"OPERATOR_CARDINALITY", 0}};
duckdb::map<string, double> cumulative_result {
{"CPU_TIME", 0},
{"CUMULATIVE_CARDINALITY", 0},
};
TraverseTree(info, cumulative_counter, cumulative_result, 0);
REQUIRE(ConvertToInt(cumulative_result["CPU_TIME"]) == ConvertToInt(cumulative_counter["OPERATOR_TIMING"]));
REQUIRE(ConvertToInt(cumulative_result["CUMULATIVE_CARDINALITY"]) ==
ConvertToInt(cumulative_counter["OPERATOR_CARDINALITY"]));
tester.Cleanup();
}
TEST_CASE("Test profiling without profiling enabled", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
// Retrieve info without profiling enabled.
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info == nullptr);
tester.Cleanup();
}
TEST_CASE("Test profiling with detailed profiling mode enabled", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
REQUIRE_NO_FAIL(tester.Query("PRAGMA profiling_mode = 'detailed'"));
REQUIRE_NO_FAIL(tester.Query("SELECT 42"));
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info != nullptr);
duckdb::map<string, double> cumulative_counter;
duckdb::map<string, double> cumulative_result;
TraverseTree(info, cumulative_counter, cumulative_result, 0);
tester.Cleanup();
}
TEST_CASE("Test invalid use of profiling API", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
REQUIRE_NO_FAIL(tester.Query("PRAGMA profiling_mode = 'detailed'"));
REQUIRE_NO_FAIL(tester.Query("SELECT 42"));
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info != nullptr);
// Incorrect usage tests.
auto map = duckdb_profiling_info_get_metrics(nullptr);
REQUIRE(map == nullptr);
map = duckdb_profiling_info_get_metrics(info);
auto dummy_value = duckdb_create_bool(true);
auto count = duckdb_get_map_size(nullptr);
REQUIRE(count == 0);
count = duckdb_get_map_size(dummy_value);
REQUIRE(count == 0);
count = duckdb_get_map_size(map);
for (idx_t i = 0; i < count; i++) {
auto key = duckdb_get_map_key(nullptr, i);
REQUIRE(key == nullptr);
key = duckdb_get_map_key(map, DConstants::INVALID_INDEX);
REQUIRE(key == nullptr);
key = duckdb_get_map_key(dummy_value, i);
REQUIRE(key == nullptr);
auto value = duckdb_get_map_value(nullptr, i);
REQUIRE(value == nullptr);
value = duckdb_get_map_value(map, DConstants::INVALID_INDEX);
REQUIRE(value == nullptr);
value = duckdb_get_map_value(dummy_value, i);
REQUIRE(value == nullptr);
break;
}
duckdb_destroy_value(&dummy_value);
duckdb_destroy_value(&map);
tester.Cleanup();
}
TEST_CASE("Test profiling after throwing an error", "[capi]") {
CAPITester tester;
auto main_db = TestCreatePath("profiling_error.db");
REQUIRE(tester.OpenDatabase(main_db.c_str()));
auto path = TestCreatePath("profiling_error.db");
REQUIRE_NO_FAIL(tester.Query("ATTACH IF NOT EXISTS '" + path + "' (TYPE DUCKDB)"));
REQUIRE_NO_FAIL(tester.Query("CREATE TABLE profiling_error.tbl AS SELECT range AS id FROM range(10)"));
REQUIRE_NO_FAIL(tester.Query("SET enable_profiling = 'no_output'"));
REQUIRE_NO_FAIL(tester.Query("SET profiling_mode = 'standard'"));
CAPIPrepared prepared_q1;
CAPIPending pending_q1;
REQUIRE(prepared_q1.Prepare(tester, "SELECT * FROM profiling_error.tbl"));
REQUIRE(pending_q1.Pending(prepared_q1));
auto result = pending_q1.Execute();
REQUIRE(result);
REQUIRE(!result->HasError());
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info != nullptr);
CAPIPrepared prepared_q2;
REQUIRE(!prepared_q2.Prepare(tester, "SELECT * FROM profiling_error.does_not_exist"));
info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info == nullptr);
tester.Cleanup();
}
TEST_CASE("Test profiling with Extra Info enabled", "[capi]") {
CAPITester tester;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
duckdb::vector<string> settings = {"EXTRA_INFO"};
REQUIRE_NO_FAIL(tester.Query("PRAGMA custom_profiling_settings=" + BuildSettingsString(settings)));
REQUIRE_NO_FAIL(tester.Query("SELECT 1"));
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info);
// Retrieve the child node.
auto child_info = duckdb_profiling_info_get_child(info, 0);
REQUIRE(duckdb_profiling_info_get_child_count(child_info) != 0);
auto map = duckdb_profiling_info_get_metrics(child_info);
REQUIRE(map);
auto count = duckdb_get_map_size(map);
REQUIRE(count != 0);
bool found_extra_info = false;
for (idx_t i = 0; i < count; i++) {
auto key = duckdb_get_map_key(map, i);
REQUIRE(key);
auto key_c_str = duckdb_get_varchar(key);
auto key_str = duckdb::string(key_c_str);
auto value = duckdb_get_map_value(map, i);
REQUIRE(value);
auto value_c_str = duckdb_get_varchar(value);
auto value_str = duckdb::string(value_c_str);
if (key_str == EnumUtil::ToString(MetricsType::EXTRA_INFO)) {
REQUIRE(value_str.find("__order_by__"));
REQUIRE(value_str.find("ASC"));
found_extra_info = true;
}
if (key) {
duckdb_destroy_value(&key);
duckdb_free(key_c_str);
}
if (value) {
duckdb_destroy_value(&value);
duckdb_free(value_c_str);
}
}
REQUIRE(found_extra_info);
duckdb_destroy_value(&map);
tester.Cleanup();
}
TEST_CASE("Test profiling with the appender", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
tester.Query("CREATE TABLE tbl (i INT PRIMARY KEY, value VARCHAR)");
REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
REQUIRE_NO_FAIL(tester.Query("SET profiling_coverage='ALL'"));
duckdb_appender appender;
string query = "INSERT INTO tbl FROM my_appended_data";
duckdb_logical_type types[2];
types[0] = duckdb_create_logical_type(DUCKDB_TYPE_INTEGER);
types[1] = duckdb_create_logical_type(DUCKDB_TYPE_VARCHAR);
auto status = duckdb_appender_create_query(tester.connection, query.c_str(), 2, types, "my_appended_data", nullptr,
&appender);
duckdb_destroy_logical_type(&types[0]);
duckdb_destroy_logical_type(&types[1]);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_appender_error(appender) == nullptr);
REQUIRE(duckdb_appender_begin_row(appender) == DuckDBSuccess);
REQUIRE(duckdb_append_int32(appender, 1) == DuckDBSuccess);
REQUIRE(duckdb_append_varchar(appender, "hello world") == DuckDBSuccess);
REQUIRE(duckdb_appender_end_row(appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_flush(appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_close(appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_destroy(&appender) == DuckDBSuccess);
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info);
// Check that the query name matches the appender query.
auto query_name = duckdb_profiling_info_get_value(info, "QUERY_NAME");
REQUIRE(query_name);
auto query_name_c_str = duckdb_get_varchar(query_name);
auto query_name_str = duckdb::string(query_name_c_str);
REQUIRE(query_name_str == query);
duckdb_destroy_value(&query_name);
duckdb_free(query_name_c_str);
duckdb::map<string, double> cumulative_counter;
duckdb::map<string, double> cumulative_result;
TraverseTree(info, cumulative_counter, cumulative_result, 0);
tester.Cleanup();
}
TEST_CASE("Test profiling with the non-query appender", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
duckdb_state status;
REQUIRE(tester.OpenDatabase(nullptr));
tester.Query("CREATE TABLE test (i INTEGER)");
REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
REQUIRE_NO_FAIL(tester.Query("SET profiling_coverage='ALL'"));
duckdb_appender appender;
REQUIRE(duckdb_appender_create(tester.connection, nullptr, "test", &appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_error(appender) == nullptr);
// Appending a row.
REQUIRE(duckdb_appender_begin_row(appender) == DuckDBSuccess);
REQUIRE(duckdb_append_int32(appender, 42) == DuckDBSuccess);
// Finish and flush.
REQUIRE(duckdb_appender_end_row(appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_flush(appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_close(appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_destroy(&appender) == DuckDBSuccess);
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info);
// Check that the query name matches the appender query.
auto query_name = duckdb_profiling_info_get_value(info, "QUERY_NAME");
REQUIRE(query_name);
auto query_name_c_str = duckdb_get_varchar(query_name);
auto query_name_str = duckdb::string(query_name_c_str);
auto query = "INSERT INTO main.test FROM __duckdb_internal_appended_data";
REQUIRE(query_name_str == query);
duckdb_destroy_value(&query_name);
duckdb_free(query_name_c_str);
duckdb::map<string, double> cumulative_counter;
duckdb::map<string, double> cumulative_result;
TraverseTree(info, cumulative_counter, cumulative_result, 0);
tester.Cleanup();
}

View File

@@ -0,0 +1,81 @@
#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
struct MyBaseNumber {
int number;
};
void destroy_base_number(void *data) {
auto num = (MyBaseNumber *)data;
delete num;
}
void number_scanner(duckdb_replacement_scan_info info, const char *table_name, void *data) {
// check if the table name is a number
long long number;
try {
number = std::stoll(table_name);
} catch (...) {
// not a number!
return;
}
auto num_data = (MyBaseNumber *)data;
duckdb_replacement_scan_set_function_name(info, "range");
auto val = duckdb_create_int64(number + num_data->number);
duckdb_replacement_scan_add_parameter(info, val);
duckdb_destroy_value(&val);
}
TEST_CASE("Test replacement scans in C API", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
// open the database in in-memory mode
REQUIRE(tester.OpenDatabase(nullptr));
auto base_number = new MyBaseNumber();
base_number->number = 3;
duckdb_add_replacement_scan(tester.database, number_scanner, (void *)base_number, destroy_base_number);
// 0-4
result = tester.Query("SELECT * FROM \"2\"");
REQUIRE(result->row_count() == 5);
REQUIRE(result->Fetch<int64_t>(0, 0) == 0);
REQUIRE(result->Fetch<int64_t>(0, 1) == 1);
REQUIRE(result->Fetch<int64_t>(0, 2) == 2);
REQUIRE(result->Fetch<int64_t>(0, 3) == 3);
REQUIRE(result->Fetch<int64_t>(0, 4) == 4);
base_number->number = 1;
// 0-2
result = tester.Query("SELECT * FROM \"2\"");
REQUIRE(result->row_count() == 3);
REQUIRE(result->Fetch<int64_t>(0, 0) == 0);
REQUIRE(result->Fetch<int64_t>(0, 1) == 1);
REQUIRE(result->Fetch<int64_t>(0, 2) == 2);
// not a number
REQUIRE_FAIL(tester.Query("SELECT * FROM nonexistant"));
}
void error_replacement_scan(duckdb_replacement_scan_info info, const char *table_name, void *data) {
duckdb_replacement_scan_set_error(NULL, NULL);
duckdb_replacement_scan_set_error(info, NULL);
duckdb_replacement_scan_set_error(info, "error in replacement scan");
}
TEST_CASE("Test error replacement scan", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
// open the database in in-memory mode
REQUIRE(tester.OpenDatabase(nullptr));
duckdb_add_replacement_scan(tester.database, error_replacement_scan, NULL, NULL);
// error
REQUIRE_FAIL(tester.Query("SELECT * FROM nonexistant"));
}

View File

@@ -0,0 +1,184 @@
#include "capi_tester.hpp"
#include "duckdb.h"
using namespace duckdb;
using namespace std;
TEST_CASE("Test streaming results in C API", "[capi]") {
CAPITester tester;
CAPIPrepared prepared;
CAPIPending pending;
duckdb::unique_ptr<CAPIResult> result;
// open the database in in-memory mode
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE(prepared.Prepare(tester, "SELECT i::UINT32 FROM range(1000000) tbl(i)"));
REQUIRE(pending.PendingStreaming(prepared));
while (true) {
auto state = pending.ExecuteTask();
REQUIRE(state != DUCKDB_PENDING_ERROR);
if (state == DUCKDB_PENDING_RESULT_READY) {
break;
}
}
result = pending.Execute();
REQUIRE(result);
REQUIRE(!result->HasError());
auto chunk = result->StreamChunk();
idx_t value = duckdb::DConstants::INVALID_INDEX;
idx_t result_count = 0;
while (chunk) {
auto old_value = value;
auto vector = chunk->GetVector(0);
uint32_t *data = (uint32_t *)duckdb_vector_get_data(vector);
value = data[0];
if (old_value != duckdb::DConstants::INVALID_INDEX) {
// We select from a range, so we can expect every starting value of a new chunk to be higher than the last
// one.
REQUIRE(value > old_value);
}
REQUIRE(chunk->size() > 0);
result_count += chunk->size();
REQUIRE(result_count <= 1000000);
chunk = result->StreamChunk();
}
}
TEST_CASE("Test other methods on streaming results in C API", "[capi]") {
CAPITester tester;
CAPIPrepared prepared;
CAPIPending pending;
duckdb::unique_ptr<CAPIResult> result;
// open the database in in-memory mode
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE(prepared.Prepare(tester, "SELECT i::UINT32 FROM range(1000000) tbl(i)"));
REQUIRE(pending.PendingStreaming(prepared));
while (true) {
auto state = pending.ExecuteTask();
REQUIRE(state != DUCKDB_PENDING_ERROR);
if (state == DUCKDB_PENDING_RESULT_READY) {
break;
}
}
// Once we've done this, the StreamQueryResult is made
result = pending.Execute();
REQUIRE(result);
REQUIRE(!result->HasError());
REQUIRE(result->IsStreaming());
// interrogate the result with various methods
auto chunk_count = result->ChunkCount();
REQUIRE(chunk_count == 0);
auto column_count = result->ColumnCount();
(void)column_count;
auto column_name = result->ColumnName(0);
(void)column_name;
auto column_type = result->ColumnType(0);
(void)column_type;
auto error_message = result->ErrorMessage();
REQUIRE(error_message == nullptr);
auto fetched_chunk = result->FetchChunk(0);
REQUIRE(fetched_chunk == nullptr);
auto has_error = result->HasError();
REQUIRE(has_error == false);
auto row_count = result->row_count();
REQUIRE(row_count == 0);
auto rows_changed = result->rows_changed();
REQUIRE(rows_changed == 0);
// this succeeds because the result is materialized if a stream-result method hasn't being used yet
auto column_data = result->ColumnData<uint32_t>(0);
REQUIRE(column_data != nullptr);
// this materializes the result
auto is_null = result->IsNull(0, 0);
REQUIRE(is_null == false);
}
TEST_CASE("Test streaming arrow results in C API", "[capi][arrow]") {
CAPITester tester;
CAPIPrepared prepared;
CAPIPending pending;
duckdb::unique_ptr<CAPIResult> result;
// open the database in in-memory mode
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE(prepared.Prepare(tester, "SELECT i::UINT32 FROM range(1000000) tbl(i)"));
REQUIRE(pending.PendingStreaming(prepared));
while (true) {
auto state = pending.ExecuteTask();
REQUIRE(state != DUCKDB_PENDING_ERROR);
if (state == DUCKDB_PENDING_RESULT_READY) {
break;
}
}
result = pending.Execute();
REQUIRE(result);
REQUIRE(!result->HasError());
auto chunk = result->StreamChunk();
// Check handle null out_array
duckdb_result_arrow_array(result->InternalResult(), chunk->GetChunk(), nullptr);
int nb_row = 0;
while (chunk) {
ArrowArray *arrow_array = new ArrowArray();
duckdb_result_arrow_array(result->InternalResult(), chunk->GetChunk(), (duckdb_arrow_array *)&arrow_array);
nb_row += arrow_array->length;
chunk = result->StreamChunk();
arrow_array->release(arrow_array);
delete arrow_array;
}
REQUIRE(nb_row == 1000000);
}
TEST_CASE("Test query progress and interrupt in C API", "[capi]") {
CAPITester tester;
CAPIPrepared prepared;
CAPIPending pending;
duckdb::unique_ptr<CAPIResult> result;
// test null handling
REQUIRE(duckdb_query_progress(nullptr).percentage == -1.0);
duckdb_interrupt(nullptr);
// open the database in in-memory mode
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE_NO_FAIL(tester.Query("SET threads=1"));
REQUIRE_NO_FAIL(tester.Query("create table tbl as select range a, mod(range,10) b from range(10000);"));
REQUIRE_NO_FAIL(tester.Query("create table tbl_2 as select range a from range(10000);"));
REQUIRE_NO_FAIL(tester.Query("set enable_progress_bar=true;"));
REQUIRE_NO_FAIL(tester.Query("set enable_progress_bar_print=false;"));
// test no progress before query
REQUIRE(duckdb_query_progress(tester.connection).percentage == -1.0);
// test zero progress with query
REQUIRE(prepared.Prepare(tester, "select count(*) from tbl where a = (select min(a) from tbl_2)"));
REQUIRE(pending.PendingStreaming(prepared));
REQUIRE(duckdb_query_progress(tester.connection).percentage == 0.0);
// test progress
while (duckdb_query_progress(tester.connection).percentage == 0.0) {
auto state = pending.ExecuteTask();
REQUIRE(state == DUCKDB_PENDING_RESULT_NOT_READY);
}
REQUIRE(duckdb_query_progress(tester.connection).percentage >= 0.0);
// test interrupt
duckdb_interrupt(tester.connection);
while (true) {
auto state = pending.ExecuteTask();
REQUIRE(state != DUCKDB_PENDING_RESULT_READY);
if (state == DUCKDB_PENDING_ERROR) {
break;
}
}
}

View File

@@ -0,0 +1,127 @@
#include "capi_tester.hpp"
#include "duckdb.h"
using namespace duckdb;
using namespace std;
TEST_CASE("Test the table description in the C API", "[capi]") {
CAPITester tester;
REQUIRE(tester.OpenDatabase(nullptr));
duckdb_table_description table_description = nullptr;
tester.Query("SET threads=1;");
// Test a non-existent table.
auto status = duckdb_table_description_create(tester.connection, nullptr, "test", &table_description);
REQUIRE(status == DuckDBError);
duckdb_table_description_destroy(&table_description);
status = duckdb_table_description_create_ext(tester.connection, "hello", "world", "test", &table_description);
REQUIRE(status == DuckDBError);
duckdb_table_description_destroy(&table_description);
// Create an in-memory table and a table in an external file.
tester.Query("CREATE TABLE test (i INTEGER, j INTEGER default 5)");
auto test_dir = TestDirectoryPath();
auto attach_query = "ATTACH '" + test_dir + "/ext_description.db'";
tester.Query(attach_query);
tester.Query("CREATE TABLE ext_description.test(my_column INTEGER)");
// Test invalid catalog and schema.
status =
duckdb_table_description_create_ext(tester.connection, "non-existent", nullptr, "test", &table_description);
REQUIRE(status == DuckDBError);
duckdb_table_description_destroy(&table_description);
status = duckdb_table_description_create(tester.connection, "non-existent", "test", &table_description);
REQUIRE(status == DuckDBError);
duckdb_table_description_destroy(&table_description);
status = duckdb_table_description_create(tester.connection, nullptr, "test", &table_description);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_table_description_error(table_description) == nullptr);
bool has_default;
SECTION("Passing nullptr to has_default") {
REQUIRE(duckdb_column_has_default(table_description, 2, nullptr) == DuckDBError);
REQUIRE(duckdb_column_has_default(nullptr, 2, &has_default) == DuckDBError);
}
SECTION("Out of range column for has_default") {
REQUIRE(duckdb_column_has_default(table_description, 2, &has_default) == DuckDBError);
}
SECTION("In range column - not default") {
REQUIRE(duckdb_column_has_default(table_description, 0, &has_default) == DuckDBSuccess);
REQUIRE(has_default == false);
}
SECTION("In range column - default") {
REQUIRE(duckdb_column_has_default(table_description, 1, &has_default) == DuckDBSuccess);
REQUIRE(has_default == true);
}
duckdb_table_description_destroy(&table_description);
// Let's get information about the external table.
status =
duckdb_table_description_create_ext(tester.connection, "ext_description", nullptr, "test", &table_description);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_table_description_error(table_description) == nullptr);
SECTION("Passing nullptr to get_column_count") {
REQUIRE(duckdb_table_description_get_column_count(nullptr) == 0);
}
SECTION("Passing nullptr to get_name") {
REQUIRE(duckdb_table_description_get_column_name(nullptr, 0) == nullptr);
}
SECTION("Passing nullptr to get_type") {
REQUIRE(duckdb_table_description_get_column_type(nullptr, 0) == nullptr);
}
SECTION("Out of range column for get_name") {
REQUIRE(duckdb_table_description_get_column_name(table_description, 1) == nullptr);
}
SECTION("Out of range column for get_type") {
REQUIRE(duckdb_table_description_get_column_type(table_description, 1) == nullptr);
}
SECTION("get the column count") {
auto column_count = duckdb_table_description_get_column_count(table_description);
REQUIRE(column_count == 1);
}
SECTION("In range column - get the name") {
auto column_name = duckdb_table_description_get_column_name(table_description, 0);
string expected = "my_column";
REQUIRE(!expected.compare(column_name));
duckdb_free(column_name);
}
SECTION("In range column - get the type") {
auto column_type = duckdb_table_description_get_column_type(table_description, 0);
auto type_id = duckdb_get_type_id(column_type);
REQUIRE(type_id == DUCKDB_TYPE_INTEGER);
duckdb_destroy_logical_type(&column_type);
}
duckdb_table_description_destroy(&table_description);
}
TEST_CASE("Test getting the table names of a query in the C API", "[capi]") {
CAPITester tester;
REQUIRE(tester.OpenDatabase(nullptr));
tester.Query("CREATE SCHEMA schema1");
tester.Query("CREATE SCHEMA \"schema.2\"");
tester.Query("CREATE TABLE schema1.\"table.1\"(i INT)");
tester.Query("CREATE TABLE \"schema.2\".\"table.2\"(i INT)");
string query = "SELECT * FROM schema1.\"table.1\", \"schema.2\".\"table.2\"";
auto table_name_values = duckdb_get_table_names(tester.connection, query.c_str(), true);
auto size = duckdb_get_list_size(table_name_values);
REQUIRE(size == 2);
duckdb::unordered_set<string> expected_names = {"schema1.\"table.1\"", "\"schema.2\".\"table.2\""};
for (idx_t i = 0; i < size; i++) {
auto name_value = duckdb_get_list_child(table_name_values, i);
auto name = duckdb_get_varchar(name_value);
REQUIRE(expected_names.count(name) == 1);
duckdb_free(name);
duckdb_destroy_value(&name_value);
}
duckdb_destroy_value(&table_name_values);
tester.Cleanup();
}

View File

@@ -0,0 +1,64 @@
#include "capi_tester.hpp"
#include "duckdb.h"
using namespace duckdb;
using namespace std;
int64_t Difference(int64_t left, int64_t right) {
return abs(left - right);
}
void CompareDuckDBDecimal(const duckdb_decimal &left, const duckdb_decimal &right) {
REQUIRE(left.scale == right.scale);
REQUIRE(left.width == right.width);
REQUIRE(left.value.upper == right.value.upper);
}
void TestFetchAsDecimal(CAPITester &tester, string query, string type_cast) {
auto result = tester.Query(StringUtil::Format(query, type_cast));
REQUIRE_NO_FAIL(*result);
// (ANYTHING BUT DECIMAL) -> DECIMAL results in 0
duckdb_decimal expected_res;
expected_res.scale = 0;
expected_res.width = 0;
expected_res.value.lower = 0;
expected_res.value.upper = 0;
auto converted_res = result->Fetch<duckdb_decimal>(0, 0);
CompareDuckDBDecimal(expected_res, converted_res);
}
TEST_CASE("Test CAPI duckdb_decimal_as_properties", "[capi]") {
CAPITester tester;
// open the database in in-memory mode
REQUIRE(tester.OpenDatabase(nullptr));
//! From DOUBLE
TestFetchAsDecimal(tester, "SELECT CAST(123.45678 AS %s)", "DOUBLE");
//! From FLOAT
TestFetchAsDecimal(tester, "SELECT CAST(123.45678 AS %s)", "FLOAT");
//! From HUGEINT
TestFetchAsDecimal(tester, "SELECT CAST(123124 AS %s)", "HUGEINT");
//! From UHUGEINT
TestFetchAsDecimal(tester, "SELECT CAST(123124 AS %s)", "UHUGEINT");
//! From BIGINT
TestFetchAsDecimal(tester, "SELECT CAST(123124 AS %s)", "BIGINT");
//! From UBIGINT
TestFetchAsDecimal(tester, "SELECT CAST(123124 AS %s)", "UBIGINT");
//! From INTEGER
TestFetchAsDecimal(tester, "SELECT CAST(123124 AS %s)", "INTEGER");
//! From UINTEGER
TestFetchAsDecimal(tester, "SELECT CAST(123124 AS %s)", "UINTEGER");
//! From SMALLINT
TestFetchAsDecimal(tester, "SELECT CAST(12312 AS %s)", "SMALLINT");
//! From USMALLINT
TestFetchAsDecimal(tester, "SELECT CAST(12312 AS %s)", "USMALLINT");
//! From TINYINT
TestFetchAsDecimal(tester, "SELECT CAST(-123 AS %s)", "TINYINT");
//! From UTINYINT
TestFetchAsDecimal(tester, "SELECT CAST(255 AS %s)", "UTINYINT");
//! From VARCHAR
TestFetchAsDecimal(tester, "SELECT CAST(123124.2342 AS %s)", "VARCHAR");
}

View File

@@ -0,0 +1,438 @@
#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
TEST_CASE("Test MAP getters", "[capi]") {
auto uint_val = duckdb_create_uint64(42);
REQUIRE(uint_val);
auto size = duckdb_get_map_size(nullptr);
REQUIRE(size == 0);
size = duckdb_get_map_size(uint_val);
REQUIRE(size == 0);
auto key = duckdb_get_map_key(nullptr, 0);
REQUIRE(!key);
key = duckdb_get_map_key(uint_val, 0);
REQUIRE(!key);
auto value = duckdb_get_map_value(nullptr, 0);
REQUIRE(!value);
value = duckdb_get_map_value(uint_val, 0);
REQUIRE(!value);
duckdb_destroy_value(&uint_val);
}
TEST_CASE("Test LIST getters", "[capi]") {
duckdb_value list_vals[2];
list_vals[0] = duckdb_create_uint64(42);
list_vals[1] = duckdb_create_uint64(43);
duckdb_logical_type uint64_type = duckdb_create_logical_type(DUCKDB_TYPE_UBIGINT);
duckdb_value list_value = duckdb_create_list_value(uint64_type, list_vals, 2);
duckdb_destroy_value(&list_vals[0]);
duckdb_destroy_value(&list_vals[1]);
duckdb_destroy_logical_type(&uint64_type);
auto size = duckdb_get_list_size(nullptr);
REQUIRE(size == 0);
size = duckdb_get_list_size(list_value);
REQUIRE(size == 2);
auto val = duckdb_get_list_child(nullptr, 0);
REQUIRE(!val);
duckdb_destroy_value(&val);
val = duckdb_get_list_child(list_value, 0);
REQUIRE(val);
REQUIRE(duckdb_get_uint64(val) == 42);
duckdb_destroy_value(&val);
val = duckdb_get_list_child(list_value, 1);
REQUIRE(val);
REQUIRE(duckdb_get_uint64(val) == 43);
duckdb_destroy_value(&val);
val = duckdb_get_list_child(list_value, 2);
REQUIRE(!val);
duckdb_destroy_value(&val);
duckdb_destroy_value(&list_value);
}
TEST_CASE("Test ENUM getters", "[capi]") {
const char *mnames[5] = {"apple", "banana", "cherry", "orange", "elderberry"};
duckdb_logical_type enum_type = duckdb_create_enum_type(mnames, 5);
duckdb_value enum_val = duckdb_create_enum_value(enum_type, 2);
REQUIRE(enum_val);
auto val = duckdb_get_enum_value(nullptr);
REQUIRE(val == 0);
val = duckdb_get_enum_value(enum_val);
REQUIRE(val == 2);
duckdb_destroy_value(&enum_val);
enum_val = duckdb_create_enum_value(enum_type, 4);
REQUIRE(enum_val);
val = duckdb_get_enum_value(enum_val);
REQUIRE(val == 4);
duckdb_destroy_value(&enum_val);
enum_val = duckdb_create_enum_value(enum_type, 5);
REQUIRE(!enum_val);
enum_val = duckdb_create_enum_value(enum_type, 6);
REQUIRE(!enum_val);
duckdb_destroy_value(&enum_val);
duckdb_destroy_logical_type(&enum_type);
}
TEST_CASE("Test STRUCT getters", "[capi]") {
duckdb_logical_type mtypes[2] = {duckdb_create_logical_type(DUCKDB_TYPE_UBIGINT),
duckdb_create_logical_type(DUCKDB_TYPE_BIGINT)};
const char *mnames[2] = {"a", "b"};
duckdb_logical_type struct_type = duckdb_create_struct_type(mtypes, mnames, 2);
duckdb_destroy_logical_type(&mtypes[0]);
duckdb_destroy_logical_type(&mtypes[1]);
duckdb_value svals[2] = {duckdb_create_uint64(42), duckdb_create_int64(-42)};
duckdb_value struct_val = duckdb_create_struct_value(struct_type, svals);
duckdb_destroy_logical_type(&struct_type);
duckdb_destroy_value(&svals[0]);
duckdb_destroy_value(&svals[1]);
auto val = duckdb_get_struct_child(nullptr, 0);
REQUIRE(!val);
val = duckdb_get_struct_child(struct_val, 0);
REQUIRE(val);
REQUIRE(duckdb_get_uint64(val) == 42);
duckdb_destroy_value(&val);
val = duckdb_get_struct_child(struct_val, 1);
REQUIRE(val);
REQUIRE(duckdb_get_int64(val) == -42);
duckdb_destroy_value(&val);
val = duckdb_get_struct_child(struct_val, 2);
REQUIRE(!val);
duckdb_destroy_value(&struct_val);
}
TEST_CASE("Test NULL value", "[capi]") {
auto null_value = duckdb_create_null_value();
REQUIRE(null_value);
REQUIRE(!duckdb_is_null_value(nullptr));
auto uint_val = duckdb_create_uint64(42);
REQUIRE(!duckdb_is_null_value(uint_val));
REQUIRE(duckdb_is_null_value(null_value));
duckdb_destroy_value(&uint_val);
duckdb_destroy_value(&null_value);
}
TEST_CASE("Test BIGNUM value", "[capi]") {
{
uint8_t data[] {0};
duckdb_bignum input {data, 1, false};
auto value = duckdb_create_bignum(input);
REQUIRE(duckdb_get_type_id(duckdb_get_value_type(value)) == DUCKDB_TYPE_BIGNUM);
auto output = duckdb_get_bignum(value);
REQUIRE(output.is_negative == input.is_negative);
REQUIRE(output.size == input.size);
REQUIRE_FALSE(memcmp(output.data, input.data, input.size));
duckdb_free(output.data);
duckdb_destroy_value(&value);
}
{
uint8_t data[] {1};
duckdb_bignum input {data, 1, true};
auto value = duckdb_create_bignum(input);
REQUIRE(duckdb_get_type_id(duckdb_get_value_type(value)) == DUCKDB_TYPE_BIGNUM);
auto output = duckdb_get_bignum(value);
REQUIRE(output.is_negative == input.is_negative);
REQUIRE(output.size == input.size);
REQUIRE_FALSE(memcmp(output.data, input.data, input.size));
duckdb_free(output.data);
duckdb_destroy_value(&value);
}
{ // max bignum == max double == 2^1023 * (1 + (1 2^52)) == 2^1024 - 2^971 ==
// 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368
uint8_t data[] {
// little endian
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};
duckdb_bignum input {data, 128, false};
auto value = duckdb_create_bignum(input);
REQUIRE(duckdb_get_type_id(duckdb_get_value_type(value)) == DUCKDB_TYPE_BIGNUM);
auto output = duckdb_get_bignum(value);
REQUIRE(output.is_negative == input.is_negative);
REQUIRE(output.size == input.size);
REQUIRE_FALSE(memcmp(output.data, input.data, input.size));
duckdb_free(output.data);
duckdb_destroy_value(&value);
}
{ // min bignum == min double == -(2^1023 * (1 + (1 2^52))) == -(2^1024 - 2^971) ==
// -179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368
uint8_t data[] {
// little endian (absolute value)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};
duckdb_bignum input {data, 128, true};
auto value = duckdb_create_bignum(input);
REQUIRE(duckdb_get_type_id(duckdb_get_value_type(value)) == DUCKDB_TYPE_BIGNUM);
auto output = duckdb_get_bignum(value);
REQUIRE(output.is_negative == input.is_negative);
REQUIRE(output.size == input.size);
REQUIRE_FALSE(memcmp(output.data, input.data, input.size));
duckdb_free(output.data);
duckdb_destroy_value(&value);
}
}
TEST_CASE("Test DECIMAL value", "[capi]") {
{
auto hugeint = Hugeint::POWERS_OF_TEN[4] - hugeint_t(1);
duckdb_decimal input {4, 1, {hugeint.lower, hugeint.upper}};
auto value = duckdb_create_decimal(input);
auto type = duckdb_get_value_type(value);
REQUIRE(duckdb_get_type_id(type) == DUCKDB_TYPE_DECIMAL);
REQUIRE(duckdb_decimal_width(type) == input.width);
REQUIRE(duckdb_decimal_scale(type) == input.scale);
REQUIRE(duckdb_decimal_internal_type(type) == DUCKDB_TYPE_SMALLINT);
auto output = duckdb_get_decimal(value);
REQUIRE(output.width == input.width);
REQUIRE(output.scale == input.scale);
REQUIRE(output.value.lower == input.value.lower);
REQUIRE(output.value.upper == input.value.upper);
duckdb_destroy_value(&value);
}
{
auto hugeint = -(Hugeint::POWERS_OF_TEN[4] - hugeint_t(1));
duckdb_decimal input {4, 1, {hugeint.lower, hugeint.upper}};
auto value = duckdb_create_decimal(input);
auto type = duckdb_get_value_type(value);
REQUIRE(duckdb_get_type_id(type) == DUCKDB_TYPE_DECIMAL);
REQUIRE(duckdb_decimal_width(type) == input.width);
REQUIRE(duckdb_decimal_scale(type) == input.scale);
REQUIRE(duckdb_decimal_internal_type(type) == DUCKDB_TYPE_SMALLINT);
auto output = duckdb_get_decimal(value);
REQUIRE(output.width == input.width);
REQUIRE(output.scale == input.scale);
REQUIRE(output.value.lower == input.value.lower);
REQUIRE(output.value.upper == input.value.upper);
duckdb_destroy_value(&value);
}
{
auto hugeint = Hugeint::POWERS_OF_TEN[9] - hugeint_t(1);
duckdb_decimal input {9, 4, {hugeint.lower, hugeint.upper}};
auto value = duckdb_create_decimal(input);
auto type = duckdb_get_value_type(value);
REQUIRE(duckdb_get_type_id(type) == DUCKDB_TYPE_DECIMAL);
REQUIRE(duckdb_decimal_width(type) == input.width);
REQUIRE(duckdb_decimal_scale(type) == input.scale);
REQUIRE(duckdb_decimal_internal_type(type) == DUCKDB_TYPE_INTEGER);
auto output = duckdb_get_decimal(value);
REQUIRE(output.width == input.width);
REQUIRE(output.scale == input.scale);
REQUIRE(output.value.lower == input.value.lower);
REQUIRE(output.value.upper == input.value.upper);
duckdb_destroy_value(&value);
}
{
auto hugeint = -(Hugeint::POWERS_OF_TEN[9] - hugeint_t(1));
duckdb_decimal input {9, 4, {hugeint.lower, hugeint.upper}};
auto value = duckdb_create_decimal(input);
auto type = duckdb_get_value_type(value);
REQUIRE(duckdb_get_type_id(type) == DUCKDB_TYPE_DECIMAL);
REQUIRE(duckdb_decimal_width(type) == input.width);
REQUIRE(duckdb_decimal_scale(type) == input.scale);
REQUIRE(duckdb_decimal_internal_type(type) == DUCKDB_TYPE_INTEGER);
auto output = duckdb_get_decimal(value);
REQUIRE(output.width == input.width);
REQUIRE(output.scale == input.scale);
REQUIRE(output.value.lower == input.value.lower);
REQUIRE(output.value.upper == input.value.upper);
duckdb_destroy_value(&value);
}
{
auto hugeint = Hugeint::POWERS_OF_TEN[18] - hugeint_t(1);
duckdb_decimal input {18, 6, {hugeint.lower, hugeint.upper}};
auto value = duckdb_create_decimal(input);
auto type = duckdb_get_value_type(value);
REQUIRE(duckdb_get_type_id(type) == DUCKDB_TYPE_DECIMAL);
REQUIRE(duckdb_decimal_width(type) == input.width);
REQUIRE(duckdb_decimal_scale(type) == input.scale);
REQUIRE(duckdb_decimal_internal_type(type) == DUCKDB_TYPE_BIGINT);
auto output = duckdb_get_decimal(value);
REQUIRE(output.width == input.width);
REQUIRE(output.scale == input.scale);
REQUIRE(output.value.lower == input.value.lower);
REQUIRE(output.value.upper == input.value.upper);
duckdb_destroy_value(&value);
}
{
auto hugeint = -(Hugeint::POWERS_OF_TEN[18] - hugeint_t(1));
duckdb_decimal input {18, 8, {hugeint.lower, hugeint.upper}};
auto value = duckdb_create_decimal(input);
auto type = duckdb_get_value_type(value);
REQUIRE(duckdb_get_type_id(type) == DUCKDB_TYPE_DECIMAL);
REQUIRE(duckdb_decimal_width(type) == input.width);
REQUIRE(duckdb_decimal_scale(type) == input.scale);
REQUIRE(duckdb_decimal_internal_type(type) == DUCKDB_TYPE_BIGINT);
auto output = duckdb_get_decimal(value);
REQUIRE(output.width == input.width);
REQUIRE(output.scale == input.scale);
REQUIRE(output.value.lower == input.value.lower);
REQUIRE(output.value.upper == input.value.upper);
duckdb_destroy_value(&value);
}
{
auto hugeint = Hugeint::POWERS_OF_TEN[38] - hugeint_t(1);
duckdb_decimal input {38, 10, {hugeint.lower, hugeint.upper}};
auto value = duckdb_create_decimal(input);
auto type = duckdb_get_value_type(value);
REQUIRE(duckdb_get_type_id(type) == DUCKDB_TYPE_DECIMAL);
REQUIRE(duckdb_decimal_width(type) == input.width);
REQUIRE(duckdb_decimal_scale(type) == input.scale);
REQUIRE(duckdb_decimal_internal_type(type) == DUCKDB_TYPE_HUGEINT);
auto output = duckdb_get_decimal(value);
REQUIRE(output.width == input.width);
REQUIRE(output.scale == input.scale);
REQUIRE(output.value.lower == input.value.lower);
REQUIRE(output.value.upper == input.value.upper);
duckdb_destroy_value(&value);
}
{
auto hugeint = -(Hugeint::POWERS_OF_TEN[38] - hugeint_t(1));
duckdb_decimal input {38, 10, {hugeint.lower, hugeint.upper}};
auto value = duckdb_create_decimal(input);
auto type = duckdb_get_value_type(value);
REQUIRE(duckdb_get_type_id(type) == DUCKDB_TYPE_DECIMAL);
REQUIRE(duckdb_decimal_width(type) == input.width);
REQUIRE(duckdb_decimal_scale(type) == input.scale);
REQUIRE(duckdb_decimal_internal_type(type) == DUCKDB_TYPE_HUGEINT);
auto output = duckdb_get_decimal(value);
REQUIRE(output.width == input.width);
REQUIRE(output.scale == input.scale);
REQUIRE(output.value.lower == input.value.lower);
REQUIRE(output.value.upper == input.value.upper);
duckdb_destroy_value(&value);
}
}
TEST_CASE("Test BIT value", "[capi]") {
{
uint8_t data[] {5, 0xf9, 0x56}; // 0b11111001 0b01010110
duckdb_bit input {data, 3};
auto value = duckdb_create_bit(input);
REQUIRE(duckdb_get_type_id(duckdb_get_value_type(value)) == DUCKDB_TYPE_BIT);
auto output = duckdb_get_bit(value);
REQUIRE(output.size == input.size);
REQUIRE_FALSE(memcmp(output.data, input.data, input.size));
duckdb_free(output.data);
duckdb_destroy_value(&value);
}
{
uint8_t data[] {0, 0x00};
duckdb_bit input {data, 2};
auto value = duckdb_create_bit(input);
REQUIRE(duckdb_get_type_id(duckdb_get_value_type(value)) == DUCKDB_TYPE_BIT);
auto output = duckdb_get_bit(value);
REQUIRE(output.size == input.size);
REQUIRE_FALSE(memcmp(output.data, input.data, input.size));
duckdb_free(output.data);
duckdb_destroy_value(&value);
}
}
TEST_CASE("Test UUID value", "[capi]") {
{
duckdb_uhugeint uhugeint_input {0x0000000000000000, 0x0000000000000000};
auto uuid_value = duckdb_create_uuid(uhugeint_input);
REQUIRE(duckdb_get_type_id(duckdb_get_value_type(uuid_value)) == DUCKDB_TYPE_UUID);
auto uhugeint_output = duckdb_get_uuid(uuid_value);
REQUIRE(uhugeint_output.lower == uhugeint_input.lower);
REQUIRE(uhugeint_output.upper == uhugeint_input.upper);
duckdb_destroy_value(&uuid_value);
}
{
duckdb_uhugeint uhugeint_input {0x0000000000000001, 0x0000000000000000};
auto uuid_value = duckdb_create_uuid(uhugeint_input);
REQUIRE(duckdb_get_type_id(duckdb_get_value_type(uuid_value)) == DUCKDB_TYPE_UUID);
auto uhugeint_output = duckdb_get_uuid(uuid_value);
REQUIRE(uhugeint_output.lower == uhugeint_input.lower);
REQUIRE(uhugeint_output.upper == uhugeint_input.upper);
duckdb_destroy_value(&uuid_value);
}
{
duckdb_uhugeint uhugeint_input {0xffffffffffffffff, 0xffffffffffffffff};
auto uuid_value = duckdb_create_uuid(uhugeint_input);
REQUIRE(duckdb_get_type_id(duckdb_get_value_type(uuid_value)) == DUCKDB_TYPE_UUID);
auto uhugeint_output = duckdb_get_uuid(uuid_value);
REQUIRE(uhugeint_output.lower == uhugeint_input.lower);
REQUIRE(uhugeint_output.upper == uhugeint_input.upper);
duckdb_destroy_value(&uuid_value);
}
{
duckdb_uhugeint uhugeint_input {0xfffffffffffffffe, 0xffffffffffffffff};
auto uuid_value = duckdb_create_uuid(uhugeint_input);
REQUIRE(duckdb_get_type_id(duckdb_get_value_type(uuid_value)) == DUCKDB_TYPE_UUID);
auto uhugeint_output = duckdb_get_uuid(uuid_value);
REQUIRE(uhugeint_output.lower == uhugeint_input.lower);
REQUIRE(uhugeint_output.upper == uhugeint_input.upper);
duckdb_destroy_value(&uuid_value);
}
{
duckdb_uhugeint uhugeint_input {0xffffffffffffffff, 0x8fffffffffffffff};
auto uuid_value = duckdb_create_uuid(uhugeint_input);
REQUIRE(duckdb_get_type_id(duckdb_get_value_type(uuid_value)) == DUCKDB_TYPE_UUID);
auto uhugeint_output = duckdb_get_uuid(uuid_value);
REQUIRE(uhugeint_output.lower == uhugeint_input.lower);
REQUIRE(uhugeint_output.upper == uhugeint_input.upper);
duckdb_destroy_value(&uuid_value);
}
{
duckdb_uhugeint uhugeint_input {0x0000000000000000, 0x7000000000000000};
auto uuid_value = duckdb_create_uuid(uhugeint_input);
REQUIRE(duckdb_get_type_id(duckdb_get_value_type(uuid_value)) == DUCKDB_TYPE_UUID);
auto uhugeint_output = duckdb_get_uuid(uuid_value);
REQUIRE(uhugeint_output.lower == uhugeint_input.lower);
REQUIRE(uhugeint_output.upper == uhugeint_input.upper);
duckdb_destroy_value(&uuid_value);
}
}
TEST_CASE("Test SQL string conversion", "[capi]") {
auto uint_val = duckdb_create_uint64(42);
auto uint_val_str = duckdb_value_to_string(uint_val);
REQUIRE(string(uint_val_str).compare("42") == 0);
duckdb_destroy_value(&uint_val);
duckdb_free(uint_val_str);
}

View File

@@ -0,0 +1,276 @@
#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
string get_string_from_duckdb_string_t(duckdb_string_t *input) {
const char *ptr = duckdb_string_is_inlined(*input) ? input->value.inlined.inlined : input->value.pointer.ptr;
return string(ptr, duckdb_string_t_length(*input));
}
duckdb_vector create_src_vector_for_copy_selection_test(duckdb_logical_type type) {
// Create a source vector of BIGINTs with 6 elements.
auto vector = duckdb_create_vector(type, 6);
auto data = (int64_t *)duckdb_vector_get_data(vector);
duckdb_vector_ensure_validity_writable(vector);
auto src_validity = duckdb_vector_get_validity(vector);
// Populate with data: {10, 20, NULL, 40, 50, 60}
data[0] = 10;
data[1] = 20;
src_validity[0] = ~0x04;
data[3] = 40;
data[4] = 50;
data[5] = 60;
return vector;
}
duckdb_selection_vector create_selection_vector_for_copy_selection_test() {
// Selects rows in the order: 5, 3, 2, 0
idx_t selection_data[] = {5, 3, 2, 0};
auto sel = duckdb_create_selection_vector(4);
sel_t *sel_data = duckdb_selection_vector_get_data_ptr(sel);
for (idx_t i = 0; i < 4; ++i) {
sel_data[i] = selection_data[i];
}
return sel;
}
TEST_CASE("Test duckdb_vector_copy_sel", "[capi]") {
duckdb_logical_type type = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
SECTION("Test basic selection copy") {
auto src_vector = create_src_vector_for_copy_selection_test(type);
auto sel_vector = create_selection_vector_for_copy_selection_test();
auto dst_vector = duckdb_create_vector(type, 4);
auto dst_data = (int64_t *)duckdb_vector_get_data(dst_vector);
duckdb_vector_ensure_validity_writable(dst_vector);
auto dst_validity = duckdb_vector_get_validity(dst_vector);
// Copy 4 elements from the start of the selection vector to the start of the destination.
duckdb_vector_copy_sel(src_vector, dst_vector, sel_vector, 4, 0, 0);
// Verify the copied data: should be {60, 40, NULL, 10}
REQUIRE(dst_data[0] == 60);
REQUIRE((dst_validity[0] & 0x01) == 0x01);
REQUIRE(dst_data[1] == 40);
REQUIRE((dst_validity[0] & 0x02) == 0x02);
// Check that the NULL was copied correctly
REQUIRE((~dst_validity[0] & 0x04) == 0x04);
REQUIRE(dst_data[3] == 10);
REQUIRE((dst_validity[0] & 0x08) == 0x08);
duckdb_destroy_vector(&src_vector);
duckdb_destroy_vector(&dst_vector);
duckdb_destroy_selection_vector(sel_vector);
}
SECTION("Test copy with source and destination offsets") {
auto src_vector = create_src_vector_for_copy_selection_test(type);
auto sel_vector = create_selection_vector_for_copy_selection_test();
// Create a destination vector pre-filled with some data.
auto dst_vector = duckdb_create_vector(type, 6);
auto dst_data = (int64_t *)duckdb_vector_get_data(dst_vector);
duckdb_vector_ensure_validity_writable(dst_vector);
for (int i = 0; i < 6; i++) {
dst_data[i] = 999;
}
// Copy 2 elements, starting from offset 1 in `sel` (`{3, 2}`).
// Copy them into `dst_vector` starting at offset 2.
duckdb_vector_copy_sel(src_vector, dst_vector, sel_vector, 3, 1, 2);
// Verify destination: should be {999, 999, 40, NULL, 999, 999}
auto dst_validity = duckdb_vector_get_validity(dst_vector);
// Unchanged elements
REQUIRE(dst_data[0] == 999);
REQUIRE(dst_data[1] == 999);
REQUIRE(dst_data[4] == 999);
REQUIRE(dst_data[5] == 999);
// Copied elements
REQUIRE(dst_data[2] == 40);
REQUIRE((dst_validity[0] & 0x04) == 0x04);
REQUIRE((~dst_validity[0] & 0x08) == 0x08); // The NULL value from src[2]
duckdb_destroy_vector(&src_vector);
duckdb_destroy_vector(&dst_vector);
duckdb_destroy_selection_vector(sel_vector);
}
SECTION("Test copy with zero count") {
auto src_vector = create_src_vector_for_copy_selection_test(type);
auto sel_vector = create_selection_vector_for_copy_selection_test();
auto dst_vector = duckdb_create_vector(type, 4);
auto dst_data = (int64_t *)duckdb_vector_get_data(dst_vector);
for (int i = 0; i < 4; i++) {
dst_data[i] = 123; // Pre-fill
}
// copy 0 elements.
duckdb_vector_copy_sel(src_vector, dst_vector, sel_vector, 0, 0, 0);
for (int i = 0; i < 4; i++) {
REQUIRE(dst_data[i] == 123);
}
duckdb_destroy_vector(&src_vector);
duckdb_destroy_vector(&dst_vector);
duckdb_destroy_selection_vector(sel_vector);
}
duckdb_destroy_logical_type(&type);
}
void copy_data_chunk_using_vector_copy_sel(duckdb_data_chunk src, duckdb_data_chunk dst) {
idx_t src_size = duckdb_data_chunk_get_size(src);
auto incr_sel_vector = duckdb_create_selection_vector(src_size);
sel_t *data_ptr = duckdb_selection_vector_get_data_ptr(incr_sel_vector);
for (sel_t i = 0; i < sel_t(src_size); i++) {
data_ptr[i] = i;
}
for (idx_t i = 0; i < duckdb_data_chunk_get_column_count(src); i++) {
duckdb_vector src_vector = duckdb_data_chunk_get_vector(src, i);
duckdb_vector dst_vector = duckdb_data_chunk_get_vector(dst, i);
duckdb_vector_copy_sel(src_vector, dst_vector, incr_sel_vector, src_size, 0, 0);
}
duckdb_data_chunk_set_size(dst, src_size);
duckdb_destroy_selection_vector(incr_sel_vector);
}
TEST_CASE("Test copying data_chunk by using duckdb_vector_copy_sel", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
SECTION("Test basic data chunk copy") {
duckdb_logical_type types[] = {duckdb_create_logical_type(DUCKDB_TYPE_INTEGER),
duckdb_create_logical_type(DUCKDB_TYPE_VARCHAR)};
auto src_chunk = duckdb_create_data_chunk(types, 2);
auto dst_chunk = duckdb_create_data_chunk(types, 2);
int32_t *int_data =
reinterpret_cast<int32_t *>(duckdb_vector_get_data(duckdb_data_chunk_get_vector(src_chunk, 0)));
int_data[0] = 42;
int_data[1] = 99;
auto varchar_vector = duckdb_data_chunk_get_vector(src_chunk, 1);
duckdb_vector_assign_string_element(varchar_vector, 0, "hello");
duckdb_vector_assign_string_element(varchar_vector, 1, "world");
duckdb_data_chunk_set_size(src_chunk, 2);
copy_data_chunk_using_vector_copy_sel(src_chunk, dst_chunk);
REQUIRE(duckdb_data_chunk_get_size(dst_chunk) == 2);
REQUIRE(duckdb_data_chunk_get_column_count(dst_chunk) == 2);
int32_t *dst_int_data = (int32_t *)duckdb_vector_get_data(duckdb_data_chunk_get_vector(dst_chunk, 0));
CHECK(dst_int_data[0] == 42);
CHECK(dst_int_data[1] == 99);
auto dst_vector = duckdb_data_chunk_get_vector(dst_chunk, 1);
auto validity = duckdb_vector_get_validity(dst_vector);
auto string_data = (duckdb_string_t *)duckdb_vector_get_data(dst_vector);
CHECK(duckdb_validity_row_is_valid(validity, 0));
CHECK(duckdb_validity_row_is_valid(validity, 1));
CHECK(get_string_from_duckdb_string_t(&string_data[0]).compare("hello") == 0);
CHECK(get_string_from_duckdb_string_t(&string_data[1]).compare("world") == 0);
duckdb_destroy_data_chunk(&src_chunk);
duckdb_destroy_data_chunk(&dst_chunk);
for (size_t i = 0; i < 2; i++) {
duckdb_destroy_logical_type(&types[i]);
}
}
}
void reference_data_chunk_using_vector_reference_vector(duckdb_data_chunk src, duckdb_data_chunk dst,
const idx_t *ref_indices, idx_t ref_len) {
duckdb_data_chunk_reset(dst);
idx_t src_size = duckdb_data_chunk_get_size(src);
for (idx_t i = 0; i < ref_len; i++) {
idx_t idx = ref_indices[i];
auto src_vector = duckdb_data_chunk_get_vector(src, idx);
auto dst_vector = duckdb_data_chunk_get_vector(dst, i);
duckdb_vector_reference_vector(dst_vector, src_vector);
}
duckdb_data_chunk_set_size(dst, src_size);
}
TEST_CASE("Test referencing data chunks by using duckdb_vector_reference_vector", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
duckdb_logical_type src_types[] = {duckdb_create_logical_type(DUCKDB_TYPE_INTEGER),
duckdb_create_logical_type(DUCKDB_TYPE_DOUBLE),
duckdb_create_logical_type(DUCKDB_TYPE_BIGINT)};
auto src_chunk = duckdb_create_data_chunk(src_types, 3);
auto src_int_vector = duckdb_data_chunk_get_vector(src_chunk, 0);
auto src_double_vector = duckdb_data_chunk_get_vector(src_chunk, 1);
auto src_bigint_vector = duckdb_data_chunk_get_vector(src_chunk, 2);
auto src_int_data = (int32_t *)duckdb_vector_get_data(src_int_vector);
auto src_double_data = (double *)duckdb_vector_get_data(src_double_vector);
auto src_bigint_data = (int64_t *)duckdb_vector_get_data(src_bigint_vector);
src_int_data[0] = 42;
src_int_data[1] = 99;
src_double_data[0] = 0.5;
src_double_data[1] = 1.5;
src_bigint_data[0] = 1000;
src_bigint_data[1] = 2000;
duckdb_data_chunk_set_size(src_chunk, 2);
duckdb_logical_type dst_types[] = {duckdb_create_logical_type(DUCKDB_TYPE_BIGINT),
duckdb_create_logical_type(DUCKDB_TYPE_INTEGER)};
auto dst_chunk = duckdb_create_data_chunk(dst_types, 2);
idx_t ref_indices[] = {2, 0};
reference_data_chunk_using_vector_reference_vector(src_chunk, dst_chunk, ref_indices, 2);
REQUIRE(duckdb_data_chunk_get_column_count(dst_chunk) == 2);
REQUIRE(duckdb_data_chunk_get_size(dst_chunk) == 2);
auto dst_type_0 = duckdb_vector_get_column_type(duckdb_data_chunk_get_vector(dst_chunk, 0));
auto dst_type_1 = duckdb_vector_get_column_type(duckdb_data_chunk_get_vector(dst_chunk, 1));
REQUIRE(duckdb_get_type_id(dst_type_0) == DUCKDB_TYPE_BIGINT);
REQUIRE(duckdb_get_type_id(dst_type_1) == DUCKDB_TYPE_INTEGER);
duckdb_destroy_logical_type(&dst_type_0);
duckdb_destroy_logical_type(&dst_type_1);
// Verify that the data pointers are the same
auto dst_bigint_vector = duckdb_data_chunk_get_vector(dst_chunk, 0);
auto dst_int_vector = duckdb_data_chunk_get_vector(dst_chunk, 1);
REQUIRE(duckdb_vector_get_data(dst_bigint_vector) == duckdb_vector_get_data(src_bigint_vector));
REQUIRE(duckdb_vector_get_data(dst_int_vector) == duckdb_vector_get_data(src_int_vector));
src_bigint_data[0] = 9999;
auto dst_bigint_data = (int64_t *)duckdb_vector_get_data(dst_bigint_vector);
REQUIRE(dst_bigint_data[0] == 9999);
duckdb_destroy_data_chunk(&dst_chunk);
for (size_t i = 0; i < 2; i++) {
duckdb_destroy_logical_type(&dst_types[i]);
}
duckdb_destroy_data_chunk(&src_chunk);
for (size_t i = 0; i < 3; i++) {
duckdb_destroy_logical_type(&src_types[i]);
}
}

View File

@@ -0,0 +1,187 @@
#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
TEST_CASE("Test C API examples from the website", "[capi]") {
// NOTE: if any of these break and need to be changed, the website also needs to be updated!
SECTION("connect") {
duckdb_database db;
duckdb_connection con;
if (duckdb_open(NULL, &db) == DuckDBError) {
// handle error
}
if (duckdb_connect(db, &con) == DuckDBError) {
// handle error
}
// run queries...
// cleanup
duckdb_disconnect(&con);
duckdb_close(&db);
}
SECTION("config") {
duckdb_database db;
duckdb_config config;
// create the configuration object
if (duckdb_create_config(&config) == DuckDBError) {
REQUIRE(1 == 0);
}
// set some configuration options
duckdb_set_config(config, "access_mode", "READ_WRITE");
duckdb_set_config(config, "threads", "8");
duckdb_set_config(config, "max_memory", "8GB");
duckdb_set_config(config, "default_order", "DESC");
// open the database using the configuration
if (duckdb_open_ext(NULL, &db, config, NULL) == DuckDBError) {
REQUIRE(1 == 0);
}
// cleanup the configuration object
duckdb_destroy_config(&config);
// run queries...
// cleanup
duckdb_close(&db);
}
SECTION("query") {
duckdb_database db;
duckdb_connection con;
duckdb_state state;
duckdb_result result;
duckdb_open(NULL, &db);
duckdb_connect(db, &con);
// create a table
state = duckdb_query(con, "CREATE TABLE integers(i INTEGER, j INTEGER);", NULL);
if (state == DuckDBError) {
REQUIRE(1 == 0);
}
// insert three rows into the table
state = duckdb_query(con, "INSERT INTO integers VALUES (3, 4), (5, 6), (7, NULL);", NULL);
if (state == DuckDBError) {
REQUIRE(1 == 0);
}
// query rows again
state = duckdb_query(con, "SELECT * FROM integers", &result);
if (state == DuckDBError) {
REQUIRE(1 == 0);
}
// handle the result
idx_t row_count = duckdb_row_count(&result);
idx_t column_count = duckdb_column_count(&result);
for (idx_t row = 0; row < row_count; row++) {
for (idx_t col = 0; col < column_count; col++) {
// if (col > 0) printf(",");
auto str_val = duckdb_value_varchar(&result, col, row);
// printf("%s", str_val);
REQUIRE(1 == 1);
duckdb_free(str_val);
}
// printf("\n");
}
int32_t *i_data = (int32_t *)duckdb_column_data(&result, 0);
int32_t *j_data = (int32_t *)duckdb_column_data(&result, 1);
bool *i_mask = duckdb_nullmask_data(&result, 0);
bool *j_mask = duckdb_nullmask_data(&result, 1);
for (idx_t row = 0; row < row_count; row++) {
if (i_mask[row]) {
// printf("NULL");
} else {
REQUIRE(i_data[row] > 0);
// printf("%d", i_data[row]);
}
// printf(",");
if (j_mask[row]) {
// printf("NULL");
} else {
REQUIRE(j_data[row] > 0);
// printf("%d", j_data[row]);
}
// printf("\n");
}
// destroy the result after we are done with it
duckdb_destroy_result(&result);
duckdb_disconnect(&con);
duckdb_close(&db);
}
SECTION("prepared") {
duckdb_database db;
duckdb_connection con;
duckdb_open(NULL, &db);
duckdb_connect(db, &con);
duckdb_query(con, "CREATE TABLE integers(i INTEGER, j INTEGER)", NULL);
duckdb_prepared_statement stmt;
duckdb_result result;
if (duckdb_prepare(con, "INSERT INTO integers VALUES ($1, $2)", &stmt) == DuckDBError) {
REQUIRE(1 == 0);
}
duckdb_bind_int32(stmt, 1, 42); // the parameter index starts counting at 1!
duckdb_bind_int32(stmt, 2, 43);
// NULL as second parameter means no result set is requested
duckdb_execute_prepared(stmt, NULL);
duckdb_destroy_prepare(&stmt);
// we can also query result sets using prepared statements
if (duckdb_prepare(con, "SELECT * FROM integers WHERE i = ?", &stmt) == DuckDBError) {
REQUIRE(1 == 0);
}
duckdb_bind_int32(stmt, 1, 42);
duckdb_execute_prepared(stmt, &result);
// do something with result
// clean up
duckdb_destroy_result(&result);
duckdb_destroy_prepare(&stmt);
duckdb_disconnect(&con);
duckdb_close(&db);
}
SECTION("appender") {
duckdb_database db;
duckdb_connection con;
duckdb_open(NULL, &db);
duckdb_connect(db, &con);
duckdb_query(con, "CREATE TABLE people(id INTEGER, name VARCHAR)", NULL);
duckdb_appender appender;
if (duckdb_appender_create(con, NULL, "people", &appender) == DuckDBError) {
REQUIRE(1 == 0);
}
duckdb_append_int32(appender, 1);
duckdb_append_varchar(appender, "Mark");
duckdb_appender_end_row(appender);
duckdb_append_int32(appender, 2);
duckdb_append_varchar(appender, "Hannes");
duckdb_appender_end_row(appender);
duckdb_appender_destroy(&appender);
duckdb_result result;
duckdb_query(con, "SELECT * FROM people", &result);
REQUIRE(duckdb_value_int32(&result, 0, 0) == 1);
REQUIRE(duckdb_value_int32(&result, 0, 1) == 2);
REQUIRE(string(duckdb_value_varchar_internal(&result, 1, 0)) == "Mark");
REQUIRE(string(duckdb_value_varchar_internal(&result, 1, 1)) == "Hannes");
// error conditions: we cannot
REQUIRE(duckdb_value_varchar_internal(&result, 0, 0) == nullptr);
REQUIRE(duckdb_value_varchar_internal(nullptr, 0, 0) == nullptr);
duckdb_destroy_result(&result);
duckdb_disconnect(&con);
duckdb_close(&db);
}
}

View File

@@ -0,0 +1,35 @@
#include "catch.hpp"
#include "duckdb.h"
using namespace std;
TEST_CASE("Simple In-Memory DB Start Up and Shutdown", "[simplestartup]") {
duckdb_database database;
duckdb_connection connection;
// open and close a database in in-memory mode
REQUIRE(duckdb_open(NULL, &database) == DuckDBSuccess);
REQUIRE(duckdb_connect(database, &connection) == DuckDBSuccess);
duckdb_disconnect(&connection);
duckdb_close(&database);
}
TEST_CASE("Multiple In-Memory DB Start Up and Shutdown", "[multiplestartup]") {
duckdb_database database[10];
duckdb_connection connection[100];
// open and close 10 databases
// and open 10 connections per database
for (size_t i = 0; i < 10; i++) {
REQUIRE(duckdb_open(NULL, &database[i]) == DuckDBSuccess);
for (size_t j = 0; j < 10; j++) {
REQUIRE(duckdb_connect(database[i], &connection[i * 10 + j]) == DuckDBSuccess);
}
}
for (size_t i = 0; i < 10; i++) {
for (size_t j = 0; j < 10; j++) {
duckdb_disconnect(&connection[i * 10 + j]);
}
duckdb_close(&database[i]);
}
}

View File

@@ -0,0 +1,40 @@
#include "catch.hpp"
#define DUCKDB_API_NO_DEPRECATED
#include "duckdb.h"
using namespace std;
// we only use functions that are cool to use in the 1.0 API
TEST_CASE("Test without deprecated or future moved functions", "[capi]") {
duckdb_database database;
duckdb_connection connection;
duckdb_prepared_statement statement;
duckdb_result result;
REQUIRE(duckdb_open(NULL, &database) == DuckDBSuccess);
REQUIRE(duckdb_connect(database, &connection) == DuckDBSuccess);
REQUIRE(duckdb_prepare(connection, "SELECT ?::INTEGER AS a", &statement) == DuckDBSuccess);
REQUIRE(duckdb_bind_int32(statement, 1, 42) == DuckDBSuccess);
REQUIRE(duckdb_execute_prepared(statement, &result) == DuckDBSuccess);
REQUIRE(duckdb_column_count(&result) == 1);
REQUIRE(string(duckdb_column_name(&result, 0)) == "a");
REQUIRE(duckdb_column_type(&result, 0) == DUCKDB_TYPE_INTEGER);
auto chunk = duckdb_fetch_chunk(result);
REQUIRE(chunk);
auto vector = duckdb_data_chunk_get_vector(chunk, 0);
REQUIRE(vector);
auto validity = duckdb_vector_get_validity(vector);
REQUIRE(duckdb_validity_row_is_valid(validity, 0));
auto data = (int *)duckdb_vector_get_data(vector);
REQUIRE(data);
REQUIRE(data[0] == 42);
duckdb_destroy_data_chunk(&chunk);
duckdb_destroy_result(&result);
duckdb_destroy_prepare(&statement);
duckdb_disconnect(&connection);
duckdb_close(&database);
}