should be it
This commit is contained in:
32
external/duckdb/test/api/capi/CMakeLists.txt
vendored
Normal file
32
external/duckdb/test/api/capi/CMakeLists.txt
vendored
Normal 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)
|
||||
440
external/duckdb/test/api/capi/capi_aggregate_functions.cpp
vendored
Normal file
440
external/duckdb/test/api/capi/capi_aggregate_functions.cpp
vendored
Normal 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 ®ister_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);
|
||||
}
|
||||
260
external/duckdb/test/api/capi/capi_custom_type.cpp
vendored
Normal file
260
external/duckdb/test/api/capi/capi_custom_type.cpp
vendored
Normal 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);
|
||||
}
|
||||
159
external/duckdb/test/api/capi/capi_file_system.cpp
vendored
Normal file
159
external/duckdb/test/api/capi/capi_file_system.cpp
vendored
Normal 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);
|
||||
}
|
||||
646
external/duckdb/test/api/capi/capi_scalar_functions.cpp
vendored
Normal file
646
external/duckdb/test/api/capi/capi_scalar_functions.cpp
vendored
Normal 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);
|
||||
}
|
||||
322
external/duckdb/test/api/capi/capi_table_functions.cpp
vendored
Normal file
322
external/duckdb/test/api/capi/capi_table_functions.cpp
vendored
Normal 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(¶m);
|
||||
|
||||
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(¶m);
|
||||
|
||||
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(¶m);
|
||||
|
||||
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);
|
||||
}
|
||||
737
external/duckdb/test/api/capi/test_capi.cpp
vendored
Normal file
737
external/duckdb/test/api/capi/test_capi.cpp
vendored
Normal 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);
|
||||
}
|
||||
262
external/duckdb/test/api/capi/test_capi_any_invalid_type.cpp
vendored
Normal file
262
external/duckdb/test/api/capi/test_capi_any_invalid_type.cpp
vendored
Normal 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);
|
||||
}
|
||||
140
external/duckdb/test/api/capi/test_capi_append_data_chunk.cpp
vendored
Normal file
140
external/duckdb/test/api/capi/test_capi_append_data_chunk.cpp
vendored
Normal 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]);
|
||||
}
|
||||
1254
external/duckdb/test/api/capi/test_capi_appender.cpp
vendored
Normal file
1254
external/duckdb/test/api/capi/test_capi_appender.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
463
external/duckdb/test/api/capi/test_capi_arrow.cpp
vendored
Normal file
463
external/duckdb/test/api/capi/test_capi_arrow.cpp
vendored
Normal 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]);
|
||||
}
|
||||
}
|
||||
1074
external/duckdb/test/api/capi/test_capi_complex_types.cpp
vendored
Normal file
1074
external/duckdb/test/api/capi/test_capi_complex_types.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
549
external/duckdb/test/api/capi/test_capi_data_chunk.cpp
vendored
Normal file
549
external/duckdb/test/api/capi/test_capi_data_chunk.cpp
vendored
Normal 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);
|
||||
}
|
||||
86
external/duckdb/test/api/capi/test_capi_extract.cpp
vendored
Normal file
86
external/duckdb/test/api/capi/test_capi_extract.cpp
vendored
Normal 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);
|
||||
}
|
||||
67
external/duckdb/test/api/capi/test_capi_instance_cache.cpp
vendored
Normal file
67
external/duckdb/test/api/capi/test_capi_instance_cache.cpp
vendored
Normal 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);
|
||||
}
|
||||
30
external/duckdb/test/api/capi/test_capi_pending.cpp
vendored
Normal file
30
external/duckdb/test/api/capi/test_capi_pending.cpp
vendored
Normal 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);
|
||||
}
|
||||
609
external/duckdb/test/api/capi/test_capi_prepared.cpp
vendored
Normal file
609
external/duckdb/test/api/capi/test_capi_prepared.cpp
vendored
Normal 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, ¶meter_index, "invalid");
|
||||
REQUIRE(status == DuckDBError);
|
||||
|
||||
status = duckdb_bind_parameter_index(stmt, ¶meter_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);
|
||||
}
|
||||
416
external/duckdb/test/api/capi/test_capi_profiling.cpp
vendored
Normal file
416
external/duckdb/test/api/capi/test_capi_profiling.cpp
vendored
Normal 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();
|
||||
}
|
||||
81
external/duckdb/test/api/capi/test_capi_replacement_scan.cpp
vendored
Normal file
81
external/duckdb/test/api/capi/test_capi_replacement_scan.cpp
vendored
Normal 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"));
|
||||
}
|
||||
184
external/duckdb/test/api/capi/test_capi_streaming.cpp
vendored
Normal file
184
external/duckdb/test/api/capi/test_capi_streaming.cpp
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
127
external/duckdb/test/api/capi/test_capi_table_description.cpp
vendored
Normal file
127
external/duckdb/test/api/capi/test_capi_table_description.cpp
vendored
Normal 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();
|
||||
}
|
||||
64
external/duckdb/test/api/capi/test_capi_to_decimal.cpp
vendored
Normal file
64
external/duckdb/test/api/capi/test_capi_to_decimal.cpp
vendored
Normal 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");
|
||||
}
|
||||
438
external/duckdb/test/api/capi/test_capi_values.cpp
vendored
Normal file
438
external/duckdb/test/api/capi/test_capi_values.cpp
vendored
Normal 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);
|
||||
}
|
||||
276
external/duckdb/test/api/capi/test_capi_vector.cpp
vendored
Normal file
276
external/duckdb/test/api/capi/test_capi_vector.cpp
vendored
Normal 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]);
|
||||
}
|
||||
}
|
||||
187
external/duckdb/test/api/capi/test_capi_website.cpp
vendored
Normal file
187
external/duckdb/test/api/capi/test_capi_website.cpp
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
35
external/duckdb/test/api/capi/test_starting_database.cpp
vendored
Normal file
35
external/duckdb/test/api/capi/test_starting_database.cpp
vendored
Normal 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]);
|
||||
}
|
||||
}
|
||||
40
external/duckdb/test/api/capi/test_without_disabled_functions.cpp
vendored
Normal file
40
external/duckdb/test/api/capi/test_without_disabled_functions.cpp
vendored
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user