should be it
This commit is contained in:
62
external/duckdb/test/CMakeLists.txt
vendored
Normal file
62
external/duckdb/test/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
include_directories(../third_party/catch)
|
||||
include_directories(../third_party/tpce-tool/include)
|
||||
include_directories(../third_party/sqlite/include)
|
||||
include_directories(include)
|
||||
|
||||
# Allows loading tests from registered extensions
|
||||
include_directories("${PROJECT_BINARY_DIR}/codegen/include/")
|
||||
add_definitions(-DGENERATED_EXTENSION_HEADERS=1)
|
||||
add_definitions(-DDUCKDB_MAJOR_VERSION=${DUCKDB_MAJOR_VERSION})
|
||||
add_definitions(-DDUCKDB_MINOR_VERSION=${DUCKDB_MINOR_VERSION})
|
||||
add_definitions(-DDUCKDB_PATCH_VERSION=${DUCKDB_PATCH_VERSION})
|
||||
|
||||
if(${ENABLE_UNITTEST_CPP_TESTS})
|
||||
add_subdirectory(api)
|
||||
add_subdirectory(appender)
|
||||
add_subdirectory(arrow)
|
||||
add_subdirectory(catalog)
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(logging)
|
||||
add_subdirectory(memoryleak)
|
||||
add_subdirectory(parallel_csv)
|
||||
add_subdirectory(secrets)
|
||||
add_subdirectory(optimizer)
|
||||
add_subdirectory(serialize)
|
||||
add_subdirectory(sql)
|
||||
add_subdirectory(ossfuzz)
|
||||
add_subdirectory(mbedtls)
|
||||
endif()
|
||||
add_subdirectory(extension)
|
||||
add_subdirectory(helpers)
|
||||
add_subdirectory(sqlite)
|
||||
|
||||
if(NOT WIN32 AND NOT SUN)
|
||||
if(${BUILD_TPCE})
|
||||
add_subdirectory(tpce)
|
||||
endif()
|
||||
add_subdirectory(persistence)
|
||||
endif()
|
||||
|
||||
set(UNITTEST_ROOT_DIRECTORY
|
||||
""
|
||||
CACHE
|
||||
STRING
|
||||
"The unittest root directory (defaults to the project source directory)")
|
||||
if(DEFINED UNITTEST_ROOT_DIRECTORY AND NOT UNITTEST_ROOT_DIRECTORY STREQUAL "")
|
||||
add_definitions(-DDUCKDB_ROOT_DIRECTORY="${UNITTEST_ROOT_DIRECTORY}")
|
||||
else()
|
||||
add_definitions(-DDUCKDB_ROOT_DIRECTORY="${PROJECT_SOURCE_DIR}")
|
||||
endif()
|
||||
|
||||
add_executable(unittest unittest.cpp ${ALL_OBJECT_FILES})
|
||||
|
||||
if(NOT WIN32
|
||||
AND NOT SUN
|
||||
AND NOT ZOS)
|
||||
target_link_libraries(unittest duckdb test_helpers)
|
||||
if(${BUILD_TPCE})
|
||||
target_link_libraries(unittest tpce)
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(unittest duckdb_static test_helpers)
|
||||
endif()
|
||||
12
external/duckdb/test/README.md
vendored
Normal file
12
external/duckdb/test/README.md
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
This is the SQL and C++ DuckDB Unittests
|
||||
|
||||
## Environment Variable Flags
|
||||
|
||||
Some of the tests require a special environment flag to be set so they can properly run.
|
||||
|
||||
### Parallel CSV Reader
|
||||
The tests located in `test/parallel_csv/test_parallel_csv.cpp` run the parallel CSV reader over multiple configurations of threads
|
||||
and buffer sizes.
|
||||
|
||||
### ADBC
|
||||
The ADBC tests are in `test/api/adbc/test_adbc.cpp`. To run them the DuckDB library path must be provided in the `DUCKDB_INSTALL_LIB` variable.
|
||||
55
external/duckdb/test/api/CMakeLists.txt
vendored
Normal file
55
external/duckdb/test/api/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
if(NOT CLANG_TIDY)
|
||||
add_subdirectory(adbc)
|
||||
endif()
|
||||
|
||||
if(${DISABLE_THREADS})
|
||||
add_definitions(-DDUCKDB_NO_THREADS)
|
||||
endif()
|
||||
|
||||
add_subdirectory(capi)
|
||||
add_subdirectory(udf_function)
|
||||
|
||||
set(TEST_API_OBJECTS
|
||||
test_api.cpp
|
||||
test_config.cpp
|
||||
test_custom_allocator.cpp
|
||||
test_extension_setting_autoload.cpp
|
||||
test_instance_cache.cpp
|
||||
test_results.cpp
|
||||
test_reset.cpp
|
||||
test_get_table_names.cpp
|
||||
test_prepared_api.cpp
|
||||
test_table_info.cpp
|
||||
test_appender_api.cpp
|
||||
test_lifecycle_hooks.cpp
|
||||
test_pending_query.cpp
|
||||
test_plan_serialization.cpp
|
||||
test_relation_api.cpp
|
||||
test_query_profiler.cpp
|
||||
test_dbdir.cpp
|
||||
test_pending_with_parameters.cpp
|
||||
test_progress_bar.cpp
|
||||
test_uuid.cpp
|
||||
test_insertion_order_preserving_map.cpp
|
||||
test_bignum.cpp
|
||||
test_threads.cpp
|
||||
test_windows_header_compatibility.cpp
|
||||
test_windows_unicode_path.cpp
|
||||
test_object_cache.cpp)
|
||||
|
||||
if(NOT WIN32)
|
||||
set(TEST_API_OBJECTS ${TEST_API_OBJECTS} test_read_only.cpp)
|
||||
endif()
|
||||
|
||||
if(DUCKDB_EXTENSION_TPCH_SHOULD_LINK)
|
||||
include_directories(../../extension/tpch/include)
|
||||
set(TEST_API_OBJECTS
|
||||
${TEST_API_OBJECTS} test_tpch_with_relations.cpp
|
||||
test_tpch_with_streaming.cpp
|
||||
serialized_plans/test_plan_serialization_bwc.cpp)
|
||||
endif()
|
||||
|
||||
add_library(test_api OBJECT ${TEST_API_OBJECTS})
|
||||
set(ALL_OBJECT_FILES
|
||||
${ALL_OBJECT_FILES} $<TARGET_OBJECTS:test_api>
|
||||
PARENT_SCOPE)
|
||||
5
external/duckdb/test/api/adbc/CMakeLists.txt
vendored
Normal file
5
external/duckdb/test/api/adbc/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
add_library_unity(test_sql_adbc OBJECT test_adbc.cpp)
|
||||
|
||||
set(ALL_OBJECT_FILES
|
||||
${ALL_OBJECT_FILES} $<TARGET_OBJECTS:test_sql_adbc>
|
||||
PARENT_SCOPE)
|
||||
2372
external/duckdb/test/api/adbc/test_adbc.cpp
vendored
Normal file
2372
external/duckdb/test/api/adbc/test_adbc.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
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);
|
||||
}
|
||||
1
external/duckdb/test/api/serialized_plans/db_load.sql
vendored
Normal file
1
external/duckdb/test/api/serialized_plans/db_load.sql
vendored
Normal file
@@ -0,0 +1 @@
|
||||
CALL dbgen(sf=0.01);
|
||||
798
external/duckdb/test/api/serialized_plans/excluded_tests.list
vendored
Normal file
798
external/duckdb/test/api/serialized_plans/excluded_tests.list
vendored
Normal file
@@ -0,0 +1,798 @@
|
||||
# failure in serialization
|
||||
# this means either
|
||||
# (a) the test does not translate nicely to the serialization test (e.g. involves dropping + recreating a table)
|
||||
# (b) this tests a new feature that is not available in the old version
|
||||
test/sql/aggregate/aggregates/approx_top_k.test
|
||||
test/sql/aggregate/aggregates/arg_min_max_n.test
|
||||
test/sql/aggregate/aggregates/arg_min_max_n_tpch.test
|
||||
test/sql/aggregate/aggregates/binning.test
|
||||
test/sql/aggregate/aggregates/histogram_table_function.test
|
||||
test/sql/aggregate/aggregates/test_aggregate_types_scalar.test
|
||||
test/sql/aggregate/aggregates/test_any_value.test
|
||||
test/sql/aggregate/aggregates/test_arg_min_max.test
|
||||
test/sql/aggregate/aggregates/test_arg_min_max_null.test
|
||||
test/sql/aggregate/aggregates/test_avg.test
|
||||
test/sql/aggregate/aggregates/test_binned_histogram.test
|
||||
test/sql/aggregate/aggregates/test_bit_and.test
|
||||
test/sql/aggregate/aggregates/test_bit_or.test
|
||||
test/sql/aggregate/aggregates/test_bit_xor.test
|
||||
test/sql/aggregate/aggregates/test_bitstring_agg.test
|
||||
test/sql/aggregate/aggregates/test_count_all_types.test
|
||||
test/sql/aggregate/aggregates/test_last.test
|
||||
test/sql/aggregate/aggregates/test_product.test
|
||||
test/sql/aggregate/aggregates/test_regression.test
|
||||
test/sql/aggregate/aggregates/test_state_export.test
|
||||
test/sql/aggregate/distinct/distinct_on_order_by.test
|
||||
test/sql/aggregate/distinct/grouped/string_agg.test
|
||||
test/sql/aggregate/distinct/test_distinct.test
|
||||
test/sql/aggregate/group/group_by_limits.test
|
||||
test/sql/aggregate/group/test_group_by.test
|
||||
test/sql/aggregate/qualify/test_qualify_view.test
|
||||
test/sql/aggregate/qualify/test_qualify_view_no_view_dependencies.test
|
||||
test/sql/alter/add_col/test_add_col_transactions.test
|
||||
test/sql/alter/alter_col/test_not_null_in_tran.test
|
||||
test/sql/alter/alter_col/test_not_null_multi_tran.test
|
||||
test/sql/alter/alter_type/test_alter_type_transactions.test
|
||||
test/sql/alter/drop_col/test_drop_col_transactions.test
|
||||
test/sql/alter/rename_col/test_rename_col.test
|
||||
test/sql/alter/rename_col/test_rename_col_rollback.test
|
||||
test/sql/alter/rename_col/test_rename_col_transactions.test
|
||||
test/sql/alter/rename_table/test_rename_table.test
|
||||
test/sql/alter/rename_table/test_rename_table_chain_commit.test
|
||||
test/sql/alter/rename_table/test_rename_table_collision.test
|
||||
test/sql/alter/rename_table/test_rename_table_constraints.test
|
||||
test/sql/alter/rename_table/test_rename_table_many_transactions.test
|
||||
test/sql/alter/rename_table/test_rename_table_transactions.test
|
||||
test/sql/alter/rename_view/test_rename_view.test
|
||||
test/sql/alter/rename_view/test_rename_view_many_transactions.test
|
||||
test/sql/alter/rename_view/test_rename_view_transactions.test
|
||||
test/sql/attach/attach_all_types.test
|
||||
test/sql/attach/attach_checkpoint_vacuum.test
|
||||
test/sql/attach/attach_copy.test
|
||||
test/sql/attach/attach_custom_block_size.test
|
||||
test/sql/attach/attach_database_size.test
|
||||
test/sql/attach/attach_dependencies.test
|
||||
test/sql/attach/attach_did_you_mean.test
|
||||
test/sql/attach/attach_different_alias.test
|
||||
test/sql/attach/attach_duckdb_type.test
|
||||
test/sql/attach/attach_enums.test
|
||||
test/sql/attach/attach_export_import.test
|
||||
test/sql/attach/attach_filepath_roundtrip.test
|
||||
test/sql/attach/attach_foreign_key.test
|
||||
test/sql/attach/attach_httpfs.test
|
||||
test/sql/attach/attach_if_not_exists.test
|
||||
test/sql/attach/attach_index.test
|
||||
test/sql/attach/attach_issue_7660.test
|
||||
test/sql/attach/attach_persistent.test
|
||||
test/sql/attach/attach_pragma_storage_info.test
|
||||
test/sql/attach/attach_read_only.test
|
||||
test/sql/attach/attach_read_only_transaction.test
|
||||
test/sql/attach/attach_reserved.test
|
||||
test/sql/attach/attach_s3.test
|
||||
test/sql/attach/attach_same_db.test
|
||||
test/sql/attach/attach_sequence.test
|
||||
test/sql/attach/attach_table_constraints.test
|
||||
test/sql/attach/attach_table_info.test
|
||||
test/sql/attach/attach_transactionality.test
|
||||
test/sql/attach/attach_wal_alter.test
|
||||
test/sql/attach/reattach_schema.test
|
||||
test/sql/binder/column_value_alias_group.test
|
||||
test/sql/binder/test_case_insensitive_binding.test
|
||||
test/sql/binder/test_null_type_propagation.test
|
||||
test/sql/cast/string_to_list_cast.test
|
||||
test/sql/cast/string_to_struct_cast.test
|
||||
test/sql/cast/test_boolean_cast.test
|
||||
test/sql/catalog/case_insensitive_alter.test
|
||||
test/sql/catalog/case_insensitive_binder.test
|
||||
test/sql/catalog/case_insensitive_caps.test
|
||||
test/sql/catalog/case_insensitive_using.test
|
||||
test/sql/catalog/comment_on.test
|
||||
test/sql/catalog/comment_on_extended.test
|
||||
test/sql/catalog/comment_on_wal.test
|
||||
test/sql/catalog/dependencies/add_column_to_table_referenced_by_view.test
|
||||
test/sql/catalog/dependencies/test_alter_dependency_ownership.test
|
||||
test/sql/catalog/dependencies/test_default_value_dependency.test
|
||||
test/sql/catalog/dependencies/test_prepare_dependencies_transactions.test
|
||||
test/sql/catalog/dependencies/test_schema_dependency.test
|
||||
test/sql/catalog/drop_create_rollback.test
|
||||
test/sql/catalog/function/query_function.test
|
||||
test/sql/catalog/function/test_complex_macro.test
|
||||
test/sql/catalog/function/test_macro_default_arg.test
|
||||
test/sql/catalog/function/test_macro_default_arg_with_dependencies.test
|
||||
test/sql/catalog/function/test_macro_issue_13104.test
|
||||
test/sql/catalog/function/test_macro_overloads.test
|
||||
test/sql/catalog/function/test_sequence_macro.test
|
||||
test/sql/catalog/function/test_simple_macro.test
|
||||
test/sql/catalog/function/test_table_macro.test
|
||||
test/sql/catalog/function/test_table_macro_complex.test
|
||||
test/sql/catalog/function/test_table_macro_groups.test
|
||||
test/sql/catalog/sequence/sequence_cycle.test
|
||||
test/sql/catalog/sequence/sequence_offset_increment.test
|
||||
test/sql/catalog/sequence/sequence_overflow.test
|
||||
test/sql/catalog/sequence/test_duckdb_sequences.test
|
||||
test/sql/catalog/sequence/test_sequence.test
|
||||
test/sql/catalog/test_schema.test
|
||||
test/sql/catalog/test_schema_conflict.test
|
||||
test/sql/catalog/test_set_schema.test
|
||||
test/sql/catalog/test_set_search_path.test
|
||||
test/sql/catalog/test_standard_schema.test
|
||||
test/sql/catalog/test_temporary.test
|
||||
test/sql/catalog/view/test_view.test
|
||||
test/sql/catalog/view/test_view_alias.test
|
||||
test/sql/catalog/view/test_view_schema_change.test
|
||||
test/sql/catalog/view/test_view_schema_change_with_dependencies.test
|
||||
test/sql/collate/collate_order_by_alias.test
|
||||
test/sql/collate/collate_subquery.test
|
||||
test/sql/collate/test_collate_list.test
|
||||
test/sql/collate/test_icu_collate.test
|
||||
test/sql/constraints/foreignkey/test_fk_export.test
|
||||
test/sql/constraints/foreignkey/test_fk_multiple.test
|
||||
test/sql/constraints/foreignkey/test_fk_self_referencing.test
|
||||
test/sql/constraints/foreignkey/test_fk_temporary.test
|
||||
test/sql/constraints/foreignkey/test_foreignkey.test
|
||||
test/sql/constraints/primarykey/test_primary_key.test
|
||||
test/sql/constraints/unique/test_unique.test
|
||||
test/sql/constraints/unique/test_unique_multi_column.test
|
||||
test/sql/constraints/unique/test_unique_string.test
|
||||
test/sql/constraints/unique/test_unique_temp.test
|
||||
test/sql/copy/csv/7702.test
|
||||
test/sql/copy/csv/auto/test_auto_5378.test
|
||||
test/sql/copy/csv/auto/test_auto_8860.test
|
||||
test/sql/copy/csv/auto/test_auto_column_type_opt.test
|
||||
test/sql/copy/csv/auto/test_auto_cranlogs.test
|
||||
test/sql/copy/csv/auto/test_auto_greek_ncvoter.test
|
||||
test/sql/copy/csv/auto/test_auto_greek_utf8.test
|
||||
test/sql/copy/csv/auto/test_auto_imdb.test
|
||||
test/sql/copy/csv/auto/test_auto_lineitem.test
|
||||
test/sql/copy/csv/auto/test_auto_ontime.test
|
||||
test/sql/copy/csv/auto/test_auto_web_page.test
|
||||
test/sql/copy/csv/auto/test_csv_auto.test
|
||||
test/sql/copy/csv/auto/test_date_format_bug_linux.test
|
||||
test/sql/copy/csv/auto/test_fallback_all_varchar.test
|
||||
test/sql/copy/csv/auto/test_header_completion.test
|
||||
test/sql/copy/csv/auto/test_header_detection.test
|
||||
test/sql/copy/csv/auto/test_normalize_names.test
|
||||
test/sql/copy/csv/auto/test_sample_size.test
|
||||
test/sql/copy/csv/auto/test_sniffer_blob.test
|
||||
test/sql/copy/csv/auto/test_type_candidates.test
|
||||
test/sql/copy/csv/auto/test_type_detection.test
|
||||
test/sql/copy/csv/batched_write/batch_csv_write.test
|
||||
test/sql/copy/csv/code_cov/buffer_manager_finalize.test
|
||||
test/sql/copy/csv/code_cov/csv_disk_reload.test
|
||||
test/sql/copy/csv/code_cov/csv_state_machine_invalid_utf.test
|
||||
test/sql/copy/csv/column_names.test
|
||||
test/sql/copy/csv/copy_disable_parallelism.test
|
||||
test/sql/copy/csv/csv_dtypes.test
|
||||
test/sql/copy/csv/csv_dtypes_union_by_name.test
|
||||
test/sql/copy/csv/csv_error_message.test
|
||||
test/sql/copy/csv/csv_external_access.test
|
||||
test/sql/copy/csv/csv_hive.test
|
||||
test/sql/copy/csv/csv_home_directory.test
|
||||
test/sql/copy/csv/csv_limit_copy.test
|
||||
test/sql/copy/csv/csv_names.test
|
||||
test/sql/copy/csv/csv_null_padding.test
|
||||
test/sql/copy/csv/csv_projection_pushdown.test
|
||||
test/sql/copy/csv/csv_roundtrip_single_null.test
|
||||
test/sql/copy/csv/csv_windows_mixed_separators.test
|
||||
test/sql/copy/csv/glob/copy_csv_glob.test
|
||||
test/sql/copy/csv/glob/copy_csv_glob_s3.test
|
||||
test/sql/copy/csv/glob/read_csv_glob.test
|
||||
test/sql/copy/csv/glob/read_csv_glob_crawl.test
|
||||
test/sql/copy/csv/glob/read_csv_glob_s3.test
|
||||
test/sql/copy/csv/overwrite/test_copy_overwrite.test
|
||||
test/sql/copy/csv/parallel/csv_parallel_buffer_size.test
|
||||
test/sql/copy/csv/parallel/csv_parallel_httpfs.test
|
||||
test/sql/copy/csv/parallel/csv_parallel_null_option.test
|
||||
test/sql/copy/csv/parallel/test_5438.test
|
||||
test/sql/copy/csv/parallel/test_5566.test
|
||||
test/sql/copy/csv/parallel/test_multiple_files.test
|
||||
test/sql/copy/csv/parallel/test_parallel_csv.test
|
||||
test/sql/copy/csv/read_csv_variable.test
|
||||
test/sql/copy/csv/recursive_read_csv.test
|
||||
test/sql/copy/csv/rejects/csv_incorrect_columns_amount_rejects.test
|
||||
test/sql/copy/csv/rejects/csv_rejects_auto.test
|
||||
test/sql/copy/csv/rejects/csv_rejects_flush_cast.test
|
||||
test/sql/copy/csv/rejects/csv_rejects_flush_message.test
|
||||
test/sql/copy/csv/rejects/csv_rejects_maximum_line.test
|
||||
test/sql/copy/csv/rejects/csv_rejects_read.test
|
||||
test/sql/copy/csv/rejects/csv_rejects_two_tables.test
|
||||
test/sql/copy/csv/rejects/csv_unquoted_rejects.test
|
||||
test/sql/copy/csv/rejects/test_invalid_parameters.test
|
||||
test/sql/copy/csv/rejects/test_invalid_utf_rejects.test
|
||||
test/sql/copy/csv/rejects/test_mixed.test
|
||||
test/sql/copy/csv/rejects/test_multiple_errors_same_line.test
|
||||
test/sql/copy/csv/struct_padding.test
|
||||
test/sql/copy/csv/test_12596.test
|
||||
test/sql/copy/csv/test_all_quotes.test
|
||||
test/sql/copy/csv/test_allow_quoted_nulls_option.test
|
||||
test/sql/copy/csv/test_bgzf_read.test
|
||||
test/sql/copy/csv/test_big_header.test
|
||||
test/sql/copy/csv/test_blob.test
|
||||
test/sql/copy/csv/test_comment_midline.test
|
||||
test/sql/copy/csv/test_comment_option.test
|
||||
test/sql/copy/csv/test_compression_flag.test
|
||||
test/sql/copy/csv/test_copy.test
|
||||
test/sql/copy/csv/test_copy_default.test
|
||||
test/sql/copy/csv/test_copy_gzip.test
|
||||
test/sql/copy/csv/test_copy_many_empty_lines.test
|
||||
test/sql/copy/csv/test_copy_null.test
|
||||
test/sql/copy/csv/test_csv_duplicate_columns.test
|
||||
test/sql/copy/csv/test_csv_httpfs_prepared.test
|
||||
test/sql/copy/csv/test_csv_json.test
|
||||
test/sql/copy/csv/test_csv_no_trailing_newline.test
|
||||
test/sql/copy/csv/test_csv_projection_pushdown.test
|
||||
test/sql/copy/csv/test_csv_projection_pushdown_glob.test
|
||||
test/sql/copy/csv/test_csv_remote.test
|
||||
test/sql/copy/csv/test_csv_timestamp_tz_icu.test
|
||||
test/sql/copy/csv/test_date.test
|
||||
test/sql/copy/csv/test_date_sniffer.test
|
||||
test/sql/copy/csv/test_dateformat.test
|
||||
test/sql/copy/csv/test_double_sniffer.test
|
||||
test/sql/copy/csv/test_empty_quote.test
|
||||
test/sql/copy/csv/test_escape_long_value.test
|
||||
test/sql/copy/csv/test_export_force_quotes.test
|
||||
test/sql/copy/csv/test_export_not_null.test
|
||||
test/sql/copy/csv/test_force_not_null.test
|
||||
test/sql/copy/csv/test_force_quote.test
|
||||
test/sql/copy/csv/test_glob_reorder_lineitem.test
|
||||
test/sql/copy/csv/test_glob_reorder_null.test
|
||||
test/sql/copy/csv/test_greek_utf8.test
|
||||
test/sql/copy/csv/test_ignore_errors.test
|
||||
test/sql/copy/csv/test_ignore_errors_end_of_chunk.test
|
||||
test/sql/copy/csv/test_ignore_mid_null_line.test
|
||||
test/sql/copy/csv/test_imdb.test
|
||||
test/sql/copy/csv/test_issue3562_assertion.test
|
||||
test/sql/copy/csv/test_lineitem.test
|
||||
test/sql/copy/csv/test_long_line.test
|
||||
test/sql/copy/csv/test_many_columns.test
|
||||
test/sql/copy/csv/test_mismatch_schemas.test
|
||||
test/sql/copy/csv/test_missing_row.test
|
||||
test/sql/copy/csv/test_mixed_line_endings.test
|
||||
test/sql/copy/csv/test_ncvoter.test
|
||||
test/sql/copy/csv/test_nfc.test
|
||||
test/sql/copy/csv/test_nfc_suite.test
|
||||
test/sql/copy/csv/test_non_unicode_header.test
|
||||
test/sql/copy/csv/test_null_padding_projection.test
|
||||
test/sql/copy/csv/test_ontime.test
|
||||
test/sql/copy/csv/test_partition_compression.test
|
||||
test/sql/copy/csv/test_quoted_newline.test
|
||||
test/sql/copy/csv/test_read_csv.test
|
||||
test/sql/copy/csv/test_replacement_scan_alias.test
|
||||
test/sql/copy/csv/test_skip_bom.test
|
||||
test/sql/copy/csv/test_sniff_csv.test
|
||||
test/sql/copy/csv/test_sniff_csv_options.test
|
||||
test/sql/copy/csv/test_sniffer_tab_delimiter.test
|
||||
test/sql/copy/csv/test_time.test
|
||||
test/sql/copy/csv/test_timestamp_offset.test
|
||||
test/sql/copy/csv/test_timestamptz_12926.test
|
||||
test/sql/copy/csv/test_union_by_name.test
|
||||
test/sql/copy/csv/test_web_page.test
|
||||
test/sql/copy/csv/test_windows_newline.test
|
||||
test/sql/copy/csv/tsv_copy.test
|
||||
test/sql/copy/csv/write_header_default.test
|
||||
test/sql/copy/csv/zstd_crash.test
|
||||
test/sql/copy/csv/zstd_fs.test
|
||||
test/sql/copy/file_size_bytes.test
|
||||
test/sql/copy/format_uuid.test
|
||||
test/sql/copy/parquet/batched_write/batch_memory_usage.test
|
||||
test/sql/copy/parquet/batched_write/batched_parquet_write.test
|
||||
test/sql/copy/parquet/delta_byte_array_length_mismatch.test
|
||||
test/sql/copy/parquet/delta_byte_array_multiple_pages.test
|
||||
test/sql/copy/parquet/dictionary_compression_ratio_threshold.test
|
||||
test/sql/copy/parquet/hive_partitioning_struct.test
|
||||
test/sql/copy/parquet/hive_timestamps.test
|
||||
test/sql/copy/parquet/infer_copy_format.test
|
||||
test/sql/copy/parquet/kv_metadata.test
|
||||
test/sql/copy/parquet/lineitem_arrow.test
|
||||
test/sql/copy/parquet/multi_file_conversion_error.test
|
||||
test/sql/copy/parquet/parquet_12621.test
|
||||
test/sql/copy/parquet/parquet_13053_duplicate_column_names.test
|
||||
test/sql/copy/parquet/parquet_3896.test
|
||||
test/sql/copy/parquet/parquet_3989.test
|
||||
test/sql/copy/parquet/parquet_5209.test
|
||||
test/sql/copy/parquet/parquet_5968.test
|
||||
test/sql/copy/parquet/parquet_6044.test
|
||||
test/sql/copy/parquet/parquet_6933.test
|
||||
test/sql/copy/parquet/parquet_copy_type_mismatch.test
|
||||
test/sql/copy/parquet/parquet_encryption.test
|
||||
test/sql/copy/parquet/parquet_encryption_httpfs.test
|
||||
test/sql/copy/parquet/parquet_filename.test
|
||||
test/sql/copy/parquet/parquet_glob.test
|
||||
test/sql/copy/parquet/parquet_glob_s3.test
|
||||
test/sql/copy/parquet/parquet_hive.test
|
||||
test/sql/copy/parquet/parquet_hive2.test
|
||||
test/sql/copy/parquet/parquet_hive_empty.test
|
||||
test/sql/copy/parquet/parquet_hive_null.test
|
||||
test/sql/copy/parquet/parquet_http_prefetch.test
|
||||
test/sql/copy/parquet/parquet_metadata_cache.test
|
||||
test/sql/copy/parquet/parquet_stats.test
|
||||
test/sql/copy/parquet/parquet_write_repeated_lists.test
|
||||
test/sql/copy/parquet/snowflake_lineitem.test
|
||||
test/sql/copy/parquet/test_parquet_duplicate_columns.test
|
||||
test/sql/copy/parquet/test_parquet_force_download.test
|
||||
test/sql/copy/parquet/test_parquet_remote.test
|
||||
test/sql/copy/parquet/test_parquet_remote_foreign_files.test
|
||||
test/sql/copy/parquet/timetz_parquet.test
|
||||
test/sql/copy/parquet/union_by_name_pushdown.test
|
||||
test/sql/copy/parquet/writer/parquet_test_all_types.test
|
||||
test/sql/copy/parquet/writer/parquet_write_booleans.test
|
||||
test/sql/copy/parquet/writer/parquet_write_compression_level.test
|
||||
test/sql/copy/parquet/writer/parquet_write_date.test
|
||||
test/sql/copy/parquet/writer/parquet_write_decimals.test
|
||||
test/sql/copy/parquet/writer/parquet_write_enums.test
|
||||
test/sql/copy/parquet/writer/parquet_write_field_id.test
|
||||
test/sql/copy/parquet/writer/parquet_write_home_directory.test
|
||||
test/sql/copy/parquet/writer/parquet_write_hugeint.test
|
||||
test/sql/copy/parquet/writer/parquet_write_interval.test
|
||||
test/sql/copy/parquet/writer/parquet_write_issue_5779.test
|
||||
test/sql/copy/parquet/writer/parquet_write_string_distinct.test
|
||||
test/sql/copy/parquet/writer/parquet_write_strings.test
|
||||
test/sql/copy/parquet/writer/parquet_write_timestamp.test
|
||||
test/sql/copy/parquet/writer/parquet_write_uhugeint.test
|
||||
test/sql/copy/parquet/writer/parquet_write_uuid.test
|
||||
test/sql/copy/parquet/writer/row_group_size_bytes.test
|
||||
test/sql/copy/parquet/writer/test_copy_overwrite_parquet.test
|
||||
test/sql/copy/parquet/writer/test_parquet_write.test
|
||||
test/sql/copy/parquet/writer/test_parquet_write_complex.test
|
||||
test/sql/copy/parquet/writer/write_complex_nested.test
|
||||
test/sql/copy/parquet/writer/write_list.test
|
||||
test/sql/copy/parquet/writer/write_map.test
|
||||
test/sql/copy/parquet/writer/write_stats_big_string.test
|
||||
test/sql/copy/parquet/writer/write_stats_null_count.test
|
||||
test/sql/copy/parquet/writer/write_struct.test
|
||||
test/sql/copy/partitioned/hive_filter_pushdown.test
|
||||
test/sql/copy/partitioned/hive_partition_append.test
|
||||
test/sql/copy/partitioned/hive_partition_compression.test
|
||||
test/sql/copy/partitioned/hive_partition_duplicate_name.test
|
||||
test/sql/copy/partitioned/hive_partition_escape.test
|
||||
test/sql/copy/partitioned/hive_partition_join_pushdown.test
|
||||
test/sql/copy/partitioned/hive_partition_recursive_cte.test
|
||||
test/sql/copy/partitioned/hive_partitioned_auto_detect.test
|
||||
test/sql/copy/partitioned/hive_partitioned_write.test
|
||||
test/sql/copy/partitioned/hive_partitioning_overwrite.test
|
||||
test/sql/copy/partitioned/partition_issue_6304.test
|
||||
test/sql/copy/partitioned/skip_partition_column_writes.test
|
||||
test/sql/copy/per_thread_output.test
|
||||
test/sql/copy/return_files.test
|
||||
test/sql/copy/row_groups_per_file.test
|
||||
test/sql/copy/s3/download_config.test
|
||||
test/sql/copy/s3/fully_qualified_s3_url.test
|
||||
test/sql/copy/s3/hive_partitioned_write_s3.test
|
||||
test/sql/copy/s3/http_proxy.test
|
||||
test/sql/copy/s3/http_secret.test
|
||||
test/sql/copy/s3/metadata_cache.test
|
||||
test/sql/copy/s3/s3_hive_partition.test
|
||||
test/sql/copy/s3/s3_presigned_read.test
|
||||
test/sql/copy/s3/starstar.test
|
||||
test/sql/copy/s3/upload_small_file.test
|
||||
test/sql/copy/s3/url_encode.test
|
||||
test/sql/copy/tmp_file.test
|
||||
test/sql/copy_database/copy_database_errors.test
|
||||
test/sql/copy_database/copy_database_gen_col.test
|
||||
test/sql/copy_database/copy_database_index.test
|
||||
test/sql/create/create_table_compression.test
|
||||
test/sql/cte/materialized/test_recursive_cte_tutorial_materialized.test
|
||||
test/sql/cte/test_cte.test
|
||||
test/sql/cte/test_cte_in_cte.test
|
||||
test/sql/cte/test_recursive_cte_tutorial.test
|
||||
test/sql/cte/test_recursive_cte_tutorial.test
|
||||
test/sql/delete/test_segment_deletes.test
|
||||
test/sql/detailed_profiler/test_detailed_profiler.test
|
||||
test/sql/explain/test_explain.test
|
||||
test/sql/explain/test_explain_analyze.test
|
||||
test/sql/export/empty_export.test
|
||||
test/sql/export/export_database.test
|
||||
test/sql/export/export_external_access.test
|
||||
test/sql/export/export_functions.test
|
||||
test/sql/export/export_generated_columns.test
|
||||
test/sql/export/export_macros.test
|
||||
test/sql/export/export_quoted_enum.test
|
||||
test/sql/export/export_quoted_structs.test
|
||||
test/sql/export/export_quoted_union.test
|
||||
test/sql/export/parquet/export_parquet_bit.test
|
||||
test/sql/export/parquet/export_parquet_enum.test
|
||||
test/sql/export/parquet/export_parquet_hugeint.test
|
||||
test/sql/export/parquet/export_parquet_json.test
|
||||
test/sql/export/parquet/export_parquet_list.test
|
||||
test/sql/export/parquet/export_parquet_map.test
|
||||
test/sql/export/parquet/export_parquet_struct.test
|
||||
test/sql/export/parquet/export_parquet_union.test
|
||||
test/sql/export/parquet_export.test
|
||||
test/sql/extensions/version_is_valid_sqlite.test
|
||||
test/sql/filter/test_struct_pushdown.test
|
||||
test/sql/fts/issue_12330.test
|
||||
test/sql/fts/test_fts_attach.test
|
||||
test/sql/fts/test_issue_10254.test
|
||||
test/sql/fts/test_issue_10281.test
|
||||
test/sql/function/date/test_date_part.test
|
||||
test/sql/function/generic/can_cast_implicitly.test
|
||||
test/sql/function/generic/test_in.test
|
||||
test/sql/function/generic/test_null_if.test
|
||||
test/sql/function/interval/test_date_part.test
|
||||
test/sql/function/list/aggregates/any_value.test
|
||||
test/sql/function/list/aggregates/avg.test
|
||||
test/sql/function/list/aggregates/bit_and.test
|
||||
test/sql/function/list/aggregates/bit_or.test
|
||||
test/sql/function/list/aggregates/bit_xor.test
|
||||
test/sql/function/list/aggregates/first.test
|
||||
test/sql/function/list/aggregates/last.test
|
||||
test/sql/function/list/aggregates/max.test
|
||||
test/sql/function/list/aggregates/min.test
|
||||
test/sql/function/list/array_to_string_comma_default.test
|
||||
test/sql/function/list/lambdas/list_comprehension.test
|
||||
test/sql/function/list/lambdas/table_functions.test
|
||||
test/sql/function/list/list_contains.test
|
||||
test/sql/function/list/list_has_any_and_has_all.test
|
||||
test/sql/function/list/list_inner_product.test
|
||||
test/sql/function/list/list_position.test
|
||||
test/sql/function/list/list_sort_having.test
|
||||
test/sql/function/list/list_zip.test
|
||||
test/sql/function/list/repeat_list.test
|
||||
test/sql/function/numeric/test_fdiv_fmod.test
|
||||
test/sql/function/numeric/test_random.test
|
||||
test/sql/function/numeric/test_trigo.test
|
||||
test/sql/function/operator/test_bitwise_ops.test
|
||||
test/sql/function/operator/test_bitwise_ops_types.test
|
||||
test/sql/function/operator/test_in_empty_table.test
|
||||
test/sql/function/string/md5.test
|
||||
test/sql/function/string/regex_search.test
|
||||
test/sql/function/string/sha1.test
|
||||
test/sql/function/string/sha256.test
|
||||
test/sql/function/string/test_damerau_levenshtein.test
|
||||
test/sql/function/string/test_format_extensions.test
|
||||
test/sql/function/string/test_mismatches.test
|
||||
test/sql/function/string/test_printf.test
|
||||
test/sql/function/string/test_url_encode.test
|
||||
test/sql/function/time/test_date_part.test
|
||||
test/sql/function/timestamp/age.test
|
||||
test/sql/function/timestamp/test_date_part.test
|
||||
test/sql/function/timestamp/test_icu_age.test
|
||||
test/sql/function/timestamp/test_now.test
|
||||
test/sql/function/timetz/test_date_part.test
|
||||
test/sql/function/uuid/test_uuid.test
|
||||
test/sql/generated_columns/virtual/rename.test
|
||||
test/sql/httpfs/internal_issue_2490.test
|
||||
test/sql/index/art/create_drop/test_art_create_index_delete.test
|
||||
test/sql/index/art/create_drop/test_art_create_many_duplicates.test
|
||||
test/sql/index/art/create_drop/test_art_many_versions.test
|
||||
test/sql/index/art/insert_update_delete/test_art_update_other_column.test
|
||||
test/sql/index/art/issues/test_art_issue_6603.test
|
||||
test/sql/index/art/issues/test_art_issue_7349.test
|
||||
test/sql/index/art/nodes/test_art_leaf.test
|
||||
test/sql/index/art/nodes/test_art_node_16.test
|
||||
test/sql/index/art/nodes/test_art_node_256.test
|
||||
test/sql/index/art/nodes/test_art_node_4.test
|
||||
test/sql/index/art/nodes/test_art_node_48.test
|
||||
test/sql/index/art/scan/test_art_adaptive_scan.test
|
||||
test/sql/index/art/scan/test_art_many_matches.test
|
||||
test/sql/index/art/scan/test_art_negative_range_scan.test
|
||||
test/sql/index/art/scan/test_art_range_scan.test
|
||||
test/sql/index/art/scan/test_art_scan_thresholds.test
|
||||
test/sql/index/art/storage/test_art_auto_checkpoint.test
|
||||
test/sql/index/art/storage/test_art_duckdb_versions.test
|
||||
test/sql/index/art/storage/test_art_import.test
|
||||
test/sql/index/art/storage/test_art_import_export.test
|
||||
test/sql/index/art/types/test_art_varchar.test
|
||||
test/sql/index/art/vacuum/test_art_vacuum_rollback.test
|
||||
test/sql/insert/unaligned_interleaved_appends.test
|
||||
test/sql/join/iejoin/iejoin_issue_6861.test
|
||||
test/sql/join/iejoin/iejoin_issue_7278.test
|
||||
test/sql/join/iejoin/merge_join_switch.test
|
||||
test/sql/join/iejoin/predicate_expressions.test
|
||||
test/sql/join/iejoin/test_iejoin.test
|
||||
test/sql/join/iejoin/test_iejoin_east_west.test
|
||||
test/sql/join/iejoin/test_iejoin_events.test
|
||||
test/sql/join/iejoin/test_iejoin_null_keys.test
|
||||
test/sql/join/iejoin/test_iejoin_overlaps.test
|
||||
test/sql/join/inner/empty_tinyint_column.test
|
||||
test/sql/join/inner/equality_join_limits.test
|
||||
test/sql/join/inner/test_using_chain.test
|
||||
test/sql/join/natural/natural_join.test
|
||||
test/sql/join/semianti/antijoin.test
|
||||
test/sql/join/semianti/right_anti.test
|
||||
test/sql/join/semianti/right_semi.test
|
||||
test/sql/join/semianti/semijoin.test
|
||||
test/sql/json/issues/internal_issue2732.test
|
||||
test/sql/json/issues/issue12188.test
|
||||
test/sql/json/issues/issue12861.test
|
||||
test/sql/json/issues/issue13212.test
|
||||
test/sql/json/issues/read_json_memory_usage.test
|
||||
test/sql/json/scalar/test_json_exists.test
|
||||
test/sql/json/scalar/test_json_path.test
|
||||
test/sql/json/scalar/test_json_pretty.test
|
||||
test/sql/json/scalar/test_json_value.test
|
||||
test/sql/json/table/json_multi_file_reader.test
|
||||
test/sql/json/table/read_json.test
|
||||
test/sql/json/table/read_json_objects.test
|
||||
test/sql/json/test_json_empty_object.test
|
||||
test/sql/json/test_json_export.test
|
||||
test/sql/json/test_json_serialize_sql.test
|
||||
test/sql/limit/test_preserve_insertion_order.test
|
||||
test/sql/optimizer/expression/test_nop_arithmetic.test
|
||||
test/sql/overflow/table_overflow.test
|
||||
test/sql/parallelism/intraquery/parallel_sample.test
|
||||
test/sql/parallelism/intraquery/test_parallel_nested_aggregates.test
|
||||
test/sql/parallelism/intraquery/test_persistent_parallelism.test
|
||||
test/sql/parallelism/intraquery/test_simple_parallelism.test
|
||||
test/sql/parallelism/intraquery/test_verify_parallelism.test
|
||||
test/sql/parser/test_columns_unpacked.test
|
||||
test/sql/parser/test_value_functions.test
|
||||
test/sql/pg_catalog/pg_privilege.test
|
||||
test/sql/pivot/optional_pivots.test
|
||||
test/sql/pivot/pivot_6390.test
|
||||
test/sql/pivot/pivot_bigquery.test
|
||||
test/sql/pivot/pivot_databricks.test
|
||||
test/sql/pivot/pivot_empty.test
|
||||
test/sql/pivot/pivot_example.test
|
||||
test/sql/pivot/pivot_expressions.test
|
||||
test/sql/pivot/pivot_in_boolean.test
|
||||
test/sql/pivot/pivot_in_subquery.test
|
||||
test/sql/pivot/pivot_struct_aggregate.test
|
||||
test/sql/pivot/pivot_subquery.test
|
||||
test/sql/pivot/top_level_pivot_syntax.test
|
||||
test/sql/pragma/pragma_database_size_readonly.test
|
||||
test/sql/pragma/test_custom_optimizer_profiling.test
|
||||
test/sql/pragma/test_custom_profiling_settings.test
|
||||
test/sql/pragma/test_metadata_info.test
|
||||
test/sql/pragma/test_pragma_database_list.test
|
||||
test/sql/pragma/test_pragma_database_size.test
|
||||
test/sql/pragma/test_pragma_functions.test
|
||||
test/sql/pragma/test_pragma_parsing.test
|
||||
test/sql/pragma/test_pragma_version.test
|
||||
test/sql/pragma/test_query_log.test
|
||||
test/sql/pragma/test_show_tables.test
|
||||
test/sql/pragma/test_storage_info.test
|
||||
test/sql/pragma/test_table_info.test
|
||||
test/sql/prepared/prepare_copy.test
|
||||
test/sql/prepared/prepare_maintain_types.test
|
||||
test/sql/prepared/prepared_null_binding.test
|
||||
test/sql/prepared/test_prepare_delete.test
|
||||
test/sql/prepared/test_prepare_delete_update.test
|
||||
test/sql/projection/test_row_id_expression.test
|
||||
test/sql/returning/returning_update.test
|
||||
test/sql/secrets/create_secret_binding.test
|
||||
test/sql/secrets/create_secret_defaults.test
|
||||
test/sql/secrets/create_secret_hffs.test
|
||||
test/sql/secrets/create_secret_minio.test
|
||||
test/sql/secrets/create_secret_name_conflicts.test
|
||||
test/sql/secrets/create_secret_non_writable_persistent_dir.test
|
||||
test/sql/secrets/create_secret_overwriting.test
|
||||
test/sql/secrets/create_secret_persistence.test
|
||||
test/sql/secrets/create_secret_persistence_error_handling.test
|
||||
test/sql/secrets/create_secret_r2.test
|
||||
test/sql/secrets/create_secret_r2_serialization.test
|
||||
test/sql/secrets/create_secret_s3_serialization.test
|
||||
test/sql/secrets/create_secret_scope_matching.test
|
||||
test/sql/secrets/create_secret_settings.test
|
||||
test/sql/secrets/create_secret_storage_backends.test
|
||||
test/sql/secrets/create_secret_transactional.test
|
||||
test/sql/select/test_multi_column_reference.test
|
||||
test/sql/setops/test_union_all_by_name.test
|
||||
test/sql/settings/test_disabled_file_system_httpfs.test
|
||||
test/sql/settings/test_disabled_file_systems.test
|
||||
test/sql/show_select/test_describe_quoted.test
|
||||
test/sql/show_select/test_summarize_quoted.test
|
||||
test/sql/storage/bc/test_view_v092.test
|
||||
test/sql/storage/buffer_manager_temp_dir.test
|
||||
test/sql/storage/catalog/test_macro_storage.test
|
||||
test/sql/storage/catalog/test_sequence_uncommitted_transaction.test
|
||||
test/sql/storage/catalog/test_store_default_sequence.test
|
||||
test/sql/storage/catalog/test_store_rename_column.test
|
||||
test/sql/storage/catalog/test_store_rename_table.test
|
||||
test/sql/storage/catalog/test_store_rename_view.test
|
||||
test/sql/storage/catalog/test_store_sequences.test
|
||||
test/sql/storage/catalog/test_store_temporary.test
|
||||
test/sql/storage/catalog/test_table_macro_storage.test
|
||||
test/sql/storage/catalog/test_view_explicit_aliases.test
|
||||
test/sql/storage/catalog/test_view_storage.test
|
||||
test/sql/storage/catalog/test_view_storage_no_view_dependencies.test
|
||||
test/sql/storage/commit_abort.test
|
||||
test/sql/storage/compact_block_size/block_size_with_rollback.test
|
||||
test/sql/storage/compact_block_size/compact_block_size.test
|
||||
test/sql/storage/compact_block_size/compact_vector_size.test
|
||||
test/sql/storage/compact_block_size/create_table_compression.test
|
||||
test/sql/storage/compact_block_size/default_block_size.test
|
||||
test/sql/storage/compact_block_size/ensure_bitpacking.test
|
||||
test/sql/storage/compact_block_size/ensure_no_bitpacking.test
|
||||
test/sql/storage/compact_block_size/insertion_order_odd_batches.test
|
||||
test/sql/storage/compact_block_size/mixed_block_sizes.test
|
||||
test/sql/storage/compression/alp/alp_inf_null_nan.test
|
||||
test/sql/storage/compression/alp/alp_nulls.test
|
||||
test/sql/storage/compression/alp/alp_nulls_simple.test
|
||||
test/sql/storage/compression/alprd/alprd_inf_null_nan.test
|
||||
test/sql/storage/compression/alprd/alprd_nulls.test
|
||||
test/sql/storage/compression/alprd/alprd_nulls_simple.test
|
||||
test/sql/storage/compression/bitpacking/bitpacking_hugeint.test
|
||||
test/sql/storage/compression/bitpacking/bitpacking_size_calculation.test
|
||||
test/sql/storage/compression/bitpacking/bitpacking_uhugeint.test
|
||||
test/sql/storage/multiple_clients_checkpoint_pending_updates.test
|
||||
test/sql/storage/shutdown_running_transaction.test
|
||||
test/sql/storage/shutdown_running_transaction_updates.test
|
||||
test/sql/storage/temp_directory/max_swap_space_error.test
|
||||
test/sql/storage/temp_directory/max_swap_space_explicit.test
|
||||
test/sql/storage/temp_directory/max_swap_space_inmemory.test
|
||||
test/sql/storage/temp_directory/max_swap_space_persistent.test
|
||||
test/sql/storage/unicode_filename.test
|
||||
test/sql/storage/wal/test_wal_bc.test
|
||||
test/sql/storage/wal/wal_lazy_creation.test
|
||||
test/sql/storage/wal/wal_sequence_uncommitted_transaction.test
|
||||
test/sql/storage/wal/wal_storage_types.test
|
||||
test/sql/storage/wal/wal_store_default_sequence.test
|
||||
test/sql/storage/wal/wal_store_rename_column.test
|
||||
test/sql/storage/wal/wal_store_rename_table.test
|
||||
test/sql/storage/wal/wal_store_rename_view.test
|
||||
test/sql/storage/wal/wal_store_sequences.test
|
||||
test/sql/storage/wal/wal_store_temporary.test
|
||||
test/sql/storage/wal/wal_view_explicit_aliases.test
|
||||
test/sql/storage/wal/wal_view_explicit_aliases_no_view_dependencies.test
|
||||
test/sql/storage/wal/wal_view_storage.test
|
||||
test/sql/storage/wal/wal_view_storage_no_view_dependencies.test
|
||||
test/sql/subquery/any_all/subquery_in.test
|
||||
test/sql/subquery/lateral/lateral_binding_views.test
|
||||
test/sql/subquery/lateral/test_lateral_join.test
|
||||
test/sql/subquery/scalar/test_correlated_subquery.test
|
||||
test/sql/subquery/scalar/test_correlated_subquery_cte.test
|
||||
test/sql/subquery/scalar/test_issue_6136.test
|
||||
test/sql/subquery/scalar/test_scalar_subquery.test
|
||||
test/sql/subquery/scalar/test_scalar_subquery_cte.test
|
||||
test/sql/subquery/scalar/test_uncorrelated_scalar_subquery.test
|
||||
test/sql/table_function/duckdb_constraints_fk.test
|
||||
test/sql/table_function/duckdb_databases.test
|
||||
test/sql/table_function/information_schema.test
|
||||
test/sql/table_function/range_function_lateral.test
|
||||
test/sql/table_function/range_timestamp.test
|
||||
test/sql/table_function/sqlite_master_connections.test
|
||||
test/sql/tpcds/dsdgen_readonly.test
|
||||
test/sql/tpch/dbgen_readonly.test
|
||||
test/sql/transactions/aborted_transaction_commit.test
|
||||
test/sql/transactions/test_basic_transactions.test
|
||||
test/sql/transactions/test_from_update_conflict.test
|
||||
test/sql/transactions/test_index_abort.test
|
||||
test/sql/transactions/test_index_rollback_flushed_data.test
|
||||
test/sql/transactions/test_index_transaction_local.test
|
||||
test/sql/transactions/test_interleaved_versions.test
|
||||
test/sql/transactions/test_multi_transaction_append.test
|
||||
test/sql/transactions/test_read_only_transactions.test
|
||||
test/sql/transactions/test_transactional_sequences.test
|
||||
test/sql/transactions/transaction_insert_delete_chunks.test
|
||||
test/sql/transactions/transaction_insert_mixed_deletes.test
|
||||
test/sql/types/alias/test_alias_table.test
|
||||
test/sql/types/bit/test_bit.test
|
||||
test/sql/types/enum/test_enum_schema.test
|
||||
test/sql/types/enum/test_enum_table.test
|
||||
test/sql/types/enum/test_enum_to_numbers.test
|
||||
test/sql/types/interval/interval_try_cast.test
|
||||
test/sql/types/list/unnest_complex_types.test
|
||||
test/sql/types/list/unnest_table_function.test
|
||||
test/sql/types/list/unnest_types.test
|
||||
test/sql/types/map/map_const_and_col_combination.test
|
||||
test/sql/types/nested/array/array_aggregate.test
|
||||
test/sql/types/nested/array/array_fuzzer_failures.test
|
||||
test/sql/types/nested/array/array_roundtrip_csv.test
|
||||
test/sql/types/nested/array/array_roundtrip_json.test
|
||||
test/sql/types/nested/array/array_roundtrip_parquet.test
|
||||
test/sql/types/nested/array/array_simple.test
|
||||
test/sql/types/nested/array/array_tupleformat.test
|
||||
test/sql/types/nested/list/test_list_extract.test
|
||||
test/sql/types/nested/map/map_from_entries/column_null_entry.test
|
||||
test/sql/types/nested/map/test_map_cardinality.test
|
||||
test/sql/types/nested/map/test_map_concat.test
|
||||
test/sql/types/nested/map/test_map_contains.test
|
||||
test/sql/types/nested/map/test_map_subscript.test
|
||||
test/sql/types/nested/map/test_map_subscript_composite.test
|
||||
test/sql/types/nested/map/test_map_vector_types.test
|
||||
test/sql/types/nested/struct/struct_aggregates_types.test
|
||||
test/sql/types/null/test_null.test
|
||||
test/sql/types/struct/struct_cast.test
|
||||
test/sql/types/struct/unnest_struct_mix.test
|
||||
test/sql/types/time/test_time.test
|
||||
test/sql/types/union/struct_to_json_union.test
|
||||
test/sql/types/union/union_cast.test
|
||||
test/sql/types/union/union_join.test
|
||||
test/sql/types/bignum/test_big_bignum.test
|
||||
test/sql/types/bignum/test_double_bignum.test
|
||||
test/sql/types/bignum/test_int_bignum_conversion.test
|
||||
test/sql/types/bignum/test_varchar_bignum_conversion.test
|
||||
test/sql/types/bignum/test_varchar_bignum_unhappy.test
|
||||
test/sql/types/bignum/test_bignum_boundaries.test
|
||||
test/sql/types/bignum/test_bignum_comparisons.test
|
||||
test/sql/types/bignum/test_bignum_double.test
|
||||
test/sql/types/bignum/test_bignum_hugeint.test
|
||||
test/sql/types/bignum/test_bignum_implicit_cast.test
|
||||
test/sql/update/string_update_transaction_local_7348.test
|
||||
test/sql/update/test_cascading_updates.test
|
||||
test/sql/update/test_update_delete_same_tuple.test
|
||||
test/sql/update/test_update_issue_3170.test
|
||||
test/sql/update/test_update_many_updaters.test
|
||||
test/sql/update/test_update_many_updaters_nulls.test
|
||||
test/sql/upsert/postgres/planner_preprocessing.test
|
||||
test/sql/upsert/upsert_conflict_in_different_chunk.test
|
||||
test/sql/upsert/upsert_distinct_bug.test
|
||||
test/sql/upsert/upsert_lambda.test
|
||||
test/sql/upsert/upsert_local_no_tuples.test
|
||||
test/sql/variables/test_variables.test
|
||||
test/sql/window/test_empty_frames.test
|
||||
test/sql/window/test_thread_count.test
|
||||
test/sql/window/test_tpcds_q49.test
|
||||
test/sql/window/test_window_bool.test
|
||||
test/sql/window/test_window_order_collate.test
|
||||
test/sql/window/test_window_range.test
|
||||
test/sql/window/test_window_tpcds.test
|
||||
test/sql/pg_catalog/system_functions.test
|
||||
test/sql/copy/csv/test_soccer_kaggle.test
|
||||
test/sql/copy/csv/test_headers_12089.test
|
||||
test/sql/order/test_order_range_mapping.test
|
||||
test/sql/order/test_order_by_exceptions.test
|
||||
test/sql/order/test_order_by.test
|
||||
test/sql/order/test_limit.test
|
||||
test/sql/table_function/duckdb_constraints.test
|
||||
test/sql/table_function/duckdb_constraints_issue12863.test
|
||||
test/sql/table_function/information_schema_fkey_constraint_names.test
|
||||
|
||||
# infinite loop bug in older version
|
||||
test/sql/cte/test_recursive_cte_union.test
|
||||
test/sql/cte/test_recursive_cte_union_all.test
|
||||
|
||||
# collation fixes
|
||||
test/sql/collate/collate_ordered_aggregate.test
|
||||
|
||||
# timetz ordering fixes
|
||||
test/sql/types/time/test_time_tz.test
|
||||
test/sql/function/operator/test_date_arithmetic.test
|
||||
|
||||
# histogram/list_distinct type fix
|
||||
test/sql/function/list/list_distinct.test
|
||||
test/sql/function/list/aggregates/histogram.test
|
||||
test/sql/aggregate/aggregates/test_mode.test
|
||||
test/sql/aggregate/aggregates/test_histogram.test
|
||||
test/sql/aggregate/aggregates/test_approx_quantile.test
|
||||
|
||||
# least/greatest type fix
|
||||
test/sql/function/generic/test_least_greatest.test
|
||||
test/sql/function/generic/least_greatest_enum.test
|
||||
test/sql/types/uhugeint/test_uhugeint_functions.test
|
||||
|
||||
# null type changes
|
||||
test/sql/types/list/list_concat_null.test
|
||||
test/sql/function/string/test_concat_function.test
|
||||
test/sql/function/string/test_concat_binding.test
|
||||
test/sql/function/string/test_concat.test
|
||||
|
||||
# lateral join plan fix
|
||||
test/sql/subquery/lateral/lateral_grouping_sets.test
|
||||
|
||||
# median/quantile type fixes
|
||||
test/sql/types/nested/array/array_summarize.test
|
||||
test/sql/show_select/test_summarize.test
|
||||
|
||||
# information schema fixes
|
||||
test/sql/table_function/information_schema_issue12867.test
|
||||
|
||||
# test_all_types changes
|
||||
test/sql/json/scalar/json_nested_casts.test
|
||||
test/sql/function/string/hex.test
|
||||
test/sql/function/list/lambdas/lambdas_and_group_by.test
|
||||
test/sql/subquery/exists/test_exists_union_by_name.test
|
||||
|
||||
# extension load mismatch
|
||||
test/sql/function/timestamp/test_try_strptime.test
|
||||
test/sql/function/timestamp/test_strptime.test
|
||||
test/sql/function/timestamp/test_icu_strftime.test
|
||||
test/sql/extensions/version_is_valid_httpfs.test
|
||||
|
||||
# quantile bug fixes
|
||||
test/sql/aggregate/aggregates/test_quantile_disc_list.test
|
||||
test/sql/aggregate/aggregates/test_quantile_disc.test
|
||||
test/sql/aggregate/aggregates/test_median.test
|
||||
|
||||
# nanosecond fix
|
||||
test/sql/function/timestamp/test_strftime_timestamp_ns.test
|
||||
|
||||
# concat type change
|
||||
test/sql/types/blob/test_blob_function.test
|
||||
|
||||
# rowsort (FIXME: this can be addressed in the testing framework)
|
||||
test/sql/function/string/regex_capture.test
|
||||
|
||||
# dollar quote parser fix
|
||||
test/sql/parser/dollar_quotes_internal_issue2224.test
|
||||
24
external/duckdb/test/api/serialized_plans/queries.sql
vendored
Normal file
24
external/duckdb/test/api/serialized_plans/queries.sql
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
SELECT l_returnflag, l_linestatus, sum(l_quantity) AS sum_qty, sum(l_extendedprice) AS sum_base_price, sum(l_extendedprice * (1 - l_discount)) AS sum_disc_price, sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) AS sum_charge, avg(l_quantity) AS avg_qty, avg(l_extendedprice) AS avg_price, avg(l_discount) AS avg_disc, count(*) AS count_order FROM lineitem WHERE l_shipdate <= CAST('1998-09-02' AS date) GROUP BY l_returnflag, l_linestatus ORDER BY l_returnflag, l_linestatus;
|
||||
SELECT s_acctbal, s_name, n_name, p_partkey, p_mfgr, s_address, s_phone, s_comment FROM part, supplier, partsupp, nation, region WHERE p_partkey = ps_partkey AND s_suppkey = ps_suppkey AND p_size = 15 AND p_type LIKE '%BRASS' AND s_nationkey = n_nationkey AND n_regionkey = r_regionkey AND r_name = 'EUROPE' AND ps_supplycost = ( SELECT min(ps_supplycost) FROM partsupp, supplier, nation, region WHERE p_partkey = ps_partkey AND s_suppkey = ps_suppkey AND s_nationkey = n_nationkey AND n_regionkey = r_regionkey AND r_name = 'EUROPE') ORDER BY s_acctbal DESC, n_name, s_name, p_partkey LIMIT 100;
|
||||
SELECT l_orderkey, sum(l_extendedprice * (1 - l_discount)) AS revenue, o_orderdate, o_shippriority FROM customer, orders, lineitem WHERE c_mktsegment = 'BUILDING' AND c_custkey = o_custkey AND l_orderkey = o_orderkey AND o_orderdate < CAST('1995-03-15' AS date) AND l_shipdate > CAST('1995-03-15' AS date) GROUP BY l_orderkey, o_orderdate, o_shippriority ORDER BY revenue DESC, o_orderdate LIMIT 10;
|
||||
SELECT o_orderpriority, count(*) AS order_count FROM orders WHERE o_orderdate >= CAST('1993-07-01' AS date) AND o_orderdate < CAST('1993-10-01' AS date) AND EXISTS ( SELECT * FROM lineitem WHERE l_orderkey = o_orderkey AND l_commitdate < l_receiptdate) GROUP BY o_orderpriority ORDER BY o_orderpriority;
|
||||
SELECT n_name, sum(l_extendedprice * (1 - l_discount)) AS revenue FROM customer, orders, lineitem, supplier, nation, region WHERE c_custkey = o_custkey AND l_orderkey = o_orderkey AND l_suppkey = s_suppkey AND c_nationkey = s_nationkey AND s_nationkey = n_nationkey AND n_regionkey = r_regionkey AND r_name = 'ASIA' AND o_orderdate >= CAST('1994-01-01' AS date) AND o_orderdate < CAST('1995-01-01' AS date) GROUP BY n_name ORDER BY revenue DESC;
|
||||
SELECT sum(l_extendedprice * l_discount) AS revenue FROM lineitem WHERE l_shipdate >= CAST('1994-01-01' AS date) AND l_shipdate < CAST('1995-01-01' AS date) AND l_discount BETWEEN 0.05 AND 0.07 AND l_quantity < 24;
|
||||
SELECT supp_nation, cust_nation, l_year, sum(volume) AS revenue FROM ( SELECT n1.n_name AS supp_nation, n2.n_name AS cust_nation, extract(year FROM l_shipdate) AS l_year, l_extendedprice * (1 - l_discount) AS volume FROM supplier, lineitem, orders, customer, nation n1, nation n2 WHERE s_suppkey = l_suppkey AND o_orderkey = l_orderkey AND c_custkey = o_custkey AND s_nationkey = n1.n_nationkey AND c_nationkey = n2.n_nationkey AND ((n1.n_name = 'FRANCE' AND n2.n_name = 'GERMANY') OR (n1.n_name = 'GERMANY' AND n2.n_name = 'FRANCE')) AND l_shipdate BETWEEN CAST('1995-01-01' AS date) AND CAST('1996-12-31' AS date)) AS shipping GROUP BY supp_nation, cust_nation, l_year ORDER BY supp_nation, cust_nation, l_year;
|
||||
SELECT o_year, sum( CASE WHEN nation = 'BRAZIL' THEN volume ELSE 0 END) / sum(volume) AS mkt_share FROM ( SELECT extract(year FROM o_orderdate) AS o_year, l_extendedprice * (1 - l_discount) AS volume, n2.n_name AS nation FROM part, supplier, lineitem, orders, customer, nation n1, nation n2, region WHERE p_partkey = l_partkey AND s_suppkey = l_suppkey AND l_orderkey = o_orderkey AND o_custkey = c_custkey AND c_nationkey = n1.n_nationkey AND n1.n_regionkey = r_regionkey AND r_name = 'AMERICA' AND s_nationkey = n2.n_nationkey AND o_orderdate BETWEEN CAST('1995-01-01' AS date) AND CAST('1996-12-31' AS date) AND p_type = 'ECONOMY ANODIZED STEEL') AS all_nations GROUP BY o_year ORDER BY o_year;
|
||||
SELECT nation, o_year, sum(amount) AS sum_profit FROM ( SELECT n_name AS nation, extract(year FROM o_orderdate) AS o_year, l_extendedprice * (1 - l_discount) - ps_supplycost * l_quantity AS amount FROM part, supplier, lineitem, partsupp, orders, nation WHERE s_suppkey = l_suppkey AND ps_suppkey = l_suppkey AND ps_partkey = l_partkey AND p_partkey = l_partkey AND o_orderkey = l_orderkey AND s_nationkey = n_nationkey AND p_name LIKE '%green%') AS profit GROUP BY nation, o_year ORDER BY nation, o_year DESC;
|
||||
SELECT c_custkey, c_name, sum(l_extendedprice * (1 - l_discount)) AS revenue, c_acctbal, n_name, c_address, c_phone, c_comment FROM customer, orders, lineitem, nation WHERE c_custkey = o_custkey AND l_orderkey = o_orderkey AND o_orderdate >= CAST('1993-10-01' AS date) AND o_orderdate < CAST('1994-01-01' AS date) AND l_returnflag = 'R' AND c_nationkey = n_nationkey GROUP BY c_custkey, c_name, c_acctbal, c_phone, n_name, c_address, c_comment ORDER BY revenue DESC LIMIT 20;
|
||||
SELECT ps_partkey, sum(ps_supplycost * ps_availqty) AS value FROM partsupp, supplier, nation WHERE ps_suppkey = s_suppkey AND s_nationkey = n_nationkey AND n_name = 'GERMANY' GROUP BY ps_partkey HAVING sum(ps_supplycost * ps_availqty) > ( SELECT sum(ps_supplycost * ps_availqty) * 0.0001000000 FROM partsupp, supplier, nation WHERE ps_suppkey = s_suppkey AND s_nationkey = n_nationkey AND n_name = 'GERMANY') ORDER BY value DESC;
|
||||
SELECT l_shipmode, sum( CASE WHEN o_orderpriority = '1-URGENT' OR o_orderpriority = '2-HIGH' THEN 1 ELSE 0 END) AS high_line_count, sum( CASE WHEN o_orderpriority <> '1-URGENT' AND o_orderpriority <> '2-HIGH' THEN 1 ELSE 0 END) AS low_line_count FROM orders, lineitem WHERE o_orderkey = l_orderkey AND l_shipmode IN ('MAIL', 'SHIP') AND l_commitdate < l_receiptdate AND l_shipdate < l_commitdate AND l_receiptdate >= CAST('1994-01-01' AS date) AND l_receiptdate < CAST('1995-01-01' AS date) GROUP BY l_shipmode ORDER BY l_shipmode;
|
||||
SELECT c_count, count(*) AS custdist FROM ( SELECT c_custkey, count(o_orderkey) FROM customer LEFT OUTER JOIN orders ON c_custkey = o_custkey AND o_comment NOT LIKE '%special%requests%' GROUP BY c_custkey) AS c_orders (c_custkey, c_count) GROUP BY c_count ORDER BY custdist DESC, c_count DESC;
|
||||
SELECT 100.00 * sum( CASE WHEN p_type LIKE 'PROMO%' THEN l_extendedprice * (1 - l_discount) ELSE 0 END) / sum(l_extendedprice * (1 - l_discount)) AS promo_revenue FROM lineitem, part WHERE l_partkey = p_partkey AND l_shipdate >= date '1995-09-01' AND l_shipdate < CAST('1995-10-01' AS date);
|
||||
SELECT s_suppkey, s_name, s_address, s_phone, total_revenue FROM supplier, ( SELECT l_suppkey AS supplier_no, sum(l_extendedprice * (1 - l_discount)) AS total_revenue FROM lineitem WHERE l_shipdate >= CAST('1996-01-01' AS date) AND l_shipdate < CAST('1996-04-01' AS date) GROUP BY supplier_no) revenue0 WHERE s_suppkey = supplier_no AND total_revenue = ( SELECT max(total_revenue) FROM ( SELECT l_suppkey AS supplier_no, sum(l_extendedprice * (1 - l_discount)) AS total_revenue FROM lineitem WHERE l_shipdate >= CAST('1996-01-01' AS date) AND l_shipdate < CAST('1996-04-01' AS date) GROUP BY supplier_no) revenue1) ORDER BY s_suppkey;
|
||||
SELECT p_brand, p_type, p_size, count(DISTINCT ps_suppkey) AS supplier_cnt FROM partsupp, part WHERE p_partkey = ps_partkey AND p_brand <> 'Brand#45' AND p_type NOT LIKE 'MEDIUM POLISHED%' AND p_size IN (49, 14, 23, 45, 19, 3, 36, 9) AND ps_suppkey NOT IN ( SELECT s_suppkey FROM supplier WHERE s_comment LIKE '%Customer%Complaints%') GROUP BY p_brand, p_type, p_size ORDER BY supplier_cnt DESC, p_brand, p_type, p_size;
|
||||
SELECT sum(l_extendedprice) / 7.0 AS avg_yearly FROM lineitem, part WHERE p_partkey = l_partkey AND p_brand = 'Brand#23' AND p_container = 'MED BOX' AND l_quantity < ( SELECT 0.2 * avg(l_quantity) FROM lineitem WHERE l_partkey = p_partkey);
|
||||
SELECT c_name, c_custkey, o_orderkey, o_orderdate, o_totalprice, sum(l_quantity) FROM customer, orders, lineitem WHERE o_orderkey IN ( SELECT l_orderkey FROM lineitem GROUP BY l_orderkey HAVING sum(l_quantity) > 300) AND c_custkey = o_custkey AND o_orderkey = l_orderkey GROUP BY c_name, c_custkey, o_orderkey, o_orderdate, o_totalprice ORDER BY o_totalprice DESC, o_orderdate LIMIT 100;
|
||||
SELECT sum(l_extendedprice * (1 - l_discount)) AS revenue FROM lineitem, part WHERE (p_partkey = l_partkey AND p_brand = 'Brand#12' AND p_container IN ('SM CASE', 'SM BOX', 'SM PACK', 'SM PKG') AND l_quantity >= 1 AND l_quantity <= 1 + 10 AND p_size BETWEEN 1 AND 5 AND l_shipmode IN ('AIR', 'AIR REG') AND l_shipinstruct = 'DELIVER IN PERSON') OR (p_partkey = l_partkey AND p_brand = 'Brand#23' AND p_container IN ('MED BAG', 'MED BOX', 'MED PKG', 'MED PACK') AND l_quantity >= 10 AND l_quantity <= 10 + 10 AND p_size BETWEEN 1 AND 10 AND l_shipmode IN ('AIR', 'AIR REG') AND l_shipinstruct = 'DELIVER IN PERSON') OR (p_partkey = l_partkey AND p_brand = 'Brand#34' AND p_container IN ('LG CASE', 'LG BOX', 'LG PACK', 'LG PKG') AND l_quantity >= 20 AND l_quantity <= 20 + 10 AND p_size BETWEEN 1 AND 15 AND l_shipmode IN ('AIR', 'AIR REG') AND l_shipinstruct = 'DELIVER IN PERSON');
|
||||
SELECT s_name, s_address FROM supplier, nation WHERE s_suppkey IN ( SELECT ps_suppkey FROM partsupp WHERE ps_partkey IN ( SELECT p_partkey FROM part WHERE p_name LIKE 'forest%') AND ps_availqty > ( SELECT 0.5 * sum(l_quantity) FROM lineitem WHERE l_partkey = ps_partkey AND l_suppkey = ps_suppkey AND l_shipdate >= CAST('1994-01-01' AS date) AND l_shipdate < CAST('1995-01-01' AS date))) AND s_nationkey = n_nationkey AND n_name = 'CANADA' ORDER BY s_name;
|
||||
SELECT s_name, count(*) AS numwait FROM supplier, lineitem l1, orders, nation WHERE s_suppkey = l1.l_suppkey AND o_orderkey = l1.l_orderkey AND o_orderstatus = 'F' AND l1.l_receiptdate > l1.l_commitdate AND EXISTS ( SELECT * FROM lineitem l2 WHERE l2.l_orderkey = l1.l_orderkey AND l2.l_suppkey <> l1.l_suppkey) AND NOT EXISTS ( SELECT * FROM lineitem l3 WHERE l3.l_orderkey = l1.l_orderkey AND l3.l_suppkey <> l1.l_suppkey AND l3.l_receiptdate > l3.l_commitdate) AND s_nationkey = n_nationkey AND n_name = 'SAUDI ARABIA' GROUP BY s_name ORDER BY numwait DESC, s_name LIMIT 100;
|
||||
SELECT * FROM range(10);
|
||||
SELECT * FROM range(0, 100);
|
||||
SELECT * FROM generate_series(0, 10) t(i);
|
||||
BIN
external/duckdb/test/api/serialized_plans/serialized_plans.binary
vendored
Normal file
BIN
external/duckdb/test/api/serialized_plans/serialized_plans.binary
vendored
Normal file
Binary file not shown.
133
external/duckdb/test/api/serialized_plans/test_plan_serialization_bwc.cpp
vendored
Normal file
133
external/duckdb/test/api/serialized_plans/test_plan_serialization_bwc.cpp
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/common/serializer/buffered_file_reader.hpp"
|
||||
#include "duckdb/common/serializer/buffered_file_writer.hpp"
|
||||
#include "duckdb/parser/parser.hpp"
|
||||
#include "duckdb/planner/planner.hpp"
|
||||
#include "duckdb/parser/statement/logical_plan_statement.hpp"
|
||||
#include "duckdb/common/serializer/binary_serializer.hpp"
|
||||
#include "duckdb/common/serializer/binary_deserializer.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "tpch_extension.hpp"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
string get_full_file_name(const string &file_name) {
|
||||
auto my_name = string("test_plan_serialization_bwc.cpp");
|
||||
auto path = string(__FILE__);
|
||||
return path.replace(path.rfind(my_name), my_name.length(), file_name);
|
||||
}
|
||||
|
||||
void load_db(Connection &con) {
|
||||
std::ifstream queries(get_full_file_name("db_load.sql"));
|
||||
string query;
|
||||
while (std::getline(queries, query)) {
|
||||
REQUIRE_NO_FAIL(con.Query(query));
|
||||
}
|
||||
}
|
||||
|
||||
void test_deserialization(const string &file_location);
|
||||
|
||||
const char *PERSISTENT_FILE_NAME = "serialized_plans.binary";
|
||||
|
||||
TEST_CASE("Generate serialized plans file", "[.][serialization]") {
|
||||
string file_location;
|
||||
if (std::getenv("GEN_PLAN_STORAGE") != nullptr) {
|
||||
// there is no way in catch2 to only run a test if explicitly requested. Hidden tests will
|
||||
// run when "*" is used - which we do to run slow tests. To avoid re-generating the bin file
|
||||
// we require an env variable to be explicitly set.
|
||||
//
|
||||
// set `GEN_PLAN_STORAGE` as an environment variable to generate the serialized file
|
||||
file_location = get_full_file_name(PERSISTENT_FILE_NAME);
|
||||
} else {
|
||||
file_location = TestCreatePath("serialized_plans.new.binary");
|
||||
}
|
||||
|
||||
DuckDB db;
|
||||
Connection con(db);
|
||||
load_db(con);
|
||||
BufferedFileWriter target(db.GetFileSystem(), file_location);
|
||||
std::ifstream queries(get_full_file_name("queries.sql"));
|
||||
string query;
|
||||
while (std::getline(queries, query)) {
|
||||
con.BeginTransaction();
|
||||
Parser p;
|
||||
p.ParseQuery(query);
|
||||
|
||||
Planner planner(*con.context);
|
||||
|
||||
planner.CreatePlan(std::move(p.statements[0]));
|
||||
auto plan = std::move(planner.plan);
|
||||
|
||||
BinarySerializer serializer(target);
|
||||
serializer.Begin();
|
||||
plan->Serialize(serializer);
|
||||
serializer.End();
|
||||
|
||||
con.Rollback();
|
||||
}
|
||||
|
||||
target.Sync();
|
||||
|
||||
test_deserialization(file_location);
|
||||
}
|
||||
|
||||
TEST_CASE("Test deserialized plans from file", "[.][serialization]") {
|
||||
test_deserialization(get_full_file_name(PERSISTENT_FILE_NAME));
|
||||
}
|
||||
|
||||
void test_deserialization(const string &file_location) {
|
||||
DuckDB db;
|
||||
Connection con(db);
|
||||
load_db(con);
|
||||
BufferedFileReader file_source(db.GetFileSystem(), file_location.c_str());
|
||||
|
||||
std::ifstream queries(get_full_file_name("queries.sql"));
|
||||
string query;
|
||||
while (std::getline(queries, query)) {
|
||||
INFO("evaluating " << query)
|
||||
con.BeginTransaction();
|
||||
|
||||
BinaryDeserializer deserializer(file_source);
|
||||
deserializer.Set<ClientContext &>(*con.context);
|
||||
deserializer.Begin();
|
||||
auto deserialized_plan = LogicalOperator::Deserialize(deserializer);
|
||||
deserializer.End();
|
||||
|
||||
deserialized_plan->ResolveOperatorTypes();
|
||||
auto deserialized_results =
|
||||
con.context->Query(make_uniq<LogicalPlanStatement>(std::move(deserialized_plan)), false);
|
||||
REQUIRE_NO_FAIL(*deserialized_results);
|
||||
|
||||
Parser p;
|
||||
p.ParseQuery(query);
|
||||
Planner planner(*con.context);
|
||||
planner.CreatePlan(std::move(p.statements[0]));
|
||||
auto expected_plan = std::move(planner.plan);
|
||||
expected_plan->ResolveOperatorTypes();
|
||||
auto expected_results = con.Query(query);
|
||||
REQUIRE_NO_FAIL(*expected_results);
|
||||
|
||||
if (deserialized_results->names.size() == expected_results->names.size()) {
|
||||
// ignore names
|
||||
deserialized_results->names = expected_results->names;
|
||||
}
|
||||
|
||||
if (!deserialized_results->Equals(*expected_results)) {
|
||||
fprintf(stderr, "-----------------------------------\n");
|
||||
fprintf(stderr, "Deserialized result does not match!\n");
|
||||
fprintf(stderr, "-----------------------------------\n");
|
||||
fprintf(stderr, "Query: %s\n", query.c_str());
|
||||
fprintf(stderr, "-------------Deserialized----------\n");
|
||||
deserialized_results->Print();
|
||||
fprintf(stderr, "---------------Expected------------\n");
|
||||
expected_results->Print();
|
||||
fprintf(stderr, "-----------------------------------\n");
|
||||
FAIL("Deserialized result does not match");
|
||||
}
|
||||
|
||||
con.Rollback();
|
||||
}
|
||||
}
|
||||
757
external/duckdb/test/api/test_api.cpp
vendored
Normal file
757
external/duckdb/test/api/test_api.cpp
vendored
Normal file
@@ -0,0 +1,757 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/parser/parser.hpp"
|
||||
#include "duckdb/planner/logical_operator.hpp"
|
||||
#include "duckdb/main/connection_manager.hpp"
|
||||
#include "duckdb/parser/statement/select_statement.hpp"
|
||||
#include "duckdb/parser/query_node/select_node.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test comment in CPP API", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
con.SendQuery("--ups");
|
||||
//! Should not crash
|
||||
REQUIRE(1);
|
||||
}
|
||||
|
||||
TEST_CASE("Test StarExpression replace_list parameter", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
auto sql = "select * replace(i * $n as i) from range(1, 10) t(i)";
|
||||
auto stmts = con.ExtractStatements(sql);
|
||||
|
||||
auto &select_stmt = stmts[0]->Cast<SelectStatement>();
|
||||
auto &select_node = select_stmt.node->Cast<SelectNode>();
|
||||
|
||||
REQUIRE(select_node.select_list[0]->HasParameter());
|
||||
}
|
||||
|
||||
TEST_CASE("Test using connection after database is gone", "[api]") {
|
||||
auto db = make_uniq<DuckDB>(nullptr);
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
// check that the connection works
|
||||
auto result = conn->Query("SELECT 42");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
// destroy the database
|
||||
db.reset();
|
||||
// try to use the connection
|
||||
// it still works: the database remains until all connections are destroyed
|
||||
REQUIRE_NO_FAIL(conn->Query("SELECT 42"));
|
||||
|
||||
// now try it with an open transaction
|
||||
db = make_uniq<DuckDB>(nullptr);
|
||||
conn = make_uniq<Connection>(*db);
|
||||
|
||||
REQUIRE_NO_FAIL(conn->Query("BEGIN TRANSACTION"));
|
||||
result = conn->Query("SELECT 42");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
|
||||
db.reset();
|
||||
|
||||
REQUIRE_NO_FAIL(conn->Query("SELECT 42"));
|
||||
}
|
||||
|
||||
TEST_CASE("Test destroying connections with open transactions", "[api]") {
|
||||
auto db = make_uniq<DuckDB>(nullptr);
|
||||
{
|
||||
Connection con(*db);
|
||||
con.Query("BEGIN TRANSACTION");
|
||||
con.Query("CREATE TABLE test(i INTEGER);");
|
||||
}
|
||||
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
REQUIRE_NO_FAIL(conn->Query("CREATE TABLE test(i INTEGER)"));
|
||||
}
|
||||
|
||||
static void long_running_query(Connection *conn, bool *correct) {
|
||||
*correct = true;
|
||||
auto result = conn->Query("SELECT i1.i FROM integers i1, integers i2, integers i3, integers i4, integers i5, "
|
||||
"integers i6, integers i7, integers i8, integers i9, integers i10,"
|
||||
"integers i11, integers i12, integers i13");
|
||||
// the query should fail
|
||||
*correct = result->HasError();
|
||||
}
|
||||
|
||||
TEST_CASE("Test closing database during long running query", "[api]") {
|
||||
auto db = make_uniq<DuckDB>(nullptr);
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
// create the database
|
||||
REQUIRE_NO_FAIL(conn->Query("CREATE TABLE integers(i INTEGER)"));
|
||||
REQUIRE_NO_FAIL(conn->Query("INSERT INTO integers FROM range(10000)"));
|
||||
conn->DisableProfiling();
|
||||
// perform a long running query in the background (many cross products)
|
||||
bool correct = true;
|
||||
auto background_thread = thread(long_running_query, conn.get(), &correct);
|
||||
// wait a little bit
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
// destroy the database
|
||||
conn->Interrupt();
|
||||
db.reset();
|
||||
// wait for the thread
|
||||
background_thread.join();
|
||||
REQUIRE(correct);
|
||||
// try to use the connection
|
||||
REQUIRE_NO_FAIL(conn->Query("SELECT 42"));
|
||||
}
|
||||
|
||||
TEST_CASE("Test closing result after database is gone", "[api]") {
|
||||
auto db = make_uniq<DuckDB>(nullptr);
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
// check that the connection works
|
||||
auto result = conn->Query("SELECT 42");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
// destroy the database
|
||||
db.reset();
|
||||
conn.reset();
|
||||
result.reset();
|
||||
|
||||
// now the streaming result
|
||||
db = make_uniq<DuckDB>(nullptr);
|
||||
conn = make_uniq<Connection>(*db);
|
||||
// check that the connection works
|
||||
auto streaming_result = conn->SendQuery("SELECT 42");
|
||||
// destroy the database
|
||||
db.reset();
|
||||
conn.reset();
|
||||
REQUIRE(CHECK_COLUMN(streaming_result, 0, {42}));
|
||||
streaming_result.reset();
|
||||
}
|
||||
|
||||
TEST_CASE("Test closing database with open prepared statements", "[api]") {
|
||||
auto db = make_uniq<DuckDB>(nullptr);
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
|
||||
auto p1 = conn->Prepare("CREATE TABLE a (i INTEGER)");
|
||||
REQUIRE_NO_FAIL(p1->Execute());
|
||||
auto p2 = conn->Prepare("INSERT INTO a VALUES (42)");
|
||||
REQUIRE_NO_FAIL(p2->Execute());
|
||||
|
||||
db.reset();
|
||||
conn.reset();
|
||||
|
||||
// the prepared statements are still valid
|
||||
// the database is only destroyed when the prepared statements are destroyed
|
||||
REQUIRE_NO_FAIL(p2->Execute());
|
||||
p1.reset();
|
||||
p2.reset();
|
||||
}
|
||||
|
||||
static void parallel_query(Connection *conn, bool *correct, size_t threadnr) {
|
||||
correct[threadnr] = true;
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
auto result = conn->Query("SELECT * FROM integers ORDER BY i");
|
||||
if (!CHECK_COLUMN(result, 0, {1, 2, 3, Value()})) {
|
||||
correct[threadnr] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test temp_directory defaults", "[api][.]") {
|
||||
const char *db_paths[] = {nullptr, "", ":memory:"};
|
||||
for (auto &path : db_paths) {
|
||||
auto db = make_uniq<DuckDB>(path);
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
|
||||
REQUIRE(db->instance->config.options.temporary_directory == ".tmp");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test parallel usage of single client", "[api][.]") {
|
||||
auto db = make_uniq<DuckDB>(nullptr);
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
|
||||
REQUIRE_NO_FAIL(conn->Query("CREATE TABLE integers(i INTEGER)"));
|
||||
REQUIRE_NO_FAIL(conn->Query("INSERT INTO integers VALUES (1), (2), (3), (NULL)"));
|
||||
|
||||
bool correct[20];
|
||||
thread threads[20];
|
||||
for (size_t i = 0; i < 20; i++) {
|
||||
threads[i] = thread(parallel_query, conn.get(), correct, i);
|
||||
}
|
||||
for (size_t i = 0; i < 20; i++) {
|
||||
threads[i].join();
|
||||
REQUIRE(correct[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void parallel_query_with_new_connection(DuckDB *db, bool *correct, size_t threadnr) {
|
||||
correct[threadnr] = true;
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
auto result = conn->Query("SELECT * FROM integers ORDER BY i");
|
||||
if (!CHECK_COLUMN(result, 0, {1, 2, 3, Value()})) {
|
||||
correct[threadnr] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test making and dropping connections in parallel to a single database", "[api][.]") {
|
||||
auto db = make_uniq<DuckDB>(nullptr);
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
|
||||
REQUIRE_NO_FAIL(conn->Query("CREATE TABLE integers(i INTEGER)"));
|
||||
REQUIRE_NO_FAIL(conn->Query("INSERT INTO integers VALUES (1), (2), (3), (NULL)"));
|
||||
|
||||
bool correct[20];
|
||||
thread threads[20];
|
||||
for (size_t i = 0; i < 20; i++) {
|
||||
threads[i] = thread(parallel_query_with_new_connection, db.get(), correct, i);
|
||||
}
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
auto result = conn->Query("SELECT * FROM integers ORDER BY i");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3, Value()}));
|
||||
}
|
||||
for (size_t i = 0; i < 20; i++) {
|
||||
threads[i].join();
|
||||
REQUIRE(correct[i]);
|
||||
}
|
||||
auto result = conn->Query("SELECT * FROM integers ORDER BY i");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3, Value()}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test multiple result sets", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
con.DisableQueryVerification();
|
||||
con.EnableQueryVerification();
|
||||
|
||||
con.ForceParallelism();
|
||||
|
||||
result = con.Query("SELECT 42; SELECT 84");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
result = std::move(result->next);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {84}));
|
||||
REQUIRE(!result->next);
|
||||
|
||||
// also with stream api
|
||||
result = con.SendQuery("SELECT 42; SELECT 84");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
result = std::move(result->next);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {84}));
|
||||
REQUIRE(!result->next);
|
||||
}
|
||||
|
||||
TEST_CASE("Test streaming API errors", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result, result2;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
// multiple streaming result
|
||||
result = con.SendQuery("SELECT 42;");
|
||||
result2 = con.SendQuery("SELECT 42;");
|
||||
// "result" is invalidated
|
||||
REQUIRE_THROWS(CHECK_COLUMN(result, 0, {42}));
|
||||
// "result2" we can read
|
||||
REQUIRE(CHECK_COLUMN(result2, 0, {42}));
|
||||
|
||||
// streaming result followed by non-streaming result
|
||||
result = con.SendQuery("SELECT 42;");
|
||||
result2 = con.Query("SELECT 42;");
|
||||
// "result" is invalidated
|
||||
REQUIRE_THROWS(CHECK_COLUMN(result, 0, {42}));
|
||||
// "result2" we can read
|
||||
REQUIRE(CHECK_COLUMN(result2, 0, {42}));
|
||||
|
||||
// error in binding
|
||||
result = con.SendQuery("SELECT * FROM nonexistanttable");
|
||||
REQUIRE(!result->ToString().empty());
|
||||
REQUIRE(result->type == QueryResultType::MATERIALIZED_RESULT);
|
||||
REQUIRE_FAIL(result);
|
||||
|
||||
// error in stream that only happens after fetching
|
||||
result = con.SendQuery(
|
||||
"SELECT x::INT FROM (SELECT x::VARCHAR x FROM range(10) tbl(x) UNION ALL SELECT 'hello' x) tbl(x);");
|
||||
while (!result->HasError()) {
|
||||
auto chunk = result->Fetch();
|
||||
if (!chunk || chunk->size() == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
REQUIRE(!result->ToString().empty());
|
||||
REQUIRE_FAIL(result);
|
||||
|
||||
// same query but call Materialize
|
||||
result = con.SendQuery(
|
||||
"SELECT x::INT FROM (SELECT x::VARCHAR x FROM range(10) tbl(x) UNION ALL SELECT 'hello' x) tbl(x);");
|
||||
REQUIRE(!result->ToString().empty());
|
||||
REQUIRE(result->type == QueryResultType::STREAM_RESULT);
|
||||
result = ((StreamQueryResult &)*result).Materialize();
|
||||
REQUIRE_FAIL(result);
|
||||
|
||||
// same query but call materialize after fetching
|
||||
result = con.SendQuery(
|
||||
"SELECT x::INT FROM (SELECT x::VARCHAR x FROM range(10) tbl(x) UNION ALL SELECT 'hello' x) tbl(x);");
|
||||
while (!result->HasError()) {
|
||||
auto chunk = result->Fetch();
|
||||
if (!chunk || chunk->size() == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
REQUIRE(!result->ToString().empty());
|
||||
REQUIRE(result->type == QueryResultType::STREAM_RESULT);
|
||||
result = ((StreamQueryResult &)*result).Materialize();
|
||||
REQUIRE_FAIL(result);
|
||||
}
|
||||
|
||||
TEST_CASE("Test fetch API", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
|
||||
// fetch from an error
|
||||
result = con.Query("SELECT 'hello'::INT");
|
||||
REQUIRE_THROWS(result->Fetch());
|
||||
|
||||
result = con.SendQuery("CREATE TABLE test (a INTEGER);");
|
||||
|
||||
result = con.Query("select a from test where 1 <> 1");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {}));
|
||||
|
||||
result = con.SendQuery("INSERT INTO test VALUES (42)");
|
||||
result = con.SendQuery("SELECT a from test");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
|
||||
auto materialized_result = con.Query("select a from test");
|
||||
REQUIRE(CHECK_COLUMN(materialized_result, 0, {42}));
|
||||
|
||||
// override fetch result
|
||||
result = con.SendQuery("SELECT a from test");
|
||||
result = con.SendQuery("SELECT a from test");
|
||||
result = con.SendQuery("SELECT a from test");
|
||||
result = con.SendQuery("SELECT a from test");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test fetch API not to completion", "[api]") {
|
||||
auto db = make_uniq<DuckDB>(nullptr);
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
// remove connection with active stream result
|
||||
auto result = conn->SendQuery("SELECT 42");
|
||||
// close the connection
|
||||
conn.reset();
|
||||
// now try to fetch a chunk, this should not return a nullptr
|
||||
auto chunk = result->Fetch();
|
||||
REQUIRE(chunk);
|
||||
// Only if we would call Fetch again would we Close the QueryResult
|
||||
// this is testing that it can get cleaned up without this.
|
||||
|
||||
db.reset();
|
||||
}
|
||||
|
||||
TEST_CASE("Test fetch API robustness", "[api]") {
|
||||
auto db = make_uniq<DuckDB>(nullptr);
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
|
||||
// remove connection with active stream result
|
||||
auto result = conn->SendQuery("SELECT 42");
|
||||
// close the connection
|
||||
conn.reset();
|
||||
// now try to fetch a chunk, this should not return a nullptr
|
||||
auto chunk = result->Fetch();
|
||||
REQUIRE(chunk);
|
||||
|
||||
// now close the entire database
|
||||
conn = make_uniq<Connection>(*db);
|
||||
result = conn->SendQuery("SELECT 42");
|
||||
|
||||
db.reset();
|
||||
// fetch should not fail
|
||||
chunk = result->Fetch();
|
||||
REQUIRE(chunk);
|
||||
// new queries on the connection should not fail either
|
||||
REQUIRE_NO_FAIL(conn->SendQuery("SELECT 42"));
|
||||
|
||||
// override fetch result
|
||||
db = make_uniq<DuckDB>(nullptr);
|
||||
conn = make_uniq<Connection>(*db);
|
||||
auto result1 = conn->SendQuery("SELECT 42");
|
||||
auto result2 = conn->SendQuery("SELECT 84");
|
||||
REQUIRE_NO_FAIL(*result1);
|
||||
REQUIRE_NO_FAIL(*result2);
|
||||
|
||||
// result1 should be closed now
|
||||
REQUIRE_THROWS(result1->Fetch());
|
||||
// result2 should work
|
||||
REQUIRE(result2->Fetch());
|
||||
|
||||
// test materialize
|
||||
result1 = conn->SendQuery("SELECT 42");
|
||||
REQUIRE(result1->type == QueryResultType::STREAM_RESULT);
|
||||
auto materialized = ((StreamQueryResult &)*result1).Materialize();
|
||||
result2 = conn->SendQuery("SELECT 84");
|
||||
|
||||
// we can read materialized still, even after opening a new result
|
||||
REQUIRE(CHECK_COLUMN(materialized, 0, {42}));
|
||||
REQUIRE(CHECK_COLUMN(result2, 0, {84}));
|
||||
}
|
||||
|
||||
static void VerifyStreamResult(duckdb::unique_ptr<QueryResult> result) {
|
||||
REQUIRE(result->types[0] == LogicalType::INTEGER);
|
||||
size_t current_row = 0;
|
||||
int current_expected_value = 0;
|
||||
size_t expected_rows = 500 * 5;
|
||||
while (true) {
|
||||
auto chunk = result->Fetch();
|
||||
if (!chunk || chunk->size() == 0) {
|
||||
break;
|
||||
}
|
||||
auto col1_data = FlatVector::GetData<int>(chunk->data[0]);
|
||||
for (size_t k = 0; k < chunk->size(); k++) {
|
||||
if (current_row % 500 == 0) {
|
||||
current_expected_value++;
|
||||
}
|
||||
REQUIRE(col1_data[k] == current_expected_value);
|
||||
current_row++;
|
||||
}
|
||||
}
|
||||
REQUIRE(current_row == expected_rows);
|
||||
}
|
||||
|
||||
TEST_CASE("Test fetch API with big results", "[api][.]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
|
||||
// create table that consists of multiple chunks
|
||||
REQUIRE_NO_FAIL(con.Query("BEGIN TRANSACTION"));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE test(a INTEGER)"));
|
||||
for (size_t i = 0; i < 500; i++) {
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO test VALUES (1); INSERT INTO test VALUES (2); INSERT INTO test VALUES "
|
||||
"(3); INSERT INTO test VALUES (4); INSERT INTO test VALUES (5);"));
|
||||
}
|
||||
REQUIRE_NO_FAIL(con.Query("COMMIT"));
|
||||
|
||||
// stream the results using the Fetch() API
|
||||
auto result = con.SendQuery("SELECT CAST(a AS INTEGER) FROM test ORDER BY a");
|
||||
VerifyStreamResult(std::move(result));
|
||||
// we can also stream a materialized result
|
||||
auto materialized = con.Query("SELECT CAST(a AS INTEGER) FROM test ORDER BY a");
|
||||
VerifyStreamResult(std::move(materialized));
|
||||
// return multiple results using the stream API
|
||||
result = con.SendQuery("SELECT CAST(a AS INTEGER) FROM test ORDER BY a; SELECT CAST(a AS INTEGER) FROM test ORDER "
|
||||
"BY a; SELECT CAST(a AS INTEGER) FROM test ORDER BY a;");
|
||||
auto next = std::move(result->next);
|
||||
while (next) {
|
||||
auto nextnext = std::move(next->next);
|
||||
VerifyStreamResult(std::move(nextnext));
|
||||
next = std::move(nextnext);
|
||||
}
|
||||
VerifyStreamResult(std::move(result));
|
||||
}
|
||||
|
||||
TEST_CASE("Test TryFlushCachingOperators interrupted ExecutePushInternal", "[api][.]") {
|
||||
DuckDB db;
|
||||
Connection con(db);
|
||||
|
||||
con.Query("create table tbl as select 100000 a from range(2) t(a);");
|
||||
con.Query("pragma threads=1");
|
||||
|
||||
// Use PhysicalCrossProduct with a very low amount of produced tuples, this caches the result in the
|
||||
// CachingOperatorState This gets flushed with FinalExecute in PipelineExecutor::TryFlushCachingOperator
|
||||
auto pending_query = con.PendingQuery("select unnest(range(a.a)) from tbl a, tbl b;");
|
||||
|
||||
// Through `unnest(range(a.a.))` this FinalExecute multiple chunks, more than the ExecutionBudget can handle with
|
||||
// PROCESS_PARTIAL
|
||||
pending_query->ExecuteTask();
|
||||
|
||||
// query the connection as normal after
|
||||
auto res = pending_query->Execute();
|
||||
REQUIRE(!res->HasError());
|
||||
auto &materialized_res = res->Cast<MaterializedQueryResult>();
|
||||
idx_t initial_tuples = 2 * 2;
|
||||
REQUIRE(materialized_res.RowCount() == initial_tuples * 100000);
|
||||
for (idx_t i = 0; i < initial_tuples; i++) {
|
||||
for (idx_t j = 0; j < 100000; j++) {
|
||||
auto value = static_cast<idx_t>(materialized_res.GetValue<int64_t>(0, (i * 100000) + j));
|
||||
REQUIRE(value == j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test streaming query during stack unwinding", "[api]") {
|
||||
DuckDB db;
|
||||
Connection con(db);
|
||||
|
||||
try {
|
||||
auto result = con.SendQuery("SELECT * FROM range(1000000)");
|
||||
|
||||
throw std::runtime_error("hello");
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test prepare dependencies with multiple connections", "[catalog]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
auto con = make_uniq<Connection>(db);
|
||||
auto con2 = make_uniq<Connection>(db);
|
||||
auto con3 = make_uniq<Connection>(db);
|
||||
|
||||
// simple prepare: begin transaction before the second client calls PREPARE
|
||||
REQUIRE_NO_FAIL(con->Query("CREATE TABLE integers(i INTEGER)"));
|
||||
// open a transaction in con2, this forces the prepared statement to be kept around until this transaction is closed
|
||||
REQUIRE_NO_FAIL(con2->Query("BEGIN TRANSACTION"));
|
||||
// we prepare a statement in con
|
||||
REQUIRE_NO_FAIL(con->Query("PREPARE s1 AS SELECT * FROM integers"));
|
||||
// now we drop con while the second client still has an active transaction
|
||||
con.reset();
|
||||
// now commit the transaction in the second client
|
||||
REQUIRE_NO_FAIL(con2->Query("COMMIT"));
|
||||
|
||||
con = make_uniq<Connection>(db);
|
||||
// three transactions
|
||||
// open a transaction in con2, this forces the prepared statement to be kept around until this transaction is closed
|
||||
REQUIRE_NO_FAIL(con2->Query("BEGIN TRANSACTION"));
|
||||
// create a prepare, this creates a dependency from s1 -> integers
|
||||
REQUIRE_NO_FAIL(con->Query("PREPARE s1 AS SELECT * FROM integers"));
|
||||
// drop the client
|
||||
con.reset();
|
||||
// now begin a transaction in con3
|
||||
REQUIRE_NO_FAIL(con3->Query("BEGIN TRANSACTION"));
|
||||
// drop the table integers with cascade, this should drop s1 as well
|
||||
REQUIRE_NO_FAIL(con3->Query("DROP TABLE integers CASCADE"));
|
||||
REQUIRE_NO_FAIL(con2->Query("COMMIT"));
|
||||
REQUIRE_NO_FAIL(con3->Query("COMMIT"));
|
||||
}
|
||||
|
||||
TEST_CASE("Test connection API", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
|
||||
// extract a plan node
|
||||
REQUIRE_NOTHROW(con.ExtractPlan("SELECT 42"));
|
||||
// can only extract one statement at a time
|
||||
REQUIRE_THROWS(con.ExtractPlan("SELECT 42; SELECT 84"));
|
||||
|
||||
// append to a table
|
||||
con.Query("CREATE TABLE integers(i integer);");
|
||||
auto table_info = con.TableInfo("integers");
|
||||
|
||||
DataChunk chunk;
|
||||
REQUIRE_NOTHROW(con.Append(*table_info, chunk));
|
||||
|
||||
// no transaction active
|
||||
REQUIRE_THROWS(con.Commit());
|
||||
REQUIRE_THROWS(con.Rollback());
|
||||
|
||||
// cannot start a transaction within a transaction
|
||||
REQUIRE_NOTHROW(con.BeginTransaction());
|
||||
REQUIRE_THROWS(con.BeginTransaction());
|
||||
|
||||
con.SetAutoCommit(false);
|
||||
REQUIRE(!con.IsAutoCommit());
|
||||
|
||||
con.SetAutoCommit(true);
|
||||
REQUIRE(con.IsAutoCommit());
|
||||
}
|
||||
|
||||
TEST_CASE("Test parser tokenize", "[api]") {
|
||||
Parser parser;
|
||||
REQUIRE_NOTHROW(parser.Tokenize("SELECT * FROM table WHERE i+1=3 AND j='hello'; --tokenize example query"));
|
||||
}
|
||||
|
||||
TEST_CASE("Test opening an invalid database file", "[api]") {
|
||||
duckdb::unique_ptr<DuckDB> db;
|
||||
bool success = false;
|
||||
try {
|
||||
db = make_uniq<DuckDB>("duckdb:data/parquet-testing/blob.parquet");
|
||||
success = true;
|
||||
} catch (std::exception &ex) {
|
||||
REQUIRE(StringUtil::Contains(ex.what(), "DuckDB"));
|
||||
}
|
||||
REQUIRE(!success);
|
||||
try {
|
||||
db = make_uniq<DuckDB>("duckdb:data/parquet-testing/h2oai/h2oai_group_small.parquet");
|
||||
success = true;
|
||||
} catch (std::exception &ex) {
|
||||
REQUIRE(StringUtil::Contains(ex.what(), "DuckDB"));
|
||||
}
|
||||
REQUIRE(!success);
|
||||
}
|
||||
|
||||
TEST_CASE("Test large number of connections to a single database", "[api]") {
|
||||
auto db = make_uniq<DuckDB>(nullptr);
|
||||
auto context = make_uniq<ClientContext>((*db).instance);
|
||||
auto &connection_manager = ConnectionManager::Get(*context);
|
||||
|
||||
duckdb::vector<duckdb::unique_ptr<Connection>> connections;
|
||||
size_t createdConnections = 5000;
|
||||
size_t remainingConnections = 500;
|
||||
size_t toRemove = createdConnections - remainingConnections;
|
||||
|
||||
for (size_t i = 0; i < createdConnections; i++) {
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
connections.push_back(std::move(conn));
|
||||
}
|
||||
|
||||
REQUIRE(connection_manager.GetConnectionCount() == createdConnections);
|
||||
|
||||
for (size_t i = 0; i < toRemove; i++) {
|
||||
connections.erase(connections.begin());
|
||||
}
|
||||
|
||||
REQUIRE(connection_manager.GetConnectionCount() == remainingConnections);
|
||||
}
|
||||
|
||||
TEST_CASE("Issue #4583: Catch Insert/Update/Delete errors", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
|
||||
con.EnableQueryVerification();
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE t0 (c0 int);"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO t0 VALUES (1);"));
|
||||
|
||||
result = con.SendQuery(
|
||||
"INSERT INTO t0(VALUES('\\x15\\x00\\x00\\x00\\x00@\\x01\\x0A\\x27:!\\x0A\\x00\\x00x12e\"\\x00'::BLOB));");
|
||||
//! Should not terminate the process
|
||||
REQUIRE_FAIL(result);
|
||||
|
||||
result = con.SendQuery("SELECT MIN(c0) FROM t0;");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
}
|
||||
|
||||
TEST_CASE("Issue #14130: InsertStatement::ToString causes InternalException later on", "[api][.]") {
|
||||
auto db = DuckDB(nullptr);
|
||||
auto conn = Connection(db);
|
||||
|
||||
conn.Query("CREATE TABLE foo(a int, b varchar, c int)");
|
||||
|
||||
auto query = "INSERT INTO Foo values (1, 'qwerty', 42)";
|
||||
|
||||
auto stmts = conn.ExtractStatements(query);
|
||||
auto &stmt = stmts[0];
|
||||
|
||||
// Issue was here: calling ToString destroyed the 'alias' of the ValuesList
|
||||
stmt->ToString();
|
||||
// Which caused an 'InternalException: expected non-empty binding_name' here
|
||||
auto prepared_stmt = conn.Prepare(std::move(stmt));
|
||||
REQUIRE(!prepared_stmt->HasError());
|
||||
REQUIRE_NO_FAIL(prepared_stmt->Execute());
|
||||
}
|
||||
|
||||
TEST_CASE("Issue #6284: CachingPhysicalOperator in pull causes issues", "[api][.]") {
|
||||
|
||||
DBConfig config;
|
||||
config.options.maximum_threads = 8;
|
||||
DuckDB db(nullptr, &config);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("select setseed(0.1); CREATE TABLE T0 AS SELECT DISTINCT (RANDOM()*9999999)::BIGINT "
|
||||
"record_nb, 0.0 x_0, 1.0 y_0 FROM range(1000000) tbl"));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE T1 AS SELECT record_nb, 0.0 x_1, 1.0 y_1 FROM T0"));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE T2 AS SELECT record_nb, 0.0 x_2, 1.0 y_2 FROM T0"));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE T3 AS SELECT record_nb, 0.0 x_3, 1.0 y_3 FROM T0"));
|
||||
auto result = con.SendQuery(R"(
|
||||
SELECT T0.record_nb,
|
||||
T1.x_1 x_1,
|
||||
T1.y_1 y_1,
|
||||
T2.x_2 x_2,
|
||||
T2.y_2 y_2,
|
||||
T3.x_3 x_3,
|
||||
T3.y_3 y_3
|
||||
FROM T0
|
||||
INNER JOIN T1 on T0.record_nb = T1.record_nb
|
||||
INNER JOIN T2 on T0.record_nb = T2.record_nb
|
||||
INNER JOIN T3 on T0.record_nb = T3.record_nb
|
||||
)");
|
||||
|
||||
idx_t count = 0;
|
||||
while (true) {
|
||||
auto chunk = result->Fetch();
|
||||
if (!chunk) {
|
||||
break;
|
||||
}
|
||||
if (chunk->size() == 0) {
|
||||
break;
|
||||
}
|
||||
count += chunk->size();
|
||||
}
|
||||
|
||||
REQUIRE(951382 == count);
|
||||
}
|
||||
|
||||
TEST_CASE("Fuzzer 50 - Alter table heap-use-after-free", "[api]") {
|
||||
// FIXME: not fixed yet
|
||||
return;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
con.SendQuery("CREATE TABLE t0(c0 INT);");
|
||||
con.SendQuery("ALTER TABLE t0 ADD c1 TIMESTAMP_SEC;");
|
||||
}
|
||||
|
||||
TEST_CASE("Test loading database with enable_external_access set to false", "[api]") {
|
||||
DBConfig config;
|
||||
config.options.enable_external_access = false;
|
||||
auto path = TestCreatePath("external_access_test");
|
||||
DuckDB db(path, &config);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_FAIL(con.Query("ATTACH 'mydb.db' AS external_access_test"));
|
||||
}
|
||||
|
||||
TEST_CASE("Test insert returning in CPP API", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.Query("CREATE TABLE test(val VARCHAR);");
|
||||
|
||||
con.Query("INSERT INTO test(val) VALUES ('query_1')");
|
||||
auto res = con.Query("INSERT INTO test(val) VALUES ('query_2') returning *");
|
||||
REQUIRE(CHECK_COLUMN(res, 0, {"query_2"}));
|
||||
|
||||
con.Query("INSERT INTO test(val) VALUES (?);", "query_arg_1");
|
||||
auto returning_args = con.Query("INSERT INTO test(val) VALUES (?) RETURNING *;", "query_arg_2");
|
||||
REQUIRE(CHECK_COLUMN(returning_args, 0, {"query_arg_2"}));
|
||||
|
||||
con.Prepare("INSERT INTO test(val) VALUES (?);")->Execute("prepared_arg_1");
|
||||
auto prepared_returning_args =
|
||||
con.Prepare("INSERT INTO test(val) VALUES (?) returning *;")->Execute("prepared_arg_2");
|
||||
REQUIRE(CHECK_COLUMN(prepared_returning_args, 0, {"prepared_arg_2"}));
|
||||
|
||||
// make sure all inserts actually inserted
|
||||
auto result = con.Query("SELECT * from test;");
|
||||
REQUIRE(CHECK_COLUMN(result, 0,
|
||||
{"query_1", "query_2", "query_arg_1", "query_arg_2", "prepared_arg_1", "prepared_arg_2"}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test a logical execute still has types after an optimization pass", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.Query("PREPARE test AS SELECT 42::INTEGER;");
|
||||
const auto query_plan = con.ExtractPlan("EXECUTE test");
|
||||
REQUIRE((query_plan->type == LogicalOperatorType::LOGICAL_EXECUTE));
|
||||
REQUIRE((query_plan->types.size() == 1));
|
||||
REQUIRE((query_plan->types[0].id() == LogicalTypeId::INTEGER));
|
||||
}
|
||||
|
||||
TEST_CASE("Test SqlStatement::ToString for UPDATE, INSERT, DELETE statements with alias of RETURNING clause", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
std::string sql;
|
||||
con.Query("CREATE TABLE test(id INT);");
|
||||
|
||||
sql = "INSERT INTO test (id) VALUES (1) RETURNING id AS inserted";
|
||||
auto stmts = con.ExtractStatements(sql);
|
||||
REQUIRE(stmts[0]->ToString() == "INSERT INTO test (id ) (VALUES (1)) RETURNING id AS inserted");
|
||||
|
||||
sql = "UPDATE test SET id = 1 RETURNING id AS updated";
|
||||
stmts = con.ExtractStatements(sql);
|
||||
REQUIRE(stmts[0]->ToString() == sql);
|
||||
|
||||
sql = "DELETE FROM test WHERE (id = 1) RETURNING id AS deleted";
|
||||
stmts = con.ExtractStatements(sql);
|
||||
REQUIRE(stmts[0]->ToString() == sql);
|
||||
}
|
||||
290
external/duckdb/test/api/test_appender_api.cpp
vendored
Normal file
290
external/duckdb/test/api/test_appender_api.cpp
vendored
Normal file
@@ -0,0 +1,290 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/main/appender.hpp"
|
||||
#include "duckdb/common/types/hugeint.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
template <class SRC>
|
||||
void TestAppendingSingleDecimalValue(SRC value, Value expected_result, uint8_t width, uint8_t scale) {
|
||||
auto db = make_uniq<DuckDB>(nullptr);
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
duckdb::unique_ptr<Appender> appender;
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
REQUIRE_NO_FAIL(conn->Query(StringUtil::Format("CREATE TABLE decimals(i DECIMAL(%d,%d))", width, scale)));
|
||||
appender = make_uniq<Appender>(*conn, "decimals");
|
||||
|
||||
appender->BeginRow();
|
||||
appender->Append<SRC>(value);
|
||||
appender->EndRow();
|
||||
|
||||
appender->Flush();
|
||||
|
||||
result = conn->Query("SELECT * FROM decimals");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {expected_result}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test appending to a decimal column", "[api]") {
|
||||
TestAppendingSingleDecimalValue<int32_t>(1, Value::DECIMAL(1000, 4, 3), 4, 3);
|
||||
TestAppendingSingleDecimalValue<int16_t>(-9999, Value::DECIMAL(-9999, 4, 0), 4, 0);
|
||||
TestAppendingSingleDecimalValue<int16_t>(9999, Value::DECIMAL(9999, 4, 0), 4, 0);
|
||||
TestAppendingSingleDecimalValue<int32_t>(99999999, Value::DECIMAL(99999999, 8, 0), 8, 0);
|
||||
TestAppendingSingleDecimalValue<const char *>("1.234", Value::DECIMAL(1234, 4, 3), 4, 3);
|
||||
TestAppendingSingleDecimalValue<const char *>("123.4", Value::DECIMAL(1234, 4, 1), 4, 1);
|
||||
hugeint_t hugeint_value;
|
||||
bool result;
|
||||
result = Hugeint::TryConvert<const char *>("3245234123123", hugeint_value);
|
||||
REQUIRE(result);
|
||||
TestAppendingSingleDecimalValue<const char *>("3245234.123123", Value::DECIMAL(hugeint_value, 19, 6), 19, 6);
|
||||
int64_t bigint_reference_value = 3245234123123;
|
||||
TestAppendingSingleDecimalValue<const char *>("3245234.123123", Value::DECIMAL(bigint_reference_value, 13, 6), 13,
|
||||
6);
|
||||
// Precision loss
|
||||
TestAppendingSingleDecimalValue<float>(12.3124324f, Value::DECIMAL(123124320, 9, 7), 9, 7);
|
||||
|
||||
// Precision loss
|
||||
result = Hugeint::TryConvert<const char *>("12345234234312432287744000", hugeint_value);
|
||||
REQUIRE(result);
|
||||
TestAppendingSingleDecimalValue<double>(12345234234.31243244234324, Value::DECIMAL(hugeint_value, 26, 15), 26, 15);
|
||||
}
|
||||
|
||||
TEST_CASE("Test using appender after connection is gone", "[api]") {
|
||||
auto db = make_uniq<DuckDB>(nullptr);
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
duckdb::unique_ptr<Appender> appender;
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
// create an appender for a non-existing table fails
|
||||
REQUIRE_THROWS(make_uniq<Appender>(*conn, "integers"));
|
||||
// now create the table and create the appender
|
||||
REQUIRE_NO_FAIL(conn->Query("CREATE TABLE integers(i INTEGER)"));
|
||||
appender = make_uniq<Appender>(*conn, "integers");
|
||||
|
||||
// we can use the appender
|
||||
appender->BeginRow();
|
||||
appender->Append<int32_t>(1);
|
||||
appender->EndRow();
|
||||
|
||||
appender->Flush();
|
||||
|
||||
result = conn->Query("SELECT * FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
|
||||
// removing the connection invalidates the appender
|
||||
conn.reset();
|
||||
appender->BeginRow();
|
||||
appender->Append<int32_t>(2);
|
||||
appender->EndRow();
|
||||
|
||||
// the connection is gone
|
||||
REQUIRE_THROWS(appender->Flush());
|
||||
}
|
||||
|
||||
TEST_CASE("Test appender and connection destruction order", "[api]") {
|
||||
for (idx_t i = 0; i < 6; i++) {
|
||||
auto db = make_uniq<DuckDB>(nullptr);
|
||||
auto con = make_uniq<Connection>(*db);
|
||||
REQUIRE_NO_FAIL(con->Query("CREATE TABLE integers(i INTEGER)"));
|
||||
auto appender = make_uniq<Appender>(*con, "integers");
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
// db - con - appender
|
||||
db.reset();
|
||||
con.reset();
|
||||
appender.reset();
|
||||
break;
|
||||
case 1:
|
||||
// db - appender - con
|
||||
db.reset();
|
||||
appender.reset();
|
||||
con.reset();
|
||||
break;
|
||||
case 2:
|
||||
// con - db - appender
|
||||
con.reset();
|
||||
db.reset();
|
||||
appender.reset();
|
||||
break;
|
||||
case 3:
|
||||
// con - appender - db
|
||||
con.reset();
|
||||
appender.reset();
|
||||
db.reset();
|
||||
break;
|
||||
case 4:
|
||||
// appender - con - db
|
||||
appender.reset();
|
||||
con.reset();
|
||||
db.reset();
|
||||
break;
|
||||
default:
|
||||
// appender - db - con
|
||||
appender.reset();
|
||||
db.reset();
|
||||
con.reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test using appender after table is dropped", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
// create the table
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
|
||||
// now create the appender
|
||||
Appender appender(con, "integers");
|
||||
|
||||
// appending works initially
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
appender.EndRow();
|
||||
appender.Flush();
|
||||
|
||||
// now drop the table
|
||||
REQUIRE_NO_FAIL(con.Query("DROP TABLE integers"));
|
||||
// now appending fails
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
appender.EndRow();
|
||||
REQUIRE_THROWS(appender.Flush());
|
||||
}
|
||||
|
||||
TEST_CASE("Test using appender after table is altered", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
// create the table
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
|
||||
// now create the appender
|
||||
Appender appender(con, "integers");
|
||||
|
||||
// appending works initially
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
appender.EndRow();
|
||||
|
||||
// now create a new table with the same name but with different types
|
||||
REQUIRE_NO_FAIL(con.Query("DROP TABLE integers"));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i VARCHAR, j INTEGER)"));
|
||||
// now appending fails
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
appender.EndRow();
|
||||
REQUIRE_THROWS(appender.Flush());
|
||||
}
|
||||
|
||||
TEST_CASE("Test appenders and transactions", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
// create the table
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
|
||||
// now create the appender
|
||||
Appender appender(con, "integers");
|
||||
|
||||
// rollback an append
|
||||
REQUIRE_NO_FAIL(con.Query("BEGIN TRANSACTION"));
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
appender.EndRow();
|
||||
appender.Flush();
|
||||
result = con.Query("SELECT * FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("ROLLBACK"));
|
||||
result = con.Query("SELECT * FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {}));
|
||||
|
||||
// we can still use the appender in auto commit mode
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
appender.EndRow();
|
||||
appender.Flush();
|
||||
|
||||
result = con.Query("SELECT * FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test using multiple appenders", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
// create the table
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE t1(i INTEGER)"));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE t2(i VARCHAR, j DATE)"));
|
||||
// now create the appender
|
||||
Appender a1(con, "t1");
|
||||
Appender a2(con, "t2");
|
||||
|
||||
// begin appending from both
|
||||
REQUIRE_NO_FAIL(con.Query("BEGIN TRANSACTION"));
|
||||
a1.BeginRow();
|
||||
a1.Append<int32_t>(1);
|
||||
a1.EndRow();
|
||||
a1.Flush();
|
||||
|
||||
a2.BeginRow();
|
||||
a2.Append<const char *>("hello");
|
||||
a2.Append<Value>(Value::DATE(1992, 1, 1));
|
||||
a2.EndRow();
|
||||
a2.Flush();
|
||||
|
||||
result = con.Query("SELECT * FROM t1");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
result = con.Query("SELECT * FROM t2");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"hello"}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {Value::DATE(1992, 1, 1)}));
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("ROLLBACK"));
|
||||
result = con.Query("SELECT * FROM t1");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test usage of appender interleaved with connection usage", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
// create the table
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE t1(i INTEGER)"));
|
||||
Appender appender(con, "t1");
|
||||
|
||||
appender.AppendRow(1);
|
||||
appender.Flush();
|
||||
|
||||
result = con.Query("SELECT * FROM t1");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
|
||||
appender.AppendRow(2);
|
||||
appender.Flush();
|
||||
|
||||
result = con.Query("SELECT * FROM t1");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1, 2}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test appender during stack unwinding", "[api]") {
|
||||
// test appender exception
|
||||
DuckDB db;
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
|
||||
{
|
||||
Appender appender(con, "integers");
|
||||
appender.AppendRow(1);
|
||||
|
||||
// closing the apender throws an exception, because we changed the table's columns
|
||||
REQUIRE_NO_FAIL(con.Query("ALTER TABLE integers ADD COLUMN j VARCHAR"));
|
||||
REQUIRE_THROWS(appender.Close());
|
||||
}
|
||||
REQUIRE_NO_FAIL(con.Query("DROP TABLE integers"));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
|
||||
try {
|
||||
// now we do the same, but we trigger the destructor of the appender during stack unwinding
|
||||
Appender appender(con, "integers");
|
||||
appender.AppendRow(1);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("ALTER TABLE integers ADD COLUMN j VARCHAR"));
|
||||
{ throw std::runtime_error("Hello"); }
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
83
external/duckdb/test/api/test_bignum.cpp
vendored
Normal file
83
external/duckdb/test/api/test_bignum.cpp
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/common/types/bignum.hpp"
|
||||
#include "catch.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test Bignum::FromByteArray", "[api]") {
|
||||
{ // 0
|
||||
uint8_t data[] {0};
|
||||
idx_t size = 1;
|
||||
bool is_negative = false;
|
||||
auto str = Bignum::FromByteArray(data, size, is_negative);
|
||||
REQUIRE(str.size() == size + 3);
|
||||
REQUIRE(uint8_t(str[0]) == 0x80);
|
||||
REQUIRE(uint8_t(str[1]) == 0x00);
|
||||
REQUIRE(uint8_t(str[2]) == uint8_t(size));
|
||||
for (idx_t i = 0; i < size; i++) {
|
||||
REQUIRE(uint8_t(str[3 + i]) == data[i]);
|
||||
}
|
||||
}
|
||||
{ // -1
|
||||
uint8_t data[] {1};
|
||||
idx_t size = 1;
|
||||
bool is_negative = true;
|
||||
auto str = Bignum::FromByteArray(data, size, is_negative);
|
||||
REQUIRE(str.size() == size + 3);
|
||||
REQUIRE(uint8_t(str[0]) == 0x7f);
|
||||
REQUIRE(uint8_t(str[1]) == 0xff);
|
||||
REQUIRE(uint8_t(str[2]) == uint8_t(~size));
|
||||
for (idx_t i = 0; i < size; i++) {
|
||||
REQUIRE(uint8_t(str[3 + i]) == uint8_t(~data[i]));
|
||||
}
|
||||
}
|
||||
{ // 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,
|
||||
};
|
||||
idx_t size = 128;
|
||||
bool is_negative = false;
|
||||
auto str = Bignum::FromByteArray(data, size, is_negative);
|
||||
REQUIRE(str.size() == size + 3);
|
||||
REQUIRE(uint8_t(str[0]) == 0x80);
|
||||
REQUIRE(uint8_t(str[1]) == 0x00);
|
||||
REQUIRE(uint8_t(str[2]) == uint8_t(size));
|
||||
for (idx_t i = 0; i < size; i++) {
|
||||
REQUIRE(uint8_t(str[3 + i]) == data[i]);
|
||||
}
|
||||
}
|
||||
{ // 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,
|
||||
};
|
||||
idx_t size = 128;
|
||||
bool is_negative = true;
|
||||
auto str = Bignum::FromByteArray(data, size, is_negative);
|
||||
REQUIRE(str.size() == size + 3);
|
||||
REQUIRE(uint8_t(str[0]) == 0x7f);
|
||||
REQUIRE(uint8_t(str[1]) == 0xff);
|
||||
REQUIRE(uint8_t(str[2]) == uint8_t(~size));
|
||||
for (idx_t i = 0; i < size; i++) {
|
||||
REQUIRE(uint8_t(str[3 + i]) == uint8_t(~data[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
117
external/duckdb/test/api/test_config.cpp
vendored
Normal file
117
external/duckdb/test/api/test_config.cpp
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test DB config configuration", "[api]") {
|
||||
DBConfig config;
|
||||
|
||||
auto options = config.GetOptions();
|
||||
|
||||
map<string, duckdb::vector<string>> test_options;
|
||||
test_options["access_mode"] = {"automatic", "read_only", "read_write"};
|
||||
test_options["default_order"] = {"asc", "desc"};
|
||||
test_options["default_null_order"] = {"nulls_first", "nulls_last"};
|
||||
test_options["enable_external_access"] = {"true", "false"};
|
||||
test_options["max_memory"] = {"-1", "16GB"};
|
||||
test_options["threads"] = {"1", "4"};
|
||||
|
||||
REQUIRE(config.GetOptionByName("unknownoption") == nullptr);
|
||||
|
||||
for (auto &option : options) {
|
||||
auto op = config.GetOptionByName(option.name);
|
||||
REQUIRE(op);
|
||||
|
||||
auto entry = test_options.find(option.name);
|
||||
if (entry != test_options.end()) {
|
||||
for (auto &str_val : entry->second) {
|
||||
Value val(str_val);
|
||||
REQUIRE_NOTHROW(config.SetOption(option, val));
|
||||
}
|
||||
Value invalid_val("___this_is_probably_invalid");
|
||||
REQUIRE_THROWS(config.SetOption(option, invalid_val));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test allowed options", "[api]") {
|
||||
case_insensitive_map_t<Value> config_dict;
|
||||
string option;
|
||||
|
||||
SECTION("allowed_directories") {
|
||||
config_dict.emplace("allowed_directories", Value::LIST({Value("test")}));
|
||||
option = "allowed_directories";
|
||||
}
|
||||
SECTION("allowed_paths") {
|
||||
config_dict.emplace("allowed_paths", Value::LIST({Value("test")}));
|
||||
option = "allowed_paths";
|
||||
}
|
||||
|
||||
SECTION("enable_logging") {
|
||||
config_dict.emplace("enable_logging", Value::BOOLEAN(false));
|
||||
option = "enable_logging";
|
||||
}
|
||||
|
||||
SECTION("disabled_filesystems") {
|
||||
config_dict.emplace("disabled_filesystems", Value::BOOLEAN(false));
|
||||
option = "disabled_filesystems";
|
||||
}
|
||||
|
||||
SECTION("logging_mode") {
|
||||
config_dict.emplace("logging_mode", Value::BOOLEAN(false));
|
||||
option = "logging_mode";
|
||||
}
|
||||
|
||||
SECTION("logging_storage") {
|
||||
config_dict.emplace("logging_storage", Value::BOOLEAN(false));
|
||||
option = "logging_storage";
|
||||
}
|
||||
|
||||
SECTION("logging_level") {
|
||||
config_dict.emplace("logging_level", Value::BOOLEAN(false));
|
||||
option = "logging_level";
|
||||
}
|
||||
|
||||
SECTION("enabled_log_types") {
|
||||
config_dict.emplace("enabled_log_types", Value::BOOLEAN(false));
|
||||
option = "enabled_log_types";
|
||||
}
|
||||
|
||||
SECTION("disabled_log_types") {
|
||||
config_dict.emplace("disabled_log_types", Value::BOOLEAN(false));
|
||||
option = "disabled_log_types";
|
||||
}
|
||||
|
||||
try {
|
||||
DBConfig config(config_dict, false);
|
||||
} catch (std::exception &ex) {
|
||||
ErrorData error_data(ex);
|
||||
REQUIRE(error_data.Type() == ExceptionType::INVALID_INPUT);
|
||||
REQUIRE(error_data.RawMessage() ==
|
||||
StringUtil::Format("Cannot change/set %s before the database is started", option));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test user_agent", "[api]") {
|
||||
{
|
||||
// Default duckdb_api is cpp
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
auto res = con.Query("PRAGMA user_agent");
|
||||
REQUIRE_THAT(res->GetValue(0, 0).ToString(), Catch::Matchers::Matches("duckdb/.*(.*) cpp"));
|
||||
}
|
||||
{
|
||||
// The latest provided duckdb_api is used
|
||||
DBConfig config;
|
||||
config.SetOptionByName("duckdb_api", "capi");
|
||||
config.SetOptionByName("duckdb_api", "go");
|
||||
DuckDB db("", &config);
|
||||
Connection con(db);
|
||||
auto res = con.Query("PRAGMA user_agent");
|
||||
REQUIRE_THAT(res->GetValue(0, 0).ToString(), Catch::Matchers::Matches("duckdb/.*(.*) go"));
|
||||
}
|
||||
}
|
||||
56
external/duckdb/test/api/test_custom_allocator.cpp
vendored
Normal file
56
external/duckdb/test/api/test_custom_allocator.cpp
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/main/appender.hpp"
|
||||
#include "duckdb/common/atomic.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
struct MyAllocateData : public PrivateAllocatorData {
|
||||
MyAllocateData(atomic<idx_t> *memory_counter_p) : memory_counter(memory_counter_p) {
|
||||
}
|
||||
|
||||
atomic<idx_t> *memory_counter;
|
||||
};
|
||||
|
||||
data_ptr_t my_allocate_function(PrivateAllocatorData *private_data, idx_t size) {
|
||||
auto my_allocate_data = (MyAllocateData *)private_data;
|
||||
*my_allocate_data->memory_counter += size;
|
||||
return data_ptr_cast(malloc(size));
|
||||
}
|
||||
|
||||
void my_free_function(PrivateAllocatorData *private_data, data_ptr_t pointer, idx_t size) {
|
||||
auto my_allocate_data = (MyAllocateData *)private_data;
|
||||
*my_allocate_data->memory_counter -= size;
|
||||
free(pointer);
|
||||
}
|
||||
|
||||
data_ptr_t my_reallocate_function(PrivateAllocatorData *private_data, data_ptr_t pointer, idx_t old_size, idx_t size) {
|
||||
auto my_allocate_data = (MyAllocateData *)private_data;
|
||||
*my_allocate_data->memory_counter -= old_size;
|
||||
*my_allocate_data->memory_counter += size;
|
||||
return data_ptr_cast(realloc(pointer, size));
|
||||
}
|
||||
|
||||
TEST_CASE("Test using a custom allocator", "[api][.]") {
|
||||
atomic<idx_t> memory_counter;
|
||||
memory_counter = 0;
|
||||
|
||||
REQUIRE(memory_counter.load() == 0);
|
||||
|
||||
DBConfig config;
|
||||
config.allocator = make_uniq<Allocator>(my_allocate_function, my_free_function, my_reallocate_function,
|
||||
make_uniq<MyAllocateData>(&memory_counter));
|
||||
DuckDB db(nullptr, &config);
|
||||
Connection con(db);
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE tbl AS SELECT * FROM range(1000000)"));
|
||||
|
||||
// check that the memory counter reported anything
|
||||
REQUIRE(memory_counter.load() > 0);
|
||||
auto table_memory_usage = memory_counter.load();
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("DROP TABLE tbl"));
|
||||
|
||||
// check that the memory counter usage has decreased after we dropped the table
|
||||
REQUIRE(memory_counter.load() < table_memory_usage);
|
||||
}
|
||||
48
external/duckdb/test/api/test_dbdir.cpp
vendored
Normal file
48
external/duckdb/test/api/test_dbdir.cpp
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/common/file_system.hpp"
|
||||
#include "duckdb/storage/storage_manager.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
static void test_in_memory_initialization(string dbdir) {
|
||||
duckdb::unique_ptr<FileSystem> fs = FileSystem::CreateLocal();
|
||||
duckdb::unique_ptr<DuckDB> db;
|
||||
duckdb::unique_ptr<Connection> con;
|
||||
string in_memory_tmp = ".tmp";
|
||||
|
||||
// make sure the temporary folder does not exist
|
||||
DeleteDatabase(dbdir);
|
||||
fs->RemoveDirectory(in_memory_tmp);
|
||||
|
||||
// cannot create an in-memory database using ":memory:" argument
|
||||
REQUIRE_NOTHROW(db = make_uniq<DuckDB>(dbdir));
|
||||
REQUIRE_NOTHROW(con = make_uniq<Connection>(*db));
|
||||
|
||||
// force the in-memory directory to be created by creating a table bigger than the memory limit
|
||||
REQUIRE_NO_FAIL(con->Query("PRAGMA memory_limit='2MB'"));
|
||||
REQUIRE_NO_FAIL(con->Query("CREATE TABLE integers AS SELECT * FROM range(1000000)"));
|
||||
|
||||
// the temporary folder .tmp should be created in in-memory mode
|
||||
REQUIRE(fs->DirectoryExists(in_memory_tmp));
|
||||
|
||||
// the database dir should not be created in in-memory mode
|
||||
REQUIRE(!fs->DirectoryExists(dbdir));
|
||||
|
||||
// clean up
|
||||
con.reset();
|
||||
db.reset();
|
||||
|
||||
// make sure to clean up the database & temporary folder
|
||||
DeleteDatabase(dbdir);
|
||||
fs->RemoveDirectory(in_memory_tmp);
|
||||
}
|
||||
|
||||
TEST_CASE("Test in-memory database initialization argument \":memory:\"", "[api][.]") {
|
||||
test_in_memory_initialization(":memory:");
|
||||
}
|
||||
|
||||
TEST_CASE("Test in-memory database initialization argument \"\"", "[api][.]") {
|
||||
test_in_memory_initialization("");
|
||||
}
|
||||
31
external/duckdb/test/api/test_extension_setting_autoload.cpp
vendored
Normal file
31
external/duckdb/test/api/test_extension_setting_autoload.cpp
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test autoload of extension settings", "[api]") {
|
||||
DBConfig config;
|
||||
config.SetOptionByName("timezone", "America/Los_Angeles");
|
||||
|
||||
config.options.allow_unsigned_extensions = true;
|
||||
config.options.autoload_known_extensions = true;
|
||||
auto env_var = std::getenv("LOCAL_EXTENSION_REPO");
|
||||
if (!env_var) {
|
||||
return;
|
||||
}
|
||||
config.options.autoinstall_extension_repo = std::string(env_var);
|
||||
REQUIRE(config.options.unrecognized_options.count("timezone"));
|
||||
|
||||
// Create a connection
|
||||
duckdb::unique_ptr<DuckDB> db;
|
||||
REQUIRE_NOTHROW(db = make_uniq<DuckDB>(nullptr, &config));
|
||||
Connection con(*db);
|
||||
|
||||
auto res = con.Query("select current_setting('timezone')");
|
||||
REQUIRE(CHECK_COLUMN(res, 0, {Value("America/Los_Angeles")}));
|
||||
}
|
||||
271
external/duckdb/test/api/test_get_table_names.cpp
vendored
Normal file
271
external/duckdb/test/api/test_get_table_names.cpp
vendored
Normal file
@@ -0,0 +1,271 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/parser/parser.hpp"
|
||||
#include "duckdb/planner/logical_operator.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test GetTableNames", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
unordered_set<string> table_names;
|
||||
|
||||
// standard
|
||||
table_names = con.GetTableNames("SELECT * FROM my_table");
|
||||
REQUIRE(table_names.size() == 1);
|
||||
REQUIRE(table_names.count("my_table"));
|
||||
|
||||
// fetch a specific column
|
||||
table_names = con.GetTableNames("SELECT col_a FROM my_table");
|
||||
REQUIRE(table_names.size() == 1);
|
||||
REQUIRE(table_names.count("my_table"));
|
||||
|
||||
// multiple tables
|
||||
table_names = con.GetTableNames("SELECT * FROM my_table1, my_table2, my_table3");
|
||||
REQUIRE(table_names.size() == 3);
|
||||
REQUIRE(table_names.count("my_table1"));
|
||||
REQUIRE(table_names.count("my_table2"));
|
||||
REQUIRE(table_names.count("my_table3"));
|
||||
|
||||
// same table is mentioned multiple times
|
||||
table_names = con.GetTableNames("SELECT col_a FROM my_table, my_table m2, my_table m3");
|
||||
REQUIRE(table_names.size() == 1);
|
||||
REQUIRE(table_names.count("my_table"));
|
||||
|
||||
// cte
|
||||
table_names = con.GetTableNames("WITH cte AS (SELECT * FROM my_table) SELECT * FROM cte");
|
||||
REQUIRE(table_names.size() == 1);
|
||||
REQUIRE(table_names.count("my_table"));
|
||||
|
||||
// subqueries
|
||||
table_names = con.GetTableNames("SELECT * FROM (SELECT * FROM (SELECT * FROM my_table) bla) bla3");
|
||||
REQUIRE(table_names.size() == 1);
|
||||
REQUIRE(table_names.count("my_table"));
|
||||
|
||||
// join
|
||||
table_names = con.GetTableNames("SELECT col_a FROM my_table JOIN my_table2 ON (my_table.col_b=my_table2.col_d)");
|
||||
REQUIRE(table_names.size() == 2);
|
||||
REQUIRE(table_names.count("my_table"));
|
||||
REQUIRE(table_names.count("my_table2"));
|
||||
|
||||
// scalar subquery
|
||||
table_names = con.GetTableNames("SELECT (SELECT COUNT(*) FROM my_table)");
|
||||
REQUIRE(table_names.size() == 1);
|
||||
REQUIRE(table_names.count("my_table"));
|
||||
|
||||
// set operations
|
||||
table_names =
|
||||
con.GetTableNames("SELECT * FROM my_table UNION ALL SELECT * FROM my_table2 INTERSECT SELECT * FROM my_table3");
|
||||
REQUIRE(table_names.size() == 3);
|
||||
REQUIRE(table_names.count("my_table"));
|
||||
REQUIRE(table_names.count("my_table2"));
|
||||
REQUIRE(table_names.count("my_table3"));
|
||||
|
||||
// window functions
|
||||
table_names = con.GetTableNames("SELECT row_number() OVER (ORDER BY (SELECT i+j FROM my_table2)) FROM my_table");
|
||||
REQUIRE(table_names.size() == 2);
|
||||
REQUIRE(table_names.count("my_table"));
|
||||
REQUIRE(table_names.count("my_table2"));
|
||||
|
||||
// views are expanded
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE my_table_for_view(i INT)"));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE VIEW v1 AS SELECT * FROM my_table_for_view"));
|
||||
|
||||
table_names = con.GetTableNames("SELECT col_a FROM v1");
|
||||
REQUIRE(table_names.size() == 1);
|
||||
REQUIRE(table_names.count("my_table_for_view"));
|
||||
|
||||
// * exclude
|
||||
table_names = con.GetTableNames("select * exclude (x) from df");
|
||||
REQUIRE(table_names.size() == 1);
|
||||
REQUIRE(table_names.count("df"));
|
||||
|
||||
// * replace
|
||||
table_names = con.GetTableNames("select * replace (42 as x) from df");
|
||||
REQUIRE(table_names.size() == 1);
|
||||
REQUIRE(table_names.count("df"));
|
||||
|
||||
// qualified with schema.table and catalog.schema.table
|
||||
string query = "SELECT * FROM schema1.table1, catalog2.schema2.table2";
|
||||
table_names = con.GetTableNames(query, true);
|
||||
REQUIRE(table_names.size() == 2);
|
||||
REQUIRE(table_names.count("schema1.table1"));
|
||||
REQUIRE(table_names.count("catalog2.schema2.table2"));
|
||||
|
||||
// qualified and escaped
|
||||
query = "SELECT * FROM schema1.table1, catalog3.\"schema.2\".\"table.2\"";
|
||||
table_names = con.GetTableNames(query, true);
|
||||
REQUIRE(table_names.size() == 2);
|
||||
REQUIRE(table_names.count("schema1.table1"));
|
||||
REQUIRE(table_names.count("catalog3.\"schema.2\".\"table.2\""));
|
||||
|
||||
// With alias
|
||||
query = "SELECT * FROM schema1.table1 alias1, catalog3.\"schema.2\".\"table.2\" alias2";
|
||||
table_names = con.GetTableNames(query, true); // qualified
|
||||
REQUIRE(table_names.size() == 2);
|
||||
REQUIRE(table_names.count("schema1.table1 AS alias1"));
|
||||
REQUIRE(table_names.count("catalog3.\"schema.2\".\"table.2\" AS alias2"));
|
||||
table_names = con.GetTableNames(query); // default
|
||||
REQUIRE(table_names.size() == 2);
|
||||
REQUIRE(table_names.count("table1"));
|
||||
REQUIRE(table_names.count("table.2"));
|
||||
|
||||
// generate_series
|
||||
table_names = con.GetTableNames("with series_generator as (select * from generate_series(TIMESTAMP '2001-04-10', "
|
||||
"TIMESTAMP '2001-04-11', INTERVAL 1 HOUR)) select * from series_generator");
|
||||
REQUIRE(table_names.empty());
|
||||
|
||||
if (!db.ExtensionIsLoaded("tpch")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TPCH
|
||||
// run all TPC-H queries twice
|
||||
// one WITHOUT the tables in the catalog
|
||||
// once WITH the tables in the catalog
|
||||
for (idx_t i = 0; i < 2; i++) {
|
||||
table_names = con.GetTableNames("PRAGMA tpch(1)");
|
||||
REQUIRE(table_names.size() == 1);
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(2)");
|
||||
REQUIRE(table_names.size() == 5);
|
||||
REQUIRE(table_names.count("part"));
|
||||
REQUIRE(table_names.count("supplier"));
|
||||
REQUIRE(table_names.count("partsupp"));
|
||||
REQUIRE(table_names.count("nation"));
|
||||
REQUIRE(table_names.count("region"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(3)");
|
||||
REQUIRE(table_names.size() == 3);
|
||||
REQUIRE(table_names.count("customer"));
|
||||
REQUIRE(table_names.count("orders"));
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(4)");
|
||||
REQUIRE(table_names.size() == 2);
|
||||
REQUIRE(table_names.count("orders"));
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(5)");
|
||||
REQUIRE(table_names.size() == 6);
|
||||
REQUIRE(table_names.count("customer"));
|
||||
REQUIRE(table_names.count("orders"));
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
REQUIRE(table_names.count("supplier"));
|
||||
REQUIRE(table_names.count("nation"));
|
||||
REQUIRE(table_names.count("region"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(6)");
|
||||
REQUIRE(table_names.size() == 1);
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(7)");
|
||||
REQUIRE(table_names.size() == 5);
|
||||
REQUIRE(table_names.count("supplier"));
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
REQUIRE(table_names.count("orders"));
|
||||
REQUIRE(table_names.count("customer"));
|
||||
REQUIRE(table_names.count("nation"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(8)");
|
||||
REQUIRE(table_names.size() == 7);
|
||||
REQUIRE(table_names.count("part"));
|
||||
REQUIRE(table_names.count("supplier"));
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
REQUIRE(table_names.count("orders"));
|
||||
REQUIRE(table_names.count("customer"));
|
||||
REQUIRE(table_names.count("nation"));
|
||||
REQUIRE(table_names.count("region"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(9)");
|
||||
REQUIRE(table_names.size() == 6);
|
||||
REQUIRE(table_names.count("part"));
|
||||
REQUIRE(table_names.count("supplier"));
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
REQUIRE(table_names.count("partsupp"));
|
||||
REQUIRE(table_names.count("orders"));
|
||||
REQUIRE(table_names.count("nation"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(10)");
|
||||
REQUIRE(table_names.size() == 4);
|
||||
REQUIRE(table_names.count("customer"));
|
||||
REQUIRE(table_names.count("orders"));
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
REQUIRE(table_names.count("nation"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(11)");
|
||||
REQUIRE(table_names.size() == 3);
|
||||
REQUIRE(table_names.count("partsupp"));
|
||||
REQUIRE(table_names.count("supplier"));
|
||||
REQUIRE(table_names.count("nation"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(12)");
|
||||
REQUIRE(table_names.size() == 2);
|
||||
REQUIRE(table_names.count("orders"));
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(13)");
|
||||
REQUIRE(table_names.size() == 2);
|
||||
REQUIRE(table_names.count("customer"));
|
||||
REQUIRE(table_names.count("orders"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(14)");
|
||||
REQUIRE(table_names.size() == 2);
|
||||
REQUIRE(table_names.count("part"));
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(15)");
|
||||
REQUIRE(table_names.size() == 2);
|
||||
REQUIRE(table_names.count("supplier"));
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(16)");
|
||||
REQUIRE(table_names.size() == 3);
|
||||
REQUIRE(table_names.count("partsupp"));
|
||||
REQUIRE(table_names.count("part"));
|
||||
REQUIRE(table_names.count("supplier"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(17)");
|
||||
REQUIRE(table_names.size() == 2);
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
REQUIRE(table_names.count("part"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(18)");
|
||||
REQUIRE(table_names.size() == 3);
|
||||
REQUIRE(table_names.count("customer"));
|
||||
REQUIRE(table_names.count("orders"));
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(19)");
|
||||
REQUIRE(table_names.size() == 2);
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
REQUIRE(table_names.count("part"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(20)");
|
||||
REQUIRE(table_names.size() == 5);
|
||||
REQUIRE(table_names.count("supplier"));
|
||||
REQUIRE(table_names.count("nation"));
|
||||
REQUIRE(table_names.count("partsupp"));
|
||||
REQUIRE(table_names.count("part"));
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(21)");
|
||||
REQUIRE(table_names.size() == 4);
|
||||
REQUIRE(table_names.count("supplier"));
|
||||
REQUIRE(table_names.count("lineitem"));
|
||||
REQUIRE(table_names.count("orders"));
|
||||
REQUIRE(table_names.count("nation"));
|
||||
|
||||
table_names = con.GetTableNames("PRAGMA tpch(22)");
|
||||
REQUIRE(table_names.size() == 2);
|
||||
REQUIRE(table_names.count("customer"));
|
||||
REQUIRE(table_names.count("orders"));
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CALL dbgen(sf=0)"));
|
||||
}
|
||||
}
|
||||
57
external/duckdb/test/api/test_insertion_order_preserving_map.cpp
vendored
Normal file
57
external/duckdb/test/api/test_insertion_order_preserving_map.cpp
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/common/file_system.hpp"
|
||||
#include "duckdb/storage/storage_manager.hpp"
|
||||
#include "duckdb/common/insertion_order_preserving_map.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test Insertion Order Preserving Map: duplicate insert", "[api][.]") {
|
||||
InsertionOrderPreservingMap<int> map;
|
||||
|
||||
map.insert("a", 1);
|
||||
map.insert("b", 2);
|
||||
map.insert("c", 3);
|
||||
map.insert("b", 4);
|
||||
|
||||
auto it = map.find(string("c"));
|
||||
map.erase(it);
|
||||
|
||||
int count = 0;
|
||||
for (auto it = map.begin(); it != map.end(); it++) {
|
||||
count++;
|
||||
}
|
||||
REQUIRE(count == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Test Insertion Order Preserving Map: double erase", "[api][.]") {
|
||||
InsertionOrderPreservingMap<idx_t> map;
|
||||
map.insert("a", 1);
|
||||
map.insert("b", 2);
|
||||
map.insert("c", 3);
|
||||
map.insert("b", 4);
|
||||
map.erase(map.find("c"));
|
||||
|
||||
int count = 0;
|
||||
for (auto it = map.begin(); it != map.end(); it++) {
|
||||
count++;
|
||||
}
|
||||
|
||||
REQUIRE(count == 2);
|
||||
|
||||
map.insert("a", 1);
|
||||
map.insert("b", 2);
|
||||
map.insert("c", 3);
|
||||
map.insert("b", 4);
|
||||
map.erase(map.find("c"));
|
||||
map.erase(map.find("b"));
|
||||
|
||||
count = 0;
|
||||
for (auto it = map.begin(); it != map.end(); it++) {
|
||||
count++;
|
||||
}
|
||||
|
||||
REQUIRE(count == 1);
|
||||
REQUIRE(map.find("b") == map.end());
|
||||
}
|
||||
207
external/duckdb/test/api/test_instance_cache.cpp
vendored
Normal file
207
external/duckdb/test/api/test_instance_cache.cpp
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/main/db_instance_cache.hpp"
|
||||
#include "duckdb/storage/storage_extension.hpp"
|
||||
#include "duckdb/transaction/duck_transaction_manager.hpp"
|
||||
#include "duckdb/catalog/duck_catalog.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
using namespace duckdb;
|
||||
|
||||
static void background_thread_connect(DBInstanceCache *instance_cache, std::string *path) {
|
||||
try {
|
||||
DBConfig config;
|
||||
auto connection = instance_cache->GetOrCreateInstance(*path, config, true);
|
||||
connection.reset();
|
||||
} catch (std::exception &ex) {
|
||||
FAIL(ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test parallel connection and destruction of connections with database instance cache", "[api][.]") {
|
||||
DBInstanceCache instance_cache;
|
||||
|
||||
for (idx_t i = 0; i < 100; i++) {
|
||||
auto path = TestCreatePath("instance_cache_parallel.db");
|
||||
|
||||
DBConfig config;
|
||||
auto shared_db = instance_cache.GetOrCreateInstance(path, config, true);
|
||||
|
||||
std::thread background_thread(background_thread_connect, &instance_cache, &path);
|
||||
shared_db.reset();
|
||||
background_thread.join();
|
||||
TestDeleteFile(path);
|
||||
REQUIRE(1);
|
||||
}
|
||||
}
|
||||
struct DelayingStorageExtension : StorageExtension {
|
||||
DelayingStorageExtension() {
|
||||
attach = [](optional_ptr<StorageExtensionInfo>, ClientContext &, AttachedDatabase &db, const string &,
|
||||
AttachInfo &info, AttachOptions &) -> unique_ptr<Catalog> {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
return make_uniq_base<Catalog, DuckCatalog>(db);
|
||||
};
|
||||
create_transaction_manager = [](optional_ptr<StorageExtensionInfo>, AttachedDatabase &db,
|
||||
Catalog &) -> unique_ptr<TransactionManager> {
|
||||
return make_uniq<DuckTransactionManager>(db);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("Test db creation does not block instance cache", "[api]") {
|
||||
DBInstanceCache instance_cache;
|
||||
using namespace std::chrono;
|
||||
|
||||
auto second_creation_was_quick = false;
|
||||
shared_ptr<DuckDB> stick_around;
|
||||
std::thread t1 {[&instance_cache, &second_creation_was_quick, &stick_around]() {
|
||||
DBConfig db_config;
|
||||
|
||||
db_config.storage_extensions["delay"] = make_uniq<DelayingStorageExtension>();
|
||||
stick_around = instance_cache.GetOrCreateInstance("delay::memory:", db_config, true);
|
||||
|
||||
const auto start_time = steady_clock::now();
|
||||
for (idx_t i = 0; i < 10; i++) {
|
||||
db_config.storage_extensions["delay"] = make_uniq<DelayingStorageExtension>();
|
||||
instance_cache.GetOrCreateInstance("delay::memory:", db_config, true);
|
||||
}
|
||||
const auto end_time = steady_clock::now();
|
||||
second_creation_was_quick = duration_cast<seconds>(end_time - start_time).count() < 1;
|
||||
}};
|
||||
std::this_thread::sleep_for(seconds(2));
|
||||
|
||||
auto opening_slow_db_takes_remaining_time = false;
|
||||
std::thread t2 {[&instance_cache, &opening_slow_db_takes_remaining_time]() {
|
||||
DBConfig db_config;
|
||||
const auto start_time = steady_clock::now();
|
||||
instance_cache.GetOrCreateInstance("delay::memory:", db_config, true);
|
||||
const auto end_time = steady_clock::now();
|
||||
const auto duration = duration_cast<milliseconds>(end_time - start_time);
|
||||
opening_slow_db_takes_remaining_time = duration > seconds(2) && duration < seconds(4);
|
||||
}};
|
||||
|
||||
auto no_delay_for_db_creation = true;
|
||||
std::thread t3 {[&instance_cache, &no_delay_for_db_creation]() {
|
||||
const auto start_time = steady_clock::now();
|
||||
DBConfig db_config;
|
||||
while (start_time + seconds(3) < steady_clock::now()) {
|
||||
auto db_start_time = steady_clock::now();
|
||||
instance_cache.GetOrCreateInstance(":memory:", db_config, false);
|
||||
no_delay_for_db_creation &= duration_cast<milliseconds>(steady_clock::now() - db_start_time).count() < 100;
|
||||
}
|
||||
}};
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
t3.join();
|
||||
REQUIRE(second_creation_was_quick);
|
||||
REQUIRE(opening_slow_db_takes_remaining_time);
|
||||
REQUIRE(no_delay_for_db_creation);
|
||||
}
|
||||
|
||||
TEST_CASE("Test attaching the same database path from different databases", "[api][.]") {
|
||||
DBInstanceCache instance_cache;
|
||||
auto test_path = TestCreatePath("instance_cache_reuse.db");
|
||||
|
||||
DBConfig config;
|
||||
auto db1 = instance_cache.GetOrCreateInstance(":memory:", config, false);
|
||||
auto db2 = instance_cache.GetOrCreateInstance(":memory:", config, false);
|
||||
|
||||
Connection con1(*db1);
|
||||
Connection con2(*db2);
|
||||
SECTION("Regular ATTACH conflict") {
|
||||
string attach_query = "ATTACH '" + test_path + "' AS db_ref";
|
||||
|
||||
REQUIRE_NO_FAIL(con1.Query(attach_query));
|
||||
|
||||
// fails - already attached in db1
|
||||
REQUIRE_FAIL(con2.Query(attach_query));
|
||||
|
||||
// if we detach from con1, we can now attach in con2
|
||||
REQUIRE_NO_FAIL(con1.Query("DETACH db_ref"));
|
||||
|
||||
REQUIRE_NO_FAIL(con2.Query(attach_query));
|
||||
|
||||
// .. but not in con1 anymore!
|
||||
REQUIRE_FAIL(con1.Query(attach_query));
|
||||
}
|
||||
SECTION("ATTACH IF NOT EXISTS") {
|
||||
string attach_query = "ATTACH IF NOT EXISTS '" + test_path + "' AS db_ref";
|
||||
|
||||
REQUIRE_NO_FAIL(con1.Query(attach_query));
|
||||
|
||||
// fails - already attached in db1
|
||||
REQUIRE_FAIL(con2.Query(attach_query));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test attaching the same database path from different databases in read-only mode", "[api][.]") {
|
||||
DBInstanceCache instance_cache;
|
||||
auto test_path = TestCreatePath("instance_cache_reuse_readonly.db");
|
||||
|
||||
// create an empty database
|
||||
{
|
||||
DuckDB db(test_path);
|
||||
Connection con(db);
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE IF NOT EXISTS integers AS FROM (VALUES (1), (2), (3)) t(i)"));
|
||||
}
|
||||
|
||||
DBConfig config;
|
||||
auto db1 = instance_cache.GetOrCreateInstance(":memory:", config, false);
|
||||
auto db2 = instance_cache.GetOrCreateInstance(":memory:", config, false);
|
||||
auto db3 = instance_cache.GetOrCreateInstance(":memory:", config, false);
|
||||
|
||||
Connection con1(*db1);
|
||||
Connection con2(*db2);
|
||||
Connection con3(*db3);
|
||||
|
||||
SECTION("Regular ATTACH conflict") {
|
||||
string attach_query = "ATTACH '" + test_path + "' AS db_ref";
|
||||
string read_only_attach = attach_query + " (READ_ONLY)";
|
||||
|
||||
REQUIRE_NO_FAIL(con1.Query(read_only_attach));
|
||||
|
||||
// succeeds - we can attach the same database multiple times in read-only mode
|
||||
REQUIRE_NO_FAIL(con2.Query(read_only_attach));
|
||||
|
||||
// fails - we cannot attach in read-write
|
||||
REQUIRE_FAIL(con3.Query(attach_query));
|
||||
|
||||
// if we detach from con1, we still cannot attach in read-write in con3
|
||||
REQUIRE_NO_FAIL(con1.Query("DETACH db_ref"));
|
||||
REQUIRE_FAIL(con3.Query(attach_query));
|
||||
|
||||
// but if we detach in con2, we can attach in read-write mode now
|
||||
REQUIRE_NO_FAIL(con2.Query("DETACH db_ref"));
|
||||
REQUIRE_NO_FAIL(con3.Query(attach_query));
|
||||
|
||||
// and now we can no longer attach in read-only mode
|
||||
REQUIRE_FAIL(con1.Query(read_only_attach));
|
||||
}
|
||||
SECTION("ATTACH IF EXISTS") {
|
||||
string attach_query = "ATTACH IF NOT EXISTS '" + test_path + "' AS db_ref";
|
||||
string read_only_attach = attach_query + " (READ_ONLY)";
|
||||
|
||||
REQUIRE_NO_FAIL(con1.Query(read_only_attach));
|
||||
|
||||
// succeeds - we can attach the same database multiple times in read-only mode
|
||||
REQUIRE_NO_FAIL(con2.Query(read_only_attach));
|
||||
|
||||
// fails - we cannot attach in read-write
|
||||
REQUIRE_FAIL(con3.Query(attach_query));
|
||||
|
||||
// if we detach from con1, we still cannot attach in read-write in con3
|
||||
REQUIRE_NO_FAIL(con1.Query("DETACH db_ref"));
|
||||
REQUIRE_FAIL(con3.Query(attach_query));
|
||||
|
||||
// but if we detach in con2, we can attach in read-write mode now
|
||||
REQUIRE_NO_FAIL(con2.Query("DETACH db_ref"));
|
||||
REQUIRE_NO_FAIL(con3.Query(attach_query));
|
||||
|
||||
// and now we can no longer attach in read-only mode
|
||||
REQUIRE_FAIL(con1.Query(read_only_attach));
|
||||
}
|
||||
}
|
||||
169
external/duckdb/test/api/test_lifecycle_hooks.cpp
vendored
Normal file
169
external/duckdb/test/api/test_lifecycle_hooks.cpp
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/main/connection.hpp"
|
||||
#include "duckdb/main/database.hpp"
|
||||
#include "duckdb/main/extension/extension_loader.hpp"
|
||||
#include "duckdb/main/extension_manager.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace Catch::Matchers;
|
||||
|
||||
struct TestClientContextState : ClientContextState {
|
||||
vector<string> query_errors;
|
||||
vector<string> transaction_errors;
|
||||
|
||||
TestClientContextState() = default;
|
||||
TestClientContextState(const TestClientContextState &) = delete;
|
||||
|
||||
void QueryEnd(ClientContext &, optional_ptr<ErrorData> error) override {
|
||||
if (error && error->HasError()) {
|
||||
query_errors.push_back(error->Message());
|
||||
}
|
||||
}
|
||||
|
||||
void TransactionRollback(MetaTransaction &transaction, ClientContext &context,
|
||||
optional_ptr<ErrorData> error) override {
|
||||
if (error && error->HasError()) {
|
||||
transaction_errors.push_back(error->Message());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
shared_ptr<TestClientContextState> WithLifecycleState(const Connection &conn) {
|
||||
auto ®ister_state = conn.context->registered_state;
|
||||
auto state = make_shared_ptr<TestClientContextState>();
|
||||
register_state->Insert("test_state", state);
|
||||
return state;
|
||||
}
|
||||
|
||||
TEST_CASE("Test ClientContextState", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection conn(db);
|
||||
conn.Query("CREATE TABLE my_table(i INT)");
|
||||
auto state = WithLifecycleState(conn);
|
||||
|
||||
const TableFunction table_fun(
|
||||
"raise_exception_tf", {},
|
||||
[](ClientContext &, TableFunctionInput &, DataChunk &) {
|
||||
throw std::runtime_error("This is a test exception.");
|
||||
},
|
||||
[](ClientContext &, TableFunctionBindInput &, vector<LogicalType> &return_types,
|
||||
vector<string> &names) -> unique_ptr<FunctionData> {
|
||||
return_types.push_back(LogicalType::VARCHAR);
|
||||
names.push_back("message");
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
ExtensionInfo extension_info {};
|
||||
ExtensionActiveLoad load_info {*db.instance, extension_info, "test_extension"};
|
||||
ExtensionLoader loader {load_info};
|
||||
loader.RegisterFunction(table_fun);
|
||||
|
||||
SECTION("No error, No explicit transaction") {
|
||||
REQUIRE_NO_FAIL(conn.Query("SELECT * FROM my_table"));
|
||||
REQUIRE(state->query_errors.empty());
|
||||
REQUIRE(state->transaction_errors.empty());
|
||||
}
|
||||
|
||||
SECTION("Error, No explicit transaction") {
|
||||
REQUIRE_FAIL(conn.Query("SELECT * FROM this_table_does_not_exist"));
|
||||
REQUIRE((state->query_errors.size() == 1));
|
||||
REQUIRE_THAT(state->query_errors.at(0), Contains("Table with name this_table_does_not_exist does not exist!"));
|
||||
REQUIRE((state->transaction_errors.size() == 1));
|
||||
REQUIRE_THAT(state->transaction_errors.at(0),
|
||||
Contains("Table with name this_table_does_not_exist does not exist!"));
|
||||
}
|
||||
|
||||
SECTION("No error, Explicit commit") {
|
||||
conn.BeginTransaction();
|
||||
REQUIRE_NO_FAIL(conn.Query("SELECT * FROM my_table"));
|
||||
conn.Commit();
|
||||
REQUIRE(state->query_errors.empty());
|
||||
REQUIRE(state->transaction_errors.empty());
|
||||
}
|
||||
|
||||
SECTION("No error, Explicit rollback") {
|
||||
conn.BeginTransaction();
|
||||
REQUIRE_NO_FAIL(conn.Query("SELECT * FROM my_table"));
|
||||
conn.Rollback();
|
||||
REQUIRE(state->query_errors.empty());
|
||||
REQUIRE(state->transaction_errors.empty());
|
||||
}
|
||||
|
||||
SECTION("Binding error, Explicit rollback") {
|
||||
// These errors do not invalidate the transaction...
|
||||
conn.BeginTransaction();
|
||||
REQUIRE_FAIL(conn.Query("SELECT * FROM this_table_does_not_exist_1"));
|
||||
REQUIRE_FAIL(conn.Query("SELECT * FROM this_table_does_not_exist_2"));
|
||||
REQUIRE_NO_FAIL(conn.Query("SELECT * FROM my_table"));
|
||||
conn.Rollback();
|
||||
REQUIRE((state->query_errors.size() == 2));
|
||||
REQUIRE_THAT(state->query_errors.at(0),
|
||||
Contains("Table with name this_table_does_not_exist_1 does not exist!"));
|
||||
REQUIRE_THAT(state->query_errors.at(1),
|
||||
Contains("Table with name this_table_does_not_exist_2 does not exist!"));
|
||||
REQUIRE((state->transaction_errors.empty()));
|
||||
}
|
||||
|
||||
SECTION("Binding error, Explicit commit") {
|
||||
// These errors do not invalidate the transaction...
|
||||
conn.BeginTransaction();
|
||||
REQUIRE_FAIL(conn.Query("SELECT * FROM this_table_does_not_exist_1"));
|
||||
REQUIRE_FAIL(conn.Query("SELECT * FROM this_table_does_not_exist_2"));
|
||||
REQUIRE_NO_FAIL(conn.Query("SELECT * FROM my_table"));
|
||||
conn.Commit();
|
||||
REQUIRE((state->query_errors.size() == 2));
|
||||
REQUIRE_THAT(state->query_errors.at(0),
|
||||
Contains("Table with name this_table_does_not_exist_1 does not exist!"));
|
||||
REQUIRE_THAT(state->query_errors.at(1),
|
||||
Contains("Table with name this_table_does_not_exist_2 does not exist!"));
|
||||
REQUIRE((state->transaction_errors.empty()));
|
||||
}
|
||||
|
||||
SECTION("Runtime error, Explicit commit") {
|
||||
conn.BeginTransaction();
|
||||
REQUIRE_NO_FAIL(conn.Query("INSERT INTO my_table VALUES (1)"));
|
||||
REQUIRE_FAIL(conn.Query("SELECT * FROM raise_exception_tf()"));
|
||||
REQUIRE_FAIL(conn.Query("CREATE TABLE my_table2(i INT)"));
|
||||
conn.Commit();
|
||||
REQUIRE((state->query_errors.size() == 2));
|
||||
REQUIRE_THAT(state->query_errors.at(0), Contains("This is a test exception."));
|
||||
REQUIRE_THAT(state->query_errors.at(1), Contains("Current transaction is aborted"));
|
||||
REQUIRE((state->transaction_errors.size() == 1));
|
||||
REQUIRE_THAT(state->transaction_errors.at(0), Contains("Failed to commit"));
|
||||
REQUIRE_FAIL(conn.Query("SELECT * FROM my_table2"));
|
||||
}
|
||||
|
||||
SECTION("Runtime error, No explicit transaction") {
|
||||
REQUIRE_FAIL(conn.Query("SELECT * FROM raise_exception_tf()"));
|
||||
REQUIRE((state->query_errors.size() == 1));
|
||||
REQUIRE_THAT(state->query_errors.at(0), Contains("This is a test exception."));
|
||||
REQUIRE((state->transaction_errors.size() == 1));
|
||||
REQUIRE_THAT(state->transaction_errors.at(0), Contains("This is a test exception."));
|
||||
}
|
||||
|
||||
SECTION("Manually invalidated transaction, Explicit commit") {
|
||||
conn.BeginTransaction();
|
||||
REQUIRE_NO_FAIL(conn.Query("CREATE TABLE my_table2(i INT)"));
|
||||
auto &transaction = conn.context->ActiveTransaction();
|
||||
ValidChecker::Invalidate(transaction, "42");
|
||||
try {
|
||||
conn.Commit();
|
||||
} catch (...) {
|
||||
// Ignore
|
||||
}
|
||||
REQUIRE((state->transaction_errors.size() == 1));
|
||||
REQUIRE_FAIL(conn.Query("SELECT * FROM my_table2"));
|
||||
}
|
||||
|
||||
SECTION("Manually invalidated transaction, Explicit rollback") {
|
||||
conn.BeginTransaction();
|
||||
REQUIRE_NO_FAIL(conn.Query("CREATE TABLE my_table2(i INT)"));
|
||||
auto &transaction = conn.context->ActiveTransaction();
|
||||
ValidChecker::Invalidate(transaction, "42");
|
||||
conn.Rollback();
|
||||
REQUIRE((state->transaction_errors.size() == 1));
|
||||
REQUIRE_FAIL(conn.Query("SELECT * FROM my_table2"));
|
||||
}
|
||||
}
|
||||
// ClientContextState
|
||||
58
external/duckdb/test/api/test_object_cache.cpp
vendored
Normal file
58
external/duckdb/test/api/test_object_cache.cpp
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include "duckdb/storage/object_cache.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
struct TestObject : public ObjectCacheEntry {
|
||||
int value;
|
||||
|
||||
TestObject(int value) : value(value) {
|
||||
}
|
||||
|
||||
string GetObjectType() override {
|
||||
return ObjectType();
|
||||
}
|
||||
|
||||
static string ObjectType() {
|
||||
return "TestObject";
|
||||
}
|
||||
};
|
||||
|
||||
struct AnotherTestObject : public ObjectCacheEntry {
|
||||
int value;
|
||||
AnotherTestObject(int value) : value(value) {
|
||||
}
|
||||
string GetObjectType() override {
|
||||
return ObjectType();
|
||||
}
|
||||
|
||||
static string ObjectType() {
|
||||
return "AnotherTestObject";
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("Test ObjectCache", "[api]") {
|
||||
DuckDB db;
|
||||
Connection con(db);
|
||||
auto &context = *con.context;
|
||||
|
||||
auto &cache = ObjectCache::GetObjectCache(context);
|
||||
|
||||
REQUIRE(cache.GetObject("test") == nullptr);
|
||||
cache.Put("test", make_shared_ptr<TestObject>(42));
|
||||
|
||||
REQUIRE(cache.GetObject("test") != nullptr);
|
||||
|
||||
cache.Delete("test");
|
||||
REQUIRE(cache.GetObject("test") == nullptr);
|
||||
|
||||
REQUIRE(cache.GetOrCreate<TestObject>("test", 42) != nullptr);
|
||||
REQUIRE(cache.Get<TestObject>("test") != nullptr);
|
||||
REQUIRE(cache.GetOrCreate<TestObject>("test", 1337)->value == 42);
|
||||
REQUIRE(cache.Get<TestObject>("test")->value == 42);
|
||||
|
||||
REQUIRE(cache.GetOrCreate<AnotherTestObject>("test", 13) == nullptr);
|
||||
}
|
||||
246
external/duckdb/test/api/test_pending_query.cpp
vendored
Normal file
246
external/duckdb/test/api/test_pending_query.cpp
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include "duckdb/common/string_util.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test Pending Query API", "[api][.]") {
|
||||
DuckDB db;
|
||||
Connection con(db);
|
||||
|
||||
SECTION("Materialized result") {
|
||||
auto pending_query = con.PendingQuery("SELECT SUM(i) FROM range(1000000) tbl(i)");
|
||||
REQUIRE(!pending_query->HasError());
|
||||
auto result = pending_query->Execute();
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(499999500000)}));
|
||||
|
||||
// cannot fetch twice from the same pending query
|
||||
REQUIRE_THROWS(pending_query->Execute());
|
||||
REQUIRE_THROWS(pending_query->Execute());
|
||||
|
||||
// query the connection as normal after
|
||||
result = con.Query("SELECT 42");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
}
|
||||
SECTION("Streaming result") {
|
||||
auto pending_query = con.PendingQuery("SELECT SUM(i) FROM range(1000000) tbl(i)", true);
|
||||
REQUIRE(!pending_query->HasError());
|
||||
auto result = pending_query->Execute();
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(499999500000)}));
|
||||
|
||||
// cannot fetch twice from the same pending query
|
||||
REQUIRE_THROWS(pending_query->Execute());
|
||||
REQUIRE_THROWS(pending_query->Execute());
|
||||
|
||||
// query the connection as normal after
|
||||
result = con.Query("SELECT 42");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
}
|
||||
SECTION("Execute tasks") {
|
||||
auto pending_query = con.PendingQuery("SELECT SUM(i) FROM range(1000000) tbl(i)", true);
|
||||
while (pending_query->ExecuteTask() == PendingExecutionResult::RESULT_NOT_READY)
|
||||
;
|
||||
REQUIRE(!pending_query->HasError());
|
||||
auto result = pending_query->Execute();
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(499999500000)}));
|
||||
|
||||
// cannot fetch twice from the same pending query
|
||||
REQUIRE_THROWS(pending_query->Execute());
|
||||
|
||||
// query the connection as normal after
|
||||
result = con.Query("SELECT 42");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
}
|
||||
SECTION("Create pending query while another pending query exists") {
|
||||
auto pending_query = con.PendingQuery("SELECT SUM(i) FROM range(1000000) tbl(i)");
|
||||
auto pending_query2 = con.PendingQuery("SELECT SUM(i) FROM range(1000000) tbl(i)", true);
|
||||
|
||||
// first pending query is now closed
|
||||
REQUIRE_THROWS(pending_query->ExecuteTask());
|
||||
REQUIRE_THROWS(pending_query->Execute());
|
||||
|
||||
// we can execute the second one
|
||||
auto result = pending_query2->Execute();
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(499999500000)}));
|
||||
|
||||
// query the connection as normal after
|
||||
result = con.Query("SELECT 42");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
}
|
||||
SECTION("Binding error in pending query") {
|
||||
auto pending_query = con.PendingQuery("SELECT XXXSUM(i) FROM range(1000000) tbl(i)");
|
||||
REQUIRE(pending_query->HasError());
|
||||
REQUIRE_THROWS(pending_query->ExecuteTask());
|
||||
REQUIRE_THROWS(pending_query->Execute());
|
||||
|
||||
// query the connection as normal after
|
||||
auto result = con.Query("SELECT 42");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
}
|
||||
SECTION("Runtime error in pending query (materialized)") {
|
||||
// this succeeds initially
|
||||
auto pending_query =
|
||||
con.PendingQuery("SELECT concat(SUM(i)::varchar, 'hello')::INT FROM range(1000000) tbl(i)");
|
||||
REQUIRE(!pending_query->HasError());
|
||||
// we only encounter the failure later on as we are executing the query
|
||||
auto result = pending_query->Execute();
|
||||
REQUIRE_FAIL(result);
|
||||
|
||||
// query the connection as normal after
|
||||
result = con.Query("SELECT 42");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
}
|
||||
|
||||
SECTION("Runtime error in pending query (streaming)") {
|
||||
// this succeeds initially
|
||||
auto pending_query =
|
||||
con.PendingQuery("SELECT concat(SUM(i)::varchar, 'hello')::INT FROM range(1000000) tbl(i)", true);
|
||||
REQUIRE(!pending_query->HasError());
|
||||
auto result = pending_query->Execute();
|
||||
REQUIRE(result->HasError());
|
||||
|
||||
// query the connection as normal after
|
||||
result = con.Query("SELECT 42");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
}
|
||||
SECTION("Pending results errors as JSON") {
|
||||
con.Query("SET errors_as_json = true;");
|
||||
auto pending_query = con.PendingQuery("SELCT 32;");
|
||||
REQUIRE(pending_query->HasError());
|
||||
REQUIRE(duckdb::StringUtil::Contains(pending_query->GetError(), "SYNTAX_ERROR"));
|
||||
}
|
||||
}
|
||||
|
||||
static void parallel_pending_query(Connection *conn, bool *correct, size_t threadnr) {
|
||||
correct[threadnr] = true;
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
// run pending query and then execute it
|
||||
auto executor = conn->PendingQuery("SELECT * FROM integers ORDER BY i");
|
||||
try {
|
||||
// this will randomly throw an exception if another thread calls pending query first
|
||||
auto result = executor->Execute();
|
||||
if (!CHECK_COLUMN(result, 0, {1, 2, 3, Value()})) {
|
||||
correct[threadnr] = false;
|
||||
}
|
||||
} catch (...) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test parallel usage of pending query API", "[api][.]") {
|
||||
auto db = make_uniq<DuckDB>(nullptr);
|
||||
auto conn = make_uniq<Connection>(*db);
|
||||
|
||||
REQUIRE_NO_FAIL(conn->Query("CREATE TABLE integers(i INTEGER)"));
|
||||
REQUIRE_NO_FAIL(conn->Query("INSERT INTO integers VALUES (1), (2), (3), (NULL)"));
|
||||
|
||||
bool correct[20];
|
||||
thread threads[20];
|
||||
for (size_t i = 0; i < 20; i++) {
|
||||
threads[i] = thread(parallel_pending_query, conn.get(), correct, i);
|
||||
}
|
||||
for (size_t i = 0; i < 20; i++) {
|
||||
threads[i].join();
|
||||
REQUIRE(correct[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test Pending Query Prepared Statements API", "[api][.]") {
|
||||
DuckDB db;
|
||||
Connection con(db);
|
||||
|
||||
SECTION("Standard prepared") {
|
||||
auto prepare = con.Prepare("SELECT SUM(i) FROM range(1000000) tbl(i) WHERE i>=$1");
|
||||
REQUIRE(!prepare->HasError());
|
||||
|
||||
auto pending_query = prepare->PendingQuery(0);
|
||||
REQUIRE(!pending_query->HasError());
|
||||
|
||||
auto result = pending_query->Execute();
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(499999500000)}));
|
||||
|
||||
// cannot fetch twice from the same pending query
|
||||
REQUIRE_THROWS(pending_query->Execute());
|
||||
REQUIRE_THROWS(pending_query->Execute());
|
||||
|
||||
// we can use the prepared query again, however
|
||||
pending_query = prepare->PendingQuery(500000);
|
||||
REQUIRE(!pending_query->HasError());
|
||||
|
||||
result = pending_query->Execute();
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(374999750000)}));
|
||||
|
||||
// cannot fetch twice from the same pending query
|
||||
REQUIRE_THROWS(pending_query->Execute());
|
||||
REQUIRE_THROWS(pending_query->Execute());
|
||||
}
|
||||
SECTION("Error during prepare") {
|
||||
auto prepare = con.Prepare("SELECT SUM(i+X) FROM range(1000000) tbl(i) WHERE i>=$1");
|
||||
REQUIRE(prepare->HasError());
|
||||
|
||||
REQUIRE_FAIL(prepare->PendingQuery(0));
|
||||
}
|
||||
SECTION("Error during execution") {
|
||||
duckdb::vector<Value> parameters;
|
||||
auto prepared = con.Prepare("SELECT concat(SUM(i)::varchar, CASE WHEN SUM(i) IS NULL THEN 0 ELSE 'hello' "
|
||||
"END)::INT FROM range(1000000) tbl(i) WHERE i>$1");
|
||||
// this succeeds initially
|
||||
parameters = {Value::INTEGER(0)};
|
||||
auto pending_query = prepared->PendingQuery(parameters, true);
|
||||
REQUIRE(!pending_query->HasError());
|
||||
// still succeeds...
|
||||
auto result = pending_query->Execute();
|
||||
REQUIRE(result->HasError());
|
||||
|
||||
// query the connection as normal after
|
||||
result = con.Query("SELECT 42");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
|
||||
// if we change the parameter this works
|
||||
parameters = {Value::INTEGER(2000000)};
|
||||
pending_query = prepared->PendingQuery(parameters, true);
|
||||
|
||||
result = pending_query->Execute();
|
||||
REQUIRE(!result->HasError());
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(0)}));
|
||||
}
|
||||
SECTION("Multiple prepared statements") {
|
||||
auto prepare1 = con.Prepare("SELECT SUM(i) FROM range(1000000) tbl(i) WHERE i>=$1");
|
||||
auto prepare2 = con.Prepare("SELECT SUM(i) FROM range(1000000) tbl(i) WHERE i<=$1");
|
||||
REQUIRE(!prepare1->HasError());
|
||||
REQUIRE(!prepare2->HasError());
|
||||
|
||||
// we can execute from both prepared statements individually
|
||||
auto pending_query = prepare1->PendingQuery(500000);
|
||||
REQUIRE(!pending_query->HasError());
|
||||
|
||||
auto result = pending_query->Execute();
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(374999750000)}));
|
||||
|
||||
pending_query = prepare2->PendingQuery(500000);
|
||||
REQUIRE(!pending_query->HasError());
|
||||
|
||||
result = pending_query->Execute();
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(125000250000)}));
|
||||
|
||||
// we can overwrite pending queries all day long
|
||||
for (idx_t i = 0; i < 10; i++) {
|
||||
pending_query = prepare1->PendingQuery(500000);
|
||||
pending_query = prepare2->PendingQuery(500000);
|
||||
}
|
||||
|
||||
result = pending_query->Execute();
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(125000250000)}));
|
||||
|
||||
// however, we can't mix and match...
|
||||
pending_query = prepare1->PendingQuery(500000);
|
||||
auto pending_query2 = prepare2->PendingQuery(500000);
|
||||
|
||||
// this result is no longer open
|
||||
REQUIRE_THROWS(pending_query->Execute());
|
||||
}
|
||||
}
|
||||
135
external/duckdb/test/api/test_pending_with_parameters.cpp
vendored
Normal file
135
external/duckdb/test/api/test_pending_with_parameters.cpp
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
static void CreateSimpleTable(Connection &con) {
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i TINYINT)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)"));
|
||||
}
|
||||
|
||||
static void ModifySimpleTable(Connection &con) {
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (14)"));
|
||||
REQUIRE_NO_FAIL(con.Query("DELETE FROM a where i=12"));
|
||||
}
|
||||
|
||||
static void CheckSimpleQuery(Connection &con) {
|
||||
auto statements = con.ExtractStatements("SELECT COUNT(*) FROM a WHERE i=12");
|
||||
REQUIRE(statements.size() == 1);
|
||||
duckdb::vector<duckdb::Value> values = {Value(12)};
|
||||
auto pending_result = con.PendingQuery("SELECT COUNT(*) FROM a WHERE i=?", values, true);
|
||||
|
||||
if (pending_result->HasError()) {
|
||||
printf("%s\n", pending_result->GetError().c_str());
|
||||
}
|
||||
|
||||
REQUIRE(!pending_result->HasError());
|
||||
|
||||
auto result = pending_result->Execute();
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
}
|
||||
|
||||
static void CheckCatalogErrorQuery(Connection &con) {
|
||||
duckdb::vector<Value> values = {Value(12)};
|
||||
auto pending_result = con.PendingQuery("SELECT COUNT(*) FROM b WHERE i=?", values, true);
|
||||
REQUIRE((pending_result->HasError() && pending_result->GetErrorType() == ExceptionType::CATALOG));
|
||||
}
|
||||
|
||||
static void CheckConversionErrorQuery(Connection &con) {
|
||||
// Check query with invalid prepared value
|
||||
duckdb::vector<Value> values = {Value("fawakaaniffoo")};
|
||||
auto pending_result = con.PendingQuery("SELECT COUNT(*) FROM a WHERE i=?", values, true);
|
||||
REQUIRE(!pending_result->HasError());
|
||||
auto result = pending_result->Execute();
|
||||
REQUIRE((result->HasError() && result->GetErrorType() == ExceptionType::CONVERSION));
|
||||
}
|
||||
|
||||
static void CheckSimpleQueryAfterModification(Connection &con) {
|
||||
duckdb::vector<Value> values = {Value(14)};
|
||||
auto pending_result = con.PendingQuery("SELECT COUNT(*) FROM a WHERE i=?", values, true);
|
||||
REQUIRE(!pending_result->HasError());
|
||||
auto result = pending_result->Execute();
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
}
|
||||
|
||||
TEST_CASE("Pending Query with Parameters", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
|
||||
CreateSimpleTable(con);
|
||||
CheckSimpleQuery(con);
|
||||
CheckSimpleQuery(con);
|
||||
}
|
||||
|
||||
TEST_CASE("Pending Query with Parameters Catalog Error", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
|
||||
CreateSimpleTable(con);
|
||||
|
||||
CheckCatalogErrorQuery(con);
|
||||
|
||||
// Verify things are still sane
|
||||
CheckSimpleQuery(con);
|
||||
}
|
||||
|
||||
TEST_CASE("Pending Query with Parameters Type Conversion Error", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
|
||||
CreateSimpleTable(con);
|
||||
|
||||
CheckConversionErrorQuery(con);
|
||||
|
||||
// Verify things are still sane
|
||||
CheckSimpleQuery(con);
|
||||
}
|
||||
|
||||
TEST_CASE("Pending Query with Parameters with transactions", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con1(db);
|
||||
Connection con2(db);
|
||||
duckdb::vector<Value> empty_values = {};
|
||||
con1.EnableQueryVerification();
|
||||
|
||||
CreateSimpleTable(con1);
|
||||
|
||||
// CheckConversionErrorQuery(con1);
|
||||
|
||||
// Begin a transaction in the PrepareAndExecute
|
||||
auto pending_result1 = con1.PendingQuery("BEGIN TRANSACTION", empty_values, true);
|
||||
if (pending_result1->HasError()) {
|
||||
printf("%s\n", pending_result1->GetError().c_str());
|
||||
}
|
||||
REQUIRE(!pending_result1->HasError());
|
||||
|
||||
auto result1 = pending_result1->Execute();
|
||||
REQUIRE(!result1->HasError());
|
||||
CheckSimpleQuery(con1);
|
||||
|
||||
// Modify table on other connection, leaving transaction open
|
||||
con2.BeginTransaction();
|
||||
ModifySimpleTable(con2);
|
||||
CheckSimpleQueryAfterModification(con2);
|
||||
|
||||
// con1 sees nothing: both transactions are open
|
||||
CheckSimpleQuery(con1);
|
||||
|
||||
con2.Commit();
|
||||
|
||||
// con1 still sees nothing: its transaction was started before con2's
|
||||
CheckSimpleQuery(con1);
|
||||
|
||||
// con 1 commits
|
||||
auto pending_result2 = con1.PendingQuery("COMMIT", empty_values, true);
|
||||
auto result2 = pending_result2->Execute();
|
||||
REQUIRE(!result2->HasError());
|
||||
|
||||
// now con1 should see changes from con2
|
||||
CheckSimpleQueryAfterModification(con1);
|
||||
CheckSimpleQueryAfterModification(con2);
|
||||
}
|
||||
156
external/duckdb/test/api/test_plan_serialization.cpp
vendored
Normal file
156
external/duckdb/test/api/test_plan_serialization.cpp
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/execution/physical_plan_generator.hpp"
|
||||
#include "duckdb/optimizer/optimizer.hpp"
|
||||
#include "duckdb/parallel/thread_context.hpp"
|
||||
#include "duckdb/planner/planner.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/parser/parser.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
static void test_helper(string sql, duckdb::vector<string> fixtures = duckdb::vector<string>()) {
|
||||
DuckDB db;
|
||||
Connection con(db);
|
||||
|
||||
for (const auto &fixture : fixtures) {
|
||||
con.SendQuery(fixture);
|
||||
}
|
||||
|
||||
Parser p;
|
||||
p.ParseQuery(sql);
|
||||
|
||||
for (auto &statement : p.statements) {
|
||||
con.context->transaction.BeginTransaction();
|
||||
// Should that be the default "ToString"?
|
||||
string statement_sql(statement->query.c_str() + statement->stmt_location, statement->stmt_length);
|
||||
Planner planner(*con.context);
|
||||
planner.CreatePlan(std::move(statement));
|
||||
auto plan = std::move(planner.plan);
|
||||
|
||||
Optimizer optimizer(*planner.binder, *con.context);
|
||||
|
||||
plan = optimizer.Optimize(std::move(plan));
|
||||
|
||||
// LogicalOperator's copy utilizes its serialize and deserialize methods
|
||||
auto new_plan = plan->Copy(*con.context);
|
||||
|
||||
auto optimized_plan = optimizer.Optimize(std::move(new_plan));
|
||||
con.context->transaction.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
static void test_helper_multi_db(string sql, duckdb::vector<string> fixtures = duckdb::vector<string>()) {
|
||||
DuckDB db;
|
||||
Connection con(db);
|
||||
REQUIRE_NO_FAIL(con.Query("ATTACH DATABASE ':memory:' AS new_db;"));
|
||||
|
||||
for (const auto &fixture : fixtures) {
|
||||
con.SendQuery(fixture);
|
||||
}
|
||||
|
||||
Parser p;
|
||||
p.ParseQuery(sql);
|
||||
|
||||
for (auto &statement : p.statements) {
|
||||
con.context->transaction.BeginTransaction();
|
||||
// Should that be the default "ToString"?
|
||||
string statement_sql(statement->query.c_str() + statement->stmt_location, statement->stmt_length);
|
||||
Planner planner(*con.context);
|
||||
planner.CreatePlan(std::move(statement));
|
||||
auto plan = std::move(planner.plan);
|
||||
|
||||
Optimizer optimizer(*planner.binder, *con.context);
|
||||
|
||||
plan = optimizer.Optimize(std::move(plan));
|
||||
|
||||
// LogicalOperator's copy utilizes its serialize and deserialize methods
|
||||
auto new_plan = plan->Copy(*con.context);
|
||||
|
||||
auto optimized_plan = optimizer.Optimize(std::move(new_plan));
|
||||
con.context->transaction.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test logical_set", "[serialization]") {
|
||||
test_helper("SET memory_limit='10GB'");
|
||||
}
|
||||
|
||||
TEST_CASE("Test logical_show", "[serialization]") {
|
||||
test_helper("SHOW SELECT 42");
|
||||
}
|
||||
|
||||
TEST_CASE("Test logical_explain", "[serialization]") {
|
||||
test_helper("EXPLAIN SELECT 42");
|
||||
}
|
||||
|
||||
TEST_CASE("Test logical_empty_result", "[serialization]") {
|
||||
test_helper("SELECT * FROM (SELECT 42) WHERE 1>2");
|
||||
}
|
||||
|
||||
TEST_CASE("Test create_table", "[serialization]") {
|
||||
test_helper("CREATE TABLE tbl (foo INTEGER)");
|
||||
}
|
||||
|
||||
TEST_CASE("Test insert_into", "[serialization]") {
|
||||
test_helper("INSERT INTO tbl VALUES(1)", {"CREATE TABLE tbl (foo INTEGER)"});
|
||||
}
|
||||
|
||||
TEST_CASE("Test logical_delete", "[serialization]") {
|
||||
test_helper("DELETE FROM tbl", {"CREATE TABLE tbl (foo INTEGER)"});
|
||||
}
|
||||
|
||||
// TODO: only select for now
|
||||
// TEST_CASE("Test logical_create_index", "[serialization]") {
|
||||
// test_helper("CREATE INDEX idx ON tbl (foo)", {"CREATE TABLE tbl (foo INTEGER)"});
|
||||
//}
|
||||
// TODO: only select for now
|
||||
// TEST_CASE("Test logical_create_schema", "[serialization]") {
|
||||
// test_helper("CREATE SCHEMA test");
|
||||
//}
|
||||
// TODO: only select for now
|
||||
// TEST_CASE("Test logical_create_view", "[serialization]") {
|
||||
// test_helper("CREATE VIEW test_view AS (SELECT 42)");
|
||||
//}
|
||||
|
||||
TEST_CASE("Test logical_update", "[serialization]") {
|
||||
test_helper("UPDATE tbl SET foo=42", {"CREATE TABLE tbl (foo INTEGER)"});
|
||||
}
|
||||
|
||||
// TODO(stephwang): revisit this later since it doesn't work yet
|
||||
// TEST_CASE("Test logical_copy_to_file", "[serialization]") {
|
||||
// test_helper("COPY tbl TO 'test_table.csv' ( DELIMITER '|', HEADER )", {"CREATE TABLE tbl (foo INTEGER)"});
|
||||
//}
|
||||
|
||||
// TODO(stephwang): revisit this later since it doesn't work yet
|
||||
// TEST_CASE("Test logical_prepare", "[serialization]") {
|
||||
// test_helper("PREPARE v1 AS SELECT 42");
|
||||
//}
|
||||
|
||||
TEST_CASE("Test logical_simple with DROP", "[serialization]") {
|
||||
test_helper("DROP TABLE tbl", {"CREATE TABLE tbl (foo INTEGER)"});
|
||||
}
|
||||
|
||||
TEST_CASE("Test logical_simple with ALTER", "[serialization]") {
|
||||
test_helper("ALTER TABLE tbl ADD COLUMN bar INTEGER", {"CREATE TABLE tbl (foo INTEGER)"});
|
||||
}
|
||||
|
||||
TEST_CASE("Test logical_simple with LOAD", "[serialization]") {
|
||||
test_helper("LOAD foo");
|
||||
}
|
||||
|
||||
// below test cases are oriented towards multi-databases
|
||||
TEST_CASE("Test create_table with catalog", "[serialization]") {
|
||||
test_helper_multi_db("CREATE TABLE new_db.main.tbl(i INTEGER);");
|
||||
}
|
||||
|
||||
TEST_CASE("Test logical_insert with catalog", "[serialization]") {
|
||||
test_helper_multi_db("INSERT INTO new_db.main.tbl VALUES(1)", {"CREATE TABLE new_db.main.tbl (foo INTEGER)"});
|
||||
}
|
||||
|
||||
TEST_CASE("Test logical_update with catalog", "[serialization]") {
|
||||
test_helper_multi_db("UPDATE new_db.main.tbl SET foo=42", {"CREATE TABLE new_db.main.tbl (foo INTEGER)"});
|
||||
}
|
||||
542
external/duckdb/test/api/test_prepared_api.cpp
vendored
Normal file
542
external/duckdb/test/api/test_prepared_api.cpp
vendored
Normal file
@@ -0,0 +1,542 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test prepared statements API", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
|
||||
// prepare no statements
|
||||
REQUIRE_FAIL(con.Prepare(""));
|
||||
|
||||
// PrepareAndExecute with no values
|
||||
duckdb::vector<Value> values;
|
||||
REQUIRE_FAIL(con.PendingQuery("", values, false));
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i TINYINT)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)"));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE strings(s VARCHAR)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO strings VALUES (NULL), ('test')"));
|
||||
|
||||
// query using a prepared statement
|
||||
// integer:
|
||||
result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1", 12);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
// strings:
|
||||
result = con.Query("SELECT COUNT(*) FROM strings WHERE s=$1", "test");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
// multiple parameters
|
||||
result = con.Query("SELECT COUNT(*) FROM a WHERE i>$1 AND i<$2", 10, 13);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {2}));
|
||||
|
||||
// test various integer types
|
||||
result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1", (int8_t)12);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1", (int16_t)12);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1", (int32_t)12);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1", (int64_t)12);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
|
||||
// create a prepared statement and use it to query
|
||||
auto prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1");
|
||||
|
||||
result = prepare->Execute(12);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
result = prepare->Execute(13);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
REQUIRE(prepare->named_param_map.size() == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Test type resolution of function with parameter expressions", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
con.EnableQueryVerification();
|
||||
|
||||
// can deduce type of prepared parameter here
|
||||
auto prepared = con.Prepare("select 1 + $1");
|
||||
REQUIRE(!prepared->error.HasError());
|
||||
|
||||
result = prepared->Execute(1);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {2}));
|
||||
|
||||
// no prepared statement
|
||||
REQUIRE_FAIL(con.SendQuery("SELECT ?"));
|
||||
}
|
||||
|
||||
TEST_CASE("Test prepared statements and dependencies", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db), con2(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE a(i TINYINT)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)"));
|
||||
|
||||
// query using a prepared statement in con1
|
||||
result = con.Query("SELECT COUNT(*) FROM a WHERE i=$1", 12);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
// now delete the table in con2
|
||||
REQUIRE_NO_FAIL(con2.Query("DROP TABLE a"));
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE a(i TINYINT)"));
|
||||
|
||||
// keep a prepared statement around
|
||||
auto prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1");
|
||||
|
||||
// we can drop the table
|
||||
REQUIRE_NO_FAIL(con2.Query("DROP TABLE a"));
|
||||
|
||||
// now the prepared statement fails when executing
|
||||
REQUIRE_FAIL(prepare->Execute(11));
|
||||
}
|
||||
|
||||
TEST_CASE("Dropping connection with prepared statement resets dependencies", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
auto con = make_uniq<Connection>(db);
|
||||
Connection con2(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con->Query("CREATE TABLE a(i TINYINT)"));
|
||||
REQUIRE_NO_FAIL(con->Query("INSERT INTO a VALUES (11), (12), (13)"));
|
||||
|
||||
auto prepared = con->Prepare("SELECT COUNT(*) FROM a WHERE i=$1");
|
||||
result = prepared->Execute(12);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
|
||||
// we can drop the table
|
||||
REQUIRE_NO_FAIL(con2.Query("DROP TABLE a"));
|
||||
|
||||
// after the table is dropped, the prepared statement no longer succeeds when run
|
||||
REQUIRE_FAIL(prepared->Execute(12));
|
||||
REQUIRE_FAIL(prepared->Execute(12));
|
||||
}
|
||||
|
||||
TEST_CASE("Alter table and prepared statements", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
auto con = make_uniq<Connection>(db);
|
||||
Connection con2(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con->Query("CREATE TABLE a(i TINYINT)"));
|
||||
REQUIRE_NO_FAIL(con->Query("INSERT INTO a VALUES (11), (12), (13)"));
|
||||
|
||||
auto prepared = con->Prepare("SELECT * FROM a WHERE i=$1");
|
||||
result = prepared->Execute(12);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {12}));
|
||||
|
||||
REQUIRE(prepared->ColumnCount() == 1);
|
||||
REQUIRE(prepared->GetStatementType() == StatementType::SELECT_STATEMENT);
|
||||
REQUIRE(prepared->GetTypes()[0].id() == LogicalTypeId::TINYINT);
|
||||
REQUIRE(prepared->GetNames()[0] == "i");
|
||||
|
||||
// we can alter the type of the column
|
||||
REQUIRE_NO_FAIL(con2.Query("ALTER TABLE a ALTER i TYPE BIGINT USING i"));
|
||||
|
||||
// after the table is altered, the return types change, but the rebind is still successful
|
||||
result = prepared->Execute(12);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {12}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test destructors of prepared statements", "[api]") {
|
||||
duckdb::unique_ptr<DuckDB> db;
|
||||
duckdb::unique_ptr<Connection> con;
|
||||
duckdb::unique_ptr<PreparedStatement> prepare;
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
|
||||
// test destruction of connection
|
||||
db = make_uniq<DuckDB>(nullptr);
|
||||
con = make_uniq<Connection>(*db);
|
||||
// create a prepared statement
|
||||
prepare = con->Prepare("SELECT $1::INTEGER+$2::INTEGER");
|
||||
// we can execute it
|
||||
result = prepare->Execute(3, 5);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {8}));
|
||||
// now destroy the connection
|
||||
con.reset();
|
||||
// we can still use the prepared statement: the connection is alive until the prepared statement is dropped
|
||||
REQUIRE_NO_FAIL(prepare->Execute(3, 5));
|
||||
// destroying the prepared statement is fine
|
||||
prepare.reset();
|
||||
|
||||
// test destruction of db
|
||||
// create a connection and prepared statement again
|
||||
con = make_uniq<Connection>(*db);
|
||||
prepare = con->Prepare("SELECT $1::INTEGER+$2::INTEGER");
|
||||
// we can execute it
|
||||
result = prepare->Execute(3, 5);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {8}));
|
||||
// destroy the db
|
||||
db.reset();
|
||||
// we can still use the prepared statement
|
||||
REQUIRE_NO_FAIL(prepare->Execute(3, 5));
|
||||
// and the connection
|
||||
REQUIRE_NO_FAIL(con->Query("SELECT 42"));
|
||||
// we can also prepare new statements
|
||||
prepare = con->Prepare("SELECT $1::INTEGER+$2::INTEGER");
|
||||
REQUIRE(!prepare->HasError());
|
||||
}
|
||||
|
||||
TEST_CASE("Test incorrect usage of prepared statements API", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i TINYINT)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)"));
|
||||
|
||||
// this fails if there is a mismatch between number of arguments in prepare and in variadic
|
||||
// too few:
|
||||
REQUIRE_FAIL(con.Query("SELECT COUNT(*) FROM a WHERE i=$1 AND i>$2", 11));
|
||||
// too many:
|
||||
REQUIRE_FAIL(con.Query("SELECT COUNT(*) FROM a WHERE i=$1 AND i>$2", 11, 13, 17));
|
||||
|
||||
// prepare an SQL string with a parse error
|
||||
auto prepare = con.Prepare("SELEC COUNT(*) FROM a WHERE i=$1");
|
||||
// we cannot execute this prepared statement
|
||||
REQUIRE(prepare->HasError());
|
||||
REQUIRE_FAIL(prepare->Execute(12));
|
||||
|
||||
// cannot prepare multiple statements at once
|
||||
prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1; SELECT 42+$2;");
|
||||
REQUIRE(prepare->HasError());
|
||||
REQUIRE_FAIL(prepare->Execute(12));
|
||||
|
||||
// also not in the Query syntax
|
||||
REQUIRE_FAIL(con.Query("SELECT COUNT(*) FROM a WHERE i=$1; SELECT 42+$2", 11));
|
||||
}
|
||||
|
||||
TEST_CASE("Test multiple prepared statements", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i TINYINT)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)"));
|
||||
|
||||
// test that we can have multiple open prepared statements at a time
|
||||
auto prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1");
|
||||
auto prepare2 = con.Prepare("SELECT COUNT(*) FROM a WHERE i>$1");
|
||||
|
||||
result = prepare->Execute(12);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
result = prepare2->Execute(11);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {2}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test prepared statements and transactions", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
// create prepared statements in a transaction
|
||||
REQUIRE_NO_FAIL(con.Query("BEGIN TRANSACTION"));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i TINYINT)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO a VALUES (11), (12), (13)"));
|
||||
|
||||
auto prepare = con.Prepare("SELECT COUNT(*) FROM a WHERE i=$1");
|
||||
auto prepare2 = con.Prepare("SELECT COUNT(*) FROM a WHERE i>$1");
|
||||
|
||||
result = prepare->Execute(12);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
result = prepare2->Execute(11);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {2}));
|
||||
// now if we rollback our prepared statements are invalidated
|
||||
REQUIRE_NO_FAIL(con.Query("ROLLBACK"));
|
||||
|
||||
REQUIRE_FAIL(prepare->Execute(12));
|
||||
REQUIRE_FAIL(prepare2->Execute(11));
|
||||
}
|
||||
|
||||
TEST_CASE("Test prepared statement parameter counting", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
auto p0 = con.Prepare("SELECT 42");
|
||||
REQUIRE(!p0->HasError());
|
||||
REQUIRE(p0->named_param_map.empty());
|
||||
|
||||
auto p1 = con.Prepare("SELECT $1::int");
|
||||
REQUIRE(!p1->HasError());
|
||||
REQUIRE(p1->named_param_map.size() == 1);
|
||||
|
||||
p1 = con.Prepare("SELECT ?::int");
|
||||
REQUIRE(!p1->HasError());
|
||||
REQUIRE(p1->named_param_map.size() == 1);
|
||||
|
||||
auto p2 = con.Prepare("SELECT $1::int");
|
||||
REQUIRE(!p2->HasError());
|
||||
REQUIRE(p2->named_param_map.size() == 1);
|
||||
|
||||
auto p3 = con.Prepare("SELECT ?::int, ?::string");
|
||||
REQUIRE(!p3->HasError());
|
||||
REQUIRE(p3->named_param_map.size() == 2);
|
||||
|
||||
auto p4 = con.Prepare("SELECT $1::int, $2::string");
|
||||
REQUIRE(!p4->HasError());
|
||||
REQUIRE(p4->named_param_map.size() == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Test ANALYZE", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
|
||||
// ANALYZE runs without errors, note that ANALYZE is actually just ignored
|
||||
REQUIRE_NO_FAIL(con.Query("ANALYZE"));
|
||||
REQUIRE_NO_FAIL(con.Query("VACUUM"));
|
||||
|
||||
auto prep = con.Prepare("ANALYZE");
|
||||
REQUIRE(!prep->HasError());
|
||||
auto res = prep->Execute();
|
||||
REQUIRE(!res->HasError());
|
||||
|
||||
prep = con.Prepare("VACUUM");
|
||||
REQUIRE(!prep->HasError());
|
||||
res = prep->Execute();
|
||||
REQUIRE(!res->HasError());
|
||||
}
|
||||
|
||||
TEST_CASE("Test DECIMAL with PreparedStatement", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
auto ps = con.Prepare("SELECT $1::DECIMAL(4,1), $2::DECIMAL(9,1), $3::DECIMAL(18,3), $4::DECIMAL(38,8)");
|
||||
result = ps->Execute(1.1, 100.1, 1401.123, "12481204981084098124.12398");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1.1}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {100.1}));
|
||||
REQUIRE(CHECK_COLUMN(result, 2, {1401.123}));
|
||||
REQUIRE(CHECK_COLUMN(result, 3, {12481204981084098124.12398}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test BLOB with PreparedStatement", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
// Creating a blob buffer with almost ALL ASCII chars
|
||||
uint8_t num_chars = 256 - 5; // skipping: '\0', '\n', '\15', ',', '\32'
|
||||
auto blob_chars = make_unsafe_uniq_array<char>(num_chars);
|
||||
char ch = '\0';
|
||||
idx_t buf_idx = 0;
|
||||
for (idx_t i = 0; i < 255; ++i, ++ch) {
|
||||
// skip chars: '\0', new line, shift in, comma, and crtl+Z
|
||||
if (ch == '\0' || ch == '\n' || ch == '\15' || ch == ',' || ch == '\32') {
|
||||
continue;
|
||||
}
|
||||
blob_chars[buf_idx] = ch;
|
||||
++buf_idx;
|
||||
}
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE blobs (b BYTEA);"));
|
||||
|
||||
// Insert blob values through a PreparedStatement
|
||||
Value blob_val = Value::BLOB(const_data_ptr_cast(blob_chars.get()), num_chars);
|
||||
duckdb::unique_ptr<PreparedStatement> ps = con.Prepare("INSERT INTO blobs VALUES (?::BYTEA)");
|
||||
ps->Execute(blob_val);
|
||||
REQUIRE(!ps->HasError());
|
||||
ps.reset();
|
||||
|
||||
// Testing if the bytes are stored correctly
|
||||
result = con.Query("SELECT OCTET_LENGTH(b) FROM blobs");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {num_chars}));
|
||||
|
||||
result = con.Query("SELECT count(b) FROM blobs");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
|
||||
result = con.Query("SELECT b FROM blobs");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {blob_val}));
|
||||
|
||||
blob_chars.reset();
|
||||
}
|
||||
|
||||
TEST_CASE("PREPARE for INSERT with dates", "[prepared]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
// prepared DATE insert
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE dates(d DATE)"));
|
||||
REQUIRE_NO_FAIL(con.Query("PREPARE s1 AS INSERT INTO dates VALUES ($1)"));
|
||||
REQUIRE_NO_FAIL(con.Query("EXECUTE s1 (DATE '1992-01-01')"));
|
||||
|
||||
result = con.Query("SELECT * FROM dates");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::DATE(1992, 1, 1)}));
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("DELETE FROM dates"));
|
||||
|
||||
auto prepared = con.Prepare("INSERT INTO dates VALUES ($1)");
|
||||
REQUIRE_NO_FAIL(prepared->Execute(Value::DATE(1992, 1, 3)));
|
||||
|
||||
result = con.Query("SELECT * FROM dates");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::DATE(1992, 1, 3)}));
|
||||
}
|
||||
|
||||
TEST_CASE("PREPARE multiple statements", "[prepared]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
string query = "SELECT $1::INTEGER; SELECT $1::INTEGER;";
|
||||
// cannot prepare multiple statements like this
|
||||
auto prepared = con.Prepare(query);
|
||||
REQUIRE(prepared->HasError());
|
||||
// we can use ExtractStatements to execute the individual statements though
|
||||
auto statements = con.ExtractStatements(query);
|
||||
for (auto &statement : statements) {
|
||||
string stmt = query.substr(statement->stmt_location, statement->stmt_length);
|
||||
prepared = con.Prepare(stmt);
|
||||
REQUIRE(!prepared->HasError());
|
||||
|
||||
result = prepared->Execute(1);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
}
|
||||
}
|
||||
|
||||
static duckdb::unique_ptr<QueryResult> TestExecutePrepared(Connection &con, string query) {
|
||||
auto prepared = con.Prepare(query);
|
||||
REQUIRE(!prepared->HasError());
|
||||
return prepared->Execute();
|
||||
}
|
||||
|
||||
TEST_CASE("Prepare all types of statements", "[prepared]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
auto &fs = db.GetFileSystem();
|
||||
|
||||
string csv_path = TestCreatePath("prepared_files");
|
||||
if (fs.DirectoryExists(csv_path)) {
|
||||
fs.RemoveDirectory(csv_path);
|
||||
}
|
||||
|
||||
// TRANSACTION
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "BEGIN TRANSACTION"));
|
||||
// SELECT
|
||||
result = TestExecutePrepared(con, "SELECT 42");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
// CREATE_SCHEMA
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "CREATE SCHEMA test"));
|
||||
// CREATE_TABLE
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "CREATE TABLE test.a(i INTEGER)"));
|
||||
// CREATE_TABLE
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "CREATE TABLE b(i INTEGER)"));
|
||||
// CREATE_INDEX
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "CREATE INDEX i_index ON test.a(i)"));
|
||||
// CREATE_VIEW
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "CREATE VIEW v1 AS SELECT * FROM test.a WHERE i=2"));
|
||||
// CREATE_SEQUENCE
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "CREATE SEQUENCE seq"));
|
||||
// PRAGMA
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "PRAGMA table_info('b')"));
|
||||
// EXPLAIN
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "EXPLAIN SELECT 42"));
|
||||
// COPY
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "COPY test.a TO '" + csv_path + "'"));
|
||||
// INSERT
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "INSERT INTO test.a VALUES (1), (2), (3)"));
|
||||
// UPDATE
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "UPDATE test.a SET i=i+1"));
|
||||
// DELETE
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "DELETE FROM test.a WHERE i<4"));
|
||||
// PREPARE
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "PREPARE p1 AS SELECT * FROM test.a"));
|
||||
// EXECUTE
|
||||
result = TestExecutePrepared(con, "EXECUTE p1");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {4}));
|
||||
// DROP
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "DROP SEQUENCE seq"));
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "DROP VIEW v1"));
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "DROP TABLE test.a CASCADE"));
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "DROP SCHEMA test CASCADE"));
|
||||
|
||||
// TRANSACTION
|
||||
REQUIRE_NO_FAIL(TestExecutePrepared(con, "COMMIT"));
|
||||
}
|
||||
|
||||
TEST_CASE("Test ambiguous prepared statement parameter types", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
result = con.Query("SELECT ?", 42);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
|
||||
result = con.Query("SELECT ?", "hello");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"hello"}));
|
||||
|
||||
auto prep = con.Prepare("SELECT ?");
|
||||
result = prep->Execute(42);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
|
||||
result = prep->Execute("hello");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"hello"}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test prepared statements with SET", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
|
||||
// create a prepared statement and use it to query
|
||||
auto prepare = con.Prepare("SET default_null_order=$1");
|
||||
REQUIRE(prepare->success);
|
||||
|
||||
// too many parameters
|
||||
REQUIRE_FAIL(prepare->Execute("xxx", "yyy"));
|
||||
// too few parameters
|
||||
REQUIRE_FAIL(prepare->Execute());
|
||||
// unsupported setting
|
||||
REQUIRE_FAIL(prepare->Execute("unsupported_mode"));
|
||||
// this works
|
||||
REQUIRE_NO_FAIL(prepare->Execute("NULLS FIRST"));
|
||||
}
|
||||
|
||||
TEST_CASE("Test prepared statements that require rebind", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con1(db);
|
||||
con1.EnableQueryVerification();
|
||||
|
||||
auto prepared = con1.Prepare("DROP TABLE IF EXISTS t1");
|
||||
|
||||
Connection con2(db);
|
||||
REQUIRE_NO_FAIL(con2.Query("CREATE OR REPLACE TABLE t1 (c1 varchar)"));
|
||||
REQUIRE_NO_FAIL(prepared->Execute());
|
||||
}
|
||||
|
||||
class TestExtensionState : public ClientContextState {
|
||||
public:
|
||||
bool CanRequestRebind() override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("Test prepared statements with extension that can request a rebind", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE OR REPLACE TABLE t1 (c1 INTEGER)"));
|
||||
|
||||
// https://github.com/duckdb/duckdb/pull/11096
|
||||
con.context->registered_state->Insert("test_extension", make_shared_ptr<TestExtensionState>());
|
||||
|
||||
// SelectStatement
|
||||
REQUIRE_NO_FAIL(con.Prepare("SELECT ?")->Execute(42));
|
||||
|
||||
// InsertStatement
|
||||
REQUIRE_NO_FAIL(con.Prepare("INSERT INTO t1 VALUES(?)")->Execute(42));
|
||||
|
||||
// UpdateStatement
|
||||
REQUIRE_NO_FAIL(con.Prepare("UPDATE t1 SET c1 = ?")->Execute(43));
|
||||
|
||||
// SetVariableStatement
|
||||
REQUIRE_NO_FAIL(con.Prepare("SET VARIABLE test_var = ?")->Execute(42));
|
||||
}
|
||||
294
external/duckdb/test/api/test_progress_bar.cpp
vendored
Normal file
294
external/duckdb/test/api/test_progress_bar.cpp
vendored
Normal file
@@ -0,0 +1,294 @@
|
||||
#ifndef DUCKDB_NO_THREADS
|
||||
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/common/progress_bar/progress_bar.hpp"
|
||||
#include "duckdb/main/client_context.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include <duckdb/execution/executor.hpp>
|
||||
#include <future>
|
||||
#include <thread>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
class TestProgressBar {
|
||||
class TestFailure {
|
||||
using failure_callback = std::function<void()>;
|
||||
|
||||
public:
|
||||
TestFailure() : callback(nullptr) {
|
||||
}
|
||||
|
||||
public:
|
||||
bool IsSet() {
|
||||
return callback != nullptr;
|
||||
}
|
||||
void SetError(failure_callback failure) {
|
||||
if (!callback) {
|
||||
callback = failure;
|
||||
}
|
||||
}
|
||||
void ThrowError() {
|
||||
D_ASSERT(IsSet());
|
||||
callback();
|
||||
}
|
||||
|
||||
private:
|
||||
failure_callback callback;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit TestProgressBar(ClientContext *context) : context(context) {
|
||||
}
|
||||
|
||||
ClientContext *context;
|
||||
atomic<bool> stop;
|
||||
std::thread check_thread;
|
||||
TestFailure error;
|
||||
|
||||
void CheckProgressThread() {
|
||||
double prev_percentage = -1;
|
||||
uint64_t total_cardinality = 0;
|
||||
uint64_t cur_rows_read = 0;
|
||||
while (!stop) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
auto query_progress = context->GetQueryProgress();
|
||||
double new_percentage = query_progress.GetPercentage();
|
||||
if (new_percentage < prev_percentage && new_percentage != -1) {
|
||||
error.SetError([new_percentage, prev_percentage]() { REQUIRE(new_percentage >= prev_percentage); });
|
||||
}
|
||||
if (new_percentage > 100) {
|
||||
error.SetError([new_percentage]() { REQUIRE(new_percentage <= 100); });
|
||||
}
|
||||
cur_rows_read = query_progress.GetRowsProcesseed();
|
||||
total_cardinality = query_progress.GetTotalRowsToProcess();
|
||||
if (cur_rows_read > total_cardinality) {
|
||||
error.SetError([cur_rows_read, total_cardinality]() { REQUIRE(cur_rows_read <= total_cardinality); });
|
||||
}
|
||||
}
|
||||
if (cur_rows_read != total_cardinality) {
|
||||
if (std::getenv("FORCE_ASYNC_SINK_SOURCE") != nullptr) {
|
||||
return;
|
||||
}
|
||||
error.SetError([cur_rows_read, total_cardinality]() { REQUIRE(cur_rows_read == total_cardinality); });
|
||||
}
|
||||
}
|
||||
void Start() {
|
||||
stop = false;
|
||||
check_thread = std::thread(&TestProgressBar::CheckProgressThread, this);
|
||||
}
|
||||
void End() {
|
||||
stop = true;
|
||||
check_thread.join();
|
||||
if (error.IsSet()) {
|
||||
error.ThrowError();
|
||||
// This should never be reached, ThrowError() should contain a failing REQUIRE statement
|
||||
REQUIRE(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("Test Progress Bar Fast", "[progress-bar]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
REQUIRE_NOTHROW(con.context->GetQueryProgress());
|
||||
|
||||
TestProgressBar test_progress(con.context.get());
|
||||
|
||||
REQUIRE_NOTHROW(con.context->GetQueryProgress());
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("create table tbl as select range a, mod(range,10) b from range(10000);"));
|
||||
REQUIRE_NO_FAIL(con.Query("create table tbl_2 as select range a from range(10000);"));
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("PRAGMA progress_bar_time=10"));
|
||||
REQUIRE_NO_FAIL(con.Query("PRAGMA disable_print_progress_bar"));
|
||||
// Simple Aggregation
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl"));
|
||||
test_progress.End();
|
||||
|
||||
// Simple Join
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl inner join tbl_2 on (tbl.a = tbl_2.a)"));
|
||||
test_progress.End();
|
||||
|
||||
// Subquery
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl where a = (select min(a) from tbl_2)"));
|
||||
test_progress.End();
|
||||
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl where a = (select min(b) from tbl)"));
|
||||
test_progress.End();
|
||||
|
||||
// Stream result
|
||||
test_progress.Start();
|
||||
auto result = con.SendQuery("select count(*) from tbl inner join tbl_2 on (tbl.a = tbl_2.a)");
|
||||
test_progress.End();
|
||||
REQUIRE_NO_FAIL(*result);
|
||||
|
||||
// Test Multiple threads
|
||||
REQUIRE_NO_FAIL(con.Query("PRAGMA threads=2"));
|
||||
REQUIRE_NO_FAIL(con.Query("PRAGMA verify_parallelism"));
|
||||
|
||||
// Simple Aggregation
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl"));
|
||||
test_progress.End();
|
||||
|
||||
// Simple Join
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl inner join tbl_2 on (tbl.a = tbl_2.a)"));
|
||||
test_progress.End();
|
||||
|
||||
// Subquery
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl where a = (select min(a) from tbl_2)"));
|
||||
test_progress.End();
|
||||
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl where a = (select min(b) from tbl)"));
|
||||
test_progress.End();
|
||||
|
||||
// Stream result
|
||||
test_progress.Start();
|
||||
result = con.SendQuery("select count(*) from tbl inner join tbl_2 on (tbl.a = tbl_2.a)");
|
||||
test_progress.End();
|
||||
REQUIRE_NO_FAIL(*result);
|
||||
}
|
||||
|
||||
TEST_CASE("Test Progress Bar", "[progress-bar][.]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
TestProgressBar test_progress(con.context.get());
|
||||
REQUIRE_NO_FAIL(con.Query("create table tbl as select range a, mod(range,10) b from range(10000000);"));
|
||||
REQUIRE_NO_FAIL(con.Query("create table tbl_2 as select range a from range(10000000);"));
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("PRAGMA progress_bar_time=10"));
|
||||
REQUIRE_NO_FAIL(con.Query("PRAGMA disable_print_progress_bar"));
|
||||
// Simple Aggregation
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl"));
|
||||
test_progress.End();
|
||||
|
||||
// Simple Join
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl inner join tbl_2 on (tbl.a = tbl_2.a)"));
|
||||
test_progress.End();
|
||||
|
||||
// Subquery
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl where a = (select min(a) from tbl_2)"));
|
||||
test_progress.End();
|
||||
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl where a = (select min(b) from tbl)"));
|
||||
test_progress.End();
|
||||
|
||||
// Stream result
|
||||
test_progress.Start();
|
||||
auto result = con.SendQuery("select count(*) from tbl inner join tbl_2 on (tbl.a = tbl_2.a)");
|
||||
test_progress.End();
|
||||
REQUIRE_NO_FAIL(*result);
|
||||
|
||||
// Test Multiple threads
|
||||
REQUIRE_NO_FAIL(con.Query("PRAGMA threads=4"));
|
||||
REQUIRE_NO_FAIL(con.Query("PRAGMA verify_parallelism"));
|
||||
|
||||
// Simple Aggregation
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl"));
|
||||
test_progress.End();
|
||||
|
||||
// Simple Join
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl inner join tbl_2 on (tbl.a = tbl_2.a)"));
|
||||
test_progress.End();
|
||||
|
||||
// Subquery
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl where a = (select min(a) from tbl_2)"));
|
||||
test_progress.End();
|
||||
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("select count(*) from tbl where a = (select min(b) from tbl)"));
|
||||
test_progress.End();
|
||||
|
||||
// Stream result
|
||||
test_progress.Start();
|
||||
result = con.SendQuery("select count(*) from tbl inner join tbl_2 on (tbl.a = tbl_2.a)");
|
||||
test_progress.End();
|
||||
REQUIRE_NO_FAIL(*result);
|
||||
}
|
||||
|
||||
TEST_CASE("Test Progress Bar CSV", "[progress-bar][.]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
TestProgressBar test_progress(con.context.get());
|
||||
REQUIRE_NO_FAIL(con.Query("PRAGMA progress_bar_time=1"));
|
||||
REQUIRE_NO_FAIL(con.Query("PRAGMA disable_print_progress_bar"));
|
||||
|
||||
// Create Tables From CSVs
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE test AS SELECT * FROM read_csv_auto ('data/csv/test/test.csv')"));
|
||||
test_progress.End();
|
||||
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(
|
||||
con.Query("CREATE TABLE test_2 AS SELECT * FROM read_csv('data/csv/test/test.csv', columns=STRUCT_PACK(a "
|
||||
":= 'INTEGER', b := 'INTEGER', c := 'VARCHAR'), sep=',', auto_detect='false')"));
|
||||
test_progress.End();
|
||||
|
||||
// Insert into existing tables
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO test SELECT * FROM read_csv_auto('data/csv/test/test.csv')"));
|
||||
test_progress.End();
|
||||
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(
|
||||
con.Query("INSERT INTO test SELECT * FROM read_csv('data/csv/test/test.csv', columns=STRUCT_PACK(a := "
|
||||
"'INTEGER', b := 'INTEGER', c := 'VARCHAR'), sep=',', auto_detect='false')"));
|
||||
test_progress.End();
|
||||
|
||||
// copy from
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("COPY test FROM 'data/csv/test/test.csv'"));
|
||||
test_progress.End();
|
||||
|
||||
// Repeat but in parallel
|
||||
REQUIRE_NO_FAIL(con.Query("DROP TABLE test"));
|
||||
REQUIRE_NO_FAIL(con.Query("DROP TABLE test_2"));
|
||||
|
||||
// Test Multiple threads
|
||||
REQUIRE_NO_FAIL(con.Query("PRAGMA threads=4"));
|
||||
REQUIRE_NO_FAIL(con.Query("PRAGMA verify_parallelism"));
|
||||
// Create Tables From CSVs
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE test AS SELECT * FROM read_csv_auto ('data/csv/test/test.csv')"));
|
||||
test_progress.End();
|
||||
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(
|
||||
con.Query("CREATE TABLE test_2 AS SELECT * FROM read_csv('data/csv/test/test.csv', columns=STRUCT_PACK(a "
|
||||
":= 'INTEGER', b := 'INTEGER', c := 'VARCHAR'), sep=',', auto_detect='false')"));
|
||||
test_progress.End();
|
||||
|
||||
// Insert into existing tables
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO test SELECT * FROM read_csv_auto('data/csv/test/test.csv')"));
|
||||
test_progress.End();
|
||||
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(
|
||||
con.Query("INSERT INTO test SELECT * FROM read_csv('data/csv/test/test.csv', columns=STRUCT_PACK(a := "
|
||||
"'INTEGER', b := 'INTEGER', c := 'VARCHAR'), sep=',', auto_detect='false')"));
|
||||
test_progress.End();
|
||||
|
||||
// copy from
|
||||
test_progress.Start();
|
||||
REQUIRE_NO_FAIL(con.Query("COPY test FROM 'data/csv/test/test.csv'"));
|
||||
test_progress.End();
|
||||
}
|
||||
#endif
|
||||
27
external/duckdb/test/api/test_query_profiler.cpp
vendored
Normal file
27
external/duckdb/test/api/test_query_profiler.cpp
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test query profiler", "[api]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
string output;
|
||||
|
||||
con.EnableQueryVerification();
|
||||
con.EnableProfiling();
|
||||
// don't pollute the console with profiler info.
|
||||
con.context->config.emit_profiler_output = false;
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("SELECT * FROM (SELECT 42) tbl1, (SELECT 33) tbl2"));
|
||||
|
||||
output = con.GetProfilingInformation();
|
||||
REQUIRE(output.size() > 0);
|
||||
|
||||
output = con.GetProfilingInformation(ProfilerPrintFormat::JSON);
|
||||
REQUIRE(output.size() > 0);
|
||||
}
|
||||
129
external/duckdb/test/api/test_read_only.cpp
vendored
Normal file
129
external/duckdb/test/api/test_read_only.cpp
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/common/file_system.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test connection using a read only database", "[readonly]") {
|
||||
auto dbdir = TestCreatePath("read_only_test");
|
||||
duckdb::unique_ptr<DuckDB> db, db2;
|
||||
duckdb::unique_ptr<Connection> con;
|
||||
// make sure the database does not exist
|
||||
DeleteDatabase(dbdir);
|
||||
|
||||
DBConfig readonly_config;
|
||||
readonly_config.options.use_temporary_directory = false;
|
||||
readonly_config.options.access_mode = AccessMode::READ_ONLY;
|
||||
|
||||
// cannot create read-only memory database
|
||||
REQUIRE_THROWS(db = make_uniq<DuckDB>(nullptr, &readonly_config));
|
||||
// cannot create a read-only database in a new directory
|
||||
REQUIRE_THROWS(db = make_uniq<DuckDB>(dbdir, &readonly_config));
|
||||
|
||||
// create the database file and initialize it with data
|
||||
db = make_uniq<DuckDB>(dbdir);
|
||||
con = make_uniq<Connection>(*db);
|
||||
REQUIRE_NO_FAIL(con->Query("CREATE TABLE integers(i INTEGER)"));
|
||||
REQUIRE_NO_FAIL(con->Query("INSERT INTO integers VALUES (1), (2), (3), (4), (5)"));
|
||||
con.reset();
|
||||
db.reset();
|
||||
|
||||
// now connect in read-only mode
|
||||
REQUIRE_NOTHROW(db = make_uniq<DuckDB>(dbdir, &readonly_config));
|
||||
con = make_uniq<Connection>(*db);
|
||||
|
||||
// we can query the database
|
||||
auto result = con->Query("SELECT * FROM integers ORDER BY i");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3, 4, 5}));
|
||||
// however, we can't perform DDL statements
|
||||
REQUIRE_FAIL(con->Query("CREATE TABLE integers2(i INTEGER)"));
|
||||
REQUIRE_FAIL(con->Query("ALTER TABLE integers RENAME COLUMN i TO k"));
|
||||
REQUIRE_FAIL(con->Query("DROP TABLE integers"));
|
||||
REQUIRE_FAIL(con->Query("CREATE SEQUENCE seq"));
|
||||
REQUIRE_FAIL(con->Query("CREATE VIEW v1 AS SELECT * FROM integers"));
|
||||
// neither can we insert/update/delete data
|
||||
REQUIRE_FAIL(con->Query("INSERT INTO integers VALUES (3)"));
|
||||
REQUIRE_FAIL(con->Query("UPDATE integers SET i=5"));
|
||||
REQUIRE_FAIL(con->Query("DELETE FROM integers"));
|
||||
// we can run explain queries
|
||||
REQUIRE_NO_FAIL(con->Query("EXPLAIN SELECT * FROM integers"));
|
||||
// and run prepared statements
|
||||
REQUIRE_NO_FAIL(con->Query("PREPARE v1 AS SELECT * FROM integers"));
|
||||
REQUIRE_NO_FAIL(con->Query("EXECUTE v1"));
|
||||
REQUIRE_NO_FAIL(con->Query("DEALLOCATE v1"));
|
||||
// we can also prepare a DDL/update statement
|
||||
REQUIRE_NO_FAIL(con->Query("PREPARE v1 AS INSERT INTO integers VALUES ($1)"));
|
||||
// however, executing it fails then!
|
||||
REQUIRE_FAIL(con->Query("EXECUTE v1(3)"));
|
||||
|
||||
// we can create, alter and query temporary tables however
|
||||
REQUIRE_NO_FAIL(con->Query("CREATE TEMPORARY TABLE integers2(i INTEGER)"));
|
||||
REQUIRE_NO_FAIL(con->Query("INSERT INTO integers2 VALUES (1), (2), (3), (4), (5)"));
|
||||
REQUIRE_NO_FAIL(con->Query("UPDATE integers2 SET i=i+1"));
|
||||
result = con->Query("DELETE FROM integers2 WHERE i=3");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
|
||||
REQUIRE_NO_FAIL(con->Query("ALTER TABLE integers2 RENAME COLUMN i TO k"));
|
||||
result = con->Query("SELECT k FROM integers2 ORDER BY 1");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {2, 4, 5, 6}));
|
||||
REQUIRE_NO_FAIL(con->Query("DROP TABLE integers2"));
|
||||
|
||||
// also temporary views and sequences
|
||||
REQUIRE_NO_FAIL(con->Query("CREATE TEMPORARY SEQUENCE seq"));
|
||||
result = con->Query("SELECT nextval('seq')");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
REQUIRE_NO_FAIL(con->Query("DROP SEQUENCE seq"));
|
||||
|
||||
REQUIRE_NO_FAIL(con->Query("CREATE TEMPORARY VIEW v1 AS SELECT 42"));
|
||||
result = con->Query("SELECT * FROM v1");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42}));
|
||||
REQUIRE_NO_FAIL(con->Query("DROP VIEW v1"));
|
||||
|
||||
con.reset();
|
||||
db.reset();
|
||||
// FIXME: these tests currently don't work as we don't do any locking of the database directory
|
||||
// this should be fixed with the new storage
|
||||
// we can connect multiple read only databases to the same dbdir
|
||||
// REQUIRE_NOTHROW(db = make_uniq<DuckDB>(dbdir, true));
|
||||
// REQUIRE_NOTHROW(db2 = make_uniq<DuckDB>(dbdir, true));
|
||||
// db.reset();
|
||||
// db2.reset();
|
||||
|
||||
// // however, if there is read-only database, we can't connect a read-write database
|
||||
// REQUIRE_NOTHROW(db = make_uniq<DuckDB>(dbdir, true));
|
||||
// REQUIRE_THROWS(db2 = make_uniq<DuckDB>(dbdir));
|
||||
// db.reset();
|
||||
// db2.reset();
|
||||
|
||||
// // if we add a read-write database first, we can't add a reading database afterwards either
|
||||
// REQUIRE_NOTHROW(db = make_uniq<DuckDB>(dbdir));
|
||||
// REQUIRE_THROWS(db2 = make_uniq<DuckDB>(dbdir, true));
|
||||
// db.reset();
|
||||
// db2.reset();
|
||||
DeleteDatabase(dbdir);
|
||||
}
|
||||
|
||||
TEST_CASE("Test view creation using a read only database", "[readonly]") {
|
||||
auto dbdir = TestCreatePath("read_only_view_test");
|
||||
duckdb::unique_ptr<DuckDB> db;
|
||||
duckdb::unique_ptr<Connection> con;
|
||||
// make sure the database does not exist
|
||||
DeleteDatabase(dbdir);
|
||||
|
||||
DBConfig readonly_config;
|
||||
readonly_config.options.use_temporary_directory = false;
|
||||
readonly_config.options.access_mode = AccessMode::READ_ONLY;
|
||||
|
||||
// create db in first place
|
||||
{ auto db_rw = DuckDB(dbdir); }
|
||||
db = make_uniq<DuckDB>(dbdir, &readonly_config);
|
||||
// create the database file and initialize it with data
|
||||
con = make_uniq<Connection>(*db);
|
||||
|
||||
REQUIRE_NOTHROW(con->TableFunction("duckdb_tables")->CreateView("boo", true, true));
|
||||
con.reset();
|
||||
db.reset();
|
||||
|
||||
DeleteDatabase(dbdir);
|
||||
}
|
||||
1146
external/duckdb/test/api/test_relation_api.cpp
vendored
Normal file
1146
external/duckdb/test/api/test_relation_api.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
292
external/duckdb/test/api/test_reset.cpp
vendored
Normal file
292
external/duckdb/test/api/test_reset.cpp
vendored
Normal file
@@ -0,0 +1,292 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
struct OptionValuePair {
|
||||
OptionValuePair() {
|
||||
}
|
||||
OptionValuePair(Value val) : input(val), output(val) {
|
||||
}
|
||||
OptionValuePair(Value input, Value output) : input(std::move(input)), output(std::move(output)) {
|
||||
}
|
||||
|
||||
Value input;
|
||||
Value output;
|
||||
};
|
||||
|
||||
struct OptionValueSet {
|
||||
OptionValueSet() {
|
||||
}
|
||||
OptionValueSet(Value val) {
|
||||
pairs.emplace_back(std::move(val));
|
||||
}
|
||||
OptionValueSet(Value input, Value output) {
|
||||
pairs.emplace_back(std::move(input), std::move(output));
|
||||
}
|
||||
OptionValueSet(duckdb::vector<std::string> pairs_p) {
|
||||
for (auto &pair : pairs_p) {
|
||||
pairs.emplace_back(pair);
|
||||
}
|
||||
}
|
||||
OptionValueSet(duckdb::vector<OptionValuePair> pairs_p) : pairs(std::move(pairs_p)) {
|
||||
}
|
||||
|
||||
duckdb::vector<OptionValuePair> pairs;
|
||||
};
|
||||
|
||||
void RequireValueEqual(const string &option, const Value &left, const Value &right, int line);
|
||||
#define REQUIRE_VALUE_EQUAL(option, lhs, rhs) RequireValueEqual(option, lhs, rhs, __LINE__)
|
||||
|
||||
OptionValueSet GetValueForOption(const string &name, const LogicalType &type) {
|
||||
static unordered_map<string, OptionValueSet> value_map = {
|
||||
{"threads", {Value::BIGINT(42), Value::BIGINT(42)}},
|
||||
{"checkpoint_threshold", {"4.0 GiB"}},
|
||||
{"debug_checkpoint_abort", {{"none", "before_truncate", "before_header", "after_free_list_write"}}},
|
||||
{"default_collation", {"nocase"}},
|
||||
{"default_order", {"DESC"}},
|
||||
{"default_null_order", {"NULLS_FIRST"}},
|
||||
{"disabled_compression_methods", {"RLE"}},
|
||||
{"disabled_optimizers", {"extension"}},
|
||||
{"debug_force_external", {Value(true)}},
|
||||
{"old_implicit_casting", {Value(true)}},
|
||||
{"prefer_range_joins", {Value(true)}},
|
||||
{"allow_persistent_secrets", {Value(false)}},
|
||||
{"secret_directory", {"/tmp/some/path"}},
|
||||
{"default_secret_storage", {"custom_storage"}},
|
||||
{"custom_extension_repository", {"duckdb.org/no-extensions-here", "duckdb.org/no-extensions-here"}},
|
||||
{"autoinstall_extension_repository", {"duckdb.org/no-extensions-here", "duckdb.org/no-extensions-here"}},
|
||||
{"lambda_syntax", {EnumUtil::ToString(LambdaSyntax::DISABLE_SINGLE_ARROW)}},
|
||||
{"allow_parser_override_extension", {"fallback"}},
|
||||
{"profiling_coverage", {EnumUtil::ToString(ProfilingCoverage::ALL)}},
|
||||
#ifdef DUCKDB_EXTENSION_AUTOLOAD_DEFAULT
|
||||
{"autoload_known_extensions", {!DUCKDB_EXTENSION_AUTOLOAD_DEFAULT}},
|
||||
#else
|
||||
{"autoload_known_extensions", {true}},
|
||||
#endif
|
||||
#ifdef DUCKDB_EXTENSION_AUTOINSTALL_DEFAULT
|
||||
{"autoinstall_known_extensions", {!DUCKDB_EXTENSION_AUTOINSTALL_DEFAULT}},
|
||||
#else
|
||||
{"autoinstall_known_extensions", {true}},
|
||||
#endif
|
||||
{"enable_profiling", {"json"}},
|
||||
{"explain_output", {{"all", "optimized_only", "physical_only"}}},
|
||||
{"file_search_path", {"test"}},
|
||||
{"force_compression", {"uncompressed", "Uncompressed"}},
|
||||
{"home_directory", {"test"}},
|
||||
{"allow_extensions_metadata_mismatch", {"true"}},
|
||||
{"extension_directory", {"test"}},
|
||||
{"max_expression_depth", {50}},
|
||||
{"max_memory", {"4.0 GiB"}},
|
||||
{"max_temp_directory_size", {"10.0 GiB"}},
|
||||
{"merge_join_threshold", {73}},
|
||||
{"nested_loop_join_threshold", {73}},
|
||||
{"memory_limit", {"4.0 GiB"}},
|
||||
{"storage_compatibility_version", {"v0.10.0"}},
|
||||
{"ordered_aggregate_threshold", {Value::UBIGINT(idx_t(1) << 12)}},
|
||||
{"null_order", {"NULLS_FIRST"}},
|
||||
{"debug_verify_vector", {"dictionary_expression"}},
|
||||
{"perfect_ht_threshold", {0}},
|
||||
{"pivot_filter_threshold", {999}},
|
||||
{"pivot_limit", {999}},
|
||||
{"partitioned_write_flush_threshold", {123}},
|
||||
{"preserve_identifier_case", {false}},
|
||||
{"preserve_insertion_order", {false}},
|
||||
{"profile_output", {"test"}},
|
||||
{"profiling_mode", {"detailed"}},
|
||||
{"disabled_log_types", {"blabla"}},
|
||||
{"enabled_log_types", {"blabla"}},
|
||||
{"enabled_log_types", {"blabla"}},
|
||||
{"enable_logging", {true}},
|
||||
{"logging_mode", {"ENABLE_SELECTED"}},
|
||||
{"logging_level", {"FATAL"}},
|
||||
{"logging_storage", {"stdout"}},
|
||||
{"enable_progress_bar_print", {false}},
|
||||
{"scalar_subquery_error_on_multiple_rows", {false}},
|
||||
{"ieee_floating_point_ops", {false}},
|
||||
{"progress_bar_time", {0}},
|
||||
{"temp_directory", {"tmp"}},
|
||||
{"wal_autocheckpoint", {"4.0 GiB"}},
|
||||
{"force_bitpacking_mode", {"constant"}},
|
||||
{"enable_http_logging", {false}},
|
||||
{"http_proxy", {"localhost:80"}},
|
||||
{"http_proxy_username", {"john"}},
|
||||
{"http_proxy_password", {"doe"}},
|
||||
{"allocator_flush_threshold", {"4.0 GiB"}},
|
||||
{"allocator_bulk_deallocation_flush_threshold", {"4.0 GiB"}},
|
||||
{"arrow_output_version", {"1.5"}},
|
||||
{"enable_external_file_cache", {false}},
|
||||
{"experimental_metadata_reuse", {false}},
|
||||
{"storage_block_prefetch", {"always_prefetch"}},
|
||||
{"pin_threads", {"off"}}};
|
||||
// Every option that's not excluded has to be part of this map
|
||||
if (!value_map.count(name)) {
|
||||
switch (type.id()) {
|
||||
case LogicalTypeId::BOOLEAN:
|
||||
return OptionValueSet(Value::BOOLEAN(true));
|
||||
case LogicalTypeId::TINYINT:
|
||||
case LogicalTypeId::SMALLINT:
|
||||
case LogicalTypeId::INTEGER:
|
||||
case LogicalTypeId::BIGINT:
|
||||
case LogicalTypeId::UTINYINT:
|
||||
case LogicalTypeId::USMALLINT:
|
||||
case LogicalTypeId::UINTEGER:
|
||||
case LogicalTypeId::UBIGINT:
|
||||
return OptionValueSet(Value::Numeric(type, 42));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
REQUIRE(name == "MISSING_FROM_MAP");
|
||||
}
|
||||
return value_map[name];
|
||||
}
|
||||
|
||||
bool OptionIsExcludedFromTest(const string &name) {
|
||||
static unordered_set<string> excluded_options = {
|
||||
"access_mode",
|
||||
"allowed_directories",
|
||||
"allowed_paths",
|
||||
"schema",
|
||||
"search_path",
|
||||
"debug_window_mode",
|
||||
"experimental_parallel_csv",
|
||||
"lock_configuration", // cant change this while db is running
|
||||
"disabled_filesystems", // cant change this while db is running
|
||||
"enable_external_access", // cant change this while db is running
|
||||
"allow_unsigned_extensions", // cant change this while db is running
|
||||
"allow_community_extensions", // cant change this while db is running
|
||||
"allow_unredacted_secrets", // cant change this while db is running
|
||||
"disable_database_invalidation", // cant change this while db is running
|
||||
"temp_file_encryption",
|
||||
"enable_object_cache",
|
||||
"streaming_buffer_size",
|
||||
"log_query_path",
|
||||
"password",
|
||||
"username",
|
||||
"user",
|
||||
"external_threads", // tested in test_threads.cpp
|
||||
"profiling_output", // just an alias
|
||||
"duckdb_api",
|
||||
"custom_user_agent",
|
||||
"custom_profiling_settings",
|
||||
"custom_user_agent",
|
||||
"default_block_size",
|
||||
"index_scan_percentage",
|
||||
"scheduler_process_partial",
|
||||
"http_logging_output",
|
||||
"enable_profiling",
|
||||
"enable_progress_bar",
|
||||
"enable_progress_bar_print",
|
||||
"progress_bar_time",
|
||||
"index_scan_max_count",
|
||||
"profiling_mode"};
|
||||
return excluded_options.count(name) == 1;
|
||||
}
|
||||
|
||||
bool ValueEqual(const Value &left, const Value &right) {
|
||||
return Value::NotDistinctFrom(left, right);
|
||||
}
|
||||
|
||||
void RequireValueEqual(const string &option_name, const Value &left, const Value &right, int line) {
|
||||
if (ValueEqual(left, right)) {
|
||||
return;
|
||||
}
|
||||
auto error = StringUtil::Format("\nLINE[%d] (Option:%s) | Expected left:'%s' and right:'%s' to be equal", line,
|
||||
option_name, left.ToString(), right.ToString());
|
||||
cerr << error << endl;
|
||||
REQUIRE(false);
|
||||
}
|
||||
|
||||
Value GetValueForSetting(Connection &con, const string &name, const LogicalType &type) {
|
||||
string new_value;
|
||||
auto result = con.Query(StringUtil::Format("SELECT value FROM duckdb_settings() WHERE name = %s", SQLString(name)));
|
||||
for (auto &row : *result) {
|
||||
new_value = row.GetValue<string>(0);
|
||||
}
|
||||
return Value(new_value).CastAs(*con.context, type);
|
||||
}
|
||||
|
||||
//! New options should be added to the value_map in GetValueForOption
|
||||
//! Or added to the 'excluded_options' in OptionIsExcludedFromTest
|
||||
TEST_CASE("Test RESET statement for ClientConfig options", "[api]") {
|
||||
// Create a connection
|
||||
DBConfig config;
|
||||
config.options.load_extensions = false;
|
||||
DuckDB db(nullptr, &config);
|
||||
Connection con(db);
|
||||
con.Query("BEGIN TRANSACTION");
|
||||
con.Query("PRAGMA disable_profiling");
|
||||
|
||||
struct ResetSettingOption {
|
||||
string name;
|
||||
Value value;
|
||||
LogicalType type;
|
||||
};
|
||||
duckdb::vector<ResetSettingOption> options;
|
||||
|
||||
auto result = con.Query("SELECT name, value, input_type FROM duckdb_settings()");
|
||||
for (auto row : *result) {
|
||||
ResetSettingOption option;
|
||||
option.name = row.GetValue<string>(0);
|
||||
option.type = DBConfig::ParseLogicalType(row.GetValue<string>(2));
|
||||
if (row.IsNull(1)) {
|
||||
option.value = Value(option.type);
|
||||
} else {
|
||||
Value str_val = Value(row.GetValue<string>(1));
|
||||
option.value = str_val.CastAs(*con.context, option.type);
|
||||
}
|
||||
|
||||
if (OptionIsExcludedFromTest(option.name)) {
|
||||
continue;
|
||||
}
|
||||
options.push_back(std::move(option));
|
||||
}
|
||||
for (auto &option : options) {
|
||||
auto value_set = GetValueForOption(option.name, option.type);
|
||||
// verify that at least one value is different
|
||||
bool any_different = false;
|
||||
string options;
|
||||
for (auto &value_pair : value_set.pairs) {
|
||||
if (!ValueEqual(option.value, value_pair.output)) {
|
||||
any_different = true;
|
||||
} else {
|
||||
if (!options.empty()) {
|
||||
options += ", ";
|
||||
}
|
||||
options += value_pair.output.ToString();
|
||||
}
|
||||
}
|
||||
if (!any_different) {
|
||||
auto error = StringUtil::Format(
|
||||
"\n(Option:%s) | Expected original value '%s' and provided option '%s' to be different", option.name,
|
||||
option.value.ToString(), options);
|
||||
cerr << error << endl;
|
||||
REQUIRE(false);
|
||||
}
|
||||
auto original_value = GetValueForSetting(con, option.name, option.type);
|
||||
for (auto &value_pair : value_set.pairs) {
|
||||
// Get the new value for the option
|
||||
auto input = value_pair.input.CastAs(*con.context, option.type);
|
||||
// Set the new option
|
||||
REQUIRE_NO_FAIL(con.Query(StringUtil::Format("SET %s = %s", option.name, input.ToSQLString())));
|
||||
|
||||
auto changed_value = GetValueForSetting(con, option.name, option.type);
|
||||
|
||||
// Get the value of the option again
|
||||
REQUIRE_VALUE_EQUAL(option.name, changed_value, value_pair.output);
|
||||
|
||||
// reset the option again
|
||||
REQUIRE_NO_FAIL(con.Query(StringUtil::Format("RESET %s", option.name)));
|
||||
|
||||
auto reset_value = GetValueForSetting(con, option.name, option.type);
|
||||
|
||||
// Get the reset value of the option
|
||||
REQUIRE_VALUE_EQUAL(option.name, reset_value, original_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
225
external/duckdb/test/api/test_results.cpp
vendored
Normal file
225
external/duckdb/test/api/test_results.cpp
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/common/types/date.hpp"
|
||||
#include "duckdb/common/types/time.hpp"
|
||||
#include "duckdb/common/types/timestamp.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test results API", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
// result equality
|
||||
auto result = con.Query("SELECT 42");
|
||||
auto result2 = con.Query("SELECT 42");
|
||||
REQUIRE(result->Equals(*result2));
|
||||
|
||||
// result inequality
|
||||
result = con.Query("SELECT 42");
|
||||
result2 = con.Query("SELECT 43");
|
||||
REQUIRE(!result->Equals(*result2));
|
||||
|
||||
// stream query to string
|
||||
auto stream_result = con.SendQuery("SELECT 42");
|
||||
auto str = stream_result->ToString();
|
||||
REQUIRE(!str.empty());
|
||||
|
||||
// materialized query to string
|
||||
result = con.Query("SELECT 42");
|
||||
str = result->ToString();
|
||||
REQUIRE(!str.empty());
|
||||
|
||||
// error to string
|
||||
result = con.Query("SELEC 42");
|
||||
str = result->ToString();
|
||||
REQUIRE(!str.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Test iterating over results", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE data(i INTEGER, j VARCHAR)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO data VALUES (1, 'hello'), (2, 'test')"));
|
||||
|
||||
duckdb::vector<int> i_values = {1, 2};
|
||||
duckdb::vector<string> j_values = {"hello", "test"};
|
||||
idx_t row_count = 0;
|
||||
auto result = con.Query("SELECT * FROM data;");
|
||||
for (auto &row : *result) {
|
||||
REQUIRE(row.GetValue<int>(0) == i_values[row.row]);
|
||||
REQUIRE(row.GetValue<string>(1) == j_values[row.row]);
|
||||
row_count++;
|
||||
}
|
||||
REQUIRE(row_count == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Test different result types", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(
|
||||
con.Query("CREATE TABLE data(i INTEGER, j VARCHAR, k DECIMAL(38,1), l DECIMAL(18,3), m HUGEINT, n DOUBLE)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO data VALUES (23, '17.1', 94289, 9842, 4982412, 17.3)"));
|
||||
|
||||
idx_t row_count = 0;
|
||||
auto result = con.Query("SELECT * FROM data;");
|
||||
for (auto &row : *result) {
|
||||
REQUIRE(row.GetValue<int>(0) == 23);
|
||||
REQUIRE(row.GetValue<int64_t>(0) == 23);
|
||||
REQUIRE(row.GetValue<double>(0) == 23);
|
||||
REQUIRE(row.GetValue<string>(0) == "23");
|
||||
|
||||
REQUIRE(row.GetValue<int>(1) == 17);
|
||||
REQUIRE(row.GetValue<int64_t>(1) == 17);
|
||||
REQUIRE(row.GetValue<double>(1) == 17.1);
|
||||
REQUIRE(row.GetValue<string>(1) == "17.1");
|
||||
|
||||
REQUIRE(row.GetValue<int>(2) == 94289);
|
||||
REQUIRE(row.GetValue<int64_t>(2) == 94289);
|
||||
REQUIRE(row.GetValue<double>(2) == 94289);
|
||||
|
||||
REQUIRE(row.GetValue<int>(3) == 9842);
|
||||
REQUIRE(row.GetValue<int64_t>(3) == 9842);
|
||||
REQUIRE(row.GetValue<double>(3) == 9842);
|
||||
|
||||
REQUIRE(row.GetValue<int>(4) == 4982412);
|
||||
REQUIRE(row.GetValue<int64_t>(4) == 4982412);
|
||||
REQUIRE(row.GetValue<double>(4) == 4982412);
|
||||
REQUIRE(row.GetValue<string>(4) == "4982412");
|
||||
|
||||
REQUIRE(row.GetValue<int>(5) == 17);
|
||||
REQUIRE(row.GetValue<int64_t>(5) == 17);
|
||||
REQUIRE(row.GetValue<double>(5) == 17.3);
|
||||
|
||||
row_count++;
|
||||
}
|
||||
REQUIRE(row_count == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Test dates/times/timestamps", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE data(i DATE, j TIME, k TIMESTAMP)"));
|
||||
REQUIRE_NO_FAIL(
|
||||
con.Query("INSERT INTO data VALUES (DATE '1992-01-01', TIME '13:00:17', TIMESTAMP '1993-01-01 14:00:17')"));
|
||||
|
||||
idx_t row_count = 0;
|
||||
auto result = con.Query("SELECT * FROM data;");
|
||||
for (auto &row : *result) {
|
||||
int32_t year, month, day;
|
||||
int32_t hour, minute, second, millisecond;
|
||||
|
||||
auto date = row.GetValue<date_t>(0);
|
||||
auto time = row.GetValue<dtime_t>(1);
|
||||
auto timestamp = row.GetValue<timestamp_t>(2);
|
||||
Date::Convert(date, year, month, day);
|
||||
REQUIRE(year == 1992);
|
||||
REQUIRE(month == 1);
|
||||
REQUIRE(day == 1);
|
||||
|
||||
Time::Convert(time, hour, minute, second, millisecond);
|
||||
REQUIRE(hour == 13);
|
||||
REQUIRE(minute == 0);
|
||||
REQUIRE(second == 17);
|
||||
REQUIRE(millisecond == 0);
|
||||
|
||||
Timestamp::Convert(timestamp, date, time);
|
||||
Date::Convert(date, year, month, day);
|
||||
Time::Convert(time, hour, minute, second, millisecond);
|
||||
|
||||
REQUIRE(year == 1993);
|
||||
REQUIRE(month == 1);
|
||||
REQUIRE(day == 1);
|
||||
REQUIRE(hour == 14);
|
||||
REQUIRE(minute == 0);
|
||||
REQUIRE(second == 17);
|
||||
REQUIRE(millisecond == 0);
|
||||
|
||||
row_count++;
|
||||
}
|
||||
REQUIRE(row_count == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Error in streaming result after initial query", "[api][.]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
// create a big table with strings that are numbers
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE strings(v VARCHAR)"));
|
||||
for (size_t i = 0; i < STANDARD_VECTOR_SIZE * 2 - 1; i++) {
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO strings VALUES ('" + to_string(i) + "')"));
|
||||
}
|
||||
// now insert one non-numeric value
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO strings VALUES ('hello')"));
|
||||
|
||||
// now create a streaming result
|
||||
auto result = con.SendQuery("SELECT CAST(v AS INTEGER) FROM strings");
|
||||
REQUIRE_FAIL(result);
|
||||
}
|
||||
|
||||
TEST_CASE("Test UUID", "[api][uuid]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE uuids (u uuid)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO uuids VALUES ('A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11'), "
|
||||
"('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11');"));
|
||||
REQUIRE_FAIL(con.Query("INSERT INTO uuids VALUES ('');"));
|
||||
REQUIRE_FAIL(con.Query("INSERT INTO uuids VALUES ('a0eebc99');"));
|
||||
REQUIRE_FAIL(con.Query("INSERT INTO uuids VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380z11');"));
|
||||
|
||||
idx_t row_count = 0;
|
||||
auto result = con.Query("SELECT * FROM uuids");
|
||||
for (auto &row : *result) {
|
||||
auto uuid = row.GetValue<string>(0);
|
||||
REQUIRE(uuid == "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11");
|
||||
row_count++;
|
||||
}
|
||||
REQUIRE(row_count == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Test ARRAY_AGG with ORDER BY", "[api][array_agg]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE t2 (a INT, b INT, c INT)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO t2 VALUES (1,1,1), (1,2,2), (2,1,3), (2,2,4)"));
|
||||
|
||||
auto result = con.Query("select a, array_agg(c ORDER BY b) from t2 GROUP BY a");
|
||||
REQUIRE(!result->HasError());
|
||||
REQUIRE(result->names[1] == "array_agg(c ORDER BY b)");
|
||||
}
|
||||
|
||||
TEST_CASE("Issue #9417", "[api][.]") {
|
||||
DBConfig config;
|
||||
config.options.allow_unsigned_extensions = true;
|
||||
|
||||
DuckDB db(TestCreatePath("issue_replication.db"), &config);
|
||||
Connection con(db);
|
||||
auto result = con.SendQuery("with max_period as ("
|
||||
" select max(reporting_date) as max_record\n"
|
||||
" from \"data/parquet-testing/issue9417.parquet\"\n"
|
||||
" )\n"
|
||||
" select\n"
|
||||
" *\n"
|
||||
" from \"data/parquet-testing/issue9417.parquet\" e\n"
|
||||
" inner join max_period\n"
|
||||
" on e.reporting_date = max_period.max_record\n"
|
||||
" where e.record_date between '2012-01-31' and '2023-06-30'");
|
||||
idx_t count = 0;
|
||||
while (true) {
|
||||
auto chunk = result->Fetch();
|
||||
if (chunk) {
|
||||
REQUIRE(count + chunk->size() <= 46);
|
||||
count += chunk->size();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE(count == 46);
|
||||
}
|
||||
38
external/duckdb/test/api/test_table_info.cpp
vendored
Normal file
38
external/duckdb/test/api/test_table_info.cpp
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test table info api", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db), con2(db);
|
||||
|
||||
//! table is not found!
|
||||
auto info = con.TableInfo("test");
|
||||
REQUIRE(info.get() == nullptr);
|
||||
|
||||
// after creating, the table can be found
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE test(i INTEGER)"));
|
||||
info = con.TableInfo("test");
|
||||
REQUIRE(info.get() != nullptr);
|
||||
REQUIRE(info->table == "test");
|
||||
REQUIRE(info->columns.size() == 1);
|
||||
REQUIRE(info->columns[0].Name() == "i");
|
||||
|
||||
// table info is transaction sensitive
|
||||
REQUIRE_NO_FAIL(con.Query("BEGIN TRANSACTION"));
|
||||
// dropping the table in a transaction will result in the table being gone
|
||||
REQUIRE_NO_FAIL(con.Query("DROP TABLE test"));
|
||||
info = con.TableInfo("test");
|
||||
REQUIRE(info.get() == nullptr);
|
||||
|
||||
// but not in a separate connection!
|
||||
info = con2.TableInfo("test");
|
||||
REQUIRE(info.get() != nullptr);
|
||||
|
||||
// rolling back brings back the table info again
|
||||
REQUIRE_NO_FAIL(con.Query("ROLLBACK"));
|
||||
info = con.TableInfo("test");
|
||||
REQUIRE(info.get() != nullptr);
|
||||
}
|
||||
142
external/duckdb/test/api/test_threads.cpp
vendored
Normal file
142
external/duckdb/test/api/test_threads.cpp
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/common/virtual_file_system.hpp"
|
||||
|
||||
#include <thread>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
void run_query_multiple_times(duckdb::unique_ptr<string> query, duckdb::unique_ptr<Connection> con) {
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
auto result = con->Query(*query);
|
||||
}
|
||||
}
|
||||
|
||||
void change_thread_counts(duckdb::DuckDB &db) {
|
||||
auto con = Connection(db);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
con.Query("SET threads=10");
|
||||
con.Query("SET threads=1");
|
||||
}
|
||||
}
|
||||
|
||||
// NumberOfThreads acquired the same lock as RelaunchThreads
|
||||
// NumberOfThreads is waiting for the lock
|
||||
// RelaunchThreads is waiting on the thread to finish, while holding the lock
|
||||
TEST_CASE("Test deadlock issue between NumberOfThreads and RelaunchThreads", "[api]") {
|
||||
duckdb::DuckDB db(nullptr);
|
||||
|
||||
int thread_count = 10;
|
||||
std::vector<std::thread> threads(thread_count);
|
||||
|
||||
// This query will hit NumberOfThreads because it uses the RadixPartitionedHashtable
|
||||
for (int i = 0; i < thread_count; ++i) {
|
||||
auto query = make_uniq<string>(R"(
|
||||
WITH dataset AS (
|
||||
SELECT * FROM (VALUES
|
||||
(1, 'Alice'),
|
||||
(2, 'Bob'),
|
||||
(3, 'Alice'),
|
||||
(4, 'Carol')
|
||||
) AS t(id, name)
|
||||
)
|
||||
SELECT DISTINCT name FROM dataset;
|
||||
)");
|
||||
|
||||
threads[i] = std::thread(run_query_multiple_times, std::move(query), make_uniq<Connection>(db));
|
||||
}
|
||||
|
||||
// Fire off queries that change the thread count,
|
||||
// causing us to relaunch the worker threads on every subsequent query.
|
||||
change_thread_counts(db);
|
||||
|
||||
for (int i = 0; i < thread_count; ++i) {
|
||||
threads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test database maximum_threads argument", "[api]") {
|
||||
// default is number of hw threads
|
||||
// FIXME: not yet
|
||||
{
|
||||
DuckDB db(nullptr);
|
||||
auto file_system = make_uniq<VirtualFileSystem>();
|
||||
REQUIRE(db.NumberOfThreads() == DBConfig().GetSystemMaxThreads(*file_system));
|
||||
}
|
||||
// but we can set another value
|
||||
{
|
||||
DBConfig config;
|
||||
config.options.maximum_threads = 10;
|
||||
DuckDB db(nullptr, &config);
|
||||
REQUIRE(db.NumberOfThreads() == 10);
|
||||
}
|
||||
// zero is not erlaubt
|
||||
{
|
||||
DBConfig config;
|
||||
config.options.maximum_threads = 0;
|
||||
DuckDB db;
|
||||
REQUIRE_THROWS(db = DuckDB(nullptr, &config));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test external threads", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
auto &config = DBConfig::GetConfig(*db.instance);
|
||||
auto options = config.GetOptions();
|
||||
|
||||
con.Query("SET threads=13");
|
||||
REQUIRE(config.options.maximum_threads == 13);
|
||||
REQUIRE(db.NumberOfThreads() == 13);
|
||||
con.Query("SET external_threads=13");
|
||||
REQUIRE(config.options.external_threads == 13);
|
||||
REQUIRE(db.NumberOfThreads() == 13);
|
||||
|
||||
con.Query("SET external_threads=0");
|
||||
REQUIRE(config.options.external_threads == 0);
|
||||
REQUIRE(db.NumberOfThreads() == 13);
|
||||
|
||||
auto res = con.Query("SET external_threads=-1");
|
||||
REQUIRE(res->HasError());
|
||||
REQUIRE(StringUtil::Contains(res->GetError(), "out of range"));
|
||||
|
||||
res = con.Query("SET external_threads=14");
|
||||
REQUIRE(res->HasError());
|
||||
REQUIRE(StringUtil::Contains(res->GetError(), "smaller"));
|
||||
|
||||
con.Query("SET external_threads=5");
|
||||
REQUIRE(config.options.external_threads == 5);
|
||||
REQUIRE(db.NumberOfThreads() == 13);
|
||||
|
||||
con.Query("RESET external_threads");
|
||||
REQUIRE(config.options.external_threads == DBConfig().options.external_threads);
|
||||
REQUIRE(db.NumberOfThreads() == 13);
|
||||
|
||||
con.Query("RESET threads");
|
||||
auto file_system = make_uniq<VirtualFileSystem>();
|
||||
REQUIRE(config.options.maximum_threads == DBConfig().GetSystemMaxThreads(*file_system));
|
||||
REQUIRE(db.NumberOfThreads() == DBConfig().GetSystemMaxThreads(*file_system));
|
||||
}
|
||||
|
||||
#ifdef DUCKDB_NO_THREADS
|
||||
TEST_CASE("Test scheduling with no threads", "[api]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con1(db);
|
||||
Connection con2(db);
|
||||
|
||||
const auto query_1 = con1.PendingQuery("SELECT 42");
|
||||
const auto query_2 = con2.PendingQuery("SELECT 42");
|
||||
// Get the completed pipelines. Because "executeTask" was never called, there should be no completed pipelines.
|
||||
auto query_1_pipelines = con1.context->GetExecutor().GetCompletedPipelines();
|
||||
REQUIRE((query_1_pipelines == 0));
|
||||
|
||||
// Execute the second query
|
||||
REQUIRE_NO_FAIL(query_2->Execute());
|
||||
|
||||
// And even after that, there should still be no completed pipelines for the first query.
|
||||
query_1_pipelines = con1.context->GetExecutor().GetCompletedPipelines();
|
||||
REQUIRE((query_1_pipelines == 0));
|
||||
REQUIRE_NO_FAIL(query_1->Execute());
|
||||
}
|
||||
#endif
|
||||
93
external/duckdb/test/api/test_tpch_with_relations.cpp
vendored
Normal file
93
external/duckdb/test/api/test_tpch_with_relations.cpp
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "tpch_extension.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include "duckdb/common/string_util.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test TPC-H SF0.01 with relations", "[tpch][.]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
double sf = 0.01;
|
||||
if (!db.ExtensionIsLoaded("tpch")) {
|
||||
return;
|
||||
}
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CALL dbgen(sf=" + to_string(sf) + ")"));
|
||||
|
||||
auto lineitem = con.Table("lineitem");
|
||||
auto part = con.Table("part");
|
||||
auto supplier = con.Table("supplier");
|
||||
auto partsupp = con.Table("partsupp");
|
||||
auto nation = con.Table("nation");
|
||||
auto region = con.Table("region");
|
||||
auto orders = con.Table("orders");
|
||||
auto customer = con.Table("customer");
|
||||
// Q01
|
||||
result =
|
||||
lineitem->Filter("l_shipdate <= DATE '1998-09-02'")
|
||||
->Aggregate(
|
||||
{"l_returnflag", "l_linestatus", "sum(l_quantity) AS sum_qty", "sum(l_extendedprice) AS sum_base_price",
|
||||
"sum(l_extendedprice * (1 - l_discount)) AS sum_disc_price",
|
||||
"sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) AS sum_charge", "avg(l_quantity) AS avg_qty",
|
||||
"avg(l_extendedprice) AS avg_price", "avg(l_discount) AS avg_disc", "count(*) AS count_order"})
|
||||
->Order("l_returnflag, l_linestatus")
|
||||
->Execute();
|
||||
COMPARE_CSV(result, TpchExtension::GetAnswer(sf, 1), true);
|
||||
|
||||
// Q02
|
||||
auto partsupp_region = partsupp->Join(supplier, "s_suppkey=ps_suppkey")
|
||||
->Join(nation, "s_nationkey=n_nationkey")
|
||||
->Join(region, "n_regionkey=r_regionkey");
|
||||
partsupp_region->CreateView("partsupp_region");
|
||||
auto part_join = partsupp_region->Join(part, "p_partkey=ps_partkey");
|
||||
result =
|
||||
part_join
|
||||
->Filter({"p_size=15", "p_type LIKE '%BRASS'", "r_name='EUROPE'",
|
||||
"ps_supplycost = ( SELECT min(ps_supplycost) FROM partsupp_region WHERE p_partkey = ps_partkey "
|
||||
"AND r_name = 'EUROPE')"})
|
||||
->Project({"s_acctbal", "s_name", "n_name", "p_partkey", "p_mfgr", "s_address", "s_phone", "s_comment"})
|
||||
->Order({"s_acctbal DESC", "n_name", "s_name", "p_partkey"})
|
||||
->Limit(100)
|
||||
->Execute();
|
||||
COMPARE_CSV(result, TpchExtension::GetAnswer(sf, 2), true);
|
||||
|
||||
// Q03
|
||||
auto cust_join = customer->Join(orders, "c_custkey=o_custkey")->Join(lineitem, "l_orderkey=o_orderkey");
|
||||
result =
|
||||
cust_join
|
||||
->Filter({"c_mktsegment = 'BUILDING'", "o_orderdate < DATE '1995-03-15'", "l_shipdate > DATE '1995-03-15'"})
|
||||
->Aggregate(
|
||||
{"l_orderkey", "sum(l_extendedprice * (1 - l_discount)) AS revenue", "o_orderdate", "o_shippriority"})
|
||||
->Order("revenue DESC, o_orderdate")
|
||||
->Limit(10)
|
||||
->Execute();
|
||||
COMPARE_CSV(result, TpchExtension::GetAnswer(sf, 3), true);
|
||||
|
||||
// Q06
|
||||
result = lineitem
|
||||
->Filter({"l_shipdate >= cast('1994-01-01' AS date)", "l_shipdate < cast('1995-01-01' AS date)",
|
||||
"l_discount BETWEEN 0.05 AND 0.07", "l_quantity < 24;"})
|
||||
->Aggregate("sum(l_extendedprice * l_discount) AS revenue")
|
||||
->Execute();
|
||||
COMPARE_CSV(result, TpchExtension::GetAnswer(sf, 6), true);
|
||||
|
||||
// Q12
|
||||
result =
|
||||
lineitem->Join(orders, "l_orderkey=o_orderkey")
|
||||
->Filter({"l_shipmode IN ('MAIL', 'SHIP')", "l_commitdate < l_receiptdate", "l_shipdate < l_commitdate",
|
||||
"l_receiptdate >= cast('1994-01-01' AS date)", "l_receiptdate < cast('1995-01-01' AS date)"})
|
||||
->Aggregate({"l_shipmode",
|
||||
"sum(CASE WHEN o_orderpriority = '1-URGENT' OR o_orderpriority = '2-HIGH' THEN 1 ELSE 0 END) "
|
||||
"AS high_line_count",
|
||||
"sum(CASE WHEN o_orderpriority <> '1-URGENT' AND o_orderpriority <> '2-HIGH' THEN 1 ELSE 0 "
|
||||
"END) AS low_line_count"})
|
||||
->Order("l_shipmode")
|
||||
->Execute();
|
||||
COMPARE_CSV(result, TpchExtension::GetAnswer(sf, 12), true);
|
||||
}
|
||||
39
external/duckdb/test/api/test_tpch_with_streaming.cpp
vendored
Normal file
39
external/duckdb/test/api/test_tpch_with_streaming.cpp
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "tpch_extension.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include "duckdb/common/string_util.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test TPC-H SF0.01 using streaming api", "[tpch][.]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
double sf = 0.01;
|
||||
if (!db.ExtensionIsLoaded("tpch")) {
|
||||
return;
|
||||
}
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CALL dbgen(sf=" + to_string(sf) + ")"));
|
||||
|
||||
for (idx_t tpch_num = 1; tpch_num <= 22; tpch_num++) {
|
||||
result = con.SendQuery("pragma tpch(" + to_string(tpch_num) + ");");
|
||||
|
||||
duckdb::ColumnDataCollection collection(duckdb::Allocator::DefaultAllocator(), result->types);
|
||||
|
||||
while (true) {
|
||||
auto chunk = result->Fetch();
|
||||
if (chunk) {
|
||||
collection.Append(*chunk);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
COMPARE_CSV_COLLECTION(collection, TpchExtension::GetAnswer(sf, tpch_num), true);
|
||||
}
|
||||
}
|
||||
44
external/duckdb/test/api/test_uuid.cpp
vendored
Normal file
44
external/duckdb/test/api/test_uuid.cpp
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/common/types/uuid.hpp"
|
||||
#include "catch.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test UUID API", "[api]") {
|
||||
REQUIRE(UUID::ToString(UUID::FromUHugeint(uhugeint_t(0))) == "00000000-0000-0000-0000-000000000000");
|
||||
REQUIRE(UUID::ToString(UUID::FromUHugeint(uhugeint_t(1))) == "00000000-0000-0000-0000-000000000001");
|
||||
REQUIRE(UUID::ToString(UUID::FromUHugeint(NumericLimits<uhugeint_t>::Maximum())) ==
|
||||
"ffffffff-ffff-ffff-ffff-ffffffffffff");
|
||||
REQUIRE(UUID::ToString(UUID::FromUHugeint(NumericLimits<uhugeint_t>::Maximum() - 1)) ==
|
||||
"ffffffff-ffff-ffff-ffff-fffffffffffe");
|
||||
REQUIRE(UUID::ToString(UUID::FromUHugeint(NumericLimits<uhugeint_t>::Maximum() / 2)) ==
|
||||
"7fffffff-ffff-ffff-ffff-ffffffffffff");
|
||||
REQUIRE(UUID::ToString(UUID::FromUHugeint((NumericLimits<uhugeint_t>::Maximum() / 2) + 1)) ==
|
||||
"80000000-0000-0000-0000-000000000000");
|
||||
|
||||
REQUIRE_THAT(UUID::ToUHugeint(UUID::FromString("00000000-0000-0000-0000-000000000000")),
|
||||
Catch::Predicate<uhugeint_t>([&](const uhugeint_t &input) {
|
||||
return input.upper == 0x0000000000000000 && input.lower == 0x0000000000000000;
|
||||
}));
|
||||
REQUIRE_THAT(UUID::ToUHugeint(UUID::FromString("00000000-0000-0000-0000-000000000001")),
|
||||
Catch::Predicate<uhugeint_t>([&](const uhugeint_t &input) {
|
||||
return input.upper == 0x0000000000000000 && input.lower == 0x0000000000000001;
|
||||
}));
|
||||
REQUIRE_THAT(UUID::ToUHugeint(UUID::FromString("ffffffff-ffff-ffff-ffff-ffffffffffff")),
|
||||
Catch::Predicate<uhugeint_t>([&](const uhugeint_t &input) {
|
||||
return input.upper == 0xffffffffffffffff && input.lower == 0xffffffffffffffff;
|
||||
}));
|
||||
REQUIRE_THAT(UUID::ToUHugeint(UUID::FromString("ffffffff-ffff-ffff-ffff-fffffffffffe")),
|
||||
Catch::Predicate<uhugeint_t>([&](const uhugeint_t &input) {
|
||||
return input.upper == 0xffffffffffffffff && input.lower == 0xfffffffffffffffe;
|
||||
}));
|
||||
REQUIRE_THAT(UUID::ToUHugeint(UUID::FromString("7fffffff-ffff-ffff-ffff-ffffffffffff")),
|
||||
Catch::Predicate<uhugeint_t>([&](const uhugeint_t &input) {
|
||||
return input.upper == 0x7fffffffffffffff && input.lower == 0xffffffffffffffff;
|
||||
}));
|
||||
REQUIRE_THAT(UUID::ToUHugeint(UUID::FromString("80000000-0000-0000-0000-000000000000")),
|
||||
Catch::Predicate<uhugeint_t>([&](const uhugeint_t &input) {
|
||||
return input.upper == 0x8000000000000000 && input.lower == 0x0000000000000000;
|
||||
}));
|
||||
}
|
||||
20
external/duckdb/test/api/test_windows_header_compatibility.cpp
vendored
Normal file
20
external/duckdb/test/api/test_windows_header_compatibility.cpp
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test compatibility with windows.h", "[windows]") {
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
// This test solely exists to check if compilation is hindered by including windows.h
|
||||
// before including duckdb.hpp
|
||||
con.BeginTransaction();
|
||||
con.Query("select 42;");
|
||||
con.Commit();
|
||||
}
|
||||
53
external/duckdb/test/api/test_windows_unicode_path.cpp
vendored
Normal file
53
external/duckdb/test/api/test_windows_unicode_path.cpp
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb.hpp"
|
||||
#include "duckdb/main/db_instance_cache.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
void TestConnectToDatabase(const string &path, bool create_table = false) {
|
||||
// connect to the database using the standard syntax
|
||||
{
|
||||
DuckDB db(path);
|
||||
Connection con(db);
|
||||
if (create_table) {
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE test AS SELECT * FROM range(10) t(i)"));
|
||||
}
|
||||
|
||||
auto result = con.Query("SELECT SUM(i) FROM test");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {45}));
|
||||
}
|
||||
|
||||
// connect to the database using the db instance cache
|
||||
{
|
||||
DBInstanceCache cache;
|
||||
DBConfig config;
|
||||
auto db = cache.CreateInstance(path, config);
|
||||
Connection con(*db);
|
||||
|
||||
auto result = con.Query("SELECT SUM(i) FROM test");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {45}));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Issue #6931 - test windows unicode path", "[windows]") {
|
||||
string dirname = "Moseguí_i_González";
|
||||
auto test_directory = TestDirectoryPath() + "/" + dirname;
|
||||
auto current_directory = TestGetCurrentDirectory();
|
||||
TestCreateDirectory(test_directory);
|
||||
TestChangeDirectory(test_directory);
|
||||
|
||||
// relative path INSIDE folder with accents
|
||||
TestConnectToDatabase("test.db", true);
|
||||
|
||||
TestChangeDirectory("..");
|
||||
// relative path TOWARDS folder with accents
|
||||
TestConnectToDatabase(dirname + "/" + "test.db");
|
||||
|
||||
// absolute path with folder with accents
|
||||
TestConnectToDatabase(current_directory + "/" + test_directory + "/" + "test.db");
|
||||
|
||||
// revert current working directory
|
||||
TestChangeDirectory(current_directory);
|
||||
}
|
||||
12
external/duckdb/test/api/udf_function/CMakeLists.txt
vendored
Normal file
12
external/duckdb/test/api/udf_function/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
add_library_unity(
|
||||
test_api_udf_function
|
||||
OBJECT
|
||||
test_templated_scalar_udf.cpp
|
||||
test_argumented_scalar_udf.cpp
|
||||
test_templated_vec_udf.cpp
|
||||
test_argumented_vec_udf.cpp
|
||||
test_aggregate_udf.cpp)
|
||||
|
||||
set(ALL_OBJECT_FILES
|
||||
${ALL_OBJECT_FILES} $<TARGET_OBJECTS:test_api_udf_function>
|
||||
PARENT_SCOPE)
|
||||
125
external/duckdb/test/api/udf_function/test_aggregate_udf.cpp
vendored
Normal file
125
external/duckdb/test/api/udf_function/test_aggregate_udf.cpp
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/common/types/date.hpp"
|
||||
#include "duckdb/common/types/time.hpp"
|
||||
#include "duckdb/common/types/timestamp.hpp"
|
||||
#include "udf_functions_to_test.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Aggregate UDFs", "[coverage][.]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
|
||||
SECTION("Testing a binary aggregate UDF using only template parameters") {
|
||||
// using DOUBLEs
|
||||
REQUIRE_NOTHROW(
|
||||
con.CreateAggregateFunction<UDFAverageFunction, udf_avg_state_t<double>, double, double>("udf_avg_double"));
|
||||
|
||||
con.Query("CREATE TABLE doubles (d DOUBLE)");
|
||||
con.Query("INSERT INTO doubles VALUES (1), (2), (3), (4), (5)");
|
||||
result = con.Query("SELECT udf_avg_double(d) FROM doubles");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {3.0}));
|
||||
|
||||
// using INTEGERs
|
||||
REQUIRE_NOTHROW(con.CreateAggregateFunction<UDFAverageFunction, udf_avg_state_t<int>, int, int>("udf_avg_int"));
|
||||
|
||||
con.Query("CREATE TABLE integers (i INTEGER)");
|
||||
con.Query("INSERT INTO integers VALUES (1), (2), (3), (4), (5)");
|
||||
result = con.Query("SELECT udf_avg_int(i) FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {3}));
|
||||
}
|
||||
|
||||
SECTION("Testing a binary aggregate UDF using only template parameters") {
|
||||
// using DOUBLEs
|
||||
con.CreateAggregateFunction<UDFCovarPopOperation, udf_covar_state_t, double, double, double>(
|
||||
"udf_covar_pop_double");
|
||||
|
||||
result = con.Query("SELECT udf_covar_pop_double(3,3), udf_covar_pop_double(NULL,3), "
|
||||
"udf_covar_pop_double(3,NULL), udf_covar_pop_double(NULL,NULL)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {0}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {Value()}));
|
||||
REQUIRE(CHECK_COLUMN(result, 2, {Value()}));
|
||||
REQUIRE(CHECK_COLUMN(result, 3, {Value()}));
|
||||
|
||||
// using INTEGERs
|
||||
con.CreateAggregateFunction<UDFCovarPopOperation, udf_covar_state_t, int, int, int>("udf_covar_pop_int");
|
||||
|
||||
result = con.Query("SELECT udf_covar_pop_int(3,3), udf_covar_pop_int(NULL,3), udf_covar_pop_int(3,NULL), "
|
||||
"udf_covar_pop_int(NULL,NULL)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {0}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {Value()}));
|
||||
REQUIRE(CHECK_COLUMN(result, 2, {Value()}));
|
||||
REQUIRE(CHECK_COLUMN(result, 3, {Value()}));
|
||||
}
|
||||
|
||||
SECTION("Testing aggregate UDF with arguments") {
|
||||
REQUIRE_NOTHROW(con.CreateAggregateFunction<UDFAverageFunction, udf_avg_state_t<int>, int, int>(
|
||||
"udf_avg_int_args", LogicalType::INTEGER, LogicalType::INTEGER));
|
||||
|
||||
con.Query("CREATE TABLE integers (i INTEGER)");
|
||||
con.Query("INSERT INTO integers VALUES (1), (2), (3), (4), (5)");
|
||||
result = con.Query("SELECT udf_avg_int_args(i) FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {3}));
|
||||
|
||||
// using TIMEs to test disambiguation
|
||||
REQUIRE_NOTHROW(con.CreateAggregateFunction<UDFAverageFunction, udf_avg_state_t<dtime_t>, dtime_t, dtime_t>(
|
||||
"udf_avg_time_args", LogicalType::TIME, LogicalType::TIME));
|
||||
con.Query("CREATE TABLE times (t TIME)");
|
||||
con.Query("INSERT INTO times VALUES ('01:00:00'), ('01:00:00'), ('01:00:00'), ('01:00:00'), ('01:00:00')");
|
||||
result = con.Query("SELECT udf_avg_time_args(t) FROM times");
|
||||
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"01:00:00"}));
|
||||
|
||||
// using DOUBLEs and a binary UDF
|
||||
con.CreateAggregateFunction<UDFCovarPopOperation, udf_covar_state_t, double, double, double>(
|
||||
"udf_covar_pop_double_args", LogicalType::DOUBLE, LogicalType::DOUBLE, LogicalType::DOUBLE);
|
||||
|
||||
result = con.Query("SELECT udf_covar_pop_double_args(3,3), udf_covar_pop_double_args(NULL,3), "
|
||||
"udf_covar_pop_double_args(3,NULL), udf_covar_pop_double_args(NULL,NULL)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {0}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {Value()}));
|
||||
REQUIRE(CHECK_COLUMN(result, 2, {Value()}));
|
||||
REQUIRE(CHECK_COLUMN(result, 3, {Value()}));
|
||||
}
|
||||
|
||||
SECTION("Testing aggregate UDF with WRONG arguments") {
|
||||
// wrong return type
|
||||
REQUIRE_THROWS(con.CreateAggregateFunction<UDFAverageFunction, udf_avg_state_t<int>, double, int>(
|
||||
"udf_avg_int_args", LogicalType::INTEGER, LogicalType::INTEGER));
|
||||
REQUIRE_THROWS(con.CreateAggregateFunction<UDFAverageFunction, udf_avg_state_t<int>, int, int>(
|
||||
"udf_avg_int_args", LogicalType::DOUBLE, LogicalType::INTEGER));
|
||||
|
||||
// wrong first argument
|
||||
REQUIRE_THROWS(con.CreateAggregateFunction<UDFAverageFunction, udf_avg_state_t<int>, int, double>(
|
||||
"udf_avg_int_args", LogicalType::INTEGER, LogicalType::INTEGER));
|
||||
REQUIRE_THROWS(con.CreateAggregateFunction<UDFAverageFunction, udf_avg_state_t<int>, int, int>(
|
||||
"udf_avg_int_args", LogicalType::INTEGER, LogicalType::DOUBLE));
|
||||
|
||||
// wrong first argument
|
||||
REQUIRE_THROWS(con.CreateAggregateFunction<UDFCovarPopOperation, udf_covar_state_t, double, double, int>(
|
||||
"udf_covar_pop_double_args", LogicalType::DOUBLE, LogicalType::DOUBLE, LogicalType::DOUBLE));
|
||||
REQUIRE_THROWS(con.CreateAggregateFunction<UDFCovarPopOperation, udf_covar_state_t, double, double, double>(
|
||||
"udf_covar_pop_double_args", LogicalType::DOUBLE, LogicalType::DOUBLE, LogicalType::INTEGER));
|
||||
}
|
||||
|
||||
SECTION("Testing the generic CreateAggregateFunction()") {
|
||||
REQUIRE_NOTHROW(con.CreateAggregateFunction(
|
||||
"udf_sum", {LogicalType::DOUBLE}, LogicalType::DOUBLE, &UDFSum::StateSize<UDFSum::sum_state_t>,
|
||||
&UDFSum::Initialize<UDFSum::sum_state_t>, &UDFSum::Update<UDFSum::sum_state_t, double>,
|
||||
&UDFSum::Combine<UDFSum::sum_state_t>, &UDFSum::Finalize<UDFSum::sum_state_t, double>,
|
||||
&UDFSum::SimpleUpdate<UDFSum::sum_state_t, double>));
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("SELECT udf_sum(1)"));
|
||||
result = con.Query("SELECT udf_sum(1)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO integers SELECT * FROM range(0, 1000, 1)"));
|
||||
result = con.Query("SELECT udf_sum(i) FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {499500}));
|
||||
}
|
||||
}
|
||||
293
external/duckdb/test/api/udf_function/test_argumented_scalar_udf.cpp
vendored
Normal file
293
external/duckdb/test/api/udf_function/test_argumented_scalar_udf.cpp
vendored
Normal file
@@ -0,0 +1,293 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/common/types/date.hpp"
|
||||
#include "duckdb/common/types/time.hpp"
|
||||
#include "duckdb/common/types/timestamp.hpp"
|
||||
#include "udf_functions_to_test.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("UDF functions with arguments", "[coverage][.]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
|
||||
string func_name, table_name, col_type;
|
||||
// The types supported by the argumented CreateScalarFunction
|
||||
const duckdb::vector<LogicalTypeId> all_sql_types = {
|
||||
LogicalTypeId::BOOLEAN, LogicalTypeId::TINYINT, LogicalTypeId::SMALLINT, LogicalTypeId::DATE,
|
||||
LogicalTypeId::TIME, LogicalTypeId::INTEGER, LogicalTypeId::BIGINT, LogicalTypeId::TIMESTAMP,
|
||||
LogicalTypeId::FLOAT, LogicalTypeId::DOUBLE, LogicalTypeId::DECIMAL, LogicalTypeId::VARCHAR};
|
||||
|
||||
// Creating the tables
|
||||
for (LogicalType sql_type : all_sql_types) {
|
||||
col_type = EnumUtil::ToString(sql_type.id());
|
||||
table_name = StringUtil::Lower(col_type);
|
||||
|
||||
con.Query("CREATE TABLE " + table_name + " (a " + col_type + ", b " + col_type + ", c " + col_type + ")");
|
||||
}
|
||||
|
||||
// Creating the UDF functions into the catalog
|
||||
for (LogicalType sql_type : all_sql_types) {
|
||||
func_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
|
||||
switch (sql_type.id()) {
|
||||
case LogicalTypeId::BOOLEAN: {
|
||||
con.CreateScalarFunction<bool, bool>(func_name + "_1", {LogicalType::BOOLEAN}, LogicalType::BOOLEAN,
|
||||
&udf_bool);
|
||||
|
||||
con.CreateScalarFunction<bool, bool, bool>(func_name + "_2", {LogicalType::BOOLEAN, LogicalType::BOOLEAN},
|
||||
LogicalType::BOOLEAN, &udf_bool);
|
||||
|
||||
con.CreateScalarFunction<bool, bool, bool, bool>(
|
||||
func_name + "_3", {LogicalType::BOOLEAN, LogicalType::BOOLEAN, LogicalType::BOOLEAN},
|
||||
LogicalType::BOOLEAN, &udf_bool);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::TINYINT: {
|
||||
con.CreateScalarFunction<int8_t, int8_t>(func_name + "_1", {LogicalType::TINYINT}, LogicalType::TINYINT,
|
||||
&udf_int8);
|
||||
|
||||
con.CreateScalarFunction<int8_t, int8_t, int8_t>(
|
||||
func_name + "_2", {LogicalType::TINYINT, LogicalType::TINYINT}, LogicalType::TINYINT, &udf_int8);
|
||||
|
||||
con.CreateScalarFunction<int8_t, int8_t, int8_t, int8_t>(
|
||||
func_name + "_3", {LogicalType::TINYINT, LogicalType::TINYINT, LogicalType::TINYINT},
|
||||
LogicalType::TINYINT, &udf_int8);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::SMALLINT: {
|
||||
con.CreateScalarFunction<int16_t, int16_t>(func_name + "_1", {LogicalType::SMALLINT}, LogicalType::SMALLINT,
|
||||
&udf_int16);
|
||||
|
||||
con.CreateScalarFunction<int16_t, int16_t, int16_t>(
|
||||
func_name + "_2", {LogicalType::SMALLINT, LogicalType::SMALLINT}, LogicalType::SMALLINT, &udf_int16);
|
||||
|
||||
con.CreateScalarFunction<int16_t, int16_t, int16_t, int16_t>(
|
||||
func_name + "_3", {LogicalType::SMALLINT, LogicalType::SMALLINT, LogicalType::SMALLINT},
|
||||
LogicalType::SMALLINT, &udf_int16);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::DATE: {
|
||||
con.CreateScalarFunction<date_t, date_t>(func_name + "_1", {LogicalType::DATE}, LogicalType::DATE,
|
||||
&udf_date);
|
||||
|
||||
con.CreateScalarFunction<date_t, date_t, date_t>(func_name + "_2", {LogicalType::DATE, LogicalType::DATE},
|
||||
LogicalType::DATE, &udf_date);
|
||||
|
||||
con.CreateScalarFunction<date_t, date_t, date_t, date_t>(
|
||||
func_name + "_3", {LogicalType::DATE, LogicalType::DATE, LogicalType::DATE}, LogicalType::DATE,
|
||||
&udf_date);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::TIME: {
|
||||
con.CreateScalarFunction<dtime_t, dtime_t>(func_name + "_1", {LogicalType::TIME}, LogicalType::TIME,
|
||||
&udf_time);
|
||||
|
||||
con.CreateScalarFunction<dtime_t, dtime_t, dtime_t>(
|
||||
func_name + "_2", {LogicalType::TIME, LogicalType::TIME}, LogicalType::TIME, &udf_time);
|
||||
|
||||
con.CreateScalarFunction<dtime_t, dtime_t, dtime_t, dtime_t>(
|
||||
func_name + "_3", {LogicalType::TIME, LogicalType::TIME, LogicalType::TIME}, LogicalType::TIME,
|
||||
&udf_time);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::INTEGER: {
|
||||
con.CreateScalarFunction<int32_t, int32_t>(func_name + "_1", {LogicalType::INTEGER}, LogicalType::INTEGER,
|
||||
&udf_int);
|
||||
|
||||
con.CreateScalarFunction<int32_t, int32_t, int32_t>(
|
||||
func_name + "_2", {LogicalType::INTEGER, LogicalType::INTEGER}, LogicalType::INTEGER, &udf_int);
|
||||
|
||||
con.CreateScalarFunction<int32_t, int32_t, int32_t, int32_t>(
|
||||
func_name + "_3", {LogicalType::INTEGER, LogicalType::INTEGER, LogicalType::INTEGER},
|
||||
LogicalType::INTEGER, &udf_int);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::BIGINT: {
|
||||
con.CreateScalarFunction<int64_t, int64_t>(func_name + "_1", {LogicalType::BIGINT}, LogicalType::BIGINT,
|
||||
&udf_int64);
|
||||
|
||||
con.CreateScalarFunction<int64_t, int64_t, int64_t>(
|
||||
func_name + "_2", {LogicalType::BIGINT, LogicalType::BIGINT}, LogicalType::BIGINT, &udf_int64);
|
||||
|
||||
con.CreateScalarFunction<int64_t, int64_t, int64_t, int64_t>(
|
||||
func_name + "_3", {LogicalType::BIGINT, LogicalType::BIGINT, LogicalType::BIGINT}, LogicalType::BIGINT,
|
||||
&udf_int64);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::TIMESTAMP: {
|
||||
con.CreateScalarFunction<timestamp_t, timestamp_t>(func_name + "_1", {LogicalType::TIMESTAMP},
|
||||
LogicalType::TIMESTAMP, &udf_timestamp);
|
||||
|
||||
con.CreateScalarFunction<timestamp_t, timestamp_t, timestamp_t>(
|
||||
func_name + "_2", {LogicalType::TIMESTAMP, LogicalType::TIMESTAMP}, LogicalType::TIMESTAMP,
|
||||
&udf_timestamp);
|
||||
|
||||
con.CreateScalarFunction<timestamp_t, timestamp_t, timestamp_t, timestamp_t>(
|
||||
func_name + "_3", {LogicalType::TIMESTAMP, LogicalType::TIMESTAMP, LogicalType::TIMESTAMP},
|
||||
LogicalType::TIMESTAMP, &udf_timestamp);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::FLOAT: {
|
||||
con.CreateScalarFunction<float, float>(func_name + "_1", {LogicalType::FLOAT}, LogicalType::FLOAT,
|
||||
&udf_float);
|
||||
|
||||
con.CreateScalarFunction<float, float, float>(func_name + "_2", {LogicalType::FLOAT, LogicalType::FLOAT},
|
||||
LogicalType::FLOAT, &udf_float);
|
||||
|
||||
con.CreateScalarFunction<float, float, float, float>(
|
||||
func_name + "_3", {LogicalType::FLOAT, LogicalType::FLOAT, LogicalType::FLOAT}, LogicalType::FLOAT,
|
||||
&udf_float);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::DOUBLE: {
|
||||
con.CreateScalarFunction<double, double>(func_name + "_1", {LogicalType::DOUBLE}, LogicalType::DOUBLE,
|
||||
&udf_double);
|
||||
|
||||
con.CreateScalarFunction<double, double, double>(
|
||||
func_name + "_2", {LogicalType::DOUBLE, LogicalType::DOUBLE}, LogicalType::DOUBLE, &udf_double);
|
||||
|
||||
con.CreateScalarFunction<double, double, double, double>(
|
||||
func_name + "_3", {LogicalType::DOUBLE, LogicalType::DOUBLE, LogicalType::DOUBLE}, LogicalType::DOUBLE,
|
||||
&udf_double);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::VARCHAR: {
|
||||
con.CreateScalarFunction<string_t, string_t>(func_name + "_1", {LogicalType::VARCHAR}, LogicalType::VARCHAR,
|
||||
&udf_varchar);
|
||||
|
||||
con.CreateScalarFunction<string_t, string_t, string_t>(
|
||||
func_name + "_2", {LogicalType::VARCHAR, LogicalType::VARCHAR}, LogicalType::VARCHAR, &udf_varchar);
|
||||
|
||||
con.CreateScalarFunction<string_t, string_t, string_t, string_t>(
|
||||
func_name + "_3", {LogicalType::VARCHAR, LogicalType::VARCHAR, LogicalType::VARCHAR},
|
||||
LogicalType::VARCHAR, &udf_varchar);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Testing UDF functions") {
|
||||
// Inserting values
|
||||
for (LogicalType sql_type : all_sql_types) {
|
||||
table_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
|
||||
string query = "INSERT INTO " + table_name + " VALUES";
|
||||
if (sql_type == LogicalType::BOOLEAN) {
|
||||
con.Query(query + "(true, true, true), (true, true, false), (false, false, false);");
|
||||
} else if (sql_type.IsNumeric()) {
|
||||
con.Query(query + "(1, 10, 100),(2, 10, 100),(3, 10, 100);");
|
||||
} else if (sql_type == LogicalType::VARCHAR) {
|
||||
con.Query(query + "('a', 'b', 'c'),('a', 'b', 'c'),('a', 'b', 'c');");
|
||||
} else if (sql_type == LogicalType::DATE) {
|
||||
con.Query(query + "('2008-01-01', '2009-01-01', '2010-01-01')," +
|
||||
"('2008-01-01', '2009-01-01', '2010-01-01')," + "('2008-01-01', '2009-01-01', '2010-01-01')");
|
||||
} else if (sql_type == LogicalType::TIME) {
|
||||
con.Query(query + "('01:00:00', '02:00:00', '03:00:00')," + "('04:00:00', '05:00:00', '06:00:00')," +
|
||||
"('07:00:00', '08:00:00', '09:00:00')");
|
||||
} else if (sql_type == LogicalType::TIMESTAMP) {
|
||||
con.Query(query + "('2008-01-01 00:00:00', '2009-01-01 00:00:00', '2010-01-01 00:00:00')," +
|
||||
"('2008-01-01 00:00:00', '2009-01-01 00:00:00', '2010-01-01 00:00:00')," +
|
||||
"('2008-01-01 00:00:00', '2009-01-01 00:00:00', '2010-01-01 00:00:00')");
|
||||
}
|
||||
}
|
||||
|
||||
// Running the UDF functions and checking the results
|
||||
for (LogicalType sql_type : all_sql_types) {
|
||||
if (sql_type.id() == LogicalTypeId::DECIMAL) {
|
||||
continue;
|
||||
}
|
||||
table_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
func_name = table_name;
|
||||
if (sql_type.IsNumeric()) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {10, 20, 30}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {111, 112, 113}));
|
||||
|
||||
} else if (sql_type == LogicalType::BOOLEAN) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {true, true, false}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {true, true, false}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {true, false, false}));
|
||||
|
||||
} else if (sql_type == LogicalType::VARCHAR) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"a", "a", "a"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"b", "b", "b"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"c", "c", "c"}));
|
||||
} else if (sql_type == LogicalType::DATE) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"2008-01-01", "2008-01-01", "2008-01-01"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"2009-01-01", "2009-01-01", "2009-01-01"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"2010-01-01", "2010-01-01", "2010-01-01"}));
|
||||
} else if (sql_type == LogicalType::TIME) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"01:00:00", "04:00:00", "07:00:00"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"02:00:00", "05:00:00", "08:00:00"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"03:00:00", "06:00:00", "09:00:00"}));
|
||||
} else if (sql_type == LogicalType::TIMESTAMP) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"2008-01-01 00:00:00", "2008-01-01 00:00:00", "2008-01-01 00:00:00"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"2009-01-01 00:00:00", "2009-01-01 00:00:00", "2009-01-01 00:00:00"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"2010-01-01 00:00:00", "2010-01-01 00:00:00", "2010-01-01 00:00:00"}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Checking NULLs with UDF functions") {
|
||||
for (LogicalType sql_type : all_sql_types) {
|
||||
if (sql_type.id() == LogicalTypeId::DECIMAL) {
|
||||
continue;
|
||||
}
|
||||
table_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
func_name = table_name;
|
||||
|
||||
// Deleting old values
|
||||
REQUIRE_NO_FAIL(con.Query("DELETE FROM " + table_name));
|
||||
|
||||
// Inserting NULLs
|
||||
string query = "INSERT INTO " + table_name + " VALUES";
|
||||
con.Query(query + "(NULL, NULL, NULL), (NULL, NULL, NULL), (NULL, NULL, NULL);");
|
||||
|
||||
// Testing NULLs
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value(nullptr), Value(nullptr), Value(nullptr)}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value(nullptr), Value(nullptr), Value(nullptr)}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value(nullptr), Value(nullptr), Value(nullptr)}));
|
||||
}
|
||||
}
|
||||
}
|
||||
354
external/duckdb/test/api/udf_function/test_argumented_vec_udf.cpp
vendored
Normal file
354
external/duckdb/test/api/udf_function/test_argumented_vec_udf.cpp
vendored
Normal file
@@ -0,0 +1,354 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/common/types/date.hpp"
|
||||
#include "duckdb/common/types/time.hpp"
|
||||
#include "duckdb/common/types/timestamp.hpp"
|
||||
#include "udf_functions_to_test.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Vectorized UDF functions using arguments", "[coverage][.]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
|
||||
string func_name, table_name, col_type;
|
||||
// The types supported by the templated CreateVectorizedFunction
|
||||
const duckdb::vector<LogicalTypeId> all_sql_types = {
|
||||
LogicalTypeId::BOOLEAN, LogicalTypeId::TINYINT, LogicalTypeId::SMALLINT, LogicalTypeId::DATE,
|
||||
LogicalTypeId::TIME, LogicalTypeId::INTEGER, LogicalTypeId::BIGINT, LogicalTypeId::TIMESTAMP,
|
||||
LogicalTypeId::FLOAT, LogicalTypeId::DOUBLE, LogicalTypeId::VARCHAR};
|
||||
|
||||
// Creating the tables
|
||||
for (LogicalType sql_type : all_sql_types) {
|
||||
col_type = EnumUtil::ToString(sql_type.id());
|
||||
table_name = StringUtil::Lower(col_type);
|
||||
|
||||
con.Query("CREATE TABLE " + table_name + " (a " + col_type + ", b " + col_type + ", c " + col_type + ")");
|
||||
}
|
||||
|
||||
// Create the UDF functions into the catalog
|
||||
for (LogicalType sql_type : all_sql_types) {
|
||||
func_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
|
||||
switch (sql_type.id()) {
|
||||
case LogicalTypeId::BOOLEAN: {
|
||||
con.CreateVectorizedFunction(func_name + "_1", {LogicalType::BOOLEAN}, LogicalType::BOOLEAN,
|
||||
&udf_unary_function<bool>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_2", {LogicalType::BOOLEAN, LogicalType::BOOLEAN},
|
||||
LogicalType::BOOLEAN, &udf_binary_function<bool>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_3",
|
||||
{LogicalType::BOOLEAN, LogicalType::BOOLEAN, LogicalType::BOOLEAN},
|
||||
LogicalType::BOOLEAN, &udf_ternary_function<bool>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::TINYINT: {
|
||||
con.CreateVectorizedFunction(func_name + "_1", {LogicalType::TINYINT}, LogicalType::TINYINT,
|
||||
&udf_unary_function<int8_t>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_2", {LogicalType::TINYINT, LogicalType::TINYINT},
|
||||
LogicalType::TINYINT, &udf_binary_function<int8_t>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_3",
|
||||
{LogicalType::TINYINT, LogicalType::TINYINT, LogicalType::TINYINT},
|
||||
LogicalType::TINYINT, &udf_ternary_function<int8_t>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::SMALLINT: {
|
||||
con.CreateVectorizedFunction(func_name + "_1", {LogicalType::SMALLINT}, LogicalType::SMALLINT,
|
||||
&udf_unary_function<int16_t>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_2", {LogicalType::SMALLINT, LogicalType::SMALLINT},
|
||||
LogicalType::SMALLINT, &udf_binary_function<int16_t>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_3",
|
||||
{LogicalType::SMALLINT, LogicalType::SMALLINT, LogicalType::SMALLINT},
|
||||
LogicalType::SMALLINT, &udf_ternary_function<int16_t>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::DATE: {
|
||||
con.CreateVectorizedFunction(func_name + "_1", {LogicalType::DATE}, LogicalType::DATE,
|
||||
&udf_unary_function<date_t>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_2", {LogicalType::DATE, LogicalType::DATE}, LogicalType::DATE,
|
||||
&udf_binary_function<date_t>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_3", {LogicalType::DATE, LogicalType::DATE, LogicalType::DATE},
|
||||
LogicalType::DATE, &udf_ternary_function<date_t>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::TIME: {
|
||||
con.CreateVectorizedFunction(func_name + "_1", {LogicalType::TIME}, LogicalType::TIME,
|
||||
&udf_unary_function<dtime_t>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_2", {LogicalType::TIME, LogicalType::TIME}, LogicalType::TIME,
|
||||
&udf_binary_function<dtime_t>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_3", {LogicalType::TIME, LogicalType::TIME, LogicalType::TIME},
|
||||
LogicalType::TIME, &udf_ternary_function<dtime_t>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::INTEGER: {
|
||||
con.CreateVectorizedFunction(func_name + "_1", {LogicalType::INTEGER}, LogicalType::INTEGER,
|
||||
&udf_unary_function<int32_t>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_2", {LogicalType::INTEGER, LogicalType::INTEGER},
|
||||
LogicalType::INTEGER, &udf_binary_function<int32_t>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_3",
|
||||
{LogicalType::INTEGER, LogicalType::INTEGER, LogicalType::INTEGER},
|
||||
LogicalType::INTEGER, &udf_ternary_function<int32_t>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::BIGINT: {
|
||||
con.CreateVectorizedFunction(func_name + "_1", {LogicalType::BIGINT}, LogicalType::BIGINT,
|
||||
&udf_unary_function<int64_t>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_2", {LogicalType::BIGINT, LogicalType::BIGINT},
|
||||
LogicalType::BIGINT, &udf_binary_function<int64_t>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_3",
|
||||
{LogicalType::BIGINT, LogicalType::BIGINT, LogicalType::BIGINT},
|
||||
LogicalType::BIGINT, &udf_ternary_function<int64_t>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::TIMESTAMP: {
|
||||
con.CreateVectorizedFunction(func_name + "_1", {LogicalType::TIMESTAMP}, LogicalType::TIMESTAMP,
|
||||
&udf_unary_function<timestamp_t>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_2", {LogicalType::TIMESTAMP, LogicalType::TIMESTAMP},
|
||||
LogicalType::TIMESTAMP, &udf_binary_function<timestamp_t>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_3",
|
||||
{LogicalType::TIMESTAMP, LogicalType::TIMESTAMP, LogicalType::TIMESTAMP},
|
||||
LogicalType::TIMESTAMP, &udf_ternary_function<timestamp_t>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::FLOAT:
|
||||
case LogicalTypeId::DOUBLE: {
|
||||
con.CreateVectorizedFunction(func_name + "_1", {LogicalType::DOUBLE}, LogicalType::DOUBLE,
|
||||
&udf_unary_function<double>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_2", {LogicalType::DOUBLE, LogicalType::DOUBLE},
|
||||
LogicalType::DOUBLE, &udf_binary_function<double>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_3",
|
||||
{LogicalType::DOUBLE, LogicalType::DOUBLE, LogicalType::DOUBLE},
|
||||
LogicalType::DOUBLE, &udf_ternary_function<double>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::VARCHAR: {
|
||||
con.CreateVectorizedFunction(func_name + "_1", {LogicalType::VARCHAR}, LogicalType::VARCHAR,
|
||||
&udf_unary_function<char *>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_2", {LogicalType::VARCHAR, LogicalType::VARCHAR},
|
||||
LogicalType::VARCHAR, &udf_binary_function<char *>);
|
||||
|
||||
con.CreateVectorizedFunction(func_name + "_3",
|
||||
{LogicalType::VARCHAR, LogicalType::VARCHAR, LogicalType::VARCHAR},
|
||||
LogicalType::VARCHAR, &udf_ternary_function<char *>);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Testing Vectorized UDF functions") {
|
||||
// Inserting values
|
||||
for (LogicalType sql_type : all_sql_types) {
|
||||
table_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
|
||||
string query = "INSERT INTO " + table_name + " VALUES";
|
||||
if (sql_type == LogicalType::BOOLEAN) {
|
||||
con.Query(query + "(true, true, true), (true, true, false), (false, false, false);");
|
||||
} else if (sql_type.IsNumeric()) {
|
||||
con.Query(query + "(1, 10, 100),(2, 20, 100),(3, 30, 100);");
|
||||
} else if (sql_type == LogicalType::VARCHAR) {
|
||||
con.Query(query + "('a', 'b', 'c'),('a', 'b', 'c'),('a', 'b', 'c');");
|
||||
} else if (sql_type == LogicalType::DATE) {
|
||||
con.Query(query + "('2008-01-01', '2009-01-01', '2010-01-01')," +
|
||||
"('2008-01-01', '2009-01-01', '2010-01-01')," + "('2008-01-01', '2009-01-01', '2010-01-01')");
|
||||
} else if (sql_type == LogicalType::TIME) {
|
||||
con.Query(query + "('01:00:00', '02:00:00', '03:00:00')," + "('04:00:00', '05:00:00', '06:00:00')," +
|
||||
"('07:00:00', '08:00:00', '09:00:00')");
|
||||
} else if (sql_type == LogicalType::TIMESTAMP) {
|
||||
con.Query(query + "('2008-01-01 00:00:00', '2009-01-01 00:00:00', '2010-01-01 00:00:00')," +
|
||||
"('2008-01-01 00:00:00', '2009-01-01 00:00:00', '2010-01-01 00:00:00')," +
|
||||
"('2008-01-01 00:00:00', '2009-01-01 00:00:00', '2010-01-01 00:00:00')");
|
||||
}
|
||||
}
|
||||
|
||||
// Running the UDF functions and checking the results
|
||||
for (LogicalType sql_type : all_sql_types) {
|
||||
table_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
func_name = table_name;
|
||||
if (sql_type.IsNumeric()) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {10, 20, 30}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {100, 100, 100}));
|
||||
|
||||
} else if (sql_type == LogicalType::BOOLEAN) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {true, true, false}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {true, true, false}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {true, false, false}));
|
||||
|
||||
} else if (sql_type == LogicalType::VARCHAR) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"a", "a", "a"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"b", "b", "b"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"c", "c", "c"}));
|
||||
} else if (sql_type == LogicalType::DATE) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"2008-01-01", "2008-01-01", "2008-01-01"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"2009-01-01", "2009-01-01", "2009-01-01"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"2010-01-01", "2010-01-01", "2010-01-01"}));
|
||||
} else if (sql_type == LogicalType::TIME) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"01:00:00", "04:00:00", "07:00:00"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"02:00:00", "05:00:00", "08:00:00"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"03:00:00", "06:00:00", "09:00:00"}));
|
||||
} else if (sql_type == LogicalType::TIMESTAMP) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"2008-01-01 00:00:00", "2008-01-01 00:00:00", "2008-01-01 00:00:00"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"2009-01-01 00:00:00", "2009-01-01 00:00:00", "2009-01-01 00:00:00"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"2010-01-01 00:00:00", "2010-01-01 00:00:00", "2010-01-01 00:00:00"}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Cheking NULLs with Vectorized UDF functions") {
|
||||
for (LogicalType sql_type : all_sql_types) {
|
||||
table_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
func_name = table_name;
|
||||
|
||||
// Deleting old values
|
||||
REQUIRE_NO_FAIL(con.Query("DELETE FROM " + table_name));
|
||||
|
||||
// Inserting NULLs
|
||||
string query = "INSERT INTO " + table_name + " VALUES";
|
||||
con.Query(query + "(NULL, NULL, NULL), (NULL, NULL, NULL), (NULL, NULL, NULL);");
|
||||
|
||||
// Testing NULLs
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value(nullptr), Value(nullptr), Value(nullptr)}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value(nullptr), Value(nullptr), Value(nullptr)}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value(nullptr), Value(nullptr), Value(nullptr)}));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Cheking Vectorized UDF functions with several input columns") {
|
||||
duckdb::vector<LogicalType> sql_args = {LogicalType::INTEGER, LogicalType::INTEGER, LogicalType::INTEGER,
|
||||
LogicalType::INTEGER};
|
||||
// UDF with 4 input ints, return the last one
|
||||
con.CreateVectorizedFunction("udf_four_ints", sql_args, LogicalType::INTEGER,
|
||||
&udf_several_constant_input<int, 4>);
|
||||
result = con.Query("SELECT udf_four_ints(1, 2, 3, 4)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {4}));
|
||||
|
||||
// UDF with 5 input ints, return the last one
|
||||
sql_args.emplace_back(LogicalType::INTEGER);
|
||||
con.CreateVectorizedFunction("udf_five_ints", sql_args, LogicalType::INTEGER,
|
||||
&udf_several_constant_input<int, 5>);
|
||||
result = con.Query("SELECT udf_five_ints(1, 2, 3, 4, 5)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {5}));
|
||||
|
||||
// UDF with 10 input ints, return the last one
|
||||
for (idx_t i = 0; i < 5; ++i) {
|
||||
// adding more 5 items
|
||||
sql_args.emplace_back(LogicalType::INTEGER);
|
||||
}
|
||||
con.CreateVectorizedFunction("udf_ten_ints", sql_args, LogicalType::INTEGER,
|
||||
&udf_several_constant_input<int, 10>);
|
||||
result = con.Query("SELECT udf_ten_ints(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {10}));
|
||||
}
|
||||
|
||||
SECTION("Cheking Vectorized UDF functions with varargs and constant values") {
|
||||
// Test udf_max with integer
|
||||
con.CreateVectorizedFunction("udf_const_max_int", {LogicalType::INTEGER}, LogicalType::INTEGER,
|
||||
&udf_max_constant<int>, LogicalType::INTEGER);
|
||||
result = con.Query("SELECT udf_const_max_int(1, 2, 3, 4, 999, 5, 6, 7)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {999}));
|
||||
|
||||
result = con.Query("SELECT udf_const_max_int(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {10}));
|
||||
|
||||
// Test udf_max with double
|
||||
con.CreateVectorizedFunction("udf_const_max_double", {LogicalType::DOUBLE}, LogicalType::DOUBLE,
|
||||
&udf_max_constant<double>, LogicalType::DOUBLE);
|
||||
result = con.Query("SELECT udf_const_max_double(1.0, 2.0, 3.0, 4.0, 999.0, 5.0, 6.0, 7.0)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {999.0}));
|
||||
|
||||
result = con.Query("SELECT udf_const_max_double(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {10.0}));
|
||||
}
|
||||
|
||||
SECTION("Cheking Vectorized UDF functions with varargs and input columns") {
|
||||
// Test udf_max with integer
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers (a INTEGER, b INTEGER, c INTEGER, d INTEGER)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES(1, 2, 3, 4), (10, 20, 30, 40), (100, 200, 300, 400), "
|
||||
"(1000, 2000, 3000, 4000)"));
|
||||
|
||||
con.CreateVectorizedFunction("udf_flat_max_int", {LogicalType::INTEGER}, LogicalType::INTEGER,
|
||||
&udf_max_flat<int>, LogicalType::INTEGER);
|
||||
result = con.Query("SELECT udf_flat_max_int(a, b, c, d) FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {4, 40, 400, 4000}));
|
||||
|
||||
result = con.Query("SELECT udf_flat_max_int(d, c, b, a) FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {4, 40, 400, 4000}));
|
||||
|
||||
result = con.Query("SELECT udf_flat_max_int(c, b) FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {3, 30, 300, 3000}));
|
||||
|
||||
// Test udf_max with double
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE doubles (a DOUBLE, b DOUBLE, c DOUBLE, d DOUBLE)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO doubles VALUES(1, 2, 3, 4), (10, 20, 30, 40), (100, 200, 300, 400), "
|
||||
"(1000, 2000, 3000, 4000)"));
|
||||
|
||||
con.CreateVectorizedFunction("udf_flat_max_double", {LogicalType::DOUBLE}, LogicalType::DOUBLE,
|
||||
&udf_max_flat<double>, LogicalType::DOUBLE);
|
||||
result = con.Query("SELECT udf_flat_max_double(a, b, c, d) FROM doubles");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {4, 40, 400, 4000}));
|
||||
|
||||
result = con.Query("SELECT udf_flat_max_double(d, c, b, a) FROM doubles");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {4, 40, 400, 4000}));
|
||||
|
||||
result = con.Query("SELECT udf_flat_max_double(c, b) FROM doubles");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {3, 30, 300, 3000}));
|
||||
}
|
||||
}
|
||||
164
external/duckdb/test/api/udf_function/test_templated_scalar_udf.cpp
vendored
Normal file
164
external/duckdb/test/api/udf_function/test_templated_scalar_udf.cpp
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "udf_functions_to_test.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("UDF functions with template", "[coverage][.]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
|
||||
string func_name, table_name, col_type;
|
||||
// The types supported by the templated CreateScalarFunction
|
||||
const duckdb::vector<LogicalType> sql_templated_types = {
|
||||
LogicalType::BOOLEAN, LogicalType::TINYINT, LogicalType::SMALLINT, LogicalType::INTEGER,
|
||||
LogicalType::BIGINT, LogicalType::FLOAT, LogicalType::DOUBLE, LogicalType::VARCHAR};
|
||||
|
||||
// Creating the tables
|
||||
for (LogicalType sql_type : sql_templated_types) {
|
||||
col_type = EnumUtil::ToString(sql_type.id());
|
||||
table_name = StringUtil::Lower(col_type);
|
||||
|
||||
con.Query("CREATE TABLE " + table_name + " (a " + col_type + ", b " + col_type + ", c " + col_type + ")");
|
||||
}
|
||||
|
||||
// Create the UDF functions into the catalog
|
||||
for (LogicalType sql_type : sql_templated_types) {
|
||||
func_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
|
||||
switch (sql_type.id()) {
|
||||
case LogicalTypeId::BOOLEAN: {
|
||||
con.CreateScalarFunction<bool, bool>(func_name + "_1", &udf_bool);
|
||||
con.CreateScalarFunction<bool, bool, bool>(func_name + "_2", &udf_bool);
|
||||
con.CreateScalarFunction<bool, bool, bool, bool>(func_name + "_3", &udf_bool);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::TINYINT: {
|
||||
con.CreateScalarFunction<int8_t, int8_t>(func_name + "_1", &udf_int8);
|
||||
con.CreateScalarFunction<int8_t, int8_t, int8_t>(func_name + "_2", &udf_int8);
|
||||
con.CreateScalarFunction<int8_t, int8_t, int8_t, int8_t>(func_name + "_3", &udf_int8);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::SMALLINT: {
|
||||
con.CreateScalarFunction<int16_t, int16_t>(func_name + "_1", &udf_int16);
|
||||
con.CreateScalarFunction<int16_t, int16_t, int16_t>(func_name + "_2", &udf_int16);
|
||||
con.CreateScalarFunction<int16_t, int16_t, int16_t, int16_t>(func_name + "_3", &udf_int16);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::INTEGER: {
|
||||
con.CreateScalarFunction<int32_t, int32_t>(func_name + "_1", &udf_int);
|
||||
con.CreateScalarFunction<int32_t, int32_t, int32_t>(func_name + "_2", &udf_int);
|
||||
con.CreateScalarFunction<int32_t, int32_t, int32_t, int32_t>(func_name + "_3", &udf_int);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::BIGINT: {
|
||||
con.CreateScalarFunction<int64_t, int64_t>(func_name + "_1", &udf_int64);
|
||||
con.CreateScalarFunction<int64_t, int64_t, int64_t>(func_name + "_2", &udf_int64);
|
||||
con.CreateScalarFunction<int64_t, int64_t, int64_t, int64_t>(func_name + "_3", &udf_int64);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::FLOAT:
|
||||
// FIXME: there is an implicit cast to DOUBLE before calling the function: float_1(CAST[DOUBLE](a)),
|
||||
// because of that we cannot invoke such a function: float udf_float(float a);
|
||||
// {
|
||||
// con.CreateScalarFunction<float, float>(func_name + "_1", &FLOAT);
|
||||
// con.CreateScalarFunction<float, float, float>(func_name + "_2", &FLOAT);
|
||||
// con.CreateScalarFunction<float, float, float, float>(func_name + "_3", &FLOAT);
|
||||
// break;
|
||||
// }
|
||||
case LogicalTypeId::DOUBLE: {
|
||||
con.CreateScalarFunction<double, double>(func_name + "_1", &udf_double);
|
||||
con.CreateScalarFunction<double, double, double>(func_name + "_2", &udf_double);
|
||||
con.CreateScalarFunction<double, double, double, double>(func_name + "_3", &udf_double);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::VARCHAR: {
|
||||
con.CreateScalarFunction<string_t, string_t>(func_name + "_1", &udf_varchar);
|
||||
con.CreateScalarFunction<string_t, string_t, string_t>(func_name + "_2", &udf_varchar);
|
||||
con.CreateScalarFunction<string_t, string_t, string_t, string_t>(func_name + "_3", &udf_varchar);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Testing UDF functions") {
|
||||
// Inserting values
|
||||
for (LogicalType sql_type : sql_templated_types) {
|
||||
table_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
|
||||
string query = "INSERT INTO " + table_name + " VALUES";
|
||||
if (sql_type == LogicalType::BOOLEAN) {
|
||||
con.Query(query + "(true, true, true), (true, true, false), (false, false, false);");
|
||||
} else if (sql_type.IsNumeric()) {
|
||||
con.Query(query + "(1, 10, 100),(2, 10, 100),(3, 10, 100);");
|
||||
} else if (sql_type == LogicalType::VARCHAR) {
|
||||
con.Query(query + "('a', 'b', 'c'),('a', 'b', 'c'),('a', 'b', 'c');");
|
||||
}
|
||||
}
|
||||
|
||||
// Running the UDF functions and checking the results
|
||||
for (LogicalType sql_type : sql_templated_types) {
|
||||
table_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
func_name = table_name;
|
||||
if (sql_type.IsNumeric()) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {10, 20, 30}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {111, 112, 113}));
|
||||
|
||||
} else if (sql_type == LogicalType::BOOLEAN) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {true, true, false}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {true, true, false}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {true, false, false}));
|
||||
|
||||
} else if (sql_type == LogicalType::VARCHAR) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"a", "a", "a"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"b", "b", "b"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"c", "c", "c"}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Checking NULLs with UDF functions") {
|
||||
for (LogicalType sql_type : sql_templated_types) {
|
||||
table_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
func_name = table_name;
|
||||
|
||||
// Deleting old values
|
||||
REQUIRE_NO_FAIL(con.Query("DELETE FROM " + table_name));
|
||||
|
||||
// Inserting NULLs
|
||||
string query = "INSERT INTO " + table_name + " VALUES";
|
||||
con.Query(query + "(NULL, NULL, NULL), (NULL, NULL, NULL), (NULL, NULL, NULL);");
|
||||
|
||||
// Testing NULLs
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value(nullptr), Value(nullptr), Value(nullptr)}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value(nullptr), Value(nullptr), Value(nullptr)}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value(nullptr), Value(nullptr), Value(nullptr)}));
|
||||
}
|
||||
}
|
||||
}
|
||||
231
external/duckdb/test/api/udf_function/test_templated_vec_udf.cpp
vendored
Normal file
231
external/duckdb/test/api/udf_function/test_templated_vec_udf.cpp
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "udf_functions_to_test.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Vectorized UDF functions using templates", "[coverage][.]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
con.EnableQueryVerification();
|
||||
|
||||
string func_name, table_name, col_type;
|
||||
// The types supported by the templated CreateVectorizedFunction
|
||||
const duckdb::vector<LogicalType> sql_templated_types = {
|
||||
LogicalType::BOOLEAN, LogicalType::TINYINT, LogicalType::SMALLINT, LogicalType::INTEGER,
|
||||
LogicalType::BIGINT, LogicalType::FLOAT, LogicalType::DOUBLE, LogicalType::VARCHAR};
|
||||
|
||||
// Creating the tables
|
||||
for (LogicalType sql_type : sql_templated_types) {
|
||||
col_type = EnumUtil::ToString(sql_type.id());
|
||||
table_name = StringUtil::Lower(col_type);
|
||||
|
||||
con.Query("CREATE TABLE " + table_name + " (a " + col_type + ", b " + col_type + ", c " + col_type + ")");
|
||||
}
|
||||
|
||||
// Create the UDF functions into the catalog
|
||||
for (LogicalType sql_type : sql_templated_types) {
|
||||
func_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
|
||||
switch (sql_type.id()) {
|
||||
case LogicalTypeId::BOOLEAN: {
|
||||
con.CreateVectorizedFunction<bool, bool>(func_name + "_1", &udf_unary_function<bool>);
|
||||
con.CreateVectorizedFunction<bool, bool, bool>(func_name + "_2", &udf_binary_function<bool>);
|
||||
con.CreateVectorizedFunction<bool, bool, bool, bool>(func_name + "_3", &udf_ternary_function<bool>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::TINYINT: {
|
||||
con.CreateVectorizedFunction<int8_t, int8_t>(func_name + "_1", &udf_unary_function<int8_t>);
|
||||
con.CreateVectorizedFunction<int8_t, int8_t, int8_t>(func_name + "_2", &udf_binary_function<int8_t>);
|
||||
con.CreateVectorizedFunction<int8_t, int8_t, int8_t, int8_t>(func_name + "_3",
|
||||
&udf_ternary_function<int8_t>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::SMALLINT: {
|
||||
con.CreateVectorizedFunction<int16_t, int16_t>(func_name + "_1", &udf_unary_function<int16_t>);
|
||||
con.CreateVectorizedFunction<int16_t, int16_t, int16_t>(func_name + "_2", &udf_binary_function<int16_t>);
|
||||
con.CreateVectorizedFunction<int16_t, int16_t, int16_t, int16_t>(func_name + "_3",
|
||||
&udf_ternary_function<int16_t>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::INTEGER: {
|
||||
con.CreateVectorizedFunction<int, int>(func_name + "_1", &udf_unary_function<int>);
|
||||
con.CreateVectorizedFunction<int, int, int>(func_name + "_2", &udf_binary_function<int>);
|
||||
con.CreateVectorizedFunction<int, int, int, int>(func_name + "_3", &udf_ternary_function<int>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::BIGINT: {
|
||||
con.CreateVectorizedFunction<int64_t, int64_t>(func_name + "_1", &udf_unary_function<int64_t>);
|
||||
con.CreateVectorizedFunction<int64_t, int64_t, int64_t>(func_name + "_2", &udf_binary_function<int64_t>);
|
||||
con.CreateVectorizedFunction<int64_t, int64_t, int64_t, int64_t>(func_name + "_3",
|
||||
&udf_ternary_function<int64_t>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::FLOAT:
|
||||
case LogicalTypeId::DOUBLE: {
|
||||
con.CreateVectorizedFunction<double, double>(func_name + "_1", &udf_unary_function<double>);
|
||||
con.CreateVectorizedFunction<double, double, double>(func_name + "_2", &udf_binary_function<double>);
|
||||
con.CreateVectorizedFunction<double, double, double, double>(func_name + "_3",
|
||||
&udf_ternary_function<double>);
|
||||
break;
|
||||
}
|
||||
case LogicalTypeId::VARCHAR: {
|
||||
con.CreateVectorizedFunction<string_t, string_t>(func_name + "_1", &udf_unary_function<char *>);
|
||||
con.CreateVectorizedFunction<string_t, string_t, string_t>(func_name + "_2", &udf_binary_function<char *>);
|
||||
con.CreateVectorizedFunction<string_t, string_t, string_t, string_t>(func_name + "_3",
|
||||
&udf_ternary_function<char *>);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Testing Vectorized UDF functions") {
|
||||
// Inserting values
|
||||
for (LogicalType sql_type : sql_templated_types) {
|
||||
table_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
|
||||
string query = "INSERT INTO " + table_name + " VALUES";
|
||||
if (sql_type == LogicalType::BOOLEAN) {
|
||||
con.Query(query + "(true, true, true), (true, true, false), (false, false, false);");
|
||||
} else if (sql_type.IsNumeric()) {
|
||||
con.Query(query + "(1, 10, 101),(2, 20, 102),(3, 30, 103);");
|
||||
} else if (sql_type == LogicalType::VARCHAR) {
|
||||
con.Query(query + "('a', 'b', 'c'),('a', 'b', 'c'),('a', 'b', 'c');");
|
||||
}
|
||||
}
|
||||
|
||||
// Running the UDF functions and checking the results
|
||||
for (LogicalType sql_type : sql_templated_types) {
|
||||
table_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
func_name = table_name;
|
||||
if (sql_type.IsNumeric()) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {10, 20, 30}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {101, 102, 103}));
|
||||
|
||||
} else if (sql_type == LogicalType::BOOLEAN) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {true, true, false}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {true, true, false}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {true, false, false}));
|
||||
|
||||
} else if (sql_type == LogicalType::VARCHAR) {
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"a", "a", "a"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"b", "b", "b"}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"c", "c", "c"}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Cheking NULLs with Vectorized UDF functions") {
|
||||
for (LogicalType sql_type : sql_templated_types) {
|
||||
table_name = StringUtil::Lower(EnumUtil::ToString(sql_type.id()));
|
||||
func_name = table_name;
|
||||
|
||||
// Deleting old values
|
||||
REQUIRE_NO_FAIL(con.Query("DELETE FROM " + table_name));
|
||||
|
||||
// Inserting NULLs
|
||||
string query = "INSERT INTO " + table_name + " VALUES";
|
||||
con.Query(query + "(NULL, NULL, NULL), (NULL, NULL, NULL), (NULL, NULL, NULL);");
|
||||
|
||||
// Testing NULLs
|
||||
result = con.Query("SELECT " + func_name + "_1(a) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value(nullptr), Value(nullptr), Value(nullptr)}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_2(a, b) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value(nullptr), Value(nullptr), Value(nullptr)}));
|
||||
|
||||
result = con.Query("SELECT " + func_name + "_3(a, b, c) FROM " + table_name);
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value(nullptr), Value(nullptr), Value(nullptr)}));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Cheking Vectorized UDF functions with several input columns") {
|
||||
// UDF with 4 input ints, return the last one
|
||||
con.CreateVectorizedFunction<int, int, int, int, int>("udf_four_ints", &udf_several_constant_input<int, 4>);
|
||||
result = con.Query("SELECT udf_four_ints(1, 2, 3, 4)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {4}));
|
||||
|
||||
// UDF with 5 input ints, return the last one
|
||||
con.CreateVectorizedFunction<int, int, int, int, int, int>("udf_five_ints",
|
||||
&udf_several_constant_input<int, 5>);
|
||||
result = con.Query("SELECT udf_five_ints(1, 2, 3, 4, 5)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {5}));
|
||||
|
||||
// UDF with 10 input ints, return the last one
|
||||
con.CreateVectorizedFunction<int, int, int, int, int, int, int, int, int, int, int>(
|
||||
"udf_ten_ints", &udf_several_constant_input<int, 10>);
|
||||
result = con.Query("SELECT udf_ten_ints(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {10}));
|
||||
}
|
||||
|
||||
SECTION("Cheking Vectorized UDF functions with varargs and constant values") {
|
||||
// Test udf_max with integer
|
||||
con.CreateVectorizedFunction<int, int>("udf_const_max_int", &udf_max_constant<int>, LogicalType::INTEGER);
|
||||
result = con.Query("SELECT udf_const_max_int(1, 2, 3, 4, 999, 5, 6, 7)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {999}));
|
||||
|
||||
result = con.Query("SELECT udf_const_max_int(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {10}));
|
||||
|
||||
// Test udf_max with double
|
||||
con.CreateVectorizedFunction<double, double>("udf_const_max_double", &udf_max_constant<double>,
|
||||
LogicalType::DOUBLE);
|
||||
result = con.Query("SELECT udf_const_max_double(1.0, 2.0, 3.0, 4.0, 999.0, 5.0, 6.0, 7.0)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {999.0}));
|
||||
|
||||
result = con.Query("SELECT udf_const_max_double(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0)");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {10.0}));
|
||||
}
|
||||
|
||||
SECTION("Cheking Vectorized UDF functions with varargs and input columns") {
|
||||
// Test udf_max with integer
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers (a INTEGER, b INTEGER, c INTEGER, d INTEGER)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES(1, 2, 3, 4), (10, 20, 30, 40), (100, 200, 300, 400), "
|
||||
"(1000, 2000, 3000, 4000)"));
|
||||
|
||||
con.CreateVectorizedFunction<int, int>("udf_flat_max_int", &udf_max_flat<int>, LogicalType::INTEGER);
|
||||
result = con.Query("SELECT udf_flat_max_int(a, b, c, d) FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {4, 40, 400, 4000}));
|
||||
|
||||
result = con.Query("SELECT udf_flat_max_int(d, c, b, a) FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {4, 40, 400, 4000}));
|
||||
|
||||
result = con.Query("SELECT udf_flat_max_int(c, b) FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {3, 30, 300, 3000}));
|
||||
|
||||
// Test udf_max with double
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE doubles (a DOUBLE, b DOUBLE, c DOUBLE, d DOUBLE)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO doubles VALUES(1, 2, 3, 4), (10, 20, 30, 40), (100, 200, 300, 400), "
|
||||
"(1000, 2000, 3000, 4000)"));
|
||||
|
||||
con.CreateVectorizedFunction<double, double>("udf_flat_max_double", &udf_max_flat<double>, LogicalType::DOUBLE);
|
||||
result = con.Query("SELECT udf_flat_max_double(a, b, c, d) FROM doubles");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {4, 40, 400, 4000}));
|
||||
|
||||
result = con.Query("SELECT udf_flat_max_double(d, c, b, a) FROM doubles");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {4, 40, 400, 4000}));
|
||||
|
||||
result = con.Query("SELECT udf_flat_max_double(c, b) FROM doubles");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {3, 30, 300, 3000}));
|
||||
}
|
||||
}
|
||||
602
external/duckdb/test/api/udf_function/udf_functions_to_test.hpp
vendored
Normal file
602
external/duckdb/test/api/udf_function/udf_functions_to_test.hpp
vendored
Normal file
@@ -0,0 +1,602 @@
|
||||
/*HEADER file with all UDF Functions to test*/
|
||||
#pragma once
|
||||
|
||||
namespace duckdb {
|
||||
|
||||
// UDF Functions to test
|
||||
inline bool udf_bool(bool a) {
|
||||
return a;
|
||||
}
|
||||
inline bool udf_bool(bool a, bool b) {
|
||||
return a & b;
|
||||
}
|
||||
inline bool udf_bool(bool a, bool b, bool c) {
|
||||
return a & b & c;
|
||||
}
|
||||
|
||||
inline int8_t udf_int8(int8_t a) {
|
||||
return a;
|
||||
}
|
||||
inline int8_t udf_int8(int8_t a, int8_t b) {
|
||||
return a * b;
|
||||
}
|
||||
inline int8_t udf_int8(int8_t a, int8_t b, int8_t c) {
|
||||
return a + b + c;
|
||||
}
|
||||
|
||||
inline int16_t udf_int16(int16_t a) {
|
||||
return a;
|
||||
}
|
||||
inline int16_t udf_int16(int16_t a, int16_t b) {
|
||||
return a * b;
|
||||
}
|
||||
inline int16_t udf_int16(int16_t a, int16_t b, int16_t c) {
|
||||
return a + b + c;
|
||||
}
|
||||
|
||||
inline date_t udf_date(date_t a) {
|
||||
return a;
|
||||
}
|
||||
inline date_t udf_date(date_t a, date_t b) {
|
||||
return b;
|
||||
}
|
||||
inline date_t udf_date(date_t a, date_t b, date_t c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
inline dtime_t udf_time(dtime_t a) {
|
||||
return a;
|
||||
}
|
||||
inline dtime_t udf_time(dtime_t a, dtime_t b) {
|
||||
return b;
|
||||
}
|
||||
inline dtime_t udf_time(dtime_t a, dtime_t b, dtime_t c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
inline int udf_int(int a) {
|
||||
return a;
|
||||
}
|
||||
inline int udf_int(int a, int b) {
|
||||
return a * b;
|
||||
}
|
||||
inline int udf_int(int a, int b, int c) {
|
||||
return a + b + c;
|
||||
}
|
||||
|
||||
inline int64_t udf_int64(int64_t a) {
|
||||
return a;
|
||||
}
|
||||
inline int64_t udf_int64(int64_t a, int64_t b) {
|
||||
return a * b;
|
||||
}
|
||||
inline int64_t udf_int64(int64_t a, int64_t b, int64_t c) {
|
||||
return a + b + c;
|
||||
}
|
||||
|
||||
inline timestamp_t udf_timestamp(timestamp_t a) {
|
||||
return a;
|
||||
}
|
||||
inline timestamp_t udf_timestamp(timestamp_t a, timestamp_t b) {
|
||||
return b;
|
||||
}
|
||||
inline timestamp_t udf_timestamp(timestamp_t a, timestamp_t b, timestamp_t c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
inline float udf_float(float a) {
|
||||
return a;
|
||||
}
|
||||
inline float udf_float(float a, float b) {
|
||||
return a * b;
|
||||
}
|
||||
inline float udf_float(float a, float b, float c) {
|
||||
return a + b + c;
|
||||
}
|
||||
|
||||
inline double udf_double(double a) {
|
||||
return a;
|
||||
}
|
||||
inline double udf_double(double a, double b) {
|
||||
return a * b;
|
||||
}
|
||||
inline double udf_double(double a, double b, double c) {
|
||||
return a + b + c;
|
||||
}
|
||||
|
||||
inline double udf_decimal(double a) {
|
||||
return a;
|
||||
}
|
||||
inline double udf_decimal(double a, double b) {
|
||||
return a * b;
|
||||
}
|
||||
inline double udf_decimal(double a, double b, double c) {
|
||||
return a + b + c;
|
||||
}
|
||||
|
||||
inline string_t udf_varchar(string_t a) {
|
||||
return a;
|
||||
}
|
||||
inline string_t udf_varchar(string_t a, string_t b) {
|
||||
return b;
|
||||
}
|
||||
inline string_t udf_varchar(string_t a, string_t b, string_t c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
// Vectorized UDF Functions -------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* This vectorized function is an unary one that copies input values to the result vector
|
||||
*/
|
||||
template <typename TYPE>
|
||||
static void udf_unary_function(DataChunk &input, ExpressionState &state, Vector &result) {
|
||||
input.Flatten();
|
||||
switch (GetTypeId<TYPE>()) {
|
||||
case PhysicalType::VARCHAR: {
|
||||
result.SetVectorType(VectorType::FLAT_VECTOR);
|
||||
auto result_data = FlatVector::GetData<string_t>(result);
|
||||
auto ldata = FlatVector::GetData<string_t>(input.data[0]);
|
||||
auto &validity = FlatVector::Validity(input.data[0]);
|
||||
|
||||
FlatVector::SetValidity(result, FlatVector::Validity(input.data[0]));
|
||||
|
||||
for (idx_t i = 0; i < input.size(); i++) {
|
||||
if (!validity.RowIsValid(i)) {
|
||||
continue;
|
||||
}
|
||||
auto input_length = ldata[i].GetSize();
|
||||
string_t target = StringVector::EmptyString(result, input_length);
|
||||
auto target_data = target.GetDataWriteable();
|
||||
memcpy(target_data, ldata[i].GetData(), input_length);
|
||||
target.Finalize();
|
||||
result_data[i] = target;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
result.SetVectorType(VectorType::FLAT_VECTOR);
|
||||
auto result_data = FlatVector::GetData<TYPE>(result);
|
||||
auto ldata = FlatVector::GetData<TYPE>(input.data[0]);
|
||||
auto mask = FlatVector::Validity(input.data[0]);
|
||||
FlatVector::SetValidity(result, mask);
|
||||
|
||||
for (idx_t i = 0; i < input.size(); i++) {
|
||||
if (!mask.RowIsValid(i)) {
|
||||
continue;
|
||||
}
|
||||
result_data[i] = ldata[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This vectorized function is a binary one that copies values from the second input vector to the result vector
|
||||
*/
|
||||
template <typename TYPE>
|
||||
static void udf_binary_function(DataChunk &input, ExpressionState &state, Vector &result) {
|
||||
input.Flatten();
|
||||
switch (GetTypeId<TYPE>()) {
|
||||
case PhysicalType::VARCHAR: {
|
||||
result.SetVectorType(VectorType::FLAT_VECTOR);
|
||||
auto result_data = FlatVector::GetData<string_t>(result);
|
||||
auto ldata = FlatVector::GetData<string_t>(input.data[1]);
|
||||
auto &validity = FlatVector::Validity(input.data[0]);
|
||||
|
||||
FlatVector::SetValidity(result, FlatVector::Validity(input.data[1]));
|
||||
|
||||
for (idx_t i = 0; i < input.size(); i++) {
|
||||
if (!validity.RowIsValid(i)) {
|
||||
continue;
|
||||
}
|
||||
auto input_length = ldata[i].GetSize();
|
||||
string_t target = StringVector::EmptyString(result, input_length);
|
||||
auto target_data = target.GetDataWriteable();
|
||||
memcpy(target_data, ldata[i].GetData(), input_length);
|
||||
target.Finalize();
|
||||
result_data[i] = target;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
result.SetVectorType(VectorType::FLAT_VECTOR);
|
||||
auto result_data = FlatVector::GetData<TYPE>(result);
|
||||
auto ldata = FlatVector::GetData<TYPE>(input.data[1]);
|
||||
auto &mask = FlatVector::Validity(input.data[1]);
|
||||
FlatVector::SetValidity(result, mask);
|
||||
|
||||
for (idx_t i = 0; i < input.size(); i++) {
|
||||
if (!mask.RowIsValid(i)) {
|
||||
continue;
|
||||
}
|
||||
result_data[i] = ldata[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This vectorized function is a ternary one that copies values from the third input vector to the result vector
|
||||
*/
|
||||
template <typename TYPE>
|
||||
static void udf_ternary_function(DataChunk &input, ExpressionState &state, Vector &result) {
|
||||
input.Flatten();
|
||||
switch (GetTypeId<TYPE>()) {
|
||||
case PhysicalType::VARCHAR: {
|
||||
result.SetVectorType(VectorType::FLAT_VECTOR);
|
||||
auto result_data = FlatVector::GetData<string_t>(result);
|
||||
auto ldata = FlatVector::GetData<string_t>(input.data[2]);
|
||||
auto &validity = FlatVector::Validity(input.data[0]);
|
||||
|
||||
FlatVector::SetValidity(result, FlatVector::Validity(input.data[2]));
|
||||
|
||||
for (idx_t i = 0; i < input.size(); i++) {
|
||||
if (!validity.RowIsValid(i)) {
|
||||
continue;
|
||||
}
|
||||
auto input_length = ldata[i].GetSize();
|
||||
string_t target = StringVector::EmptyString(result, input_length);
|
||||
auto target_data = target.GetDataWriteable();
|
||||
memcpy(target_data, ldata[i].GetData(), input_length);
|
||||
target.Finalize();
|
||||
result_data[i] = target;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
result.SetVectorType(VectorType::FLAT_VECTOR);
|
||||
auto result_data = FlatVector::GetData<TYPE>(result);
|
||||
auto ldata = FlatVector::GetData<TYPE>(input.data[2]);
|
||||
auto &mask = FlatVector::Validity(input.data[2]);
|
||||
FlatVector::SetValidity(result, mask);
|
||||
|
||||
for (idx_t i = 0; i < input.size(); i++) {
|
||||
if (!mask.RowIsValid(i)) {
|
||||
continue;
|
||||
}
|
||||
result_data[i] = ldata[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Vectorized function with the number of input as a template parameter
|
||||
*/
|
||||
template <typename TYPE, int NUM_INPUT>
|
||||
static void udf_several_constant_input(DataChunk &input, ExpressionState &state, Vector &result) {
|
||||
result.SetVectorType(VectorType::CONSTANT_VECTOR);
|
||||
auto result_data = ConstantVector::GetData<TYPE>(result);
|
||||
auto ldata = ConstantVector::GetData<TYPE>(input.data[NUM_INPUT - 1]);
|
||||
|
||||
for (idx_t i = 0; i < input.size(); i++) {
|
||||
result_data[i] = ldata[i];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Vectorized MAX function with varargs and constant inputs
|
||||
*/
|
||||
template <typename TYPE>
|
||||
static void udf_max_constant(DataChunk &args, ExpressionState &state, Vector &result) {
|
||||
TYPE max = 0;
|
||||
result.SetVectorType(VectorType::CONSTANT_VECTOR);
|
||||
for (idx_t col_idx = 0; col_idx < args.ColumnCount(); col_idx++) {
|
||||
auto &input = args.data[col_idx];
|
||||
if (ConstantVector::IsNull(input)) {
|
||||
// constant null, skip
|
||||
continue;
|
||||
}
|
||||
auto input_data = ConstantVector::GetData<TYPE>(input);
|
||||
if (max < input_data[0]) {
|
||||
max = input_data[0];
|
||||
}
|
||||
}
|
||||
auto result_data = ConstantVector::GetData<TYPE>(result);
|
||||
result_data[0] = max;
|
||||
}
|
||||
|
||||
/*
|
||||
* Vectorized MAX function with varargs and input columns
|
||||
*/
|
||||
template <typename TYPE>
|
||||
static void udf_max_flat(DataChunk &args, ExpressionState &state, Vector &result) {
|
||||
args.Flatten();
|
||||
D_ASSERT(TypeIsNumeric(GetTypeId<TYPE>()));
|
||||
|
||||
result.SetVectorType(VectorType::FLAT_VECTOR);
|
||||
auto result_data = FlatVector::GetData<TYPE>(result);
|
||||
|
||||
// Initialize the result vector with the minimum value from TYPE.
|
||||
memset(result_data, std::numeric_limits<TYPE>::min(), args.size() * sizeof(TYPE));
|
||||
|
||||
for (idx_t col_idx = 0; col_idx < args.ColumnCount(); col_idx++) {
|
||||
auto &input = args.data[col_idx];
|
||||
D_ASSERT((GetTypeId<TYPE>()) == input.GetType().InternalType());
|
||||
auto input_data = FlatVector::GetData<TYPE>(input);
|
||||
for (idx_t i = 0; i < args.size(); ++i) {
|
||||
if (result_data[i] < input_data[i]) {
|
||||
result_data[i] = input_data[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Aggregate UDF to test -------------------------------------------------------------------
|
||||
|
||||
// AVG function copied from "src/function/aggregate/algebraic/avg.cpp"
|
||||
template <class T>
|
||||
struct udf_avg_state_t {
|
||||
uint64_t count;
|
||||
T sum;
|
||||
};
|
||||
|
||||
struct UDFAverageFunction {
|
||||
template <class STATE>
|
||||
static void Initialize(STATE &state) {
|
||||
state.count = 0;
|
||||
state.sum = 0;
|
||||
}
|
||||
|
||||
template <class INPUT_TYPE, class STATE, class OP>
|
||||
static void Operation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &) {
|
||||
state.sum += input;
|
||||
state.count++;
|
||||
}
|
||||
|
||||
template <class INPUT_TYPE, class STATE, class OP>
|
||||
static void ConstantOperation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &, idx_t count) {
|
||||
state.count += count;
|
||||
state.sum += input * count;
|
||||
}
|
||||
|
||||
template <class STATE, class OP>
|
||||
static void Combine(const STATE &source, STATE &target, AggregateInputData &) {
|
||||
target.count += source.count;
|
||||
target.sum += source.sum;
|
||||
}
|
||||
|
||||
template <class T, class STATE>
|
||||
static void Finalize(STATE &state, T &target, AggregateFinalizeData &finalize_data) {
|
||||
if (state.count == 0) {
|
||||
finalize_data.ReturnNull();
|
||||
} else {
|
||||
target = state.sum / state.count;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IgnoreNull() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// COVAR function copied from "src/function/aggregate/algebraic/covar.cpp"
|
||||
|
||||
//------------------ COVAR --------------------------------//
|
||||
struct udf_covar_state_t {
|
||||
uint64_t count;
|
||||
double meanx;
|
||||
double meany;
|
||||
double co_moment;
|
||||
};
|
||||
|
||||
struct UDFCovarOperation {
|
||||
template <class STATE>
|
||||
static void Initialize(STATE &state) {
|
||||
state.count = 0;
|
||||
state.meanx = 0;
|
||||
state.meany = 0;
|
||||
state.co_moment = 0;
|
||||
}
|
||||
|
||||
template <class A_TYPE, class B_TYPE, class STATE, class OP>
|
||||
static void Operation(STATE &state, const A_TYPE &x, const B_TYPE &y, AggregateBinaryInput &idata) {
|
||||
// update running mean and d^2
|
||||
const uint64_t n = ++(state.count);
|
||||
|
||||
const double dx = (x - state.meanx);
|
||||
const double meanx = state.meanx + dx / n;
|
||||
|
||||
const double dy = (y - state.meany);
|
||||
const double meany = state.meany + dy / n;
|
||||
|
||||
const double C = state.co_moment + dx * (y - meany);
|
||||
|
||||
state.meanx = meanx;
|
||||
state.meany = meany;
|
||||
state.co_moment = C;
|
||||
}
|
||||
|
||||
template <class STATE, class OP>
|
||||
static void Combine(const STATE &source, STATE &target, AggregateInputData &) {
|
||||
if (target.count == 0) {
|
||||
target = source;
|
||||
} else if (source.count > 0) {
|
||||
const auto count = target.count + source.count;
|
||||
const auto meanx = (source.count * source.meanx + target.count * target.meanx) / count;
|
||||
const auto meany = (source.count * source.meany + target.count * target.meany) / count;
|
||||
|
||||
// Schubert and Gertz SSDBM 2018, equation 21
|
||||
const auto deltax = target.meanx - source.meanx;
|
||||
const auto deltay = target.meany - source.meany;
|
||||
target.co_moment =
|
||||
source.co_moment + target.co_moment + deltax * deltay * source.count * target.count / count;
|
||||
target.meanx = meanx;
|
||||
target.meany = meany;
|
||||
target.count = count;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IgnoreNull() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct UDFCovarPopOperation : public UDFCovarOperation {
|
||||
template <class T, class STATE>
|
||||
static void Finalize(STATE &state, T &target, AggregateFinalizeData &finalize_data) {
|
||||
if (state.count == 0) {
|
||||
finalize_data.ReturnNull();
|
||||
} else {
|
||||
target = state.co_moment / state.count;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// UDFSum function based on "src/function/aggregate/distributive/sum.cpp"
|
||||
|
||||
//------------------ UDFSum --------------------------------//
|
||||
struct UDFSum {
|
||||
typedef struct {
|
||||
double value;
|
||||
bool isset;
|
||||
} sum_state_t;
|
||||
|
||||
template <class STATE>
|
||||
static idx_t StateSize(const AggregateFunction &function) {
|
||||
return sizeof(STATE);
|
||||
}
|
||||
|
||||
template <class STATE>
|
||||
static void Initialize(const AggregateFunction &function, data_ptr_t state) {
|
||||
((STATE *)state)->value = 0;
|
||||
((STATE *)state)->isset = false;
|
||||
}
|
||||
|
||||
template <class INPUT_TYPE, class STATE>
|
||||
static void Operation(STATE *state, AggregateInputData &, const INPUT_TYPE *input, idx_t idx) {
|
||||
state->isset = true;
|
||||
state->value += input[idx];
|
||||
}
|
||||
|
||||
template <class INPUT_TYPE, class STATE>
|
||||
static void ConstantOperation(STATE *state, AggregateInputData &, const INPUT_TYPE *input, idx_t count) {
|
||||
state->isset = true;
|
||||
state->value += (INPUT_TYPE)input[0] * (INPUT_TYPE)count;
|
||||
}
|
||||
|
||||
template <class STATE_TYPE, class INPUT_TYPE>
|
||||
static void Update(Vector inputs[], AggregateInputData &aggr_input_data, idx_t input_count, Vector &states,
|
||||
idx_t count) {
|
||||
D_ASSERT(input_count == 1);
|
||||
|
||||
if (inputs[0].GetVectorType() == VectorType::CONSTANT_VECTOR &&
|
||||
states.GetVectorType() == VectorType::CONSTANT_VECTOR) {
|
||||
if (ConstantVector::IsNull(inputs[0])) {
|
||||
// constant NULL input in function that ignores NULL values
|
||||
return;
|
||||
}
|
||||
// regular constant: get first state
|
||||
auto idata = ConstantVector::GetData<INPUT_TYPE>(inputs[0]);
|
||||
auto sdata = ConstantVector::GetData<STATE_TYPE *>(states);
|
||||
UDFSum::ConstantOperation<INPUT_TYPE, STATE_TYPE>(*sdata, aggr_input_data, idata, count);
|
||||
} else {
|
||||
inputs[0].Flatten(input_count);
|
||||
auto idata = FlatVector::GetData<INPUT_TYPE>(inputs[0]);
|
||||
auto sdata = FlatVector::GetData<STATE_TYPE *>(states);
|
||||
auto mask = FlatVector::Validity(inputs[0]);
|
||||
if (!mask.AllValid()) {
|
||||
// potential NULL values and NULL values are ignored
|
||||
for (idx_t i = 0; i < count; i++) {
|
||||
if (mask.RowIsValid(i)) {
|
||||
UDFSum::Operation<INPUT_TYPE, STATE_TYPE>(sdata[i], aggr_input_data, idata, i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// quick path: no NULL values or NULL values are not ignored
|
||||
for (idx_t i = 0; i < count; i++) {
|
||||
UDFSum::Operation<INPUT_TYPE, STATE_TYPE>(sdata[i], aggr_input_data, idata, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class STATE_TYPE, class INPUT_TYPE>
|
||||
static void SimpleUpdate(Vector inputs[], AggregateInputData &aggr_input_data, idx_t input_count, data_ptr_t state,
|
||||
idx_t count) {
|
||||
D_ASSERT(input_count == 1);
|
||||
switch (inputs[0].GetVectorType()) {
|
||||
case VectorType::CONSTANT_VECTOR: {
|
||||
if (ConstantVector::IsNull(inputs[0])) {
|
||||
return;
|
||||
}
|
||||
auto idata = ConstantVector::GetData<INPUT_TYPE>(inputs[0]);
|
||||
UDFSum::ConstantOperation<INPUT_TYPE, STATE_TYPE>((STATE_TYPE *)state, aggr_input_data, idata, count);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
inputs[0].Flatten(count);
|
||||
auto idata = FlatVector::GetData<INPUT_TYPE>(inputs[0]);
|
||||
auto &mask = FlatVector::Validity(inputs[0]);
|
||||
if (!mask.AllValid()) {
|
||||
// potential NULL values and NULL values are ignored
|
||||
for (idx_t i = 0; i < count; i++) {
|
||||
if (mask.RowIsValid(i)) {
|
||||
UDFSum::Operation<INPUT_TYPE, STATE_TYPE>((STATE_TYPE *)state, aggr_input_data, idata, i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// quick path: no NULL values or NULL values are not ignored
|
||||
for (idx_t i = 0; i < count; i++) {
|
||||
UDFSum::Operation<INPUT_TYPE, STATE_TYPE>((STATE_TYPE *)state, aggr_input_data, idata, i);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class STATE_TYPE>
|
||||
static void Combine(Vector &source, Vector &target, AggregateInputData &, idx_t count) {
|
||||
D_ASSERT(source.GetType().id() == LogicalTypeId::POINTER && target.GetType().id() == LogicalTypeId::POINTER);
|
||||
auto sdata = FlatVector::GetData<const STATE_TYPE *>(source);
|
||||
auto tdata = FlatVector::GetData<STATE_TYPE *>(target);
|
||||
// OP::template Combine<STATE_TYPE, OP>(*sdata[i], tdata[i]);
|
||||
for (idx_t i = 0; i < count; i++) {
|
||||
if (!sdata[i]->isset) {
|
||||
// source is NULL, nothing to do
|
||||
return;
|
||||
}
|
||||
if (!tdata[i]->isset) {
|
||||
// target is NULL, use source value directly
|
||||
*tdata[i] = *sdata[i];
|
||||
} else {
|
||||
// else perform the operation
|
||||
tdata[i]->value += sdata[i]->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class STATE_TYPE, class RESULT_TYPE>
|
||||
static void Finalize(Vector &states, AggregateInputData &, Vector &result, idx_t count, idx_t offset) {
|
||||
if (states.GetVectorType() == VectorType::CONSTANT_VECTOR) {
|
||||
result.SetVectorType(VectorType::CONSTANT_VECTOR);
|
||||
|
||||
auto sdata = ConstantVector::GetData<STATE_TYPE *>(states);
|
||||
auto rdata = ConstantVector::GetData<RESULT_TYPE>(result);
|
||||
UDFSum::Finalize<RESULT_TYPE, STATE_TYPE>(result, *sdata, rdata, ConstantVector::Validity(result), 0);
|
||||
} else {
|
||||
D_ASSERT(states.GetVectorType() == VectorType::FLAT_VECTOR);
|
||||
result.SetVectorType(VectorType::FLAT_VECTOR);
|
||||
|
||||
auto sdata = FlatVector::GetData<STATE_TYPE *>(states);
|
||||
auto rdata = FlatVector::GetData<RESULT_TYPE>(result);
|
||||
for (idx_t i = 0; i < count; i++) {
|
||||
UDFSum::Finalize<RESULT_TYPE, STATE_TYPE>(result, sdata[i], rdata, FlatVector::Validity(result),
|
||||
i + offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class T, class STATE>
|
||||
static void Finalize(Vector &result, STATE *state, T *target, ValidityMask &mask, idx_t idx) {
|
||||
if (!state->isset) {
|
||||
mask.SetInvalid(idx);
|
||||
} else {
|
||||
target[idx] = state->value;
|
||||
}
|
||||
}
|
||||
}; // end UDFSum
|
||||
|
||||
} // namespace duckdb
|
||||
12
external/duckdb/test/appender/CMakeLists.txt
vendored
Normal file
12
external/duckdb/test/appender/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
add_library_unity(
|
||||
test_appender
|
||||
OBJECT
|
||||
test_appender_abort.cpp
|
||||
test_appender.cpp
|
||||
test_appender_query.cpp
|
||||
test_concurrent_append.cpp
|
||||
test_appender_transactions.cpp
|
||||
test_nested_appender.cpp)
|
||||
set(ALL_OBJECT_FILES
|
||||
${ALL_OBJECT_FILES} $<TARGET_OBJECTS:test_appender>
|
||||
PARENT_SCOPE)
|
||||
779
external/duckdb/test/appender/test_appender.cpp
vendored
Normal file
779
external/duckdb/test/appender/test_appender.cpp
vendored
Normal file
@@ -0,0 +1,779 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/main/appender.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/common/types/date.hpp"
|
||||
#include "duckdb/common/types/time.hpp"
|
||||
#include "duckdb/common/types/timestamp.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Basic appender tests", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
// create a table to append to
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
|
||||
|
||||
// append a bunch of values
|
||||
{
|
||||
Appender appender(con, "integers");
|
||||
for (idx_t i = 0; i < 2000; i++) {
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
appender.EndRow();
|
||||
}
|
||||
appender.Close();
|
||||
}
|
||||
|
||||
con.Query("BEGIN TRANSACTION");
|
||||
|
||||
// check that the values have been added to the database
|
||||
result = con.Query("SELECT SUM(i) FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {2000}));
|
||||
|
||||
// test a rollback of the appender
|
||||
{
|
||||
Appender appender2(con, "integers");
|
||||
// now append a bunch of values
|
||||
for (idx_t i = 0; i < 2000; i++) {
|
||||
appender2.BeginRow();
|
||||
appender2.Append<int32_t>(1);
|
||||
appender2.EndRow();
|
||||
}
|
||||
appender2.Close();
|
||||
}
|
||||
con.Query("ROLLBACK");
|
||||
|
||||
// the data in the database should not be changed
|
||||
result = con.Query("SELECT SUM(i) FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {2000}));
|
||||
|
||||
// test different types
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE vals(i TINYINT, j SMALLINT, k BIGINT, l VARCHAR, m DECIMAL)"));
|
||||
|
||||
// now append a bunch of values
|
||||
{
|
||||
Appender appender(con, "vals");
|
||||
|
||||
for (idx_t i = 0; i < 2000; i++) {
|
||||
appender.BeginRow();
|
||||
appender.Append<int8_t>(1);
|
||||
appender.Append<int16_t>(1);
|
||||
appender.Append<int64_t>(1);
|
||||
appender.Append<const char *>("hello");
|
||||
appender.Append<double>(3.33);
|
||||
appender.EndRow();
|
||||
}
|
||||
}
|
||||
|
||||
// check that the values have been added to the database
|
||||
result = con.Query("SELECT l, SUM(k) FROM vals GROUP BY l");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"hello"}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {2000}));
|
||||
|
||||
// now test various error conditions
|
||||
// too few values per row
|
||||
{
|
||||
Appender appender(con, "integers");
|
||||
appender.BeginRow();
|
||||
REQUIRE_THROWS(appender.EndRow());
|
||||
}
|
||||
// too many values per row
|
||||
{
|
||||
Appender appender(con, "integers");
|
||||
appender.BeginRow();
|
||||
appender.Append<Value>(Value::INTEGER(2000));
|
||||
REQUIRE_THROWS(appender.Append<Value>(Value::INTEGER(2000)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test AppendRow", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
// create a table to append to
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
|
||||
|
||||
// append a bunch of values
|
||||
{
|
||||
Appender appender(con, "integers");
|
||||
for (idx_t i = 0; i < 2000; i++) {
|
||||
appender.AppendRow(1);
|
||||
}
|
||||
appender.Close();
|
||||
}
|
||||
|
||||
// check that the values have been added to the database
|
||||
result = con.Query("SELECT SUM(i) FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {2000}));
|
||||
|
||||
{
|
||||
Appender appender(con, "integers");
|
||||
// test wrong types in append row
|
||||
REQUIRE_THROWS(appender.AppendRow("hello"));
|
||||
}
|
||||
|
||||
// test different types
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE vals(i TINYINT, j SMALLINT, k BIGINT, l VARCHAR, m DECIMAL)"));
|
||||
// now append a bunch of values
|
||||
{
|
||||
Appender appender(con, "vals");
|
||||
for (idx_t i = 0; i < 2000; i++) {
|
||||
appender.AppendRow(1, 1, 1, "hello", 3.33);
|
||||
// append null values
|
||||
appender.AppendRow(nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
result = con.Query("SELECT COUNT(*), COUNT(i), COUNT(j), COUNT(k), COUNT(l), COUNT(m) FROM vals");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {4000}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {2000}));
|
||||
REQUIRE(CHECK_COLUMN(result, 2, {2000}));
|
||||
REQUIRE(CHECK_COLUMN(result, 3, {2000}));
|
||||
REQUIRE(CHECK_COLUMN(result, 4, {2000}));
|
||||
REQUIRE(CHECK_COLUMN(result, 5, {2000}));
|
||||
|
||||
// check that the values have been added to the database
|
||||
result = con.Query("SELECT l, SUM(k) FROM vals WHERE i IS NOT NULL GROUP BY l");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"hello"}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {2000}));
|
||||
|
||||
// test dates and times
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE dates(d DATE, t TIME, ts TIMESTAMP)"));
|
||||
// now append a bunch of values
|
||||
{
|
||||
Appender appender(con, "dates");
|
||||
appender.AppendRow(Value::DATE(1992, 1, 1), Value::TIME(1, 1, 1, 0), Value::TIMESTAMP(1992, 1, 1, 1, 1, 1, 0));
|
||||
}
|
||||
result = con.Query("SELECT * FROM dates");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::DATE(1992, 1, 1)}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {Value::TIME(1, 1, 1, 0)}));
|
||||
REQUIRE(CHECK_COLUMN(result, 2, {Value::TIMESTAMP(1992, 1, 1, 1, 1, 1, 0)}));
|
||||
|
||||
// test dates and times without value append
|
||||
REQUIRE_NO_FAIL(con.Query("DELETE FROM dates"));
|
||||
// now append a bunch of values
|
||||
{
|
||||
Appender appender(con, "dates");
|
||||
appender.AppendRow(Date::FromDate(1992, 1, 1), Time::FromTime(1, 1, 1, 0),
|
||||
Timestamp::FromDatetime(Date::FromDate(1992, 1, 1), Time::FromTime(1, 1, 1, 0)));
|
||||
}
|
||||
result = con.Query("SELECT * FROM dates");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::DATE(1992, 1, 1)}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {Value::TIME(1, 1, 1, 0)}));
|
||||
REQUIRE(CHECK_COLUMN(result, 2, {Value::TIMESTAMP(1992, 1, 1, 1, 1, 1, 0)}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test appender with generated column", "[appender]") {
|
||||
DuckDB db(nullptr); // Create an in-memory DuckDB database
|
||||
Connection con(db); // Create a connection to the database
|
||||
|
||||
SECTION("Insert into table with generated column first") {
|
||||
// Try to create a table with a generated column
|
||||
REQUIRE_NOTHROW(con.Query(R"(
|
||||
CREATE TABLE tbl (
|
||||
b VARCHAR GENERATED ALWAYS AS (a),
|
||||
a VARCHAR
|
||||
)
|
||||
)"));
|
||||
|
||||
Appender appender(con, "tbl");
|
||||
REQUIRE_NOTHROW(appender.BeginRow());
|
||||
REQUIRE_NOTHROW(appender.Append("a"));
|
||||
|
||||
// Column 'b' is generated from 'a', so it does not need to be explicitly appended
|
||||
// End the row
|
||||
REQUIRE_NOTHROW(appender.EndRow());
|
||||
|
||||
// Close the appender
|
||||
REQUIRE_NOTHROW(appender.Close());
|
||||
|
||||
// Query the table to verify that the row was inserted correctly
|
||||
auto result = con.Query("SELECT * FROM tbl");
|
||||
REQUIRE_NO_FAIL(*result);
|
||||
|
||||
// Check that the column 'a' contains "a" and 'b' contains the generated value "a"
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value("a")}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {Value("a")}));
|
||||
}
|
||||
|
||||
SECTION("Insert into table with generated column second") {
|
||||
// Try to create a table with a generated column
|
||||
REQUIRE_NOTHROW(con.Query(R"(
|
||||
CREATE TABLE tbl (
|
||||
a VARCHAR,
|
||||
b VARCHAR GENERATED ALWAYS AS (a)
|
||||
)
|
||||
)"));
|
||||
|
||||
Appender appender(con, "tbl");
|
||||
REQUIRE_NOTHROW(appender.BeginRow());
|
||||
REQUIRE_NOTHROW(appender.Append("a"));
|
||||
|
||||
// Column 'b' is generated from 'a', so it does not need to be explicitly appended
|
||||
// End the row
|
||||
REQUIRE_NOTHROW(appender.EndRow());
|
||||
|
||||
// Close the appender
|
||||
REQUIRE_NOTHROW(appender.Close());
|
||||
|
||||
// Query the table to verify that the row was inserted correctly
|
||||
auto result = con.Query("SELECT * FROM tbl");
|
||||
REQUIRE_NO_FAIL(*result);
|
||||
|
||||
// Check that the column 'a' contains "a" and 'b' contains the generated value "a"
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value("a")}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {Value("a")}));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test default value appender", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
SECTION("Insert DEFAULT into default column") {
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i iNTEGER, j INTEGER DEFAULT 5)"));
|
||||
{
|
||||
Appender appender(con, "integers");
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(2);
|
||||
appender.AppendDefault();
|
||||
REQUIRE_NOTHROW(appender.EndRow());
|
||||
REQUIRE_NOTHROW(appender.Close());
|
||||
}
|
||||
result = con.Query("SELECT * FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::INTEGER(2)}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {Value::INTEGER(5)}));
|
||||
}
|
||||
|
||||
SECTION("Insert DEFAULT into non-default column") {
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i iNTEGER, j INTEGER DEFAULT 5)"));
|
||||
{
|
||||
Appender appender(con, "integers");
|
||||
appender.BeginRow();
|
||||
// 'i' does not have a DEFAULT value, so it gets NULL
|
||||
REQUIRE_NOTHROW(appender.AppendDefault());
|
||||
REQUIRE_NOTHROW(appender.AppendDefault());
|
||||
REQUIRE_NOTHROW(appender.EndRow());
|
||||
REQUIRE_NOTHROW(appender.Close());
|
||||
}
|
||||
result = con.Query("SELECT * FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value(LogicalTypeId::INTEGER)}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {Value::INTEGER(5)}));
|
||||
}
|
||||
|
||||
SECTION("Insert DEFAULT into column that can't be NULL") {
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i integer NOT NULL)"));
|
||||
{
|
||||
Appender appender(con, "integers");
|
||||
appender.BeginRow();
|
||||
REQUIRE_NOTHROW(appender.AppendDefault());
|
||||
REQUIRE_NOTHROW(appender.EndRow());
|
||||
// NOT NULL constraint failed
|
||||
REQUIRE_THROWS(appender.Close());
|
||||
}
|
||||
result = con.Query("SELECT * FROM integers");
|
||||
auto chunk = result->Fetch();
|
||||
REQUIRE(chunk == nullptr);
|
||||
}
|
||||
|
||||
SECTION("DEFAULT nextval('seq')") {
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq"));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i iNTEGER, j INTEGER DEFAULT nextval('seq'))"));
|
||||
{
|
||||
Appender appender(con, "integers");
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
|
||||
// NOT_IMPLEMENTED: Non-foldable default values are not supported currently
|
||||
REQUIRE_THROWS(appender.AppendDefault());
|
||||
REQUIRE_THROWS(appender.EndRow());
|
||||
REQUIRE_NOTHROW(appender.Close());
|
||||
}
|
||||
// result = con.Query("SELECT * FROM integers");
|
||||
// REQUIRE(CHECK_COLUMN(result, 0, {Value::INTEGER(1)}));
|
||||
// REQUIRE(CHECK_COLUMN(result, 1, {Value::INTEGER(1)}));
|
||||
}
|
||||
|
||||
SECTION("DEFAULT random()") {
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i iNTEGER, j DOUBLE DEFAULT random())"));
|
||||
con.Query("select setseed(0.42)");
|
||||
{
|
||||
Appender appender(con, "integers");
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
// NOT_IMPLEMENTED: Non-foldable default values are not supported currently
|
||||
REQUIRE_THROWS(appender.AppendDefault());
|
||||
REQUIRE_THROWS(appender.EndRow());
|
||||
REQUIRE_NOTHROW(appender.Close());
|
||||
}
|
||||
// result = con.Query("SELECT * FROM integers");
|
||||
// REQUIRE(CHECK_COLUMN(result, 0, {Value::INTEGER(1)}));
|
||||
// REQUIRE(CHECK_COLUMN(result, 1, {Value::DOUBLE(0.4729174713138491)}));
|
||||
}
|
||||
|
||||
SECTION("DEFAULT now()") {
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i iNTEGER, j TIMESTAMPTZ DEFAULT now())"));
|
||||
con.Query("BEGIN TRANSACTION");
|
||||
result = con.Query("select now()");
|
||||
auto &materialized_result = result->Cast<MaterializedQueryResult>();
|
||||
auto current_time = materialized_result.GetValue(0, 0);
|
||||
{
|
||||
Appender appender(con, "integers");
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
REQUIRE_NOTHROW(appender.AppendDefault());
|
||||
REQUIRE_NOTHROW(appender.EndRow());
|
||||
REQUIRE_NOTHROW(appender.Close());
|
||||
}
|
||||
result = con.Query("SELECT * FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::INTEGER(1)}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {current_time}));
|
||||
con.Query("COMMIT");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test incorrect usage of appender", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
// create a table to append to
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER, j INTEGER)"));
|
||||
|
||||
// append a bunch of values
|
||||
{
|
||||
Appender appender(con, "integers");
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
// call EndRow before all rows have been appended results in an exception
|
||||
REQUIRE_THROWS(appender.EndRow());
|
||||
// we can still close the appender
|
||||
REQUIRE_NOTHROW(appender.Close());
|
||||
}
|
||||
{
|
||||
Appender appender(con, "integers");
|
||||
// flushing results in the same error
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
REQUIRE_THROWS(appender.Flush());
|
||||
// we can still close the appender
|
||||
REQUIRE_NOTHROW(appender.Close());
|
||||
}
|
||||
{
|
||||
// we get the same exception when calling AppendRow with an incorrect number of arguments
|
||||
Appender appender(con, "integers");
|
||||
REQUIRE_THROWS(appender.AppendRow(1));
|
||||
// we can still close the appender
|
||||
REQUIRE_NOTHROW(appender.Close());
|
||||
}
|
||||
{
|
||||
// we can flush an empty appender
|
||||
Appender appender(con, "integers");
|
||||
REQUIRE_NOTHROW(appender.Flush());
|
||||
REQUIRE_NOTHROW(appender.Flush());
|
||||
REQUIRE_NOTHROW(appender.Flush());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test appending NaN and INF using appender", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE doubles(d DOUBLE, f REAL)"));
|
||||
|
||||
// appending NAN or INF succeeds
|
||||
Appender appender(con, "doubles");
|
||||
appender.AppendRow(1e308 + 1e308, 1e38f * 1e38f);
|
||||
appender.AppendRow(NAN, NAN);
|
||||
appender.Close();
|
||||
|
||||
result = con.Query("SELECT * FROM doubles");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {Value::DOUBLE(1e308 + 1e308), Value::DOUBLE(NAN)}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {Value::FLOAT(1e38f * 1e38f), Value::FLOAT(NAN)}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test appender with quotes", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE SCHEMA \"my_schema\""));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE \"my_schema\".\"my_table\"(\"i\" INTEGER)"));
|
||||
|
||||
// append a bunch of values
|
||||
{
|
||||
Appender appender(con, "my_schema", "my_table");
|
||||
appender.AppendRow(1);
|
||||
appender.Close();
|
||||
}
|
||||
result = con.Query("SELECT * FROM my_schema.my_table");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test appender with string lengths", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE my_table (s STRING)"));
|
||||
{
|
||||
Appender appender(con, "my_table");
|
||||
appender.BeginRow();
|
||||
appender.Append("asdf", 3);
|
||||
appender.EndRow();
|
||||
appender.Close();
|
||||
}
|
||||
result = con.Query("SELECT * FROM my_table");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {"asd"}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test various appender types", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE type_table(a BOOL, b UINT8, c UINT16, d UINT32, e UINT64, f FLOAT)"));
|
||||
{
|
||||
Appender appender(con, "type_table");
|
||||
appender.AppendRow(true, uint8_t(1), uint16_t(2), uint32_t(3), uint64_t(4), 5.0f);
|
||||
}
|
||||
result = con.Query("SELECT * FROM type_table");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {true}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {1}));
|
||||
REQUIRE(CHECK_COLUMN(result, 2, {2}));
|
||||
REQUIRE(CHECK_COLUMN(result, 3, {3}));
|
||||
REQUIRE(CHECK_COLUMN(result, 4, {4}));
|
||||
REQUIRE(CHECK_COLUMN(result, 5, {5}));
|
||||
// too many rows
|
||||
{
|
||||
Appender appender(con, "type_table");
|
||||
REQUIRE_THROWS(appender.AppendRow(true, uint8_t(1), uint16_t(2), uint32_t(3), uint64_t(4), 5.0f, nullptr));
|
||||
}
|
||||
{
|
||||
Appender appender(con, "type_table");
|
||||
REQUIRE_THROWS(appender.AppendRow(true, 1, 2, 3, 4, 5, 1));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test alter table in the middle of append", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
// create a table to append to
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER, j INTEGER)"));
|
||||
{
|
||||
// create the appender
|
||||
Appender appender(con, "integers");
|
||||
appender.AppendRow(1, 2);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("ALTER TABLE integers DROP COLUMN i"));
|
||||
REQUIRE_THROWS(appender.Close());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test appending to a different database file", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
auto test_dir = TestDirectoryPath();
|
||||
auto attach_query = "ATTACH '" + test_dir + "/append_to_other.db'";
|
||||
REQUIRE_NO_FAIL(con.Query(attach_query));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE OR REPLACE TABLE append_to_other.tbl(i INTEGER)"));
|
||||
|
||||
Appender appender(con, "append_to_other", "main", "tbl");
|
||||
for (idx_t i = 0; i < 200; i++) {
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(2);
|
||||
appender.EndRow();
|
||||
}
|
||||
appender.Close();
|
||||
|
||||
result = con.Query("SELECT SUM(i) FROM append_to_other.tbl");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {400}));
|
||||
bool failed;
|
||||
|
||||
try {
|
||||
failed = false;
|
||||
Appender appender_invalid(con, "invalid_database", "main", "tbl");
|
||||
} catch (std::exception &ex) {
|
||||
ErrorData error(ex);
|
||||
REQUIRE(error.Message().find("Catalog Error") != std::string::npos);
|
||||
failed = true;
|
||||
}
|
||||
REQUIRE(failed);
|
||||
|
||||
try {
|
||||
failed = false;
|
||||
Appender appender_invalid(con, "append_to_other", "invalid_schema", "tbl");
|
||||
} catch (std::exception &ex) {
|
||||
ErrorData error(ex);
|
||||
REQUIRE(error.Message().find("Catalog Error") != std::string::npos);
|
||||
failed = true;
|
||||
}
|
||||
REQUIRE(failed);
|
||||
|
||||
// Attach as readonly.
|
||||
REQUIRE_NO_FAIL(con.Query("DETACH append_to_other"));
|
||||
REQUIRE_NO_FAIL(con.Query(attach_query + " (readonly)"));
|
||||
|
||||
try {
|
||||
failed = false;
|
||||
Appender appender_readonly(con, "append_to_other", "main", "tbl");
|
||||
} catch (std::exception &ex) {
|
||||
ErrorData error(ex);
|
||||
REQUIRE(error.Message().find("Cannot append to a readonly database") != std::string::npos);
|
||||
failed = true;
|
||||
}
|
||||
REQUIRE(failed);
|
||||
}
|
||||
|
||||
TEST_CASE("Test appending to different database files", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
auto test_dir = TestDirectoryPath();
|
||||
auto attach_db1 = "ATTACH '" + test_dir + "/db1.db'";
|
||||
auto attach_db2 = "ATTACH '" + test_dir + "/db2.db'";
|
||||
REQUIRE_NO_FAIL(con.Query(attach_db1));
|
||||
REQUIRE_NO_FAIL(con.Query(attach_db2));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE OR REPLACE TABLE db1.tbl(i INTEGER)"));
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE OR REPLACE TABLE db2.tbl(i INTEGER)"));
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("START TRANSACTION"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO db1.tbl VALUES (1)"));
|
||||
|
||||
Appender appender(con, "db2", "main", "tbl");
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(2);
|
||||
appender.EndRow();
|
||||
|
||||
bool failed;
|
||||
try {
|
||||
failed = false;
|
||||
appender.Close();
|
||||
} catch (std::exception &ex) {
|
||||
ErrorData error(ex);
|
||||
REQUIRE(error.Message().find("a single transaction can only write to a single attached database") !=
|
||||
std::string::npos);
|
||||
failed = true;
|
||||
}
|
||||
REQUIRE(failed);
|
||||
REQUIRE_NO_FAIL(con.Query("COMMIT TRANSACTION"));
|
||||
}
|
||||
|
||||
void setDataChunkInt32(DataChunk &chunk, idx_t col_idx, idx_t row_idx, int32_t value) {
|
||||
auto &col = chunk.data[col_idx];
|
||||
auto data = FlatVector::GetData<int32_t>(col);
|
||||
data[row_idx] = value;
|
||||
}
|
||||
|
||||
TEST_CASE("Test appending with an active default column", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(
|
||||
con.Query("CREATE OR REPLACE TABLE tbl (i INT DEFAULT 4, j INT, k INT DEFAULT 30, l AS (random()))"));
|
||||
Appender appender(con, "main", "tbl");
|
||||
appender.AddColumn("i");
|
||||
|
||||
DataChunk chunk;
|
||||
const duckdb::vector<LogicalType> types = {LogicalType::INTEGER};
|
||||
chunk.Initialize(*con.context, types);
|
||||
|
||||
setDataChunkInt32(chunk, 0, 0, 42);
|
||||
setDataChunkInt32(chunk, 0, 1, 43);
|
||||
|
||||
chunk.SetCardinality(2);
|
||||
appender.AppendDataChunk(chunk);
|
||||
appender.Close();
|
||||
|
||||
result = con.Query("SELECT i, j, k, l IS NOT NULL FROM tbl");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42, 43}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {Value(), Value()}));
|
||||
REQUIRE(CHECK_COLUMN(result, 2, {30, 30}));
|
||||
REQUIRE(CHECK_COLUMN(result, 3, {true, true}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test appending with two active normal columns", "[appender]") {
|
||||
#if STANDARD_VECTOR_SIZE != 2048
|
||||
return;
|
||||
#endif
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(
|
||||
con.Query("CREATE OR REPLACE TABLE tbl (i INT DEFAULT 4, j INT, k INT DEFAULT 30, l AS (2 * j), m INT)"));
|
||||
Appender appender(con, "main", "tbl");
|
||||
appender.AddColumn("j");
|
||||
appender.AddColumn("m");
|
||||
|
||||
DataChunk chunk;
|
||||
const duckdb::vector<LogicalType> types = {LogicalType::INTEGER, LogicalType::INTEGER};
|
||||
chunk.Initialize(*con.context, types);
|
||||
|
||||
for (idx_t i = 0; i < 4; i++) {
|
||||
for (idx_t j = 0; j < 2; j++) {
|
||||
auto &col = chunk.data[j];
|
||||
auto col_data = FlatVector::GetData<int32_t>(col);
|
||||
|
||||
auto offset = i * STANDARD_VECTOR_SIZE;
|
||||
for (idx_t k = 0; k < STANDARD_VECTOR_SIZE; k++) {
|
||||
col_data[k] = int32_t(offset + k);
|
||||
}
|
||||
}
|
||||
chunk.SetCardinality(STANDARD_VECTOR_SIZE);
|
||||
appender.AppendDataChunk(chunk);
|
||||
chunk.Reset();
|
||||
}
|
||||
appender.Close();
|
||||
|
||||
result = con.Query("SELECT SUM(i), SUM(j), SUM(k), SUM(l), SUM(m) FROM tbl");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {32768}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {33550336}));
|
||||
REQUIRE(CHECK_COLUMN(result, 2, {245760}));
|
||||
REQUIRE(CHECK_COLUMN(result, 3, {67100672}));
|
||||
REQUIRE(CHECK_COLUMN(result, 4, {33550336}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test changing the active column configuration", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE OR REPLACE TABLE tbl (i INT DEFAULT 4, j INT, k INT DEFAULT 30)"));
|
||||
Appender appender(con, "main", "tbl");
|
||||
|
||||
// Create a data chunk with all three columns filled.
|
||||
|
||||
DataChunk chunk_all_types;
|
||||
const duckdb::vector<LogicalType> all_types = {LogicalType::INTEGER, LogicalType::INTEGER, LogicalType::INTEGER};
|
||||
chunk_all_types.Initialize(*con.context, all_types);
|
||||
|
||||
setDataChunkInt32(chunk_all_types, 0, 0, 42);
|
||||
setDataChunkInt32(chunk_all_types, 1, 0, 111);
|
||||
setDataChunkInt32(chunk_all_types, 2, 0, 50);
|
||||
|
||||
chunk_all_types.SetCardinality(1);
|
||||
appender.AppendDataChunk(chunk_all_types);
|
||||
|
||||
appender.AddColumn("j");
|
||||
appender.AddColumn("i");
|
||||
|
||||
DataChunk chunk_j_i;
|
||||
const duckdb::vector<LogicalType> types_j_i = {LogicalType::INTEGER, LogicalType::INTEGER};
|
||||
chunk_j_i.Initialize(*con.context, types_j_i);
|
||||
|
||||
setDataChunkInt32(chunk_j_i, 0, 0, 111);
|
||||
setDataChunkInt32(chunk_j_i, 1, 0, 42);
|
||||
|
||||
chunk_j_i.SetCardinality(1);
|
||||
appender.AppendDataChunk(chunk_j_i);
|
||||
|
||||
appender.ClearColumns();
|
||||
appender.AppendDataChunk(chunk_all_types);
|
||||
|
||||
appender.AddColumn("k");
|
||||
|
||||
DataChunk chunk_k;
|
||||
const duckdb::vector<LogicalType> types_k = {LogicalType::INTEGER};
|
||||
chunk_k.Initialize(*con.context, types_k);
|
||||
|
||||
setDataChunkInt32(chunk_k, 0, 0, 50);
|
||||
|
||||
chunk_k.SetCardinality(1);
|
||||
appender.AppendDataChunk(chunk_k);
|
||||
appender.Close();
|
||||
|
||||
result = con.Query("SELECT i, j, k FROM tbl");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {42, 42, 42, 4}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {111, 111, 111, Value()}));
|
||||
REQUIRE(CHECK_COLUMN(result, 2, {50, 30, 50, 50}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test edge cases for the active column configuration", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE OR REPLACE TABLE tbl (i INT DEFAULT 4, j INT, k INT DEFAULT 30, l AS (2 * j))"));
|
||||
Appender appender(con, "main", "tbl");
|
||||
|
||||
appender.AddColumn("i");
|
||||
appender.AddColumn("j");
|
||||
|
||||
bool failed;
|
||||
// Cannot add columns that do not exist.
|
||||
try {
|
||||
failed = false;
|
||||
appender.AddColumn("hello");
|
||||
} catch (std::exception &ex) {
|
||||
ErrorData error(ex);
|
||||
REQUIRE(error.Message().find("the column must exist in the table") != std::string::npos);
|
||||
failed = true;
|
||||
}
|
||||
REQUIRE(failed);
|
||||
|
||||
// Cannot add generated columns.
|
||||
try {
|
||||
failed = false;
|
||||
appender.AddColumn("l");
|
||||
} catch (std::exception &ex) {
|
||||
ErrorData error(ex);
|
||||
REQUIRE(error.Message().find("cannot add a generated column to the appender") != std::string::npos);
|
||||
failed = true;
|
||||
}
|
||||
REQUIRE(failed);
|
||||
|
||||
// Cannot add the same column twice.
|
||||
try {
|
||||
failed = false;
|
||||
appender.AddColumn("j");
|
||||
} catch (std::exception &ex) {
|
||||
ErrorData error(ex);
|
||||
REQUIRE(error.Message().find("cannot add the same column twice") != std::string::npos);
|
||||
failed = true;
|
||||
}
|
||||
REQUIRE(failed);
|
||||
appender.Close();
|
||||
}
|
||||
|
||||
TEST_CASE("Test appending rows with an active column list", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(
|
||||
con.Query("CREATE OR REPLACE TABLE tbl (i INT DEFAULT 4, j INT, k INT DEFAULT 30, l AS (2 * j), m INT)"));
|
||||
Appender appender(con, "main", "tbl");
|
||||
appender.AddColumn("j");
|
||||
appender.AddColumn("m");
|
||||
|
||||
appender.Append(42);
|
||||
appender.Append(43);
|
||||
appender.EndRow();
|
||||
|
||||
appender.Append(Value());
|
||||
appender.Append(44);
|
||||
appender.EndRow();
|
||||
appender.Close();
|
||||
|
||||
result = con.Query("SELECT i, j, k, l, m FROM tbl");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {4, 4}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {42, Value()}));
|
||||
REQUIRE(CHECK_COLUMN(result, 2, {30, 30}));
|
||||
REQUIRE(CHECK_COLUMN(result, 3, {84, Value()}));
|
||||
REQUIRE(CHECK_COLUMN(result, 4, {43, 44}));
|
||||
}
|
||||
27
external/duckdb/test/appender/test_appender_abort.cpp
vendored
Normal file
27
external/duckdb/test/appender/test_appender_abort.cpp
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/main/appender.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Abort appender due to primary key conflict", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER PRIMARY KEY)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1)"));
|
||||
|
||||
Appender appender(con, "integers");
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
appender.EndRow();
|
||||
// this should fail!
|
||||
REQUIRE_THROWS(appender.Flush());
|
||||
|
||||
result = con.Query("SELECT * FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
}
|
||||
111
external/duckdb/test/appender/test_appender_query.cpp
vendored
Normal file
111
external/duckdb/test/appender/test_appender_query.cpp
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/main/appender.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test upserting through the query appender", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE tbl(i INTEGER PRIMARY KEY, value VARCHAR)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO tbl VALUES (1, 'hello')"));
|
||||
|
||||
duckdb::vector<LogicalType> types;
|
||||
types.push_back(LogicalType::INTEGER);
|
||||
types.push_back(LogicalType::VARCHAR);
|
||||
|
||||
QueryAppender appender(con, "INSERT OR REPLACE INTO tbl FROM appended_data", types);
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
appender.Append("world");
|
||||
appender.EndRow();
|
||||
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(2);
|
||||
appender.Append("again");
|
||||
appender.EndRow();
|
||||
|
||||
// this should succeed
|
||||
appender.Flush();
|
||||
|
||||
result = con.Query("SELECT * FROM tbl ORDER BY i");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1, 2}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {"world", "again"}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test custom column + table names in the query appender", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE tbl(i INTEGER PRIMARY KEY, value VARCHAR)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO tbl VALUES (1, 'hello')"));
|
||||
|
||||
duckdb::vector<LogicalType> types;
|
||||
types.push_back(LogicalType::INTEGER);
|
||||
types.push_back(LogicalType::VARCHAR);
|
||||
|
||||
duckdb::vector<string> column_names;
|
||||
column_names.push_back("i");
|
||||
column_names.push_back("value");
|
||||
|
||||
QueryAppender appender(
|
||||
con, "MERGE INTO tbl USING my_appended_data USING (i) WHEN MATCHED THEN UPDATE WHEN NOT MATCHED THEN INSERT",
|
||||
types, column_names, "my_appended_data");
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
appender.Append("world");
|
||||
appender.EndRow();
|
||||
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(2);
|
||||
appender.Append("again");
|
||||
appender.EndRow();
|
||||
|
||||
// this should succeed
|
||||
appender.Flush();
|
||||
|
||||
result = con.Query("SELECT * FROM tbl ORDER BY i");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1, 2}));
|
||||
REQUIRE(CHECK_COLUMN(result, 1, {"world", "again"}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test various error conditions of the query appender", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE tbl(i INTEGER PRIMARY KEY, value VARCHAR)"));
|
||||
REQUIRE_NO_FAIL(con.Query("INSERT INTO tbl VALUES (1, 'hello')"));
|
||||
|
||||
duckdb::vector<LogicalType> types;
|
||||
types.push_back(LogicalType::INTEGER);
|
||||
types.push_back(LogicalType::VARCHAR);
|
||||
|
||||
SECTION("Unsupported query type") {
|
||||
QueryAppender appender(con, "SELECT * FROM appended_data", types);
|
||||
appender.AppendRow(1, "world");
|
||||
REQUIRE_THROWS(appender.Flush());
|
||||
}
|
||||
SECTION("Multiple queries") {
|
||||
QueryAppender appender(con, "INSERT INTO tbl FROM appended_data; INSERT INTO tbl FROM appended_data;", types);
|
||||
appender.AppendRow(1, "world");
|
||||
REQUIRE_THROWS(appender.Flush());
|
||||
}
|
||||
SECTION("Parser error in query") {
|
||||
QueryAppender appender(con, "INSERT INTO tbl FROM", types);
|
||||
appender.AppendRow(1, "world");
|
||||
REQUIRE_THROWS(appender.Flush());
|
||||
}
|
||||
SECTION("Run-time error in query") {
|
||||
QueryAppender appender(con, "INSERT INTO tbl SELECT col2::INTEGER, col2 FROM appended_data", types);
|
||||
appender.AppendRow(1, "world");
|
||||
REQUIRE_THROWS(appender.Flush());
|
||||
}
|
||||
}
|
||||
39
external/duckdb/test/appender/test_appender_transactions.cpp
vendored
Normal file
39
external/duckdb/test/appender/test_appender_transactions.cpp
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/main/appender.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test the way appenders interact with transactions", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER PRIMARY KEY)"));
|
||||
|
||||
// begin a transaction manually
|
||||
REQUIRE_NO_FAIL(con.Query("BEGIN TRANSACTION"));
|
||||
|
||||
// append a value to the table
|
||||
Appender appender(con, "integers");
|
||||
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
appender.EndRow();
|
||||
|
||||
appender.Close();
|
||||
|
||||
// we can select the value now
|
||||
result = con.Query("SELECT * FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {1}));
|
||||
|
||||
// rolling back cancels the transaction
|
||||
REQUIRE_NO_FAIL(con.Query("ROLLBACK"));
|
||||
|
||||
// now the values are gone
|
||||
result = con.Query("SELECT * FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {}));
|
||||
}
|
||||
55
external/duckdb/test/appender/test_concurrent_append.cpp
vendored
Normal file
55
external/duckdb/test/appender/test_concurrent_append.cpp
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/main/appender.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
atomic<int> finished_threads;
|
||||
|
||||
#define THREAD_COUNT 8
|
||||
#define INSERT_ELEMENTS 2000
|
||||
|
||||
static void append_to_integers(DuckDB *db, size_t threadnr) {
|
||||
Connection con(*db);
|
||||
|
||||
Appender appender(con, "integers");
|
||||
for (size_t i = 0; i < INSERT_ELEMENTS; i++) {
|
||||
appender.BeginRow();
|
||||
appender.Append<int32_t>(1);
|
||||
appender.EndRow();
|
||||
}
|
||||
finished_threads++;
|
||||
while (finished_threads != THREAD_COUNT)
|
||||
;
|
||||
appender.Close();
|
||||
}
|
||||
|
||||
TEST_CASE("Test concurrent appends", "[appender][.]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DBConfig config;
|
||||
config.options.maximum_threads = 1;
|
||||
DuckDB db(nullptr, &config);
|
||||
Connection con(db);
|
||||
|
||||
// create a single table to append to
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER)"));
|
||||
|
||||
finished_threads = 0;
|
||||
|
||||
thread threads[THREAD_COUNT];
|
||||
for (size_t i = 0; i < THREAD_COUNT; i++) {
|
||||
threads[i] = thread(append_to_integers, &db, i);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < THREAD_COUNT; i++) {
|
||||
threads[i].join();
|
||||
}
|
||||
// check how many entries we have
|
||||
result = con.Query("SELECT COUNT(*) FROM integers");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {THREAD_COUNT * INSERT_ELEMENTS}));
|
||||
}
|
||||
59
external/duckdb/test/appender/test_nested_appender.cpp
vendored
Normal file
59
external/duckdb/test/appender/test_nested_appender.cpp
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/main/appender.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test appender with lists", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE lists(i INTEGER[])"));
|
||||
|
||||
// append a value to the table
|
||||
Appender appender(con, "lists");
|
||||
|
||||
auto list_value = Value::LIST(LogicalType::INTEGER, {Value::INTEGER(3)});
|
||||
auto empty_list_value = Value::LIST(LogicalType::INTEGER, duckdb::vector<Value>());
|
||||
|
||||
appender.AppendRow(list_value);
|
||||
appender.AppendRow(empty_list_value);
|
||||
|
||||
appender.Close();
|
||||
|
||||
// we can select the value now
|
||||
result = con.Query("SELECT * FROM lists");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {list_value, empty_list_value}));
|
||||
}
|
||||
|
||||
TEST_CASE("Test appender with nested lists", "[appender]") {
|
||||
duckdb::unique_ptr<QueryResult> result;
|
||||
DuckDB db(nullptr);
|
||||
Connection con(db);
|
||||
|
||||
REQUIRE_NO_FAIL(con.Query("CREATE TABLE lists(i INTEGER[][])"));
|
||||
|
||||
// append a value to the table
|
||||
Appender appender(con, "lists");
|
||||
|
||||
auto int_list_type = LogicalType::LIST(LogicalType::INTEGER);
|
||||
auto nested_int_list_type = LogicalType::LIST(int_list_type);
|
||||
auto list_value =
|
||||
Value::LIST(int_list_type, {Value::LIST(LogicalType::INTEGER, {Value::INTEGER(1)}),
|
||||
Value::LIST(LogicalType::INTEGER, {Value::INTEGER(2), Value::INTEGER(3)})});
|
||||
auto empty_list_value = Value::LIST(int_list_type, duckdb::vector<Value>());
|
||||
auto null_list_value = Value(nested_int_list_type);
|
||||
appender.AppendRow(list_value);
|
||||
appender.AppendRow(empty_list_value);
|
||||
appender.AppendRow(null_list_value);
|
||||
|
||||
appender.Close();
|
||||
|
||||
// we can select the value now
|
||||
result = con.Query("SELECT * FROM lists");
|
||||
REQUIRE(CHECK_COLUMN(result, 0, {list_value, empty_list_value, null_list_value}));
|
||||
}
|
||||
5
external/duckdb/test/arrow/CMakeLists.txt
vendored
Normal file
5
external/duckdb/test/arrow/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
add_library_unity(test_arrow_roundtrip OBJECT arrow_test_helper.cpp
|
||||
arrow_roundtrip.cpp arrow_move_children.cpp)
|
||||
set(ALL_OBJECT_FILES
|
||||
${ALL_OBJECT_FILES} $<TARGET_OBJECTS:test_arrow_roundtrip>
|
||||
PARENT_SCOPE)
|
||||
146
external/duckdb/test/arrow/arrow_move_children.cpp
vendored
Normal file
146
external/duckdb/test/arrow/arrow_move_children.cpp
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
#include "catch.hpp"
|
||||
|
||||
#include "arrow/arrow_test_helper.hpp"
|
||||
#include "duckdb/common/adbc/single_batch_array_stream.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
|
||||
static void EmptyRelease(ArrowArray *array) {
|
||||
for (int64_t i = 0; i < array->n_children; i++) {
|
||||
auto child = array->children[i];
|
||||
if (child->release) {
|
||||
child->release(child);
|
||||
}
|
||||
}
|
||||
array->release = nullptr;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void AssertExpectedResult(ArrowSchema *schema, ArrowArrayWrapper &array, T expected_value, bool is_null = false) {
|
||||
ArrowArrayStream stream;
|
||||
stream.release = nullptr;
|
||||
|
||||
ArrowArray struct_array;
|
||||
struct_array.n_children = 1;
|
||||
ArrowArray *children[1];
|
||||
struct_array.children = (ArrowArray **)&children;
|
||||
struct_array.children[0] = &array.arrow_array;
|
||||
struct_array.length = array.arrow_array.length;
|
||||
struct_array.release = EmptyRelease;
|
||||
struct_array.offset = 0;
|
||||
|
||||
AdbcError unused;
|
||||
(void)duckdb_adbc::BatchToArrayStream(&struct_array, schema, &stream, &unused);
|
||||
|
||||
DuckDB db(nullptr);
|
||||
Connection conn(db);
|
||||
auto params = ArrowTestHelper::ConstructArrowScan(stream);
|
||||
|
||||
auto result = ArrowTestHelper::ScanArrowObject(conn, params);
|
||||
unique_ptr<DataChunk> chunk;
|
||||
while (true) {
|
||||
chunk = result->Fetch();
|
||||
if (!chunk) {
|
||||
break;
|
||||
}
|
||||
REQUIRE(chunk->ColumnCount() == 1);
|
||||
REQUIRE(chunk->size() == STANDARD_VECTOR_SIZE);
|
||||
auto vec = chunk->data[0];
|
||||
for (idx_t i = 0; i < STANDARD_VECTOR_SIZE; i++) {
|
||||
auto value = vec.GetValue(i);
|
||||
auto expected = Value(expected_value);
|
||||
if (is_null) {
|
||||
REQUIRE(value.IsNull());
|
||||
} else {
|
||||
REQUIRE(value == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (schema->release) {
|
||||
schema->release(schema);
|
||||
}
|
||||
}
|
||||
|
||||
vector<ArrowArrayWrapper> FetchChildrenFromArray(shared_ptr<ArrowArrayWrapper> parent) {
|
||||
D_ASSERT(parent->arrow_array.release);
|
||||
vector<ArrowArrayWrapper> children;
|
||||
children.resize(parent->arrow_array.n_children);
|
||||
for (int64_t i = 0; i < parent->arrow_array.n_children; i++) {
|
||||
auto child = parent->arrow_array.children[i];
|
||||
auto &wrapper = children[i];
|
||||
wrapper.arrow_array = *child;
|
||||
// Unset the 'release' method to null for the child inside the parent
|
||||
// to indicate that it has been moved
|
||||
child->release = nullptr;
|
||||
}
|
||||
// Release the parent, should not affect the children
|
||||
parent->arrow_array.release(&parent->arrow_array);
|
||||
return children;
|
||||
}
|
||||
|
||||
// https://arrow.apache.org/docs/format/CDataInterface.html#moving-child-arrays
|
||||
TEST_CASE("Test move children", "[arrow]") {
|
||||
auto query = StringUtil::Format("select 'a', 'this is a long string', 42, true, NULL from range(%d);",
|
||||
STANDARD_VECTOR_SIZE * 2);
|
||||
|
||||
// Create the stream that will produce arrow arrays
|
||||
DuckDB db(nullptr);
|
||||
Connection conn(db);
|
||||
auto initial_result = conn.Query(query);
|
||||
auto client_properties = conn.context->GetClientProperties();
|
||||
auto types = initial_result->types;
|
||||
auto names = initial_result->names;
|
||||
|
||||
// Scan every column
|
||||
ArrowStreamParameters parameters;
|
||||
for (idx_t idx = 0; idx < initial_result->names.size(); idx++) {
|
||||
auto col_idx = idx;
|
||||
auto &name = initial_result->names[idx];
|
||||
if (col_idx != COLUMN_IDENTIFIER_ROW_ID) {
|
||||
parameters.projected_columns.projection_map[idx] = name;
|
||||
parameters.projected_columns.columns.emplace_back(name);
|
||||
}
|
||||
}
|
||||
auto res_names = initial_result->names;
|
||||
auto res_types = initial_result->types;
|
||||
auto res_properties = initial_result->client_properties;
|
||||
|
||||
// Create a test factory and produce a stream from it
|
||||
auto factory = ArrowTestFactory(std::move(types), std::move(names), std::move(initial_result), false,
|
||||
client_properties, *conn.context);
|
||||
auto stream = ArrowTestFactory::CreateStream((uintptr_t)&factory, parameters);
|
||||
|
||||
// For every array, extract the children and scan them
|
||||
while (true) {
|
||||
auto chunk = stream->GetNextChunk();
|
||||
if (!chunk || !chunk->arrow_array.release) {
|
||||
break;
|
||||
}
|
||||
auto children = FetchChildrenFromArray(std::move(chunk));
|
||||
D_ASSERT(children.size() == 5);
|
||||
for (idx_t i = 0; i < children.size(); i++) {
|
||||
ArrowSchema schema;
|
||||
vector<LogicalType> single_type {res_types[i]};
|
||||
vector<string> single_name {res_names[i]};
|
||||
ArrowConverter::ToArrowSchema(&schema, single_type, single_name, res_properties);
|
||||
|
||||
if (i == 0) {
|
||||
AssertExpectedResult<string>(&schema, children[i], "a");
|
||||
} else if (i == 1) {
|
||||
AssertExpectedResult<string>(&schema, children[i], "this is a long string");
|
||||
} else if (i == 2) {
|
||||
AssertExpectedResult<int32_t>(&schema, children[i], 42);
|
||||
} else if (i == 3) {
|
||||
AssertExpectedResult<bool>(&schema, children[i], true);
|
||||
} else if (i == 4) {
|
||||
AssertExpectedResult<int32_t>(&schema, children[i], 0, true);
|
||||
} else {
|
||||
// Not possible
|
||||
REQUIRE(false);
|
||||
}
|
||||
if (schema.release) {
|
||||
schema.release(&schema);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
244
external/duckdb/test/arrow/arrow_roundtrip.cpp
vendored
Normal file
244
external/duckdb/test/arrow/arrow_roundtrip.cpp
vendored
Normal file
@@ -0,0 +1,244 @@
|
||||
#include "catch.hpp"
|
||||
|
||||
#include "arrow/arrow_test_helper.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
|
||||
static void TestArrowRoundtrip(const string &query, bool export_large_buffer = false,
|
||||
bool loseless_conversion = false) {
|
||||
DuckDB db;
|
||||
Connection con(db);
|
||||
if (export_large_buffer) {
|
||||
auto res = con.Query("SET arrow_large_buffer_size=True");
|
||||
REQUIRE(!res->HasError());
|
||||
}
|
||||
if (loseless_conversion) {
|
||||
auto res = con.Query("SET arrow_lossless_conversion = true");
|
||||
REQUIRE(!res->HasError());
|
||||
}
|
||||
REQUIRE(ArrowTestHelper::RunArrowComparison(con, query, true));
|
||||
REQUIRE(ArrowTestHelper::RunArrowComparison(con, query, false));
|
||||
}
|
||||
|
||||
static void TestArrowRoundtripStringView(const string &query) {
|
||||
DuckDB db;
|
||||
Connection con(db);
|
||||
auto res = con.Query("SET produce_arrow_string_view=True");
|
||||
REQUIRE(!res->HasError());
|
||||
REQUIRE(ArrowTestHelper::RunArrowComparison(con, query, false));
|
||||
}
|
||||
|
||||
static void TestParquetRoundtrip(const string &path) {
|
||||
DBConfig config;
|
||||
// This needs to be set since this test will be triggered when testing autoloading
|
||||
config.options.allow_unsigned_extensions = true;
|
||||
|
||||
DuckDB db(nullptr, &config);
|
||||
Connection con(db);
|
||||
|
||||
// run the query
|
||||
auto query = "SELECT * FROM parquet_scan('" + path + "')";
|
||||
REQUIRE(ArrowTestHelper::RunArrowComparison(con, query, true));
|
||||
REQUIRE(ArrowTestHelper::RunArrowComparison(con, query));
|
||||
}
|
||||
|
||||
TEST_CASE("Test Export Large", "[arrow]") {
|
||||
// Test with Regular Buffer Size
|
||||
TestArrowRoundtrip("SELECT 'bla' FROM range(10000)");
|
||||
|
||||
TestArrowRoundtrip("SELECT 'bla'::BLOB FROM range(10000)");
|
||||
|
||||
TestArrowRoundtrip("SELECT '3d038406-6275-4aae-bec1-1235ccdeaade'::UUID FROM range(10000) tbl(i)", false, true);
|
||||
|
||||
// Test with Large Buffer Size
|
||||
TestArrowRoundtrip("SELECT 'bla' FROM range(10000)", true);
|
||||
|
||||
TestArrowRoundtrip("SELECT 'bla'::BLOB FROM range(10000)", true);
|
||||
|
||||
TestArrowRoundtrip("SELECT '3d038406-6275-4aae-bec1-1235ccdeaade'::UUID FROM range(10000) tbl(i)", true, true);
|
||||
}
|
||||
|
||||
TEST_CASE("Test arrow roundtrip", "[arrow]") {
|
||||
TestArrowRoundtrip("SELECT * FROM range(10000) tbl(i) UNION ALL SELECT NULL");
|
||||
TestArrowRoundtrip("SELECT m from (select MAP(list_value(1), list_value(2)) from range(5) tbl(i)) tbl(m)");
|
||||
TestArrowRoundtrip("SELECT * FROM range(10) tbl(i)");
|
||||
TestArrowRoundtrip("SELECT case when i%2=0 then null else i end i FROM range(10) tbl(i)");
|
||||
TestArrowRoundtrip("SELECT case when i%2=0 then true else false end b FROM range(10) tbl(i)");
|
||||
TestArrowRoundtrip("SELECT case when i%2=0 then i%4=0 else null end b FROM range(10) tbl(i)");
|
||||
TestArrowRoundtrip("SELECT 'thisisalongstring'||i::varchar str FROM range(10) tbl(i)");
|
||||
TestArrowRoundtrip(
|
||||
"SELECT case when i%2=0 then null else 'thisisalongstring'||i::varchar end str FROM range(10) tbl(i)");
|
||||
TestArrowRoundtrip("SELECT {'i': i, 'b': 10-i} str FROM range(10) tbl(i)");
|
||||
TestArrowRoundtrip("SELECT case when i%2=0 then {'i': case when i%4=0 then null else i end, 'b': 10-i} else null "
|
||||
"end str FROM range(10) tbl(i)");
|
||||
TestArrowRoundtrip("SELECT [i, i+1, i+2] FROM range(10) tbl(i)");
|
||||
TestArrowRoundtrip(
|
||||
"SELECT MAP(LIST_VALUE({'i':1,'j':2},{'i':3,'j':4}),LIST_VALUE({'i':1,'j':2},{'i':3,'j':4})) as a");
|
||||
TestArrowRoundtrip(
|
||||
"SELECT MAP(LIST_VALUE({'i':i,'j':i+2},{'i':3,'j':NULL}),LIST_VALUE({'i':i+10,'j':2},{'i':i+4,'j':4})) as a "
|
||||
"FROM range(10) tbl(i)");
|
||||
TestArrowRoundtrip("SELECT MAP(['hello', 'world'||i::VARCHAR],[i + 1, NULL]) as a FROM range(10) tbl(i)");
|
||||
TestArrowRoundtrip("SELECT (1.5 + i)::DECIMAL(4,2) dec4, (1.5 + i)::DECIMAL(9,3) dec9, (1.5 + i)::DECIMAL(18,3) "
|
||||
"dec18, (1.5 + i)::DECIMAL(38,3) dec38 FROM range(10) tbl(i)");
|
||||
TestArrowRoundtrip(
|
||||
"SELECT case when i%2=0 then null else INTERVAL (i) seconds end AS interval FROM range(10) tbl(i)");
|
||||
#if STANDARD_VECTOR_SIZE < 64
|
||||
// FIXME: there seems to be a bug in the enum arrow reader in this test when run with vsize=2
|
||||
return;
|
||||
#endif
|
||||
TestArrowRoundtrip("SELECT * EXCLUDE(bit,time_tz, bignum) REPLACE "
|
||||
"(interval (1) seconds AS interval, hugeint::DOUBLE as hugeint, uhugeint::DOUBLE as uhugeint) "
|
||||
"FROM test_all_types()",
|
||||
false, true);
|
||||
}
|
||||
|
||||
TEST_CASE("Test Arrow Extension Types", "[arrow][.]") {
|
||||
// UUID
|
||||
TestArrowRoundtrip("SELECT '2d89ebe6-1e13-47e5-803a-b81c87660b66'::UUID str FROM range(5) tbl(i)", false, true);
|
||||
|
||||
// HUGEINT
|
||||
TestArrowRoundtrip("SELECT '170141183460469231731687303715884105727'::HUGEINT str FROM range(5) tbl(i)", false,
|
||||
true);
|
||||
|
||||
// UHUGEINT
|
||||
TestArrowRoundtrip("SELECT '170141183460469231731687303715884105727'::UHUGEINT str FROM range(5) tbl(i)", false,
|
||||
true);
|
||||
|
||||
// BIT
|
||||
TestArrowRoundtrip("SELECT '0101011'::BIT str FROM range(5) tbl(i)", false, true);
|
||||
|
||||
// TIME_TZ
|
||||
TestArrowRoundtrip("SELECT '02:30:00+04'::TIMETZ str FROM range(5) tbl(i)", false, true);
|
||||
|
||||
// BIGNUM
|
||||
TestArrowRoundtrip("SELECT 85070591730234614260976917445211069672::BIGNUM str FROM range(5) tbl(i)", false, true);
|
||||
|
||||
TestArrowRoundtrip("SELECT 85070591730234614260976917445211069672::BIGNUM str FROM range(5) tbl(i)", true, true);
|
||||
}
|
||||
|
||||
TEST_CASE("Test Arrow Extension Types - JSON", "[arrow][.]") {
|
||||
DBConfig config;
|
||||
DuckDB db(nullptr, &config);
|
||||
Connection con(db);
|
||||
|
||||
if (!db.ExtensionIsLoaded("json")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// JSON
|
||||
TestArrowRoundtrip("SELECT '{\"name\":\"Pedro\", \"age\":28, \"car\":\"VW Fox\"}'::JSON str FROM range(5) tbl(i)",
|
||||
false, true);
|
||||
}
|
||||
|
||||
TEST_CASE("Test Arrow String View", "[arrow][.]") {
|
||||
// Test Small Strings
|
||||
TestArrowRoundtripStringView("SELECT (i*10^i)::varchar str FROM range(5) tbl(i)");
|
||||
|
||||
// Test Small Strings + Nulls
|
||||
TestArrowRoundtripStringView("SELECT (i*10^i)::varchar str FROM range(5) tbl(i) UNION SELECT NULL");
|
||||
|
||||
// Test Big Strings
|
||||
TestArrowRoundtripStringView("SELECT 'Imaverybigstringmuchbiggerthanfourbytes' str FROM range(5) tbl(i)");
|
||||
|
||||
// Test Big Strings + Nulls
|
||||
TestArrowRoundtripStringView("SELECT 'Imaverybigstringmuchbiggerthanfourbytes'||i::varchar str FROM range(5) "
|
||||
"tbl(i) UNION SELECT NULL order by str");
|
||||
|
||||
// Test Mix of Small/Big/NULL Strings
|
||||
TestArrowRoundtripStringView(
|
||||
"SELECT 'Imaverybigstringmuchbiggerthanfourbytes'||i::varchar str FROM range(10000) tbl(i) UNION "
|
||||
"SELECT NULL UNION SELECT (i*10^i)::varchar str FROM range(10000) tbl(i)");
|
||||
}
|
||||
|
||||
TEST_CASE("Test TPCH arrow roundtrip", "[arrow][.]") {
|
||||
DBConfig config;
|
||||
DuckDB db(nullptr, &config);
|
||||
Connection con(db);
|
||||
|
||||
if (!db.ExtensionIsLoaded("tpch")) {
|
||||
return;
|
||||
}
|
||||
con.SendQuery("CALL dbgen(sf=0.5)");
|
||||
|
||||
// REQUIRE(ArrowTestHelper::RunArrowComparison(con, "SELECT * FROM lineitem;", false));
|
||||
// REQUIRE(ArrowTestHelper::RunArrowComparison(con, "SELECT l_orderkey, l_shipdate, l_comment FROM lineitem ORDER BY
|
||||
// l_orderkey DESC;", false)); REQUIRE(ArrowTestHelper::RunArrowComparison(con, "SELECT lineitem FROM lineitem;",
|
||||
// false)); REQUIRE(ArrowTestHelper::RunArrowComparison(con, "SELECT [lineitem] FROM lineitem;", false));
|
||||
|
||||
con.SendQuery("create table lineitem_no_constraint as from lineitem;");
|
||||
con.SendQuery("update lineitem_no_constraint set l_comment=null where l_orderkey%2=0;");
|
||||
|
||||
// REQUIRE(ArrowTestHelper::RunArrowComparison(con, "SELECT * FROM lineitem_no_constraint;", false));
|
||||
REQUIRE(ArrowTestHelper::RunArrowComparison(
|
||||
con, "SELECT l_orderkey, l_shipdate, l_comment FROM lineitem_no_constraint ORDER BY l_orderkey DESC;", false));
|
||||
REQUIRE(
|
||||
ArrowTestHelper::RunArrowComparison(con, "SELECT lineitem_no_constraint FROM lineitem_no_constraint;", false));
|
||||
REQUIRE(ArrowTestHelper::RunArrowComparison(con, "SELECT [lineitem_no_constraint] FROM lineitem_no_constraint;",
|
||||
false));
|
||||
}
|
||||
|
||||
TEST_CASE("Test Parquet Files round-trip", "[arrow][.]") {
|
||||
std::vector<std::string> data;
|
||||
// data.emplace_back("data/parquet-testing/7-set.snappy.arrow2.parquet");
|
||||
// data.emplace_back("data/parquet-testing/adam_genotypes.parquet");
|
||||
data.emplace_back("data/parquet-testing/apkwan.parquet");
|
||||
data.emplace_back("data/parquet-testing/aws1.snappy.parquet");
|
||||
// not supported by arrow
|
||||
// data.emplace_back("data/parquet-testing/aws2.parquet");
|
||||
data.emplace_back("data/parquet-testing/binary_string.parquet");
|
||||
data.emplace_back("data/parquet-testing/blob.parquet");
|
||||
data.emplace_back("data/parquet-testing/boolean_stats.parquet");
|
||||
// arrow can't read this
|
||||
// data.emplace_back("data/parquet-testing/broken-arrow.parquet");
|
||||
data.emplace_back("data/parquet-testing/bug1554.parquet");
|
||||
data.emplace_back("data/parquet-testing/bug1588.parquet");
|
||||
data.emplace_back("data/parquet-testing/bug1589.parquet");
|
||||
data.emplace_back("data/parquet-testing/bug1618_struct_strings.parquet");
|
||||
data.emplace_back("data/parquet-testing/bug2267.parquet");
|
||||
data.emplace_back("data/parquet-testing/bug2557.parquet");
|
||||
// slow
|
||||
// data.emplace_back("data/parquet-testing/bug687_nulls.parquet");
|
||||
// data.emplace_back("data/parquet-testing/complex.parquet");
|
||||
data.emplace_back("data/parquet-testing/data-types.parquet");
|
||||
data.emplace_back("data/parquet-testing/date.parquet");
|
||||
// arrow can't read this because it's a time with a timezone and it's not supported by arrow
|
||||
// data.emplace_back("data/parquet-testing/date_stats.parquet");
|
||||
data.emplace_back("data/parquet-testing/decimal_stats.parquet");
|
||||
data.emplace_back("data/parquet-testing/decimals.parquet");
|
||||
data.emplace_back("data/parquet-testing/enum.parquet");
|
||||
data.emplace_back("data/parquet-testing/filter_bug1391.parquet");
|
||||
// data.emplace_back("data/parquet-testing/fixed.parquet");
|
||||
// slow
|
||||
// data.emplace_back("data/parquet-testing/leftdate3_192_loop_1.parquet");
|
||||
data.emplace_back("data/parquet-testing/lineitem-top10000.gzip.parquet");
|
||||
data.emplace_back("data/parquet-testing/manyrowgroups.parquet");
|
||||
data.emplace_back("data/parquet-testing/manyrowgroups2.parquet");
|
||||
// data.emplace_back("data/parquet-testing/map.parquet");
|
||||
// Can't roundtrip NaNs
|
||||
data.emplace_back("data/parquet-testing/nan-float.parquet");
|
||||
// null byte in file
|
||||
// data.emplace_back("data/parquet-testing/nullbyte.parquet");
|
||||
// data.emplace_back("data/parquet-testing/nullbyte_multiple.parquet");
|
||||
// borked
|
||||
// data.emplace_back("data/parquet-testing/p2.parquet");
|
||||
// data.emplace_back("data/parquet-testing/p2strings.parquet");
|
||||
data.emplace_back("data/parquet-testing/pandas-date.parquet");
|
||||
data.emplace_back("data/parquet-testing/signed_stats.parquet");
|
||||
data.emplace_back("data/parquet-testing/silly-names.parquet");
|
||||
// borked
|
||||
// data.emplace_back("data/parquet-testing/simple.parquet");
|
||||
// data.emplace_back("data/parquet-testing/sorted.zstd_18_131072_small.parquet");
|
||||
data.emplace_back("data/parquet-testing/struct.parquet");
|
||||
data.emplace_back("data/parquet-testing/struct_skip_test.parquet");
|
||||
data.emplace_back("data/parquet-testing/timestamp-ms.parquet");
|
||||
data.emplace_back("data/parquet-testing/timestamp.parquet");
|
||||
data.emplace_back("data/parquet-testing/unsigned.parquet");
|
||||
data.emplace_back("data/parquet-testing/unsigned_stats.parquet");
|
||||
data.emplace_back("data/parquet-testing/userdata1.parquet");
|
||||
data.emplace_back("data/parquet-testing/varchar_stats.parquet");
|
||||
data.emplace_back("data/parquet-testing/zstd.parquet");
|
||||
|
||||
for (auto &parquet_path : data) {
|
||||
TestParquetRoundtrip(parquet_path);
|
||||
}
|
||||
}
|
||||
291
external/duckdb/test/arrow/arrow_test_helper.cpp
vendored
Normal file
291
external/duckdb/test/arrow/arrow_test_helper.cpp
vendored
Normal file
@@ -0,0 +1,291 @@
|
||||
#include "arrow/arrow_test_helper.hpp"
|
||||
#include "duckdb/common/arrow/physical_arrow_collector.hpp"
|
||||
#include "duckdb/common/arrow/arrow_query_result.hpp"
|
||||
#include "duckdb/main/relation/setop_relation.hpp"
|
||||
#include "duckdb/main/relation/materialized_relation.hpp"
|
||||
#include "duckdb/common/enums/set_operation_type.hpp"
|
||||
|
||||
duckdb::unique_ptr<duckdb::ArrowArrayStreamWrapper>
|
||||
ArrowStreamTestFactory::CreateStream(uintptr_t this_ptr, duckdb::ArrowStreamParameters ¶meters) {
|
||||
auto stream_wrapper = duckdb::make_uniq<duckdb::ArrowArrayStreamWrapper>();
|
||||
stream_wrapper->number_of_rows = -1;
|
||||
stream_wrapper->arrow_array_stream = *(ArrowArrayStream *)this_ptr;
|
||||
|
||||
return stream_wrapper;
|
||||
}
|
||||
|
||||
void ArrowStreamTestFactory::GetSchema(ArrowArrayStream *arrow_array_stream, ArrowSchema &schema) {
|
||||
arrow_array_stream->get_schema(arrow_array_stream, &schema);
|
||||
}
|
||||
|
||||
namespace duckdb {
|
||||
|
||||
int ArrowTestFactory::ArrowArrayStreamGetSchema(struct ArrowArrayStream *stream, struct ArrowSchema *out) {
|
||||
if (!stream->private_data) {
|
||||
throw InternalException("No private data!?");
|
||||
}
|
||||
auto &data = *((ArrowArrayStreamData *)stream->private_data);
|
||||
data.factory.ToArrowSchema(out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int NextFromMaterialized(MaterializedQueryResult &res, bool big, ClientProperties properties,
|
||||
struct ArrowArray *out) {
|
||||
auto &types = res.types;
|
||||
unordered_map<idx_t, const duckdb::shared_ptr<ArrowTypeExtensionData>> extension_type_cast;
|
||||
if (big) {
|
||||
// Combine all chunks into a single ArrowArray
|
||||
ArrowAppender appender(types, STANDARD_VECTOR_SIZE, properties, extension_type_cast);
|
||||
idx_t count = 0;
|
||||
while (true) {
|
||||
auto chunk = res.Fetch();
|
||||
if (!chunk || chunk->size() == 0) {
|
||||
break;
|
||||
}
|
||||
count += chunk->size();
|
||||
appender.Append(*chunk, 0, chunk->size(), chunk->size());
|
||||
}
|
||||
if (count > 0) {
|
||||
*out = appender.Finalize();
|
||||
}
|
||||
} else {
|
||||
auto chunk = res.Fetch();
|
||||
if (!chunk || chunk->size() == 0) {
|
||||
return 0;
|
||||
}
|
||||
ArrowConverter::ToArrowArray(*chunk, out, properties, extension_type_cast);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int NextFromArrow(ArrowTestFactory &factory, struct ArrowArray *out) {
|
||||
auto &it = factory.chunk_iterator;
|
||||
|
||||
unique_ptr<ArrowArrayWrapper> next_array;
|
||||
if (it != factory.prefetched_chunks.end()) {
|
||||
next_array = std::move(*it);
|
||||
it++;
|
||||
}
|
||||
|
||||
if (!next_array) {
|
||||
return 0;
|
||||
}
|
||||
*out = next_array->arrow_array;
|
||||
next_array->arrow_array.release = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ArrowTestFactory::ArrowArrayStreamGetNext(struct ArrowArrayStream *stream, struct ArrowArray *out) {
|
||||
if (!stream->private_data) {
|
||||
throw InternalException("No private data!?");
|
||||
}
|
||||
auto &data = *((ArrowArrayStreamData *)stream->private_data);
|
||||
if (data.factory.result->type == QueryResultType::MATERIALIZED_RESULT) {
|
||||
auto &materialized_result = data.factory.result->Cast<MaterializedQueryResult>();
|
||||
return NextFromMaterialized(materialized_result, data.factory.big_result, data.options, out);
|
||||
} else {
|
||||
D_ASSERT(data.factory.result->type == QueryResultType::ARROW_RESULT);
|
||||
return NextFromArrow(data.factory, out);
|
||||
}
|
||||
}
|
||||
|
||||
const char *ArrowTestFactory::ArrowArrayStreamGetLastError(struct ArrowArrayStream *stream) {
|
||||
throw InternalException("Error!?!!");
|
||||
}
|
||||
|
||||
void ArrowTestFactory::ArrowArrayStreamRelease(struct ArrowArrayStream *stream) {
|
||||
if (!stream || !stream->private_data) {
|
||||
return;
|
||||
}
|
||||
auto data = (ArrowArrayStreamData *)stream->private_data;
|
||||
delete data;
|
||||
stream->private_data = nullptr;
|
||||
stream->release = nullptr;
|
||||
}
|
||||
|
||||
duckdb::unique_ptr<duckdb::ArrowArrayStreamWrapper> ArrowTestFactory::CreateStream(uintptr_t this_ptr,
|
||||
ArrowStreamParameters ¶meters) {
|
||||
//! Create a new batch reader
|
||||
auto &factory = *reinterpret_cast<ArrowTestFactory *>(this_ptr); //! NOLINT
|
||||
if (!factory.result) {
|
||||
throw InternalException("Stream already consumed!");
|
||||
}
|
||||
|
||||
auto stream_wrapper = make_uniq<ArrowArrayStreamWrapper>();
|
||||
stream_wrapper->number_of_rows = -1;
|
||||
auto private_data = make_uniq<ArrowArrayStreamData>(factory, factory.options);
|
||||
stream_wrapper->arrow_array_stream.get_schema = ArrowArrayStreamGetSchema;
|
||||
stream_wrapper->arrow_array_stream.get_next = ArrowArrayStreamGetNext;
|
||||
stream_wrapper->arrow_array_stream.get_last_error = ArrowArrayStreamGetLastError;
|
||||
stream_wrapper->arrow_array_stream.release = ArrowArrayStreamRelease;
|
||||
stream_wrapper->arrow_array_stream.private_data = private_data.release();
|
||||
|
||||
return stream_wrapper;
|
||||
}
|
||||
|
||||
void ArrowTestFactory::GetSchema(ArrowArrayStream *factory_ptr, ArrowSchema &schema) {
|
||||
//! Create a new batch reader
|
||||
auto &factory = *reinterpret_cast<ArrowTestFactory *>(factory_ptr); //! NOLINT
|
||||
factory.ToArrowSchema(&schema);
|
||||
}
|
||||
|
||||
void ArrowTestFactory::ToArrowSchema(struct ArrowSchema *out) {
|
||||
ArrowConverter::ToArrowSchema(out, types, names, options);
|
||||
}
|
||||
|
||||
unique_ptr<QueryResult> ArrowTestHelper::ScanArrowObject(Connection &con, vector<Value> ¶ms) {
|
||||
auto arrow_result = con.TableFunction("arrow_scan", params)->Execute();
|
||||
if (arrow_result->type != QueryResultType::MATERIALIZED_RESULT) {
|
||||
printf("Arrow Result must materialized");
|
||||
return nullptr;
|
||||
}
|
||||
if (arrow_result->HasError()) {
|
||||
printf("-------------------------------------\n");
|
||||
printf("Arrow round-trip query error: %s\n", arrow_result->GetError().c_str());
|
||||
printf("-------------------------------------\n");
|
||||
printf("-------------------------------------\n");
|
||||
return nullptr;
|
||||
}
|
||||
return arrow_result;
|
||||
}
|
||||
|
||||
bool ArrowTestHelper::CompareResults(Connection &con, unique_ptr<QueryResult> arrow,
|
||||
unique_ptr<MaterializedQueryResult> duck, const string &query) {
|
||||
auto &materialized_arrow = (MaterializedQueryResult &)*arrow;
|
||||
// compare the results
|
||||
string error;
|
||||
|
||||
auto arrow_collection = materialized_arrow.TakeCollection();
|
||||
auto arrow_rel = make_shared_ptr<MaterializedRelation>(con.context, std::move(arrow_collection),
|
||||
materialized_arrow.names, "arrow");
|
||||
|
||||
auto duck_collection = duck->TakeCollection();
|
||||
auto duck_rel = make_shared_ptr<MaterializedRelation>(con.context, std::move(duck_collection), duck->names, "duck");
|
||||
|
||||
if (materialized_arrow.types != duck->types) {
|
||||
bool mismatch_error = false;
|
||||
std::ostringstream error_msg;
|
||||
error_msg << "-------------------------------------\n";
|
||||
error_msg << "Arrow round-trip type comparison failed\n";
|
||||
error_msg << "-------------------------------------\n";
|
||||
error_msg << "Query: " << query.c_str() << "\n";
|
||||
for (idx_t i = 0; i < materialized_arrow.types.size(); i++) {
|
||||
if (materialized_arrow.types[i] != duck->types[i] && duck->types[i].id() != LogicalTypeId::ENUM) {
|
||||
mismatch_error = true;
|
||||
error_msg << "Column " << i << "mismatch. DuckDB: '" << duck->types[i].ToString() << "'. Arrow '"
|
||||
<< materialized_arrow.types[i].ToString() << "'\n";
|
||||
}
|
||||
}
|
||||
error_msg << "-------------------------------------\n";
|
||||
if (mismatch_error) {
|
||||
printf("%s", error_msg.str().c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// We perform a SELECT * FROM "duck_rel" EXCEPT ALL SELECT * FROM "arrow_rel"
|
||||
// this will tell us if there are tuples missing from 'arrow_rel' that are present in 'duck_rel'
|
||||
auto except_rel = make_shared_ptr<SetOpRelation>(duck_rel, arrow_rel, SetOperationType::EXCEPT, /*setop_all=*/true);
|
||||
auto except_result_p = except_rel->Execute();
|
||||
auto &except_result = except_result_p->Cast<MaterializedQueryResult>();
|
||||
if (except_result.RowCount() != 0) {
|
||||
printf("-------------------------------------\n");
|
||||
printf("Arrow round-trip failed: %s\n", error.c_str());
|
||||
printf("-------------------------------------\n");
|
||||
printf("Query: %s\n", query.c_str());
|
||||
printf("-----------------DuckDB-------------------\n");
|
||||
Printer::Print(duck_rel->ToString(0));
|
||||
printf("-----------------Arrow--------------------\n");
|
||||
Printer::Print(arrow_rel->ToString(0));
|
||||
printf("-------------------------------------\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
vector<Value> ArrowTestHelper::ConstructArrowScan(ArrowTestFactory &factory) {
|
||||
vector<Value> params;
|
||||
auto arrow_object = (uintptr_t)(&factory);
|
||||
params.push_back(Value::POINTER(arrow_object));
|
||||
params.push_back(Value::POINTER((uintptr_t)&ArrowTestFactory::CreateStream));
|
||||
params.push_back(Value::POINTER((uintptr_t)&ArrowTestFactory::GetSchema));
|
||||
return params;
|
||||
}
|
||||
|
||||
vector<Value> ArrowTestHelper::ConstructArrowScan(ArrowArrayStream &stream) {
|
||||
vector<Value> params;
|
||||
auto arrow_object = (uintptr_t)(&stream);
|
||||
params.push_back(Value::POINTER(arrow_object));
|
||||
params.push_back(Value::POINTER((uintptr_t)&ArrowStreamTestFactory::CreateStream));
|
||||
params.push_back(Value::POINTER((uintptr_t)&ArrowStreamTestFactory::GetSchema));
|
||||
return params;
|
||||
}
|
||||
|
||||
bool ArrowTestHelper::RunArrowComparison(Connection &con, const string &query, bool big_result) {
|
||||
unique_ptr<QueryResult> initial_result;
|
||||
|
||||
// Using the PhysicalArrowCollector, we create a ArrowQueryResult from the result
|
||||
{
|
||||
auto &config = ClientConfig::GetConfig(*con.context);
|
||||
// we can't have a too large number here because a multiple of this batch size is passed into an allocation
|
||||
idx_t batch_size = big_result ? 1000000 : 10000;
|
||||
|
||||
// Set up the result collector to use
|
||||
ScopedConfigSetting setting(
|
||||
config,
|
||||
[&batch_size](ClientConfig &config) {
|
||||
config.get_result_collector = [&batch_size](ClientContext &context,
|
||||
PreparedStatementData &data) -> PhysicalOperator & {
|
||||
return PhysicalArrowCollector::Create(context, data, batch_size);
|
||||
};
|
||||
},
|
||||
[](ClientConfig &config) { config.get_result_collector = nullptr; });
|
||||
|
||||
// run the query
|
||||
initial_result = con.context->Query(query, false);
|
||||
if (initial_result->HasError()) {
|
||||
initial_result->Print();
|
||||
printf("Query: %s\n", query.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto client_properties = con.context->GetClientProperties();
|
||||
auto types = initial_result->types;
|
||||
auto names = initial_result->names;
|
||||
// We create an "arrow object" that consists of the arrays from our ArrowQueryResult
|
||||
ArrowTestFactory factory(std::move(types), std::move(names), std::move(initial_result), big_result,
|
||||
client_properties, *con.context);
|
||||
// And construct a `arrow_scan` to read the created "arrow object"
|
||||
auto params = ConstructArrowScan(factory);
|
||||
|
||||
// Executing the scan gives us back a MaterializedQueryResult from the ArrowQueryResult we read
|
||||
// query -> ArrowQueryResult -> arrow_scan() -> MaterializedQueryResult
|
||||
auto arrow_result = ScanArrowObject(con, params);
|
||||
if (!arrow_result) {
|
||||
printf("Query: %s\n", query.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// This query goes directly from:
|
||||
// query -> MaterializedQueryResult
|
||||
auto expected = con.Query(query);
|
||||
return CompareResults(con, std::move(arrow_result), std::move(expected), query);
|
||||
}
|
||||
|
||||
bool ArrowTestHelper::RunArrowComparison(Connection &con, const string &query, ArrowArrayStream &arrow_stream) {
|
||||
// construct the arrow scan
|
||||
auto params = ConstructArrowScan(arrow_stream);
|
||||
|
||||
// run the arrow scan over the result
|
||||
auto arrow_result = ScanArrowObject(con, params);
|
||||
arrow_stream.release = nullptr;
|
||||
|
||||
if (!arrow_result) {
|
||||
printf("Query: %s\n", query.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return CompareResults(con, std::move(arrow_result), con.Query(query), query);
|
||||
}
|
||||
|
||||
} // namespace duckdb
|
||||
5
external/duckdb/test/catalog/CMakeLists.txt
vendored
Normal file
5
external/duckdb/test/catalog/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
add_library_unity(test_catalog OBJECT test_catalog_version.cpp)
|
||||
|
||||
set(ALL_OBJECT_FILES
|
||||
${ALL_OBJECT_FILES} $<TARGET_OBJECTS:test_catalog>
|
||||
PARENT_SCOPE)
|
||||
99
external/duckdb/test/catalog/test_catalog_version.cpp
vendored
Normal file
99
external/duckdb/test/catalog/test_catalog_version.cpp
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "catch.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
#include "duckdb/main/extension/extension_loader.hpp"
|
||||
#include "duckdb/main/extension_manager.hpp"
|
||||
|
||||
#include "duckdb.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
|
||||
void SomeTableFunc(duckdb::ClientContext &, duckdb::TableFunctionInput &, duckdb::DataChunk &) {
|
||||
}
|
||||
|
||||
TEST_CASE("Test catalog versioning", "[catalog]") {
|
||||
DBConfig config;
|
||||
config.options.allow_unsigned_extensions = true;
|
||||
DuckDB db(nullptr, &config);
|
||||
Connection con1(db);
|
||||
|
||||
con1.context->RunFunctionInTransaction([&]() {
|
||||
auto &catalog = Catalog::GetCatalog(*con1.context, "");
|
||||
REQUIRE(catalog.GetCatalogVersion(*con1.context) == 0);
|
||||
});
|
||||
|
||||
REQUIRE_NO_FAIL(con1.Query("CREATE TABLE foo as SELECT 42"));
|
||||
|
||||
con1.context->RunFunctionInTransaction([&]() {
|
||||
auto &catalog = Catalog::GetCatalog(*con1.context, "");
|
||||
REQUIRE(catalog.GetCatalogVersion(*con1.context) == 1);
|
||||
});
|
||||
|
||||
{
|
||||
REQUIRE_NOTHROW(con1.BeginTransaction());
|
||||
REQUIRE_NO_FAIL(con1.Query("CREATE TABLE foo2 as SELECT 42"));
|
||||
auto &catalog = Catalog::GetCatalog(*con1.context, "");
|
||||
auto catalog_version_1 = catalog.GetCatalogVersion(*con1.context);
|
||||
REQUIRE(catalog_version_1.GetIndex() > TRANSACTION_ID_START);
|
||||
REQUIRE_NO_FAIL(con1.Query("CREATE TABLE foo3 as SELECT 42"));
|
||||
auto catalog_version_2 = catalog.GetCatalogVersion(*con1.context);
|
||||
REQUIRE(catalog_version_2.GetIndex() > catalog_version_1.GetIndex());
|
||||
REQUIRE_NOTHROW(con1.Commit());
|
||||
}
|
||||
|
||||
con1.context->RunFunctionInTransaction([&]() {
|
||||
auto &catalog = Catalog::GetCatalog(*con1.context, "");
|
||||
REQUIRE(catalog.GetCatalogVersion(*con1.context) == 2);
|
||||
});
|
||||
|
||||
// The following should not increment the version
|
||||
REQUIRE_NO_FAIL(con1.Query("CREATE TABLE IF NOT EXISTS foo3 as SELECT 42"));
|
||||
|
||||
con1.context->RunFunctionInTransaction([&]() {
|
||||
auto &catalog = Catalog::GetCatalog(*con1.context, "");
|
||||
REQUIRE(catalog.GetCatalogVersion(*con1.context) == 2);
|
||||
});
|
||||
|
||||
REQUIRE_NO_FAIL(con1.Query("CREATE SCHEMA IF NOT EXISTS my_schema"));
|
||||
|
||||
con1.context->RunFunctionInTransaction([&]() {
|
||||
auto &catalog = Catalog::GetCatalog(*con1.context, "");
|
||||
REQUIRE(catalog.GetCatalogVersion(*con1.context) == 3);
|
||||
});
|
||||
|
||||
// The following should not increment the version
|
||||
REQUIRE_NO_FAIL(con1.Query("CREATE SCHEMA IF NOT EXISTS my_schema"));
|
||||
|
||||
con1.context->RunFunctionInTransaction([&]() {
|
||||
auto &catalog = Catalog::GetCatalog(*con1.context, "");
|
||||
REQUIRE(catalog.GetCatalogVersion(*con1.context) == 3);
|
||||
});
|
||||
|
||||
// check catalog version of system catalog
|
||||
con1.context->RunFunctionInTransaction([&]() {
|
||||
auto &catalog = Catalog::GetCatalog(*con1.context, "system");
|
||||
REQUIRE(catalog.GetCatalogVersion(*con1.context) == 1);
|
||||
});
|
||||
REQUIRE_NO_FAIL(con1.Query("ATTACH ':memory:' as foo"));
|
||||
con1.context->RunFunctionInTransaction([&]() {
|
||||
auto &catalog = Catalog::GetCatalog(*con1.context, "system");
|
||||
REQUIRE(catalog.GetCatalogVersion(*con1.context) == 2);
|
||||
});
|
||||
|
||||
// check new version of attached DB starts at 0
|
||||
con1.context->RunFunctionInTransaction([&]() {
|
||||
auto &catalog = Catalog::GetCatalog(*con1.context, "foo");
|
||||
REQUIRE(catalog.GetCatalogVersion(*con1.context) == 0);
|
||||
});
|
||||
|
||||
// system transactions do not register catalog version changes :/
|
||||
duckdb::TableFunction tf("some_new_table_function", {}, SomeTableFunc);
|
||||
ExtensionInfo extension_info {};
|
||||
ExtensionActiveLoad load_info {*db.instance, extension_info, "test_catalog_extension"};
|
||||
ExtensionLoader loader {load_info};
|
||||
loader.RegisterFunction(tf);
|
||||
|
||||
con1.context->RunFunctionInTransaction([&]() {
|
||||
auto &catalog = Catalog::GetCatalog(*con1.context, "system");
|
||||
REQUIRE(catalog.GetCatalogVersion(*con1.context) == 2);
|
||||
});
|
||||
}
|
||||
17
external/duckdb/test/common/CMakeLists.txt
vendored
Normal file
17
external/duckdb/test/common/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
add_library_unity(
|
||||
test_common
|
||||
OBJECT
|
||||
test_cast.cpp
|
||||
test_checksum.cpp
|
||||
test_file_system.cpp
|
||||
test_hyperlog.cpp
|
||||
test_numeric_cast.cpp
|
||||
test_parse_logical_type.cpp
|
||||
test_utf.cpp
|
||||
test_storage_fuzz.cpp
|
||||
test_strftime.cpp
|
||||
test_string_util.cpp)
|
||||
|
||||
set(ALL_OBJECT_FILES
|
||||
${ALL_OBJECT_FILES} $<TARGET_OBJECTS:test_common>
|
||||
PARENT_SCOPE)
|
||||
333
external/duckdb/test/common/test_cast.cpp
vendored
Normal file
333
external/duckdb/test/common/test_cast.cpp
vendored
Normal file
@@ -0,0 +1,333 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/common/operator/cast_operators.hpp"
|
||||
#include "duckdb/common/string_util.hpp"
|
||||
#include "duckdb/common/limits.hpp"
|
||||
#include "duckdb/common/types.hpp"
|
||||
#include "duckdb/common/types/vector.hpp"
|
||||
#include "duckdb/common/vector.hpp"
|
||||
|
||||
using namespace duckdb; // NOLINT
|
||||
using namespace std; // NOLINT
|
||||
|
||||
template <class SRC, class DST>
|
||||
struct ExpectedNumericCast {
|
||||
static inline DST Operation(SRC value) {
|
||||
return (DST)value;
|
||||
}
|
||||
};
|
||||
|
||||
template <class DST>
|
||||
struct ExpectedNumericCast<double, DST> {
|
||||
static inline DST Operation(double value) {
|
||||
return (DST)nearbyint(value);
|
||||
}
|
||||
};
|
||||
|
||||
template <class DST>
|
||||
struct ExpectedNumericCast<float, DST> {
|
||||
static inline DST Operation(float value) {
|
||||
return (DST)nearbyintf(value);
|
||||
}
|
||||
};
|
||||
|
||||
template <class SRC, class DST>
|
||||
static void TestNumericCast(duckdb::vector<SRC> &working_values, duckdb::vector<SRC> &broken_values) {
|
||||
DST result;
|
||||
for (auto value : working_values) {
|
||||
REQUIRE_NOTHROW(Cast::Operation<SRC, DST>(value) == (DST)value);
|
||||
REQUIRE(TryCast::Operation<SRC, DST>(value, result));
|
||||
REQUIRE(result == ExpectedNumericCast<SRC, DST>::Operation(value));
|
||||
}
|
||||
for (auto value : broken_values) {
|
||||
REQUIRE_THROWS(Cast::Operation<SRC, DST>(value));
|
||||
REQUIRE(!TryCast::Operation<SRC, DST>(value, result));
|
||||
}
|
||||
}
|
||||
|
||||
template <class DST>
|
||||
static void TestStringCast(duckdb::vector<string> &working_values, duckdb::vector<DST> &expected_values,
|
||||
duckdb::vector<string> &broken_values) {
|
||||
DST result;
|
||||
for (idx_t i = 0; i < working_values.size(); i++) {
|
||||
auto &value = working_values[i];
|
||||
auto expected_value = expected_values[i];
|
||||
REQUIRE_NOTHROW(Cast::Operation<string_t, DST>(string_t(value)) == expected_value);
|
||||
REQUIRE(TryCast::Operation<string_t, DST>(string_t(value), result));
|
||||
REQUIRE(result == expected_value);
|
||||
|
||||
StringUtil::Trim(value);
|
||||
duckdb::vector<string> splits;
|
||||
splits = StringUtil::Split(value, 'e');
|
||||
if (splits.size() > 1 || value[0] == '+') {
|
||||
continue;
|
||||
}
|
||||
splits = StringUtil::Split(value, '.');
|
||||
REQUIRE(ConvertToString::Operation<DST>(result) == splits[0]);
|
||||
}
|
||||
for (auto &value : broken_values) {
|
||||
REQUIRE_THROWS(Cast::Operation<string_t, DST>(string_t(value)));
|
||||
REQUIRE(!TryCast::Operation<string_t, DST>(string_t(value), result));
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static void TestExponent() {
|
||||
T parse_result;
|
||||
string str;
|
||||
double value = 1;
|
||||
T expected_value = 1;
|
||||
for (idx_t exponent = 0; exponent < 100; exponent++) {
|
||||
if (value < (double)NumericLimits<T>::Maximum()) {
|
||||
// expect success
|
||||
str = "1e" + to_string(exponent);
|
||||
REQUIRE(TryCast::Operation<string_t, T>(string_t(str), parse_result));
|
||||
REQUIRE(parse_result == expected_value);
|
||||
str = "-1e" + to_string(exponent);
|
||||
REQUIRE(TryCast::Operation<string_t, T>(string_t(str), parse_result));
|
||||
REQUIRE(parse_result == -expected_value);
|
||||
value *= 10;
|
||||
// check again because otherwise this overflows
|
||||
if (value < (double)NumericLimits<T>::Maximum()) {
|
||||
expected_value *= 10;
|
||||
}
|
||||
} else {
|
||||
// expect failure
|
||||
str = "1e" + to_string(exponent);
|
||||
REQUIRE(!TryCast::Operation<string_t, T>(string_t(str), parse_result));
|
||||
str = "-1e" + to_string(exponent);
|
||||
REQUIRE(!TryCast::Operation<string_t, T>(string_t(str), parse_result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test casting to boolean", "[cast]") {
|
||||
duckdb::vector<string> working_values = {"true", "false", "TRUE", "FALSE", "T", "F", "1", "0", "False", "True"};
|
||||
duckdb::vector<bool> expected_values = {true, false, true, false, true, false, true, false, false, true};
|
||||
duckdb::vector<string> broken_values = {"304", "1002", "blabla", "", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"};
|
||||
|
||||
bool result;
|
||||
for (idx_t i = 0; i < working_values.size(); i++) {
|
||||
auto &value = working_values[i];
|
||||
auto expected_value = expected_values[i];
|
||||
REQUIRE_NOTHROW(Cast::Operation<string_t, bool>(value) == expected_value);
|
||||
REQUIRE(TryCast::Operation<string_t, bool>(value, result));
|
||||
REQUIRE(result == expected_value);
|
||||
}
|
||||
for (auto &value : broken_values) {
|
||||
REQUIRE_THROWS(Cast::Operation<string_t, bool>(value));
|
||||
REQUIRE(!TryCast::Operation<string_t, bool>(value, result));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test casting to int8_t", "[cast]") {
|
||||
// int16_t -> int8_t
|
||||
duckdb::vector<int16_t> working_values_int16 = {10, -10, 127, -128};
|
||||
duckdb::vector<int16_t> broken_values_int16 = {128, -129, 1000, -1000};
|
||||
TestNumericCast<int16_t, int8_t>(working_values_int16, broken_values_int16);
|
||||
// int32_t -> int8_t
|
||||
duckdb::vector<int32_t> working_values_int32 = {10, -10, 127, -128};
|
||||
duckdb::vector<int32_t> broken_values_int32 = {128, -129, 1000000, -1000000};
|
||||
TestNumericCast<int32_t, int8_t>(working_values_int32, broken_values_int32);
|
||||
// int64_t -> int8_t
|
||||
duckdb::vector<int64_t> working_values_int64 = {10, -10, 127, -128};
|
||||
duckdb::vector<int64_t> broken_values_int64 = {128, -129, 10000000000LL, -10000000000LL};
|
||||
TestNumericCast<int64_t, int8_t>(working_values_int64, broken_values_int64);
|
||||
// float -> int8_t
|
||||
duckdb::vector<float> working_values_float = {10, -10, 127, -128, 1.3f, -2.7f};
|
||||
duckdb::vector<float> broken_values_float = {128, -129, 10000000000.0f, -10000000000.0f, 1e30f, -1e30f};
|
||||
TestNumericCast<float, int8_t>(working_values_float, broken_values_float);
|
||||
// double -> int8_t
|
||||
duckdb::vector<double> working_values_double = {10, -10, 127, -128, 1.3, -2.7};
|
||||
duckdb::vector<double> broken_values_double = {128, -129, 10000000000.0, -10000000000.0, 1e100, -1e100};
|
||||
TestNumericCast<double, int8_t>(working_values_double, broken_values_double);
|
||||
// string -> int8_t
|
||||
duckdb::vector<string> working_values_str = {"10", "+10", "-10", "127", "-128", "1.3", "1e2",
|
||||
"2e1", "2e0", "20e-1", "1.", " 3", " 3 ", "\t3 \t \n"};
|
||||
duckdb::vector<int8_t> expected_values_str = {10, 10, -10, 127, -128, 1, 100, 20, 2, 2, 1, 3, 3, 3};
|
||||
duckdb::vector<string> broken_values_str = {"128",
|
||||
"-129",
|
||||
"10000000000000000000000000000000000000000000000000000000000000",
|
||||
"aaaa",
|
||||
"19A",
|
||||
"",
|
||||
"1e3",
|
||||
"1e",
|
||||
"1e-",
|
||||
"1e100",
|
||||
"1e100000000",
|
||||
"10000e-1",
|
||||
" 3 2",
|
||||
"+"};
|
||||
TestStringCast<int8_t>(working_values_str, expected_values_str, broken_values_str);
|
||||
TestExponent<int8_t>();
|
||||
}
|
||||
|
||||
TEST_CASE("Test casting to int16_t", "[cast]") {
|
||||
// int32_t -> int16_t
|
||||
duckdb::vector<int32_t> working_values_int32 = {10, -10, 127, -127, 32767, -32768};
|
||||
duckdb::vector<int32_t> broken_values_int32 = {32768, -32769, 1000000, -1000000};
|
||||
TestNumericCast<int32_t, int16_t>(working_values_int32, broken_values_int32);
|
||||
// int64_t -> int16_t
|
||||
duckdb::vector<int64_t> working_values_int64 = {10, -10, 127, -127, 32767, -32768};
|
||||
duckdb::vector<int64_t> broken_values_int64 = {32768, -32769, 10000000000LL, -10000000000LL};
|
||||
TestNumericCast<int64_t, int16_t>(working_values_int64, broken_values_int64);
|
||||
// float -> int16_t
|
||||
duckdb::vector<float> working_values_float = {10.0f, -10.0f, 32767.0f, -32768.0f, 1.3f, -2.7f};
|
||||
duckdb::vector<float> broken_values_float = {32768.0f, -32769.0f, 10000000000.0f, -10000000000.0f, 1e30f, -1e30f};
|
||||
TestNumericCast<float, int16_t>(working_values_float, broken_values_float);
|
||||
// double -> int16_t
|
||||
duckdb::vector<double> working_values_double = {10, -10, 32767, -32768, 1.3, -2.7};
|
||||
duckdb::vector<double> broken_values_double = {32768, -32769, 10000000000.0, -10000000000.0, 1e100, -1e100};
|
||||
TestNumericCast<double, int16_t>(working_values_double, broken_values_double);
|
||||
// string -> int16_t
|
||||
duckdb::vector<string> working_values_str = {"10", "-10", "32767", "-32768", "1.3",
|
||||
"3e4", "250e2", "3e+4", "3e0", "30e-1"};
|
||||
duckdb::vector<int16_t> expected_values_str = {10, -10, 32767, -32768, 1, 30000, 25000, 30000, 3, 3};
|
||||
duckdb::vector<string> broken_values_str = {
|
||||
"32768", "-32769", "10000000000000000000000000000000000000000000000000000000000000",
|
||||
"aaaa", "19A", "",
|
||||
"1.A", "1e", "1e-",
|
||||
"1e100", "1e100000000", "+"};
|
||||
TestStringCast<int16_t>(working_values_str, expected_values_str, broken_values_str);
|
||||
TestExponent<int16_t>();
|
||||
}
|
||||
|
||||
TEST_CASE("Test casting to int32_t", "[cast]") {
|
||||
// int64_t -> int32_t
|
||||
duckdb::vector<int64_t> working_values_int64 = {10, -10, 127, -127, 32767, -32768, 2147483647LL, -2147483648LL};
|
||||
duckdb::vector<int64_t> broken_values_int64 = {2147483648LL, -2147483649LL, 10000000000LL, -10000000000LL};
|
||||
TestNumericCast<int64_t, int32_t>(working_values_int64, broken_values_int64);
|
||||
// float -> int32_t
|
||||
duckdb::vector<float> working_values_float = {10.0f, -10.0f, 2000000000.0f, -2000000000.0f, 1.3f, -2.7f};
|
||||
duckdb::vector<float> broken_values_float = {3000000000.0f, -3000000000.0f, 10000000000.0f,
|
||||
-10000000000.0f, 1e30f, -1e30f};
|
||||
TestNumericCast<float, int32_t>(working_values_float, broken_values_float);
|
||||
// double -> int32_t
|
||||
duckdb::vector<double> working_values_double = {10, -10, 32767.0, -32768.0, 1.3, -2.7, 2147483647.0, -2147483648.0};
|
||||
duckdb::vector<double> broken_values_double = {2147483648.0, -2147483649.0, 10000000000.0,
|
||||
-10000000000.0, 1e100, -1e100};
|
||||
TestNumericCast<double, int32_t>(working_values_double, broken_values_double);
|
||||
// string -> int32_t
|
||||
duckdb::vector<string> working_values_str = {"10", "-10", "2147483647", "-2147483647", "1.3", "-1.3", "1e6"};
|
||||
duckdb::vector<int32_t> expected_values_str = {10, -10, 2147483647, -2147483647, 1, -1, 1000000};
|
||||
duckdb::vector<string> broken_values_str = {
|
||||
"2147483648", "-2147483649", "10000000000000000000000000000000000000000000000000000000000000",
|
||||
"aaaa", "19A", "",
|
||||
"1.A", "1e1e1e1"};
|
||||
TestStringCast<int32_t>(working_values_str, expected_values_str, broken_values_str);
|
||||
TestExponent<int32_t>();
|
||||
}
|
||||
|
||||
TEST_CASE("Test casting to int64_t", "[cast]") {
|
||||
// float -> int64_t
|
||||
duckdb::vector<float> working_values_float = {10.0f,
|
||||
-10.0f,
|
||||
32767.0f,
|
||||
-32768.0f,
|
||||
1.3f,
|
||||
-2.7f,
|
||||
2000000000.0f,
|
||||
-2000000000.0f,
|
||||
4000000000000000000.0f,
|
||||
-4000000000000000000.0f};
|
||||
duckdb::vector<float> broken_values_float = {20000000000000000000.0f, -20000000000000000000.0f, 1e30f, -1e30f};
|
||||
TestNumericCast<float, int64_t>(working_values_float, broken_values_float);
|
||||
// double -> int64_t
|
||||
duckdb::vector<double> working_values_double = {
|
||||
10, -10, 32767, -32768, 1.3, -2.7, 2147483647, -2147483648.0, 4611686018427387904.0, -4611686018427387904.0};
|
||||
duckdb::vector<double> broken_values_double = {18446744073709551616.0, -18446744073709551617.0, 1e100, -1e100};
|
||||
TestNumericCast<double, int64_t>(working_values_double, broken_values_double);
|
||||
// string -> int64_t
|
||||
duckdb::vector<string> working_values_str = {
|
||||
"10", "-10", "9223372036854775807", "-9223372036854775807", "1.3", "-9223372036854775807.1293813", "1e18",
|
||||
"1e+18", "1."};
|
||||
duckdb::vector<int64_t> expected_values_str = {10,
|
||||
-10,
|
||||
9223372036854775807LL,
|
||||
-9223372036854775807LL,
|
||||
1,
|
||||
-9223372036854775807LL,
|
||||
1000000000000000000LL,
|
||||
1000000000000000000LL,
|
||||
1};
|
||||
duckdb::vector<string> broken_values_str = {"9223372036854775808",
|
||||
"-9223372036854775809",
|
||||
"10000000000000000000000000000000000000000000000000000000000000",
|
||||
"aaaa",
|
||||
"19A",
|
||||
"",
|
||||
"1.A",
|
||||
"1.2382398723A",
|
||||
"1e++1",
|
||||
"1e+1+1",
|
||||
"1e+1-1",
|
||||
"+"};
|
||||
TestStringCast<int64_t>(working_values_str, expected_values_str, broken_values_str);
|
||||
TestExponent<int64_t>();
|
||||
}
|
||||
|
||||
template <class DST>
|
||||
static void TestStringCastDouble(duckdb::vector<string> &working_values, duckdb::vector<DST> &expected_values,
|
||||
duckdb::vector<string> &broken_values) {
|
||||
DST result;
|
||||
for (idx_t i = 0; i < working_values.size(); i++) {
|
||||
auto &value = working_values[i];
|
||||
auto expected_value = expected_values[i];
|
||||
REQUIRE_NOTHROW(Cast::Operation<string_t, DST>(string_t(value)) == expected_value);
|
||||
REQUIRE(TryCast::Operation<string_t, DST>(string_t(value), result));
|
||||
REQUIRE(ApproxEqual(result, expected_value));
|
||||
|
||||
auto to_str_and_back =
|
||||
Cast::Operation<string_t, DST>(string_t(ConvertToString::Operation<DST>(expected_value)));
|
||||
REQUIRE(ApproxEqual(to_str_and_back, expected_value));
|
||||
}
|
||||
for (auto &value : broken_values) {
|
||||
REQUIRE_THROWS(Cast::Operation<string_t, DST>(string_t(value)));
|
||||
REQUIRE(!TryCast::Operation<string_t, DST>(string_t(value), result));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test casting to float", "[cast]") {
|
||||
// string -> float
|
||||
duckdb::vector<string> working_values = {
|
||||
"1.3", "1.34514", "1e10", "1e-2", "-1e-1", "1.1781237378938173987123987123981723981723981723987123",
|
||||
"1.123456789", "1."};
|
||||
duckdb::vector<float> expected_values = {
|
||||
1.3f, 1.34514f, 1e10f, 1e-2f, -1e-1f, 1.1781237378938173987123987123981723981723981723987123f,
|
||||
1.123456789f, 1.0f};
|
||||
duckdb::vector<string> broken_values = {
|
||||
"-", "", "aaa",
|
||||
"12aaa", "1e10e10", "1e",
|
||||
"1e-", "1e10a", "1.1781237378938173987123987123981723981723981723934834583490587123w",
|
||||
"1.2.3"};
|
||||
TestStringCastDouble<float>(working_values, expected_values, broken_values);
|
||||
}
|
||||
|
||||
TEST_CASE("Test casting to double", "[cast]") {
|
||||
// string -> double
|
||||
duckdb::vector<string> working_values = {"1.3",
|
||||
"+1.3",
|
||||
"1.34514",
|
||||
"1e10",
|
||||
"1e-2",
|
||||
"-1e-1",
|
||||
"1.1781237378938173987123987123981723981723981723987123",
|
||||
"1.123456789",
|
||||
"1.",
|
||||
"-1.2",
|
||||
"-1.2e1",
|
||||
" 1.2 ",
|
||||
" 1.2e2 ",
|
||||
" \t 1.2e2 \t"};
|
||||
duckdb::vector<double> expected_values = {
|
||||
1.3, 1.3, 1.34514, 1e10, 1e-2, -1e-1, 1.1781237378938173987123987123981723981723981723987123,
|
||||
1.123456789, 1.0, -1.2, -12, 1.2, 120, 120};
|
||||
duckdb::vector<string> broken_values = {
|
||||
"-", "", "aaa",
|
||||
"12aaa", "1e10e10", "1e",
|
||||
"1e-", "1e10a", "1.1781237378938173987123987123981723981723981723934834583490587123w",
|
||||
"1.2.3", "1.222.", "1..",
|
||||
"1 . 2", "1. 2", "1.2 e20",
|
||||
"+"};
|
||||
TestStringCastDouble<double>(working_values, expected_values, broken_values);
|
||||
}
|
||||
411
external/duckdb/test/common/test_cast_hugeint.test
vendored
Normal file
411
external/duckdb/test/common/test_cast_hugeint.test
vendored
Normal file
@@ -0,0 +1,411 @@
|
||||
# name: test/common/test_cast_hugeint.test
|
||||
# description: Test hugeint casting from various types
|
||||
# group: [common]
|
||||
|
||||
# test float -> hugeint casts
|
||||
|
||||
statement ok
|
||||
CREATE TABLE working_floats(f FLOAT);
|
||||
CREATE TABLE broken_floats(f FLOAT);
|
||||
|
||||
statement ok
|
||||
INSERT INTO working_floats VALUES (10.0), (-10.0), (32767.0), (-32767.0), (1.3), (-2.7), (2000000000.0), (-2000000000.0), (4000000000000000000.0), (-4000000000000000000.0), (1329227995784915872903807060280344576.0), (-1329227995784915872903807060280344576.0);
|
||||
|
||||
statement ok
|
||||
INSERT INTO broken_floats VALUES (170141183460469231731687303715884105729.0), (-170141183460469231731687303715884105729.0);
|
||||
|
||||
query I
|
||||
SELECT f::HUGEINT::FLOAT FROM working_floats
|
||||
----
|
||||
10.0
|
||||
-10.0
|
||||
32767.0
|
||||
-32767.0
|
||||
1.0
|
||||
-3.0
|
||||
2000000000.0
|
||||
-2000000000.0
|
||||
4000000000000000000.0
|
||||
-4000000000000000000.0
|
||||
1329227995784915872903807060280344576
|
||||
-1329227995784915872903807060280344576
|
||||
|
||||
# test broken casts
|
||||
# we test one by one to ensure that every single value is broken
|
||||
loop i 0 2
|
||||
|
||||
statement error
|
||||
SELECT f::HUGEINT FROM (SELECT f FROM broken_floats ORDER BY f LIMIT 1 OFFSET ${i}) t1
|
||||
----
|
||||
<REGEX>:Conversion Error.*FLOAT.*is out of range for.*INT128.*
|
||||
|
||||
endloop
|
||||
|
||||
# test double -> hugeint casts
|
||||
statement ok
|
||||
CREATE TABLE working_doubles(f DOUBLE);
|
||||
CREATE TABLE broken_doubles(f DOUBLE);
|
||||
|
||||
statement ok
|
||||
INSERT INTO working_doubles VALUES (10.0), (-10.0), (32767.0), (-32767.0), (1.3), (-2.7), (2000000000.0), (-2000000000.0), (4000000000000000000.0), (-4000000000000000000.0), (1329227995784915872903807060280344576.0), (-1329227995784915872903807060280344576.0);
|
||||
|
||||
statement ok
|
||||
INSERT INTO broken_doubles VALUES (1361129467683753853853498429727072845824.0), (-1361129467683753853853498429727072845824.0), (1.0e100), (-1.0e100);
|
||||
|
||||
query I
|
||||
SELECT f::HUGEINT::DOUBLE FROM working_doubles
|
||||
----
|
||||
10.0
|
||||
-10.0
|
||||
32767.0
|
||||
-32767.0
|
||||
1.0
|
||||
-3.0
|
||||
2000000000.0
|
||||
-2000000000.0
|
||||
4000000000000000000.0
|
||||
-4000000000000000000.0
|
||||
1329227995784915872903807060280344576
|
||||
-1329227995784915872903807060280344576
|
||||
|
||||
# we handle the values one by one here
|
||||
loop i 0 4
|
||||
|
||||
statement error
|
||||
SELECT f::HUGEINT FROM (SELECT f FROM broken_doubles ORDER BY f LIMIT 1 OFFSET ${i}) t1
|
||||
----
|
||||
<REGEX>:Conversion Error.*DOUBLE.*is out of range for.*INT128.*
|
||||
|
||||
endloop
|
||||
|
||||
# test varchar -> hugeint casts
|
||||
statement ok
|
||||
CREATE TABLE working_strings(f VARCHAR);
|
||||
CREATE TABLE broken_strings(f VARCHAR);
|
||||
|
||||
statement ok
|
||||
INSERT INTO working_strings VALUES ('10'), ('-10'), ('-1329227995784915872903807060280344576'), ('170141183460469231731687303715884105727'), ('-170141183460469231731687303715884105728'), ('1.3'), ('-9223372036854775807.1293813'), ('1e18'), ('1e+18'), ('1.'), ('.1'), ('0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), ('1000e-40'), ('10000e-3');
|
||||
|
||||
statement ok
|
||||
INSERT INTO broken_strings VALUES ('170141183460469231731687303715884105728'), ('-170141183460469231731687303715884105729'), ('10000000000000000000000000000000000000000000000000000000000000'), ('aaaa'), ('19A'), (''), ('1.A'), ('1.2382398723A'), ('1e++1'), ('1e+1+1'), ('1e+1-1'), ('+'), ('.'), ('. '), ('10000000000e37');
|
||||
|
||||
query I
|
||||
SELECT f::HUGEINT FROM working_strings
|
||||
----
|
||||
10
|
||||
-10
|
||||
-1329227995784915872903807060280344576
|
||||
170141183460469231731687303715884105727
|
||||
-170141183460469231731687303715884105728
|
||||
1.3
|
||||
-9223372036854775807.1293813
|
||||
1e18
|
||||
1e+18
|
||||
1.
|
||||
.1
|
||||
0
|
||||
0
|
||||
10
|
||||
|
||||
loop i 0 14
|
||||
|
||||
statement error
|
||||
SELECT f::HUGEINT FROM (SELECT f FROM broken_strings ORDER BY f LIMIT 1 OFFSET ${i}) t1
|
||||
----
|
||||
<REGEX>:Conversion Error.*string.*INT128.*
|
||||
|
||||
endloop
|
||||
|
||||
# cast all powers of ten
|
||||
statement ok
|
||||
CREATE TABLE powers_of_ten(p VARCHAR);
|
||||
|
||||
statement ok
|
||||
INSERT INTO powers_of_ten VALUES ('1'), ('10'), ('100'), ('1000'), ('10000'), ('100000'), ('1000000'), ('10000000'), ('100000000'), ('1000000000'), ('10000000000'), ('100000000000'), ('1000000000000'), ('10000000000000'), ('100000000000000'), ('1000000000000000'), ('10000000000000000'), ('100000000000000000'), ('1000000000000000000'), ('10000000000000000000'), ('100000000000000000000'), ('1000000000000000000000'), ('10000000000000000000000'), ('100000000000000000000000'), ('1000000000000000000000000'), ('10000000000000000000000000'), ('100000000000000000000000000'), ('1000000000000000000000000000'), ('10000000000000000000000000000'), ('100000000000000000000000000000'), ('1000000000000000000000000000000'), ('10000000000000000000000000000000'), ('100000000000000000000000000000000'), ('1000000000000000000000000000000000'), ('10000000000000000000000000000000000'), ('100000000000000000000000000000000000'), ('1000000000000000000000000000000000000'), ('10000000000000000000000000000000000000'), ('100000000000000000000000000000000000000'), ('-1'), ('-10'), ('-100'), ('-1000'), ('-10000'), ('-100000'), ('-1000000'), ('-10000000'), ('-100000000'), ('-1000000000'), ('-10000000000'), ('-100000000000'), ('-1000000000000'), ('-10000000000000'), ('-100000000000000'), ('-1000000000000000'), ('-10000000000000000'), ('-100000000000000000'), ('-1000000000000000000'), ('-10000000000000000000'), ('-100000000000000000000'), ('-1000000000000000000000'), ('-10000000000000000000000'), ('-100000000000000000000000'), ('-1000000000000000000000000'), ('-10000000000000000000000000'), ('-100000000000000000000000000'), ('-1000000000000000000000000000'), ('-10000000000000000000000000000'), ('-100000000000000000000000000000'), ('-1000000000000000000000000000000'), ('-10000000000000000000000000000000'), ('-100000000000000000000000000000000'), ('-1000000000000000000000000000000000'), ('-10000000000000000000000000000000000'), ('-100000000000000000000000000000000000'), ('-1000000000000000000000000000000000000'), ('-10000000000000000000000000000000000000'), ('-100000000000000000000000000000000000000');
|
||||
|
||||
query I
|
||||
SELECT p::HUGEINT FROM powers_of_ten
|
||||
----
|
||||
1
|
||||
10
|
||||
100
|
||||
1000
|
||||
10000
|
||||
100000
|
||||
1000000
|
||||
10000000
|
||||
100000000
|
||||
1000000000
|
||||
10000000000
|
||||
100000000000
|
||||
1000000000000
|
||||
10000000000000
|
||||
100000000000000
|
||||
1000000000000000
|
||||
10000000000000000
|
||||
100000000000000000
|
||||
1000000000000000000
|
||||
10000000000000000000
|
||||
100000000000000000000
|
||||
1000000000000000000000
|
||||
10000000000000000000000
|
||||
100000000000000000000000
|
||||
1000000000000000000000000
|
||||
10000000000000000000000000
|
||||
100000000000000000000000000
|
||||
1000000000000000000000000000
|
||||
10000000000000000000000000000
|
||||
100000000000000000000000000000
|
||||
1000000000000000000000000000000
|
||||
10000000000000000000000000000000
|
||||
100000000000000000000000000000000
|
||||
1000000000000000000000000000000000
|
||||
10000000000000000000000000000000000
|
||||
100000000000000000000000000000000000
|
||||
1000000000000000000000000000000000000
|
||||
10000000000000000000000000000000000000
|
||||
100000000000000000000000000000000000000
|
||||
-1
|
||||
-10
|
||||
-100
|
||||
-1000
|
||||
-10000
|
||||
-100000
|
||||
-1000000
|
||||
-10000000
|
||||
-100000000
|
||||
-1000000000
|
||||
-10000000000
|
||||
-100000000000
|
||||
-1000000000000
|
||||
-10000000000000
|
||||
-100000000000000
|
||||
-1000000000000000
|
||||
-10000000000000000
|
||||
-100000000000000000
|
||||
-1000000000000000000
|
||||
-10000000000000000000
|
||||
-100000000000000000000
|
||||
-1000000000000000000000
|
||||
-10000000000000000000000
|
||||
-100000000000000000000000
|
||||
-1000000000000000000000000
|
||||
-10000000000000000000000000
|
||||
-100000000000000000000000000
|
||||
-1000000000000000000000000000
|
||||
-10000000000000000000000000000
|
||||
-100000000000000000000000000000
|
||||
-1000000000000000000000000000000
|
||||
-10000000000000000000000000000000
|
||||
-100000000000000000000000000000000
|
||||
-1000000000000000000000000000000000
|
||||
-10000000000000000000000000000000000
|
||||
-100000000000000000000000000000000000
|
||||
-1000000000000000000000000000000000000
|
||||
-10000000000000000000000000000000000000
|
||||
-100000000000000000000000000000000000000
|
||||
|
||||
query I
|
||||
SELECT p::HUGEINT::VARCHAR FROM powers_of_ten
|
||||
----
|
||||
1
|
||||
10
|
||||
100
|
||||
1000
|
||||
10000
|
||||
100000
|
||||
1000000
|
||||
10000000
|
||||
100000000
|
||||
1000000000
|
||||
10000000000
|
||||
100000000000
|
||||
1000000000000
|
||||
10000000000000
|
||||
100000000000000
|
||||
1000000000000000
|
||||
10000000000000000
|
||||
100000000000000000
|
||||
1000000000000000000
|
||||
10000000000000000000
|
||||
100000000000000000000
|
||||
1000000000000000000000
|
||||
10000000000000000000000
|
||||
100000000000000000000000
|
||||
1000000000000000000000000
|
||||
10000000000000000000000000
|
||||
100000000000000000000000000
|
||||
1000000000000000000000000000
|
||||
10000000000000000000000000000
|
||||
100000000000000000000000000000
|
||||
1000000000000000000000000000000
|
||||
10000000000000000000000000000000
|
||||
100000000000000000000000000000000
|
||||
1000000000000000000000000000000000
|
||||
10000000000000000000000000000000000
|
||||
100000000000000000000000000000000000
|
||||
1000000000000000000000000000000000000
|
||||
10000000000000000000000000000000000000
|
||||
100000000000000000000000000000000000000
|
||||
-1
|
||||
-10
|
||||
-100
|
||||
-1000
|
||||
-10000
|
||||
-100000
|
||||
-1000000
|
||||
-10000000
|
||||
-100000000
|
||||
-1000000000
|
||||
-10000000000
|
||||
-100000000000
|
||||
-1000000000000
|
||||
-10000000000000
|
||||
-100000000000000
|
||||
-1000000000000000
|
||||
-10000000000000000
|
||||
-100000000000000000
|
||||
-1000000000000000000
|
||||
-10000000000000000000
|
||||
-100000000000000000000
|
||||
-1000000000000000000000
|
||||
-10000000000000000000000
|
||||
-100000000000000000000000
|
||||
-1000000000000000000000000
|
||||
-10000000000000000000000000
|
||||
-100000000000000000000000000
|
||||
-1000000000000000000000000000
|
||||
-10000000000000000000000000000
|
||||
-100000000000000000000000000000
|
||||
-1000000000000000000000000000000
|
||||
-10000000000000000000000000000000
|
||||
-100000000000000000000000000000000
|
||||
-1000000000000000000000000000000000
|
||||
-10000000000000000000000000000000000
|
||||
-100000000000000000000000000000000000
|
||||
-1000000000000000000000000000000000000
|
||||
-10000000000000000000000000000000000000
|
||||
-100000000000000000000000000000000000000
|
||||
|
||||
# test large constants and correct parsing into either HUGEINT or DOUBLE
|
||||
query II
|
||||
SELECT typeof(4832904823908104981209840981240981277), 4832904823908104981209840981240981277
|
||||
----
|
||||
HUGEINT 4832904823908104981209840981240981277
|
||||
|
||||
query II
|
||||
SELECT typeof(48329048239081049812098409812409812772), 48329048239081049812098409812409812772
|
||||
----
|
||||
HUGEINT 48329048239081049812098409812409812772
|
||||
|
||||
query II
|
||||
SELECT typeof(483290482390810498120984098124098127725), 483290482390810498120984098124098127725
|
||||
----
|
||||
DOUBLE 483290482390810498120984098124098127725.0
|
||||
|
||||
query II
|
||||
SELECT typeof(4832904823908104981209840981240981277256), 4832904823908104981209840981240981277256
|
||||
----
|
||||
DOUBLE 4832904823908104981209840981240981277256.0
|
||||
|
||||
query II
|
||||
SELECT typeof(48329048239081049812098409812409812772568), 48329048239081049812098409812409812772568
|
||||
----
|
||||
DOUBLE 48329048239081049812098409812409812772568.0
|
||||
|
||||
query II
|
||||
SELECT typeof(483290482390810498120984098124098127725683), 483290482390810498120984098124098127725683
|
||||
----
|
||||
DOUBLE 483290482390810498120984098124098127725683.0
|
||||
|
||||
query I
|
||||
SELECT 0::HUGEINT::VARCHAR
|
||||
----
|
||||
0
|
||||
|
||||
# hugeint -> uints
|
||||
|
||||
# uint8
|
||||
query I
|
||||
select '255'::HUGEINT::UINT8
|
||||
----
|
||||
255
|
||||
|
||||
statement error
|
||||
select '-1'::hugeint::uint8
|
||||
----
|
||||
<REGEX>:Conversion Error.*INT128.*is out of range for.*UINT8.*
|
||||
|
||||
statement error
|
||||
select '256'::hugeint::uint8
|
||||
----
|
||||
<REGEX>:Conversion Error.*INT128.*is out of range for.*UINT8.*
|
||||
|
||||
# uint16
|
||||
query I
|
||||
select '65535'::HUGEINT::UINT16
|
||||
----
|
||||
65535
|
||||
|
||||
statement error
|
||||
select '-1'::hugeint::uint16
|
||||
----
|
||||
<REGEX>:Conversion Error.*INT128.*is out of range for.*UINT16.*
|
||||
|
||||
statement error
|
||||
select '65536'::hugeint::uint16
|
||||
----
|
||||
<REGEX>:Conversion Error.*INT128.*is out of range for.*UINT16.*
|
||||
|
||||
# uint32
|
||||
query I
|
||||
select '4294967295'::HUGEINT::UINT32
|
||||
----
|
||||
4294967295
|
||||
|
||||
statement error
|
||||
select '-1'::hugeint::uint32
|
||||
----
|
||||
<REGEX>:Conversion Error.*INT128.*is out of range for.*UINT32.*
|
||||
|
||||
statement error
|
||||
select '4294967296'::hugeint::uint32
|
||||
----
|
||||
<REGEX>:Conversion Error.*INT128.*is out of range for.*UINT32.*
|
||||
|
||||
# UINT64
|
||||
query I
|
||||
select '18446744073709551615'::HUGEINT::UINT64
|
||||
----
|
||||
18446744073709551615
|
||||
|
||||
statement error
|
||||
select '-1'::hugeint::UINT64
|
||||
----
|
||||
<REGEX>:Conversion Error.*INT128.*is out of range for.*UINT64.*
|
||||
|
||||
statement error
|
||||
select '18446744073709551616'::hugeint::UINT64
|
||||
----
|
||||
<REGEX>:Conversion Error.*INT128.*is out of range for.*UINT64.*
|
||||
|
||||
# uint -> hugeint
|
||||
query I
|
||||
select '255'::UINT8::HUGEINT
|
||||
----
|
||||
255
|
||||
|
||||
query I
|
||||
select '65535'::UINT16::HUGEINT
|
||||
----
|
||||
65535
|
||||
|
||||
query I
|
||||
select '4294967295'::UINT32::HUGEINT
|
||||
----
|
||||
4294967295
|
||||
|
||||
query I
|
||||
select '18446744073709551615'::UINT64::HUGEINT
|
||||
----
|
||||
18446744073709551615
|
||||
168
external/duckdb/test/common/test_cast_struct.test
vendored
Normal file
168
external/duckdb/test/common/test_cast_struct.test
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
# name: test/common/test_cast_struct.test
|
||||
# description: Test casting structs
|
||||
# group: [common]
|
||||
|
||||
statement ok
|
||||
PRAGMA enable_verification
|
||||
|
||||
statement error
|
||||
SELECT struct_pack(b => 42)::STRUCT(a INT);
|
||||
----
|
||||
<REGEX>:Binder Error.*STRUCT to STRUCT cast must have at least one matching member.*
|
||||
|
||||
statement error
|
||||
SELECT struct_extract(struct_pack(b => 42)::STRUCT(a INT), 'a');
|
||||
----
|
||||
<REGEX>:Binder Error.*STRUCT to STRUCT cast must have at least one matching member.*
|
||||
|
||||
query I
|
||||
SELECT struct_extract(struct_pack(a => 42)::STRUCT(a STRING), 'a');
|
||||
----
|
||||
42
|
||||
|
||||
statement error
|
||||
SELECT struct_extract(struct_pack(b => 42)::ROW(a INT), 'a');
|
||||
----
|
||||
<REGEX>:Binder Error.*STRUCT to STRUCT cast must have at least one matching member.*
|
||||
|
||||
query I
|
||||
SELECT struct_extract(struct_pack(a => 42)::ROW(a INT), 'a');
|
||||
----
|
||||
42
|
||||
|
||||
statement error
|
||||
SELECT struct_extract(struct_pack(b => 42::DOUBLE)::STRUCT(a INT), 'a');
|
||||
----
|
||||
<REGEX>:Binder Error.*STRUCT to STRUCT cast must have at least one matching member.*
|
||||
|
||||
query I
|
||||
SELECT struct_extract(struct_pack(a => 42::DOUBLE)::STRUCT(a INT), 'a');
|
||||
----
|
||||
42
|
||||
|
||||
statement error
|
||||
SELECT struct_extract(struct_pack(b => '42'::DOUBLE)::STRUCT(a INT), 'a');
|
||||
----
|
||||
<REGEX>:Binder Error.*STRUCT to STRUCT cast must have at least one matching member.*
|
||||
|
||||
query I
|
||||
SELECT struct_extract(struct_pack(a => '42'::DOUBLE)::STRUCT(a INT), 'a');
|
||||
----
|
||||
42
|
||||
|
||||
statement error
|
||||
SELECT struct_pack(b => '42'::DOUBLE)::STRUCT(a INT, c STRING)
|
||||
----
|
||||
<REGEX>:Binder Error.*STRUCT to STRUCT cast must have at least one matching member.*
|
||||
|
||||
statement error
|
||||
SELECT struct_pack(b => 'hello'::STRING)::STRUCT(b INT)
|
||||
----
|
||||
Could not convert string 'hello' to INT32
|
||||
|
||||
statement error
|
||||
SELECT struct_pack(a => 'hello'::STRING, b => 'world'::STRING)::STRUCT(a STRING, b INT)
|
||||
----
|
||||
Could not convert string 'world' to INT32
|
||||
|
||||
statement error
|
||||
SELECT struct_pack(a => [1, 2, 3])::STRUCT(a INT)
|
||||
----
|
||||
Unimplemented type for cast (INTEGER[] -> INTEGER)
|
||||
|
||||
statement error
|
||||
SELECT struct_pack(a => struct_pack(b => 42)::STRUCT(b INT))::STRUCT(a INT)
|
||||
----
|
||||
Unimplemented type for cast (STRUCT(b INTEGER) -> INTEGER)
|
||||
|
||||
statement error
|
||||
SELECT struct_pack(b => 'hello'::STRING)::STRUCT(a INT)
|
||||
----
|
||||
<REGEX>:Binder Error.*STRUCT to STRUCT cast must have at least one matching member.*
|
||||
|
||||
statement error
|
||||
SELECT struct_pack(b => '42'::DOUBLE, c => 'asdf'::STRING)::STRUCT(a1 INT, a2 STRING);
|
||||
----
|
||||
<REGEX>:Binder Error.*STRUCT to STRUCT cast must have at least one matching member.*
|
||||
|
||||
query I
|
||||
SELECT struct_pack(a1 => '42'::DOUBLE, a2 => 'asdf'::STRING)::STRUCT(a1 INT, a2 STRING);
|
||||
----
|
||||
{'a1': 42, 'a2': asdf}
|
||||
|
||||
query I
|
||||
SELECT ROW(42, 'asdf');
|
||||
----
|
||||
(42, asdf)
|
||||
|
||||
statement error
|
||||
SELECT ROW();
|
||||
----
|
||||
pack nothing into a struct
|
||||
|
||||
query I
|
||||
SELECT ROW(NULL);
|
||||
----
|
||||
(NULL)
|
||||
|
||||
query I
|
||||
SELECT ROW(NULL, NULL);
|
||||
----
|
||||
(NULL, NULL)
|
||||
|
||||
# MB example
|
||||
query I
|
||||
SELECT CAST(ROW(1, 2) AS ROW(a INTEGER, b INTEGER))
|
||||
----
|
||||
{'a': 1, 'b': 2}
|
||||
|
||||
query I
|
||||
SELECT a::ROW(a INT, b STRING) r FROM (VALUES (ROW(1, 'asdf')), (ROW(4, 'fdsa'))) s(a);
|
||||
----
|
||||
{'a': 1, 'b': asdf}
|
||||
{'a': 4, 'b': fdsa}
|
||||
|
||||
statement error
|
||||
SELECT struct_extract({'a': a}, a) FROM (SELECT a::VARCHAR AS a FROM range(10) tbl(a));
|
||||
----
|
||||
<REGEX>:.*Binder Error.*Key name for struct_extract needs to be a constant string.*
|
||||
|
||||
statement error
|
||||
SELECT struct_extract({'a': 42}, 42)
|
||||
----
|
||||
<REGEX>:.*Binder Error.*can only be used on unnamed structs.*
|
||||
|
||||
query I
|
||||
SELECT struct_extract_at({'a': 42}, 1)
|
||||
----
|
||||
42
|
||||
|
||||
statement error
|
||||
SELECT struct_extract_at({'a': 42}, 0)
|
||||
----
|
||||
<REGEX>:.*Binder Error.*out of range.*
|
||||
|
||||
statement error
|
||||
SELECT struct_extract_at({'a': 42}, 42)
|
||||
----
|
||||
<REGEX>:.*Binder Error.*out of range.*
|
||||
|
||||
# Test string to struct cast within struct casting.
|
||||
query I
|
||||
SELECT {a: {b: '{a: 3, b: "Hello World"}'}}::STRUCT(a STRUCT(b STRUCT(a INT, b VARCHAR)));
|
||||
----
|
||||
{'a': {'b': {'a': 3, 'b': Hello World}}}
|
||||
|
||||
# Test if try_cast continues after encountering error.
|
||||
query I
|
||||
SELECT TRY_CAST(struct_pack(a => 4, b => 'Ducky', c => '1964-06-15')
|
||||
AS STRUCT(a INT, b DOUBLE, c DATE));
|
||||
----
|
||||
{'a': 4, 'b': NULL, 'c': 1964-06-15}
|
||||
|
||||
query I
|
||||
SELECT TRY_CAST(struct_pack(a => 4, b => 'Ducky', c => 'Tommorow', d => {a:3.0})
|
||||
AS STRUCT(a VARCHAR[], b VARCHAR, c DATE, d STRUCT(a INT)));
|
||||
----
|
||||
{'a': NULL, 'b': Ducky, 'c': NULL, 'd': {'a': 3}}
|
||||
|
||||
40
external/duckdb/test/common/test_checksum.cpp
vendored
Normal file
40
external/duckdb/test/common/test_checksum.cpp
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/common/checksum.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
#define NUM_INTS 10
|
||||
|
||||
TEST_CASE("Checksum tests", "[checksum]") {
|
||||
// create a buffer
|
||||
int vals[NUM_INTS];
|
||||
for (size_t i = 0; i < NUM_INTS; i++) {
|
||||
vals[i] = i + 1;
|
||||
}
|
||||
// verify that checksum is consistent
|
||||
uint64_t c1 = Checksum((uint8_t *)vals, sizeof(int) * NUM_INTS);
|
||||
uint64_t c2 = Checksum((uint8_t *)vals, sizeof(int) * NUM_INTS);
|
||||
REQUIRE(c1 == c2);
|
||||
|
||||
// verify that checksum is sort of good
|
||||
vals[3] = 1;
|
||||
uint64_t c3 = Checksum((uint8_t *)vals, sizeof(int) * NUM_INTS);
|
||||
REQUIRE(c1 != c3);
|
||||
|
||||
// verify that zeros in the input does not zero the checksum
|
||||
vals[3] = 0;
|
||||
uint64_t c4 = Checksum((uint8_t *)vals, sizeof(int) * NUM_INTS);
|
||||
REQUIRE(c4 != 0);
|
||||
|
||||
// zero at a different location should change the checksum
|
||||
vals[3] = 4;
|
||||
vals[4] = 0;
|
||||
uint64_t c5 = Checksum((uint8_t *)vals, sizeof(int) * NUM_INTS);
|
||||
REQUIRE(c4 != c5);
|
||||
|
||||
REQUIRE(c1 != c4);
|
||||
REQUIRE(c1 != c5);
|
||||
}
|
||||
238
external/duckdb/test/common/test_file_system.cpp
vendored
Normal file
238
external/duckdb/test/common/test_file_system.cpp
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/common/file_buffer.hpp"
|
||||
#include "duckdb/common/file_system.hpp"
|
||||
#include "duckdb/common/fstream.hpp"
|
||||
#include "duckdb/common/local_file_system.hpp"
|
||||
#include "duckdb/common/vector.hpp"
|
||||
#include "duckdb/common/virtual_file_system.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
static void create_dummy_file(string fname) {
|
||||
string normalized_string;
|
||||
if (StringUtil::StartsWith(fname, "file:///")) {
|
||||
#ifdef _WIN32
|
||||
normalized_string = fname.substr(8);
|
||||
#else
|
||||
normalized_string = fname.substr(7);
|
||||
#endif
|
||||
|
||||
} else if (StringUtil::StartsWith(fname, "file://localhost/")) {
|
||||
#ifdef _WIN32
|
||||
normalized_string = fname.substr(18);
|
||||
#else
|
||||
normalized_string = fname.substr(18);
|
||||
#endif
|
||||
} else {
|
||||
normalized_string = fname;
|
||||
}
|
||||
|
||||
ofstream outfile(normalized_string);
|
||||
outfile << "I_AM_A_DUMMY" << endl;
|
||||
outfile.close();
|
||||
}
|
||||
|
||||
TEST_CASE("Make sure the file:// protocol works as expected", "[file_system]") {
|
||||
duckdb::unique_ptr<FileSystem> fs = FileSystem::CreateLocal();
|
||||
auto dname = fs->JoinPath(fs->GetWorkingDirectory(), TestCreatePath("TEST_DIR"));
|
||||
auto dname_converted_slashes = StringUtil::Replace(dname, "\\", "/");
|
||||
|
||||
// handle differences between windows and linux
|
||||
if (StringUtil::StartsWith(dname_converted_slashes, "/")) {
|
||||
dname_converted_slashes = dname_converted_slashes.substr(1);
|
||||
}
|
||||
|
||||
// Path of format file:///bla/bla on 'nix and file:///X:/bla/bla on Windows
|
||||
auto dname_triple_slash = fs->JoinPath("file://", dname_converted_slashes);
|
||||
// Path of format file://localhost/bla/bla on 'nix and file://localhost/X:/bla/bla on Windows
|
||||
auto dname_localhost = fs->JoinPath("file://localhost", dname_converted_slashes);
|
||||
auto dname_no_host = fs->JoinPath("file:", dname_converted_slashes);
|
||||
|
||||
string fname = "TEST_FILE";
|
||||
string fname2 = "TEST_FILE_TWO";
|
||||
|
||||
if (fs->DirectoryExists(dname_triple_slash)) {
|
||||
fs->RemoveDirectory(dname_triple_slash);
|
||||
}
|
||||
|
||||
fs->CreateDirectory(dname_triple_slash);
|
||||
REQUIRE(fs->DirectoryExists(dname_triple_slash));
|
||||
REQUIRE(!fs->FileExists(dname_triple_slash));
|
||||
|
||||
// we can call this again and nothing happens
|
||||
fs->CreateDirectory(dname_triple_slash);
|
||||
|
||||
auto fname_in_dir = fs->JoinPath(dname_triple_slash, fname);
|
||||
auto fname_in_dir2 = fs->JoinPath(dname_localhost, fname2);
|
||||
auto fname_in_dir3 = fs->JoinPath(dname_no_host, fname2);
|
||||
|
||||
create_dummy_file(fname_in_dir);
|
||||
REQUIRE(fs->FileExists(fname_in_dir));
|
||||
REQUIRE(!fs->DirectoryExists(fname_in_dir));
|
||||
|
||||
size_t n_files = 0;
|
||||
REQUIRE(fs->ListFiles(dname_triple_slash, [&n_files](const string &path, bool) { n_files++; }));
|
||||
|
||||
REQUIRE(n_files == 1);
|
||||
|
||||
REQUIRE(fs->FileExists(fname_in_dir));
|
||||
REQUIRE(!fs->FileExists(fname_in_dir2));
|
||||
|
||||
auto file_listing = fs->Glob(fs->JoinPath(dname_triple_slash, "*"));
|
||||
REQUIRE(file_listing[0].path == fname_in_dir);
|
||||
|
||||
fs->MoveFile(fname_in_dir, fname_in_dir2);
|
||||
|
||||
REQUIRE(!fs->FileExists(fname_in_dir));
|
||||
REQUIRE(fs->FileExists(fname_in_dir2));
|
||||
|
||||
auto file_listing_after_move = fs->Glob(fs->JoinPath(dname_no_host, "*"));
|
||||
REQUIRE(file_listing_after_move[0].path == fname_in_dir3);
|
||||
|
||||
fs->RemoveDirectory(dname_triple_slash);
|
||||
|
||||
REQUIRE(!fs->DirectoryExists(dname_triple_slash));
|
||||
REQUIRE(!fs->FileExists(fname_in_dir));
|
||||
REQUIRE(!fs->FileExists(fname_in_dir2));
|
||||
}
|
||||
|
||||
TEST_CASE("Make sure file system operators work as advertised", "[file_system]") {
|
||||
duckdb::unique_ptr<FileSystem> fs = FileSystem::CreateLocal();
|
||||
auto dname = TestCreatePath("TEST_DIR");
|
||||
string fname = "TEST_FILE";
|
||||
string fname2 = "TEST_FILE_TWO";
|
||||
|
||||
if (fs->DirectoryExists(dname)) {
|
||||
fs->RemoveDirectory(dname);
|
||||
}
|
||||
|
||||
fs->CreateDirectory(dname);
|
||||
REQUIRE(fs->DirectoryExists(dname));
|
||||
REQUIRE(!fs->FileExists(dname));
|
||||
|
||||
// we can call this again and nothing happens
|
||||
fs->CreateDirectory(dname);
|
||||
|
||||
auto fname_in_dir = fs->JoinPath(dname, fname);
|
||||
auto fname_in_dir2 = fs->JoinPath(dname, fname2);
|
||||
|
||||
create_dummy_file(fname_in_dir);
|
||||
REQUIRE(fs->FileExists(fname_in_dir));
|
||||
REQUIRE(!fs->DirectoryExists(fname_in_dir));
|
||||
|
||||
size_t n_files = 0;
|
||||
REQUIRE(fs->ListFiles(dname, [&n_files](const string &path, bool) { n_files++; }));
|
||||
|
||||
REQUIRE(n_files == 1);
|
||||
|
||||
REQUIRE(fs->FileExists(fname_in_dir));
|
||||
REQUIRE(!fs->FileExists(fname_in_dir2));
|
||||
|
||||
fs->MoveFile(fname_in_dir, fname_in_dir2);
|
||||
|
||||
REQUIRE(!fs->FileExists(fname_in_dir));
|
||||
REQUIRE(fs->FileExists(fname_in_dir2));
|
||||
|
||||
fs->RemoveDirectory(dname);
|
||||
|
||||
REQUIRE(!fs->DirectoryExists(dname));
|
||||
REQUIRE(!fs->FileExists(fname_in_dir));
|
||||
REQUIRE(!fs->FileExists(fname_in_dir2));
|
||||
}
|
||||
|
||||
// note: the integer count is chosen as 512 so that we write 512*8=4096 bytes to the file
|
||||
// this is required for the Direct-IO as on Windows Direct-IO can only write multiples of sector sizes
|
||||
// sector sizes are typically one of [512/1024/2048/4096] bytes, hence a 4096 bytes write succeeds.
|
||||
#define INTEGER_COUNT 512
|
||||
|
||||
TEST_CASE("Test file operations", "[file_system]") {
|
||||
duckdb::unique_ptr<FileSystem> fs = FileSystem::CreateLocal();
|
||||
duckdb::unique_ptr<FileHandle> handle, handle2;
|
||||
int64_t test_data[INTEGER_COUNT];
|
||||
for (int i = 0; i < INTEGER_COUNT; i++) {
|
||||
test_data[i] = i;
|
||||
}
|
||||
|
||||
auto fname = TestCreatePath("test_file");
|
||||
|
||||
// standard reading/writing test
|
||||
|
||||
// open file for writing
|
||||
REQUIRE_NOTHROW(handle = fs->OpenFile(fname, FileFlags::FILE_FLAGS_WRITE | FileFlags::FILE_FLAGS_FILE_CREATE));
|
||||
// write 10 integers
|
||||
REQUIRE_NOTHROW(handle->Write(QueryContext(), (void *)test_data, sizeof(int64_t) * INTEGER_COUNT, 0));
|
||||
// close the file
|
||||
handle.reset();
|
||||
|
||||
for (int i = 0; i < INTEGER_COUNT; i++) {
|
||||
test_data[i] = 0;
|
||||
}
|
||||
// now open the file for reading
|
||||
REQUIRE_NOTHROW(handle = fs->OpenFile(fname, FileFlags::FILE_FLAGS_READ));
|
||||
// read the 10 integers back
|
||||
REQUIRE_NOTHROW(handle->Read(QueryContext(), (void *)test_data, sizeof(int64_t) * INTEGER_COUNT, 0));
|
||||
// check the values of the integers
|
||||
for (int i = 0; i < 10; i++) {
|
||||
REQUIRE(test_data[i] == i);
|
||||
}
|
||||
handle.reset();
|
||||
fs->RemoveFile(fname);
|
||||
}
|
||||
|
||||
TEST_CASE("absolute paths", "[file_system]") {
|
||||
duckdb::LocalFileSystem fs;
|
||||
|
||||
#ifndef _WIN32
|
||||
REQUIRE(fs.IsPathAbsolute("/home/me"));
|
||||
REQUIRE(!fs.IsPathAbsolute("./me"));
|
||||
REQUIRE(!fs.IsPathAbsolute("me"));
|
||||
#else
|
||||
const std::string long_path = "\\\\?\\D:\\very long network\\";
|
||||
REQUIRE(fs.IsPathAbsolute(long_path));
|
||||
const std::string network = "\\\\network_drive\\filename.csv";
|
||||
REQUIRE(fs.IsPathAbsolute(network));
|
||||
REQUIRE(fs.IsPathAbsolute("C:\\folder\\filename.csv"));
|
||||
REQUIRE(fs.IsPathAbsolute("C:/folder\\filename.csv"));
|
||||
REQUIRE(fs.NormalizeAbsolutePath("C:/folder\\filename.csv") == "c:\\folder\\filename.csv");
|
||||
REQUIRE(fs.NormalizeAbsolutePath(network) == network);
|
||||
REQUIRE(fs.NormalizeAbsolutePath(long_path) == "\\\\?\\d:\\very long network\\");
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("extract subsystem", "[file_system]") {
|
||||
duckdb::VirtualFileSystem vfs;
|
||||
auto local_filesystem = FileSystem::CreateLocal();
|
||||
auto *local_filesystem_ptr = local_filesystem.get();
|
||||
vfs.RegisterSubSystem(std::move(local_filesystem));
|
||||
|
||||
// Extract a non-existent filesystem gets nullptr.
|
||||
REQUIRE(vfs.ExtractSubSystem("non-existent") == nullptr);
|
||||
|
||||
// Extract an existing filesystem.
|
||||
auto extracted_filesystem = vfs.ExtractSubSystem(local_filesystem_ptr->GetName());
|
||||
REQUIRE(extracted_filesystem.get() == local_filesystem_ptr);
|
||||
|
||||
// Re-extraction gets nullptr.
|
||||
REQUIRE(vfs.ExtractSubSystem("non-existent") == nullptr);
|
||||
|
||||
// Register a subfilesystem and disable, which is not allowed to extract.
|
||||
const ::duckdb::string target_fs = extracted_filesystem->GetName();
|
||||
const ::duckdb::vector<string> disabled_subfilesystems {target_fs};
|
||||
vfs.RegisterSubSystem(std::move(extracted_filesystem));
|
||||
vfs.SetDisabledFileSystems(disabled_subfilesystems);
|
||||
REQUIRE(vfs.ExtractSubSystem(target_fs) == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("re-register subsystem", "[file_system]") {
|
||||
duckdb::VirtualFileSystem vfs;
|
||||
|
||||
// First time registration should succeed.
|
||||
auto local_filesystem = FileSystem::CreateLocal();
|
||||
vfs.RegisterSubSystem(std::move(local_filesystem));
|
||||
|
||||
// Re-register an already registered subfilesystem should throw.
|
||||
auto second_local_filesystem = FileSystem::CreateLocal();
|
||||
REQUIRE_THROWS(vfs.RegisterSubSystem(std::move(second_local_filesystem)));
|
||||
}
|
||||
98
external/duckdb/test/common/test_hyperlog.cpp
vendored
Normal file
98
external/duckdb/test/common/test_hyperlog.cpp
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/common/serializer/binary_deserializer.hpp"
|
||||
#include "duckdb/common/serializer/binary_serializer.hpp"
|
||||
#include "duckdb/common/serializer/memory_stream.hpp"
|
||||
#include "duckdb/common/types/hash.hpp"
|
||||
#include "duckdb/common/types/hyperloglog.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test that hyperloglog works", "[hyperloglog]") {
|
||||
HyperLogLog log;
|
||||
// add a million elements of the same value
|
||||
int x = 4;
|
||||
for (size_t i = 0; i < 1000000; i++) {
|
||||
log.InsertElement(Hash(x));
|
||||
}
|
||||
REQUIRE(log.Count() == 1);
|
||||
|
||||
// now add a million different values
|
||||
HyperLogLog log2;
|
||||
for (size_t i = 0; i < 1000000; i++) {
|
||||
x = i;
|
||||
log2.InsertElement(Hash(x));
|
||||
}
|
||||
// the count is approximate, but should be pretty close to a million
|
||||
size_t count = log2.Count();
|
||||
REQUIRE(count > 950000LL);
|
||||
REQUIRE(count < 1050000LL);
|
||||
|
||||
// now we can merge the HLLs
|
||||
log.Merge(log2);
|
||||
// the count should be pretty much the same
|
||||
count = log.Count();
|
||||
REQUIRE(count > 950000LL);
|
||||
REQUIRE(count < 1050000LL);
|
||||
|
||||
// now test composability of the merge
|
||||
// add everything to one big_hll one
|
||||
// add chunks to small_hll ones and then merge them
|
||||
// the result should be the same
|
||||
HyperLogLog big_hll;
|
||||
HyperLogLog small_hll[16];
|
||||
for (size_t i = 0; i < 1000000; i++) {
|
||||
x = ((2 * i) + 3) % (i + 3 / 2);
|
||||
big_hll.InsertElement(Hash(x));
|
||||
small_hll[i % 16].InsertElement(Hash(x));
|
||||
}
|
||||
// now merge them into one big_hll HyperLogLog
|
||||
for (idx_t i = 1; i < 16; i++) {
|
||||
small_hll[0].Merge(small_hll[i]);
|
||||
}
|
||||
// the result should be identical to the big_hll one
|
||||
REQUIRE(small_hll[0].Count() == big_hll.Count());
|
||||
}
|
||||
|
||||
TEST_CASE("Test different hyperloglog version serialization", "[hyperloglog]") {
|
||||
Allocator allocator;
|
||||
MemoryStream stream(allocator);
|
||||
SerializationOptions options;
|
||||
options.serialization_compatibility = SerializationCompatibility::FromString("v1.0.0");
|
||||
|
||||
// Add 100M values to a NEW HyperLogLog
|
||||
HyperLogLog original_log;
|
||||
for (size_t i = 0; i < 100000000; i++) {
|
||||
original_log.InsertElement(Hash(i));
|
||||
|
||||
switch (i + 1) {
|
||||
case 1:
|
||||
case 10:
|
||||
case 100:
|
||||
case 1000:
|
||||
case 10000:
|
||||
case 100000:
|
||||
case 1000000:
|
||||
case 10000000:
|
||||
case 100000000:
|
||||
break; // We roundtrip the serialization every order of magnitude
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
// Grab the count
|
||||
const auto original_count = original_log.Count();
|
||||
|
||||
// Serialize it as an OLD HyperLogLog
|
||||
stream.Rewind();
|
||||
BinarySerializer::Serialize(original_log, stream, options);
|
||||
|
||||
// Deserialize it, creating a NEW HyperLogLog from the OLD one
|
||||
stream.Rewind();
|
||||
auto deserialized_log = BinaryDeserializer::Deserialize<HyperLogLog>(stream);
|
||||
|
||||
// Verify that the deserialized count is equal
|
||||
const auto deserialized_count = deserialized_log->Count();
|
||||
REQUIRE(original_count == deserialized_count);
|
||||
}
|
||||
}
|
||||
98
external/duckdb/test/common/test_local_file_urls.test
vendored
Normal file
98
external/duckdb/test/common/test_local_file_urls.test
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
# name: test/common/test_local_file_urls.test
|
||||
# group: [common]
|
||||
|
||||
# Note: __WORKING_DIRECTORY__ will be replaced with the full path to the working dir of the tests (root of duckdb repo)
|
||||
|
||||
statement ok
|
||||
SET VARIABLE work_dir_no_host='file:/' || ltrim(replace('__WORKING_DIRECTORY__', '\', '/'), '/')
|
||||
|
||||
statement ok
|
||||
SET VARIABLE work_dir_triple_slash='file:///' || ltrim(replace('__WORKING_DIRECTORY__', '\', '/'), '/')
|
||||
|
||||
statement ok
|
||||
SET VARIABLE work_dir_localhost='file://localhost/' || ltrim(replace('__WORKING_DIRECTORY__', '\', '/'), '/')
|
||||
|
||||
# testing file:/some/path/to/duckdb/repo
|
||||
query II
|
||||
SELECT * FROM read_csv_auto(getvariable('work_dir_no_host') || '/data/csv/normalize.csv');
|
||||
----
|
||||
John ipsum
|
||||
|
||||
# testing file:///some/path/to/duckdb/repo
|
||||
query II
|
||||
SELECT * FROM read_csv_auto(getvariable('work_dir_triple_slash') || '/data/csv/normalize.csv');
|
||||
----
|
||||
John ipsum
|
||||
|
||||
# testing file://localhost/some/path/to/duckdb/repo
|
||||
query II
|
||||
SELECT * FROM read_csv_auto(getvariable('work_dir_localhost') || '/data/csv/normalize.csv');
|
||||
----
|
||||
John ipsum
|
||||
|
||||
# Test glob with file:/some/path
|
||||
query II
|
||||
SELECT file[:6], parse_filename(file) FROM glob(getvariable('work_dir_no_host') || '/data/*/bad_date_timestamp_mix.csv')
|
||||
----
|
||||
file:/ bad_date_timestamp_mix.csv
|
||||
|
||||
# Test glob with file:///some/path
|
||||
query II
|
||||
SELECT file[:8], parse_filename(file) FROM glob(getvariable('work_dir_triple_slash') || '/data/*/bad_date_timestamp_mix.csv')
|
||||
----
|
||||
file:/// bad_date_timestamp_mix.csv
|
||||
|
||||
# Test glob with file://localhost/some/path/to/duckdb/repo
|
||||
query II
|
||||
SELECT file[:17], parse_filename(file) FROM glob(getvariable('work_dir_localhost') || '/data/*/bad_date_timestamp_mix.csv')
|
||||
----
|
||||
file://localhost/ bad_date_timestamp_mix.csv
|
||||
|
||||
# Test scanning multiple files using glob with file:/some/path
|
||||
query III
|
||||
SELECT id, filename[:6], parse_filename(filename) FROM read_csv_auto(getvariable('work_dir_no_host') || '/data/csv/hive-partitioning/simple/*/*/test.csv', filename=1) ORDER BY id
|
||||
----
|
||||
1 file:/ test.csv
|
||||
2 file:/ test.csv
|
||||
|
||||
# Test scanning multiple files using glob with file:///some/path
|
||||
query III
|
||||
SELECT id, filename[:8], parse_filename(filename) FROM read_csv_auto(getvariable('work_dir_triple_slash') || '/data/csv/hive-partitioning/simple/*/*/test.csv', filename=1) ORDER BY id
|
||||
----
|
||||
1 file:/// test.csv
|
||||
2 file:/// test.csv
|
||||
|
||||
# Test scanning multiple files using glob with file://localhost/some/path
|
||||
query III
|
||||
SELECT id, filename[:17], parse_filename(filename) FROM read_csv_auto(getvariable('work_dir_localhost') || '/data/csv/hive-partitioning/simple/*/*/test.csv', filename=1) ORDER BY id
|
||||
----
|
||||
1 file://localhost/ test.csv
|
||||
2 file://localhost/ test.csv
|
||||
|
||||
require noforcestorage
|
||||
|
||||
# Ensure secrets work correctly using the file://
|
||||
statement ok
|
||||
create secret secret_file_url_tripleslash (TYPE HTTP, scope 'file:///');
|
||||
|
||||
statement ok
|
||||
create secret secret_file_url_localhost (TYPE HTTP, scope 'file://localhost/');
|
||||
|
||||
statement ok
|
||||
create secret secret_without_file_path (TYPE HTTP);
|
||||
|
||||
query I
|
||||
SELECT name FROM which_secret(getvariable('work_dir_triple_slash') || '/data/csv/hive-partitioning/simple/*/*/test.csv', 'http')
|
||||
----
|
||||
secret_file_url_tripleslash
|
||||
|
||||
query I
|
||||
SELECT name FROM which_secret(getvariable('work_dir_localhost') || '/data/csv/hive-partitioning/simple/*/*/test.csv', 'http')
|
||||
----
|
||||
secret_file_url_localhost
|
||||
|
||||
# raw paths now do not match
|
||||
query I
|
||||
SELECT name FROM which_secret('__WORKING_DIRECTORY__/data/csv/hive-partitioning/simple/*/*/test.csv', 'http')
|
||||
----
|
||||
secret_without_file_path
|
||||
60
external/duckdb/test/common/test_numeric_cast.cpp
vendored
Normal file
60
external/duckdb/test/common/test_numeric_cast.cpp
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/common/numeric_utils.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Numeric cast checks", "[numeric_cast]") {
|
||||
#ifdef DUCKDB_CRASH_ON_ASSERT
|
||||
return;
|
||||
#endif
|
||||
// unsigned-unsiged
|
||||
// can not fail upcasting unsigned type
|
||||
REQUIRE_NOTHROW(NumericCast<uint16_t, uint8_t>(NumericLimits<uint8_t>::Maximum()));
|
||||
REQUIRE_NOTHROW(NumericCast<uint16_t, uint8_t>(NumericLimits<uint8_t>::Minimum()));
|
||||
|
||||
// we can down cast if value fits
|
||||
REQUIRE_NOTHROW(NumericCast<uint8_t, uint16_t>(NumericLimits<uint8_t>::Maximum()));
|
||||
|
||||
// but not if it doesn't
|
||||
REQUIRE_THROWS(NumericCast<uint8_t, uint16_t>(NumericLimits<uint8_t>::Maximum() + 1));
|
||||
|
||||
// signed-signed, same as above
|
||||
REQUIRE_NOTHROW(NumericCast<int16_t, int8_t>(NumericLimits<int8_t>::Maximum()));
|
||||
REQUIRE_NOTHROW(NumericCast<int16_t, int8_t>(NumericLimits<int8_t>::Minimum()));
|
||||
REQUIRE_NOTHROW(NumericCast<int8_t, int16_t>(NumericLimits<int8_t>::Maximum()));
|
||||
REQUIRE_THROWS(NumericCast<int8_t, int16_t>(NumericLimits<int8_t>::Maximum() + 1));
|
||||
REQUIRE_THROWS(NumericCast<int8_t, int16_t>(NumericLimits<int8_t>::Minimum() - 1));
|
||||
|
||||
// unsigned to signed
|
||||
REQUIRE_NOTHROW(NumericCast<int8_t, uint8_t>(NumericLimits<int8_t>::Maximum()));
|
||||
REQUIRE_NOTHROW(NumericCast<int8_t, uint8_t>(NumericLimits<uint8_t>::Minimum()));
|
||||
|
||||
// uint8 max will not fit in int8
|
||||
REQUIRE_THROWS(NumericCast<int8_t, uint8_t>(NumericLimits<uint8_t>::Maximum()));
|
||||
|
||||
// signed to unsigned
|
||||
// can cast int8 max to uint8
|
||||
REQUIRE_NOTHROW(NumericCast<uint8_t, int8_t>(NumericLimits<int8_t>::Maximum()));
|
||||
// cat cast int8 min to unit8
|
||||
REQUIRE_THROWS(NumericCast<uint8_t, int8_t>(NumericLimits<int8_t>::Minimum()));
|
||||
|
||||
// can't cast anything negative to anything unsigned
|
||||
REQUIRE_THROWS(NumericCast<uint64_t, int8_t>(-1));
|
||||
REQUIRE_THROWS(NumericCast<uint64_t, int16_t>(-1));
|
||||
REQUIRE_THROWS(NumericCast<uint64_t, int32_t>(-1));
|
||||
REQUIRE_THROWS(NumericCast<uint64_t, int64_t>(-1));
|
||||
|
||||
// can't downcast big number
|
||||
REQUIRE_THROWS(NumericCast<int64_t, uint64_t>(NumericLimits<uint64_t>::Maximum()));
|
||||
REQUIRE_THROWS(NumericCast<int32_t, uint64_t>(NumericLimits<uint64_t>::Maximum()));
|
||||
REQUIRE_THROWS(NumericCast<uint32_t, uint64_t>(NumericLimits<uint64_t>::Maximum()));
|
||||
REQUIRE_THROWS(NumericCast<int16_t, uint64_t>(NumericLimits<uint64_t>::Maximum()));
|
||||
REQUIRE_THROWS(NumericCast<uint16_t, uint64_t>(NumericLimits<uint64_t>::Maximum()));
|
||||
REQUIRE_THROWS(NumericCast<int8_t, uint64_t>(NumericLimits<uint64_t>::Maximum()));
|
||||
REQUIRE_THROWS(NumericCast<uint8_t, uint64_t>(NumericLimits<uint64_t>::Maximum()));
|
||||
|
||||
// TODO this should throw but doesn't
|
||||
// REQUIRE_THROWS(NumericCast<uint8_t, hugeint_t>(hugeint_t(-1)));
|
||||
}
|
||||
81
external/duckdb/test/common/test_parse_logical_type.cpp
vendored
Normal file
81
external/duckdb/test/common/test_parse_logical_type.cpp
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/common/exception.hpp"
|
||||
#include "duckdb/common/string.hpp"
|
||||
#include "duckdb/main/config.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
|
||||
TEST_CASE("Test parse logical type", "[parse_logical_type]") {
|
||||
SECTION("simple types") {
|
||||
REQUIRE(DBConfig::ParseLogicalType("integer") == LogicalType::INTEGER);
|
||||
REQUIRE(DBConfig::ParseLogicalType("any") == LogicalType::ANY);
|
||||
}
|
||||
|
||||
SECTION("nested types") {
|
||||
// list
|
||||
REQUIRE(DBConfig::ParseLogicalType("ANY[]") == LogicalType::LIST(LogicalType::ANY));
|
||||
REQUIRE(DBConfig::ParseLogicalType("VARCHAR[]") == LogicalType::LIST(LogicalType::VARCHAR));
|
||||
|
||||
// array
|
||||
REQUIRE(DBConfig::ParseLogicalType("ANY[3]") == LogicalType::ARRAY(LogicalType::ANY, 3));
|
||||
REQUIRE(DBConfig::ParseLogicalType("FLOAT[42]") == LogicalType::ARRAY(LogicalType::FLOAT, 42));
|
||||
REQUIRE(DBConfig::ParseLogicalType("VARCHAR[100000]") ==
|
||||
LogicalType::ARRAY(LogicalType::VARCHAR, ArrayType::MAX_ARRAY_SIZE));
|
||||
|
||||
// map
|
||||
REQUIRE(DBConfig::ParseLogicalType("MAP(VARCHAR, VARCHAR)") ==
|
||||
LogicalType::MAP(LogicalType::VARCHAR, LogicalType::VARCHAR));
|
||||
REQUIRE(DBConfig::ParseLogicalType("MAP(ANY,ANY)") == LogicalType::MAP(LogicalType::ANY, LogicalType::ANY));
|
||||
REQUIRE(DBConfig::ParseLogicalType("MAP(INTEGER,ANY)") ==
|
||||
LogicalType::MAP(LogicalType::INTEGER, LogicalType::ANY));
|
||||
REQUIRE(DBConfig::ParseLogicalType("MAP(ANY, DOUBLE)") ==
|
||||
LogicalType::MAP(LogicalType::ANY, LogicalType::DOUBLE));
|
||||
|
||||
// union
|
||||
child_list_t<LogicalType> union_members;
|
||||
union_members.emplace_back(make_pair("num", LogicalTypeId::INTEGER));
|
||||
union_members.emplace_back(make_pair("v", LogicalTypeId::VARCHAR));
|
||||
union_members.emplace_back(make_pair("f", LogicalTypeId::FLOAT));
|
||||
REQUIRE(DBConfig::ParseLogicalType("UNION(num INTEGER, v VARCHAR, f FLOAT)") ==
|
||||
LogicalType::UNION(union_members));
|
||||
|
||||
// struct
|
||||
child_list_t<LogicalType> struct_children;
|
||||
struct_children.emplace_back(make_pair("year", LogicalTypeId::BIGINT));
|
||||
struct_children.emplace_back(make_pair("month", LogicalTypeId::BIGINT));
|
||||
struct_children.emplace_back(make_pair("day", LogicalTypeId::BIGINT));
|
||||
REQUIRE(DBConfig::ParseLogicalType("STRUCT(year BIGINT, month BIGINT, day BIGINT)") ==
|
||||
LogicalType::STRUCT(struct_children));
|
||||
}
|
||||
|
||||
SECTION("deeper nested types") {
|
||||
// list of lists
|
||||
REQUIRE(DBConfig::ParseLogicalType("VARCHAR[][]") ==
|
||||
LogicalType::LIST(LogicalType::LIST(LogicalType::VARCHAR)));
|
||||
|
||||
// array of lists
|
||||
REQUIRE(DBConfig::ParseLogicalType("VARCHAR[][3]") ==
|
||||
LogicalType::ARRAY(LogicalType::LIST(LogicalType::VARCHAR), 3));
|
||||
|
||||
// list of structs
|
||||
child_list_t<LogicalType> date_struct_children;
|
||||
date_struct_children.emplace_back(make_pair("year", LogicalTypeId::BIGINT));
|
||||
date_struct_children.emplace_back(make_pair("month", LogicalTypeId::BIGINT));
|
||||
date_struct_children.emplace_back(make_pair("day", LogicalTypeId::BIGINT));
|
||||
REQUIRE(DBConfig::ParseLogicalType("STRUCT(year BIGINT, month BIGINT, day BIGINT)[]") ==
|
||||
LogicalType::LIST(LogicalType::STRUCT(date_struct_children)));
|
||||
|
||||
// map with list as key
|
||||
REQUIRE(DBConfig::ParseLogicalType("MAP(VARCHAR[],FLOAT)") ==
|
||||
LogicalType::MAP(LogicalType::LIST(LogicalType::VARCHAR), LogicalType::FLOAT));
|
||||
|
||||
// struct with list, array and map
|
||||
child_list_t<LogicalType> mix_struct_children;
|
||||
mix_struct_children.emplace_back(make_pair("my_list", LogicalType::LIST(LogicalType::ANY)));
|
||||
mix_struct_children.emplace_back(make_pair("my_array", LogicalType::ARRAY(LogicalType::VARCHAR, 2)));
|
||||
mix_struct_children.emplace_back(
|
||||
make_pair("my_map", LogicalType::MAP(LogicalType::VARCHAR, LogicalType::VARCHAR)));
|
||||
REQUIRE(DBConfig::ParseLogicalType("STRUCT(my_list ANY[], my_array VARCHAR[2], my_map MAP(VARCHAR,VARCHAR))") ==
|
||||
LogicalType::STRUCT(mix_struct_children));
|
||||
}
|
||||
}
|
||||
517
external/duckdb/test/common/test_storage_fuzz.cpp
vendored
Normal file
517
external/duckdb/test/common/test_storage_fuzz.cpp
vendored
Normal file
@@ -0,0 +1,517 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb.hpp"
|
||||
#include "duckdb/common/common.hpp"
|
||||
#include "duckdb/common/local_file_system.hpp"
|
||||
#include "duckdb/common/virtual_file_system.hpp"
|
||||
#include "duckdb/main/materialized_query_result.hpp"
|
||||
#include "test_config.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <shared_mutex>
|
||||
|
||||
using namespace duckdb;
|
||||
|
||||
bool g_enable_verbose_output = false;
|
||||
bool g_enable_info_output = true;
|
||||
|
||||
#define PRINT_VERBOSE(x) \
|
||||
do { \
|
||||
if (g_enable_verbose_output) \
|
||||
std::cout << x << std::endl; \
|
||||
} while (0)
|
||||
|
||||
#define PRINT_INFO(x) \
|
||||
do { \
|
||||
if (g_enable_info_output) \
|
||||
std::cout << x << std::endl; \
|
||||
} while (0)
|
||||
|
||||
bool ends_with(const std::string &str, const std::string &suffix) {
|
||||
// Ensure str is at least as long as suffix
|
||||
if (str.length() < suffix.length()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare the ending characters
|
||||
return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;
|
||||
}
|
||||
|
||||
class FaultInjectionFileSystem : public duckdb::LocalFileSystem {
|
||||
public:
|
||||
enum FaultInjectionSite {
|
||||
WRITE = 0,
|
||||
FSYNC = 1,
|
||||
};
|
||||
|
||||
void Write(duckdb::FileHandle &handle, void *buffer, int64_t nr_bytes, idx_t location) override {
|
||||
PRINT_VERBOSE("FS write offset=" << location << " bytes=" << nr_bytes);
|
||||
if (is_db_file(handle)) {
|
||||
ThrowInjectedFaultIfSet(FaultInjectionSite::WRITE);
|
||||
}
|
||||
return duckdb::LocalFileSystem::Write(handle, buffer, nr_bytes, location);
|
||||
}
|
||||
|
||||
void FileSync(duckdb::FileHandle &handle) override {
|
||||
PRINT_VERBOSE("FS fsync " << handle.GetPath() << " file_size=" << handle.GetFileSize());
|
||||
if (is_db_file(handle)) {
|
||||
ThrowInjectedFaultIfSet(FaultInjectionSite::FSYNC);
|
||||
}
|
||||
return duckdb::LocalFileSystem::FileSync(handle);
|
||||
}
|
||||
|
||||
void RemoveFile(const duckdb::string &filename,
|
||||
duckdb::optional_ptr<duckdb::FileOpener> opener = nullptr) override {
|
||||
PRINT_VERBOSE("FS remove " << filename);
|
||||
return duckdb::LocalFileSystem::RemoveFile(filename, opener);
|
||||
}
|
||||
|
||||
void Truncate(duckdb::FileHandle &handle, int64_t new_size) override {
|
||||
PRINT_VERBOSE("FS truncate " << handle.GetPath() << " from " << handle.GetFileSize() << " to " << new_size);
|
||||
return duckdb::LocalFileSystem::Truncate(handle, new_size);
|
||||
}
|
||||
|
||||
// In linux - trim() is equivalent to zeroing out a range (albeit in a much more efficient manner). Let's
|
||||
// reproduce this behavior regardless of whether the current environment supports it.
|
||||
bool Trim(duckdb::FileHandle &handle, idx_t offset_bytes, idx_t length_bytes) override {
|
||||
PRINT_VERBOSE("FS trim " << handle.GetPath() << " offset=" << offset_bytes << " bytes=" << length_bytes);
|
||||
|
||||
std::string nulls(length_bytes, '\0');
|
||||
duckdb::LocalFileSystem::Write(handle, (void *)nulls.data(), length_bytes, offset_bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Will inject a single occurrence of a fault
|
||||
void InjectFault(FaultInjectionSite site) {
|
||||
std::lock_guard<std::mutex> l(fault_m_);
|
||||
// Make sure this is not called twice - as we will drop a fault
|
||||
REQUIRE(faults.insert(site).second);
|
||||
}
|
||||
|
||||
protected:
|
||||
void ThrowInjectedFaultIfSet(FaultInjectionSite site) {
|
||||
std::lock_guard<std::mutex> l(fault_m_);
|
||||
auto it = faults.find(site);
|
||||
if (it != faults.end()) {
|
||||
faults.erase(it);
|
||||
PRINT_VERBOSE("Injecting fault");
|
||||
throw duckdb::IOException("Injected fault");
|
||||
}
|
||||
}
|
||||
|
||||
bool is_wal_file(const duckdb::FileHandle &handle) {
|
||||
return ends_with(handle.GetPath(), ".db.wal");
|
||||
}
|
||||
|
||||
bool is_db_file(const duckdb::FileHandle &handle) {
|
||||
return ends_with(handle.GetPath(), ".db");
|
||||
}
|
||||
|
||||
bool is_wal_or_db_file(const duckdb::FileHandle &handle) {
|
||||
return is_db_file(handle) || is_wal_file(handle);
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex fault_m_;
|
||||
std::unordered_set<FaultInjectionSite> faults;
|
||||
};
|
||||
|
||||
// This implementation of duckdb::FileSystem will cache writes to the database file in memory until fsync is called.
|
||||
// It expects all read ranges to be perfectly aligned with previous writes.
|
||||
class LazyFlushFileSystem : public FaultInjectionFileSystem {
|
||||
public:
|
||||
~LazyFlushFileSystem() {
|
||||
if (!unflushed_chunks.empty()) {
|
||||
PRINT_INFO("Unflushed chunks on shutdown for db file");
|
||||
}
|
||||
}
|
||||
|
||||
void Write(duckdb::FileHandle &handle, void *buffer, int64_t nr_bytes, idx_t location) override {
|
||||
PRINT_VERBOSE("FS write offset=" << location << " bytes=" << nr_bytes);
|
||||
|
||||
// We only perform positional writes for the db file
|
||||
REQUIRE(is_db_file(handle));
|
||||
|
||||
std::unique_lock<std::mutex> l(m_);
|
||||
|
||||
ThrowInjectedFaultIfSet(FaultInjectionSite::WRITE);
|
||||
|
||||
// Store the data in memory until fsync occurs
|
||||
PRINT_VERBOSE("Caching chunk " << location << " bytes " << nr_bytes);
|
||||
|
||||
// TODO: be lazy - don't handle partial overwrites
|
||||
REQUIRE(!partially_overlaps_existing_chunk(unflushed_chunks, location, nr_bytes));
|
||||
|
||||
auto it = unflushed_chunks.find(location);
|
||||
if (it != unflushed_chunks.end()) {
|
||||
// Check that if there is an existing chunk - it's size matches exactly
|
||||
REQUIRE(it->second.size() == nr_bytes);
|
||||
it->second = std::string((char *)buffer, nr_bytes);
|
||||
} else {
|
||||
unflushed_chunks.emplace(location, std::string((char *)buffer, nr_bytes));
|
||||
}
|
||||
}
|
||||
|
||||
int64_t Write(duckdb::FileHandle &handle, void *buffer, int64_t nr_bytes) override {
|
||||
// Check appends only occur on the WAL
|
||||
REQUIRE(is_wal_file(handle));
|
||||
|
||||
return duckdb::LocalFileSystem::Write(handle, buffer, nr_bytes);
|
||||
}
|
||||
|
||||
void Read(duckdb::FileHandle &handle, void *buffer, int64_t nr_bytes, idx_t location) override {
|
||||
REQUIRE(is_db_file(handle));
|
||||
|
||||
{
|
||||
// TODO: shared_lock
|
||||
std::unique_lock<std::mutex> l(m_);
|
||||
|
||||
// We don't handle partial overlaps for now.
|
||||
REQUIRE(!partially_overlaps_existing_chunk(unflushed_chunks, location, nr_bytes));
|
||||
|
||||
auto it = unflushed_chunks.find(location);
|
||||
if (it != unflushed_chunks.end()) {
|
||||
PRINT_VERBOSE("FS read cached chunk at offset=" << location << " bytes=" << nr_bytes);
|
||||
const auto &data = it->second;
|
||||
// Assume block-aligned reads
|
||||
REQUIRE(data.size() == nr_bytes);
|
||||
memcpy(buffer, data.data(), nr_bytes);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PRINT_VERBOSE("FS read disk chunk at offset=" << location << " bytes=" << nr_bytes);
|
||||
return duckdb::LocalFileSystem::Read(handle, buffer, nr_bytes, location);
|
||||
}
|
||||
|
||||
int64_t Read(duckdb::FileHandle &handle, void *buffer, int64_t nr_bytes) override {
|
||||
PRINT_VERBOSE("FS read at end of file, bytes=" << nr_bytes);
|
||||
REQUIRE(is_wal_or_db_file(handle));
|
||||
|
||||
if (is_db_file(handle)) {
|
||||
// Just make sure we don't miss the unflushed chunks
|
||||
REQUIRE(unflushed_chunks.empty());
|
||||
}
|
||||
return duckdb::LocalFileSystem::Read(handle, buffer, nr_bytes);
|
||||
}
|
||||
|
||||
void FileSync(duckdb::FileHandle &handle) override {
|
||||
PRINT_VERBOSE("FS fsync " << handle.GetPath() << " file_size=" << handle.GetFileSize());
|
||||
|
||||
REQUIRE(is_wal_or_db_file(handle));
|
||||
|
||||
if (!is_db_file(handle)) {
|
||||
return duckdb::LocalFileSystem::FileSync(handle);
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> l(m_);
|
||||
|
||||
ThrowInjectedFaultIfSet(FaultInjectionSite::FSYNC);
|
||||
|
||||
for (const auto &location_and_data : unflushed_chunks) {
|
||||
auto location = location_and_data.first;
|
||||
const auto &data = location_and_data.second;
|
||||
PRINT_VERBOSE("Flushing chunk " << location << " size=" << data.size());
|
||||
duckdb::LocalFileSystem::Write(handle, (void *)data.data(), data.size(), location);
|
||||
}
|
||||
unflushed_chunks.clear();
|
||||
|
||||
duckdb::LocalFileSystem::FileSync(handle);
|
||||
}
|
||||
|
||||
bool Trim(duckdb::FileHandle &handle, idx_t offset_bytes, idx_t length_bytes) override {
|
||||
REQUIRE(is_db_file(handle));
|
||||
|
||||
std::unique_lock<std::mutex> l(m_);
|
||||
|
||||
// This is just simpler to implement
|
||||
REQUIRE(unflushed_chunks.count(offset_bytes) == 0);
|
||||
|
||||
return FaultInjectionFileSystem::Trim(handle, offset_bytes, length_bytes);
|
||||
}
|
||||
|
||||
void Truncate(duckdb::FileHandle &handle, int64_t new_size) override {
|
||||
std::unique_lock<std::mutex> l(m_);
|
||||
|
||||
if (is_db_file(handle)) {
|
||||
REQUIRE(unflushed_chunks.empty());
|
||||
}
|
||||
|
||||
return duckdb::LocalFileSystem::Truncate(handle, new_size);
|
||||
}
|
||||
|
||||
private:
|
||||
// Lock for modifying unflushed_chunks:
|
||||
// 1. Adding to unflushed_chunks on write
|
||||
// 2. Flushing unflushed_chunks on fsync
|
||||
// 3. Reading from unflushed_chunks
|
||||
std::mutex m_;
|
||||
std::map<idx_t, std::string> unflushed_chunks;
|
||||
|
||||
bool partially_overlaps_existing_chunk(const std::map<idx_t, std::string> &chunks, idx_t offset, size_t length) {
|
||||
idx_t end = offset + length;
|
||||
for (const auto &off_data : chunks) {
|
||||
auto off = off_data.first;
|
||||
const auto &data = off_data.second;
|
||||
idx_t chunk_end = off + data.size();
|
||||
|
||||
// Check for any overlap
|
||||
bool overlap = offset < chunk_end && off < end;
|
||||
|
||||
// Exclude full containment and exact match
|
||||
bool exact_match = (offset == off && length == data.size());
|
||||
|
||||
if (overlap && !exact_match)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <class ResultT>
|
||||
void validate(ResultT &r, std::string expected_err_message = "") {
|
||||
// For debugging
|
||||
bool expected_err = !expected_err_message.empty();
|
||||
if (expected_err != r.HasError() && r.HasError()) {
|
||||
PRINT_INFO("Unexpected: query failed with " << r.GetError());
|
||||
}
|
||||
REQUIRE(expected_err == r.HasError());
|
||||
if (r.HasError()) {
|
||||
REQUIRE(r.GetError().find(expected_err_message) != std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
void cleanup_db_file(const std::string &filename) {
|
||||
bool removed_or_missing = std::remove(filename.c_str()) == 0 || errno == ENOENT;
|
||||
REQUIRE(removed_or_missing);
|
||||
}
|
||||
|
||||
TEST_CASE("simple fault injection storage test", "[storage][.]") {
|
||||
if (!TestConfiguration::TestRunStorageFuzzer()) {
|
||||
SKIP_TEST("storage-fuzzer not enabled");
|
||||
return;
|
||||
}
|
||||
duckdb::DBConfig config;
|
||||
|
||||
LazyFlushFileSystem *raw_fs = new LazyFlushFileSystem();
|
||||
config.file_system = duckdb::make_uniq<duckdb::VirtualFileSystem>(duckdb::unique_ptr<LazyFlushFileSystem>(raw_fs));
|
||||
|
||||
std::string file_path = TestCreatePath("pig.db");
|
||||
|
||||
cleanup_db_file(file_path);
|
||||
|
||||
{
|
||||
duckdb::DuckDB db(file_path, &config);
|
||||
|
||||
duckdb::Connection con(db);
|
||||
|
||||
validate(*con.Query("CREATE TABLE IF NOT EXISTS t(i INTEGER)"));
|
||||
validate(*con.Query("INSERT INTO t SELECT * FROM RANGE(0, 1000)"));
|
||||
validate(*con.Query("INSERT INTO t SELECT * FROM RANGE(0, 1000000)"));
|
||||
|
||||
auto res = con.Query("SELECT count(*) FROM t");
|
||||
validate(*res);
|
||||
REQUIRE(res->GetValue(0, 0).ToString() == "1001000");
|
||||
|
||||
// Writes are ok - fsync are not ok
|
||||
raw_fs->InjectFault(LazyFlushFileSystem::FaultInjectionSite::FSYNC);
|
||||
|
||||
validate(*con.Query("INSERT INTO t SELECT * FROM RANGE(0, 1000000)"),
|
||||
"TransactionContext Error: Failed to commit: Injected fault");
|
||||
|
||||
// Check that the tx was rolled back
|
||||
auto res2 = con.Query("SELECT count(*) FROM t");
|
||||
validate(*res2);
|
||||
REQUIRE(res2->GetValue(0, 0).ToString() == "1001000");
|
||||
}
|
||||
{
|
||||
duckdb::DuckDB db(file_path, &config);
|
||||
duckdb::Connection con(db);
|
||||
|
||||
auto res = con.Query("SELECT count(*) FROM t");
|
||||
validate(*res);
|
||||
REQUIRE(res->GetValue(0, 0).ToString() == "1001000");
|
||||
}
|
||||
}
|
||||
|
||||
enum ActionType {
|
||||
// This action will simply flip the setting true -> false or false -> true
|
||||
TOGGLE_SKIP_CHECKPOINTS_ON_COMMIT = 0,
|
||||
SMALL_WRITE = 2,
|
||||
LARGE_WRITE = 3,
|
||||
LARGE_WRITE_WITH_FAULT = 4,
|
||||
UPDATE = 5,
|
||||
DELETE = 6,
|
||||
RESET_TABLE = 7,
|
||||
};
|
||||
|
||||
TEST_CASE("fuzzed storage test", "[storage][.]") {
|
||||
if (!TestConfiguration::TestRunStorageFuzzer()) {
|
||||
SKIP_TEST("storage-fuzzer not enabled");
|
||||
return;
|
||||
}
|
||||
// DuckDB Configurations
|
||||
duckdb::DBConfig config;
|
||||
config.options.set_variables["debug_skip_checkpoint_on_commit"] = duckdb::Value(true);
|
||||
config.options.trim_free_blocks = true;
|
||||
config.options.checkpoint_on_shutdown = false;
|
||||
|
||||
std::string file_path = TestCreatePath("pig.db");
|
||||
|
||||
cleanup_db_file(file_path);
|
||||
|
||||
{
|
||||
duckdb::DuckDB db(file_path, &config);
|
||||
duckdb::Connection con(db);
|
||||
validate(*con.Query("CREATE TABLE IF NOT EXISTS t(i INTEGER)"));
|
||||
}
|
||||
|
||||
std::map<double, ActionType> pct_to_action = {{0.1, ActionType::TOGGLE_SKIP_CHECKPOINTS_ON_COMMIT},
|
||||
{0.3, ActionType::LARGE_WRITE},
|
||||
{0.5, ActionType::SMALL_WRITE},
|
||||
{0.7, ActionType::UPDATE},
|
||||
{0.85, ActionType::DELETE},
|
||||
{1.0, ActionType::LARGE_WRITE_WITH_FAULT}};
|
||||
|
||||
// Randomly generated sequence of actions
|
||||
std::vector<ActionType> actions = {};
|
||||
|
||||
int NUM_ACTIONS = 30;
|
||||
for (int i = 0; i < NUM_ACTIONS; i++) {
|
||||
double selection = (rand() % 100) / 100.0;
|
||||
for (const auto &prob_type : pct_to_action) {
|
||||
auto prob = prob_type.first;
|
||||
auto type = prob_type.second;
|
||||
if (selection > prob) {
|
||||
continue;
|
||||
}
|
||||
actions.push_back(type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
actions.push_back(RESET_TABLE);
|
||||
for (int i = 0; i < NUM_ACTIONS; i++) {
|
||||
double selection = (rand() % 100) / 100.0;
|
||||
for (const auto &prob_type : pct_to_action) {
|
||||
auto prob = prob_type.first;
|
||||
auto type = prob_type.second;
|
||||
if (selection > prob) {
|
||||
continue;
|
||||
}
|
||||
actions.push_back(type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t offset = 0;
|
||||
bool skip_checkpoint_on_commit = true;
|
||||
std::string expected_checksum = "";
|
||||
duckdb::unique_ptr<QueryResult> previous_result;
|
||||
for (const auto &action : actions) {
|
||||
// Note: the injected file system has to be reset each time. DuckDB construction seems to be std::move'ing them
|
||||
|
||||
/*
|
||||
LazyFlushFileSystem *raw_fs = new LazyFlushFileSystem();
|
||||
config.file_system =
|
||||
duckdb::make_uniq<duckdb::VirtualFileSystem>(duckdb::unique_ptr<LazyFlushFileSystem>(raw_fs));
|
||||
*/
|
||||
|
||||
FaultInjectionFileSystem *raw_fs = new FaultInjectionFileSystem();
|
||||
config.file_system =
|
||||
duckdb::make_uniq<duckdb::VirtualFileSystem>(duckdb::unique_ptr<FaultInjectionFileSystem>(raw_fs));
|
||||
|
||||
duckdb::DuckDB db(file_path, &config);
|
||||
duckdb::Connection con(db);
|
||||
|
||||
// Compute a checksum
|
||||
if (!expected_checksum.empty()) {
|
||||
auto checksum = con.Query("SELECT bit_xor(hash(i)) FROM t");
|
||||
validate(*checksum);
|
||||
auto computed_checksum = checksum->GetValue(0, 0).ToString();
|
||||
PRINT_INFO("Verifying checksum computed=" << computed_checksum << ", actual=" << expected_checksum);
|
||||
if (computed_checksum != expected_checksum) {
|
||||
auto result = con.Query("SELECT * FROM t ORDER BY ALL");
|
||||
string error;
|
||||
ColumnDataCollection::ResultEquals(previous_result->Cast<MaterializedQueryResult>().Collection(),
|
||||
result->Cast<MaterializedQueryResult>().Collection(), error);
|
||||
Printer::PrintF("Checksum failure\nResult comparison:\n%s", error);
|
||||
REQUIRE(computed_checksum == expected_checksum);
|
||||
}
|
||||
}
|
||||
previous_result = con.Query("SELECT * FROM t ORDER BY ALL");
|
||||
|
||||
switch (action) {
|
||||
case ActionType::TOGGLE_SKIP_CHECKPOINTS_ON_COMMIT:
|
||||
skip_checkpoint_on_commit = !skip_checkpoint_on_commit;
|
||||
PRINT_INFO("Setting skip commit=" << skip_checkpoint_on_commit);
|
||||
config.options.set_variables["debug_skip_checkpoint_on_commit"] = duckdb::Value(skip_checkpoint_on_commit);
|
||||
break;
|
||||
case ActionType::SMALL_WRITE: {
|
||||
std::string small_insert = "INSERT INTO t SELECT * FROM RANGE(" + std::to_string(offset) + ", " +
|
||||
std::to_string(offset + 100) + ")";
|
||||
PRINT_INFO("RUN: " << small_insert);
|
||||
validate(*con.Query(small_insert));
|
||||
offset += 100;
|
||||
break;
|
||||
}
|
||||
case ActionType::LARGE_WRITE: {
|
||||
std::string large_insert = "INSERT INTO t SELECT * FROM RANGE(" + std::to_string(offset) + ", " +
|
||||
std::to_string(offset + 1000 * 1000) + ")";
|
||||
PRINT_INFO("RUN: " << large_insert);
|
||||
validate(*con.Query(large_insert));
|
||||
offset += 1000 * 1000;
|
||||
break;
|
||||
}
|
||||
case ActionType::UPDATE: {
|
||||
if (offset != 0) {
|
||||
uint64_t begin = rand() % offset;
|
||||
uint64_t length = rand() % (offset - begin);
|
||||
std::string update_query = "UPDATE t SET i = i * 2 WHERE i > " + std::to_string(begin) + " and i <" +
|
||||
std::to_string(begin + length);
|
||||
|
||||
PRINT_INFO("RUN: " << update_query);
|
||||
validate(*con.Query(update_query));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ActionType::DELETE: {
|
||||
if (offset != 0) {
|
||||
uint64_t begin = rand() % offset;
|
||||
uint64_t length = rand() % (offset - begin);
|
||||
std::string delete_query =
|
||||
"DELETE FROM t WHERE i > " + std::to_string(begin) + " and i <" + std::to_string(begin + length);
|
||||
|
||||
PRINT_INFO("RUN: " << delete_query);
|
||||
validate(*con.Query(delete_query));
|
||||
break;
|
||||
}
|
||||
}
|
||||
case ActionType::LARGE_WRITE_WITH_FAULT: {
|
||||
raw_fs->InjectFault(LazyFlushFileSystem::FaultInjectionSite::FSYNC);
|
||||
std::string large_insert = "INSERT INTO t SELECT * FROM RANGE(" + std::to_string(offset) + ", " +
|
||||
std::to_string(offset + 1000 * 1000) + ")";
|
||||
|
||||
PRINT_INFO("RUN with fault: " << large_insert);
|
||||
validate(*con.Query(large_insert), "Injected fault");
|
||||
break;
|
||||
}
|
||||
case ActionType::RESET_TABLE: {
|
||||
std::string replace_query = "CREATE OR REPLACE TABLE t(i INTEGER)";
|
||||
PRINT_INFO("RUN with fault: " << replace_query);
|
||||
validate(*con.Query(replace_query));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute a checksum (unless we injected a fault - which will invalidate the database)
|
||||
if (action != ActionType::LARGE_WRITE_WITH_FAULT) {
|
||||
auto checksum = con.Query("SELECT bit_xor(hash(i)) FROM t");
|
||||
validate(*checksum);
|
||||
expected_checksum = checksum->GetValue(0, 0).ToString();
|
||||
|
||||
PRINT_INFO("Computed new checksum: " << expected_checksum);
|
||||
} else {
|
||||
PRINT_INFO("Keeping old checksum due to faults: " << expected_checksum);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
external/duckdb/test/common/test_strftime.cpp
vendored
Normal file
14
external/duckdb/test/common/test_strftime.cpp
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/function/scalar/strftime_format.hpp"
|
||||
#include "duckdb/common/types/timestamp.hpp"
|
||||
#include "duckdb/common/types/date.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE("Test that strftime format works", "[strftime]") {
|
||||
auto string = StrfTimeFormat::Format(Timestamp::FromDatetime(Date::FromDate(1992, 1, 1), dtime_t(0)), "%Y%m%d");
|
||||
REQUIRE(string == "19920101");
|
||||
}
|
||||
499
external/duckdb/test/common/test_string_util.cpp
vendored
Normal file
499
external/duckdb/test/common/test_string_util.cpp
vendored
Normal file
@@ -0,0 +1,499 @@
|
||||
#include "duckdb/common/string_util.hpp"
|
||||
#include "duckdb/common/types/value.hpp"
|
||||
#include "duckdb/common/to_string.hpp"
|
||||
#include "duckdb/common/exception/parser_exception.hpp"
|
||||
#include "catch.hpp"
|
||||
|
||||
#include "duckdb/common/vector.hpp"
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
using namespace duckdb;
|
||||
|
||||
TEST_CASE("Test strcmp() to ensure platform sanity", "[comparison]") {
|
||||
int res;
|
||||
res = strcmp("ZZZ", "ZZZ");
|
||||
REQUIRE(res == 0);
|
||||
|
||||
res = strcmp("ZZZ", "HXR");
|
||||
REQUIRE(res > 0);
|
||||
|
||||
res = strcmp("ZZZ", "NUT");
|
||||
REQUIRE(res > 0);
|
||||
|
||||
res = strcmp("HXR", "ZZZ");
|
||||
REQUIRE(res < 0);
|
||||
|
||||
res = strcmp("HXR", "HXR");
|
||||
REQUIRE(res == 0);
|
||||
|
||||
res = strcmp("HXR", "NUT");
|
||||
REQUIRE(res < 0);
|
||||
|
||||
res = strcmp("NUT", "ZZZ");
|
||||
REQUIRE(res < 0);
|
||||
|
||||
res = strcmp("NUT", "HXR");
|
||||
REQUIRE(res > 0);
|
||||
|
||||
res = strcmp("NUT", "NUT");
|
||||
REQUIRE(res == 0);
|
||||
|
||||
Value zzz("ZZZ");
|
||||
Value hxr("HXR");
|
||||
Value nut("NUT");
|
||||
|
||||
REQUIRE_FALSE(zzz > zzz);
|
||||
REQUIRE(zzz > hxr);
|
||||
REQUIRE(zzz > nut);
|
||||
|
||||
REQUIRE(zzz >= zzz);
|
||||
REQUIRE(zzz >= hxr);
|
||||
REQUIRE(zzz >= nut);
|
||||
|
||||
REQUIRE(zzz <= zzz);
|
||||
REQUIRE_FALSE(zzz <= hxr);
|
||||
REQUIRE_FALSE(zzz <= nut);
|
||||
|
||||
REQUIRE(zzz == zzz);
|
||||
REQUIRE_FALSE(zzz == hxr);
|
||||
REQUIRE_FALSE(zzz == nut);
|
||||
|
||||
REQUIRE_FALSE(zzz != zzz);
|
||||
REQUIRE(zzz != hxr);
|
||||
REQUIRE(zzz != nut);
|
||||
|
||||
REQUIRE_FALSE(hxr > zzz);
|
||||
REQUIRE_FALSE(hxr > hxr);
|
||||
REQUIRE_FALSE(hxr > nut);
|
||||
|
||||
REQUIRE_FALSE(hxr >= zzz);
|
||||
REQUIRE(hxr >= hxr);
|
||||
REQUIRE_FALSE(hxr >= nut);
|
||||
|
||||
REQUIRE(hxr <= zzz);
|
||||
REQUIRE(hxr <= hxr);
|
||||
REQUIRE(hxr <= nut);
|
||||
|
||||
REQUIRE_FALSE(hxr == zzz);
|
||||
REQUIRE(hxr == hxr);
|
||||
REQUIRE_FALSE(hxr == nut);
|
||||
|
||||
REQUIRE(hxr != zzz);
|
||||
REQUIRE_FALSE(hxr != hxr);
|
||||
REQUIRE(hxr != nut);
|
||||
|
||||
REQUIRE_FALSE(nut > zzz);
|
||||
REQUIRE(nut > hxr);
|
||||
REQUIRE_FALSE(nut > nut);
|
||||
|
||||
REQUIRE_FALSE(nut >= zzz);
|
||||
REQUIRE(nut >= hxr);
|
||||
REQUIRE(nut >= nut);
|
||||
|
||||
REQUIRE(nut <= zzz);
|
||||
REQUIRE_FALSE(nut <= hxr);
|
||||
REQUIRE(nut <= nut);
|
||||
|
||||
REQUIRE_FALSE(nut == zzz);
|
||||
REQUIRE_FALSE(nut == hxr);
|
||||
REQUIRE(nut == nut);
|
||||
|
||||
REQUIRE(nut != zzz);
|
||||
REQUIRE(nut != hxr);
|
||||
REQUIRE_FALSE(nut != nut);
|
||||
}
|
||||
|
||||
TEST_CASE("Test join vector items", "[string_util]") {
|
||||
SECTION("Three string items") {
|
||||
duckdb::vector<std::string> str_items = {"abc", "def", "ghi"};
|
||||
std::string result = StringUtil::Join(str_items, ",");
|
||||
REQUIRE(result == "abc,def,ghi");
|
||||
}
|
||||
|
||||
SECTION("One string item") {
|
||||
duckdb::vector<std::string> str_items = {"abc"};
|
||||
std::string result = StringUtil::Join(str_items, ",");
|
||||
REQUIRE(result == "abc");
|
||||
}
|
||||
|
||||
SECTION("No string items") {
|
||||
duckdb::vector<std::string> str_items;
|
||||
std::string result = StringUtil::Join(str_items, ",");
|
||||
REQUIRE(result == "");
|
||||
}
|
||||
|
||||
SECTION("Three int items") {
|
||||
duckdb::vector<int> int_items = {1, 2, 3};
|
||||
std::string result =
|
||||
StringUtil::Join(int_items, int_items.size(), ", ", [](const int &item) { return to_string(item); });
|
||||
REQUIRE(result == "1, 2, 3");
|
||||
}
|
||||
|
||||
SECTION("One int item") {
|
||||
duckdb::vector<int> int_items = {1};
|
||||
std::string result =
|
||||
StringUtil::Join(int_items, int_items.size(), ", ", [](const int &item) { return to_string(item); });
|
||||
REQUIRE(result == "1");
|
||||
}
|
||||
|
||||
SECTION("No int items") {
|
||||
duckdb::vector<int> int_items;
|
||||
std::string result =
|
||||
StringUtil::Join(int_items, int_items.size(), ", ", [](const int &item) { return to_string(item); });
|
||||
REQUIRE(result == "");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test SplitWithParentheses", "[string_util]") {
|
||||
SECTION("Standard split") {
|
||||
REQUIRE(StringUtil::SplitWithParentheses("") == duckdb::vector<string> {});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("x") == duckdb::vector<string> {"x"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("hello") == duckdb::vector<string> {"hello"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("hello,world") == duckdb::vector<string> {"hello", "world"});
|
||||
}
|
||||
|
||||
SECTION("Single item with parentheses") {
|
||||
REQUIRE(StringUtil::SplitWithParentheses("STRUCT(year BIGINT, month BIGINT, day BIGINT)") ==
|
||||
duckdb::vector<string> {"STRUCT(year BIGINT, month BIGINT, day BIGINT)"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("(apple)") == duckdb::vector<string> {"(apple)"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("(apple, pear)") == duckdb::vector<string> {"(apple, pear)"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("(apple, pear) banana") ==
|
||||
duckdb::vector<string> {"(apple, pear) banana"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("banana (apple, pear)") ==
|
||||
duckdb::vector<string> {"banana (apple, pear)"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("banana (apple, pear) banana") ==
|
||||
duckdb::vector<string> {"banana (apple, pear) banana"});
|
||||
}
|
||||
|
||||
SECTION("Multiple items with parentheses") {
|
||||
REQUIRE(StringUtil::SplitWithParentheses("map::MAP(ANY,ANY),key::ANY") ==
|
||||
duckdb::vector<string> {"map::MAP(ANY,ANY)", "key::ANY"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("extra,STRUCT(year BIGINT, month BIGINT, day BIGINT)") ==
|
||||
duckdb::vector<string> {"extra", "STRUCT(year BIGINT, month BIGINT, day BIGINT)"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("extra,STRUCT(year BIGINT, month BIGINT, day BIGINT),extra") ==
|
||||
duckdb::vector<string> {"extra", "STRUCT(year BIGINT, month BIGINT, day BIGINT)", "extra"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("aa(bb)cc,dd(ee)ff") ==
|
||||
duckdb::vector<string> {"aa(bb)cc", "dd(ee)ff"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("aa(bb cc,dd),ee(f,,f)gg") ==
|
||||
duckdb::vector<string> {"aa(bb cc,dd)", "ee(f,,f)gg"});
|
||||
}
|
||||
|
||||
SECTION("Leading and trailing separators") {
|
||||
REQUIRE(StringUtil::SplitWithParentheses(",") == duckdb::vector<string> {""});
|
||||
REQUIRE(StringUtil::SplitWithParentheses(",,") == duckdb::vector<string> {"", ""});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("aa,") == duckdb::vector<string> {"aa"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses(",aa") == duckdb::vector<string> {"", "aa"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses(",(aa,),") == duckdb::vector<string> {"", "(aa,)"});
|
||||
}
|
||||
|
||||
SECTION("Leading and trailing spaces") {
|
||||
REQUIRE(StringUtil::SplitWithParentheses(" ") == duckdb::vector<string> {" "});
|
||||
REQUIRE(StringUtil::SplitWithParentheses(" ") == duckdb::vector<string> {" "});
|
||||
REQUIRE(StringUtil::SplitWithParentheses(" , ") == duckdb::vector<string> {" ", " "});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("aa, bb") == duckdb::vector<string> {"aa", " bb"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses(" aa,(bb, cc) ") == duckdb::vector<string> {" aa", "(bb, cc) "});
|
||||
}
|
||||
|
||||
SECTION("Nested parentheses") {
|
||||
REQUIRE(StringUtil::SplitWithParentheses("STRUCT(aa BIGINT, bb STRUCT(cc BIGINT, dd BIGINT, BIGINT))") ==
|
||||
duckdb::vector<string> {"STRUCT(aa BIGINT, bb STRUCT(cc BIGINT, dd BIGINT, BIGINT))"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("(((aa)))") == duckdb::vector<string> {"(((aa)))"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("((aa, bb))") == duckdb::vector<string> {"((aa, bb))"});
|
||||
REQUIRE(StringUtil::SplitWithParentheses("aa,(bb,(cc,dd)),ee") ==
|
||||
duckdb::vector<string> {"aa", "(bb,(cc,dd))", "ee"});
|
||||
}
|
||||
|
||||
SECTION("other parentheses") {
|
||||
REQUIRE(StringUtil::SplitWithParentheses(" aa,[bb, cc] )", ',', '[', ']') ==
|
||||
duckdb::vector<string> {" aa", "[bb, cc] )"});
|
||||
}
|
||||
|
||||
SECTION("other separators") {
|
||||
REQUIRE(StringUtil::SplitWithParentheses(" aa|(bb| cc),dd", '|') ==
|
||||
duckdb::vector<string> {" aa", "(bb| cc),dd"});
|
||||
}
|
||||
|
||||
SECTION("incongruent parentheses") {
|
||||
REQUIRE_THROWS(StringUtil::SplitWithParentheses("("));
|
||||
REQUIRE_THROWS(StringUtil::SplitWithParentheses(")"));
|
||||
REQUIRE_THROWS(StringUtil::SplitWithParentheses("aa(bb"));
|
||||
REQUIRE_THROWS(StringUtil::SplitWithParentheses("aa)bb"));
|
||||
REQUIRE_THROWS(StringUtil::SplitWithParentheses("(aa)bb)"));
|
||||
REQUIRE_THROWS(StringUtil::SplitWithParentheses("(aa(bb)"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test split quoted strings", "[string_util]") {
|
||||
SECTION("Empty string") {
|
||||
REQUIRE(StringUtil::SplitWithQuote("") == duckdb::vector<string> {});
|
||||
}
|
||||
|
||||
SECTION("Empty string with space") {
|
||||
REQUIRE(StringUtil::SplitWithQuote(" ") == duckdb::vector<string> {});
|
||||
}
|
||||
|
||||
SECTION("One item") {
|
||||
REQUIRE(StringUtil::SplitWithQuote("x") == duckdb::vector<string> {"x"});
|
||||
}
|
||||
|
||||
SECTION("One item with space") {
|
||||
REQUIRE(StringUtil::SplitWithQuote(" x ") == duckdb::vector<string> {"x"});
|
||||
}
|
||||
|
||||
SECTION("One item with quote") {
|
||||
REQUIRE(StringUtil::SplitWithQuote("\"x\"") == duckdb::vector<string> {"x"});
|
||||
}
|
||||
|
||||
SECTION("One empty item with quote") {
|
||||
REQUIRE(StringUtil::SplitWithQuote("\"\"") == duckdb::vector<string> {""});
|
||||
}
|
||||
|
||||
SECTION("One empty item, followed by non-empty one - Or vise versa") {
|
||||
REQUIRE(StringUtil::SplitWithQuote("\"\",hello") == duckdb::vector<string> {"", "hello"});
|
||||
REQUIRE(StringUtil::SplitWithQuote(",\"hello\"") == duckdb::vector<string> {"", "hello"});
|
||||
REQUIRE(StringUtil::SplitWithQuote(",hello") == duckdb::vector<string> {"", "hello"});
|
||||
REQUIRE(StringUtil::SplitWithQuote("\"\",\"hello\"") == duckdb::vector<string> {"", "hello"});
|
||||
|
||||
REQUIRE(StringUtil::SplitWithQuote("\"hello\",") == duckdb::vector<string> {"hello", ""});
|
||||
REQUIRE(StringUtil::SplitWithQuote("hello,\"\"") == duckdb::vector<string> {"hello", ""});
|
||||
REQUIRE(StringUtil::SplitWithQuote("hello,") == duckdb::vector<string> {"hello", ""});
|
||||
REQUIRE(StringUtil::SplitWithQuote("\"hello\",\"\"") == duckdb::vector<string> {"hello", ""});
|
||||
}
|
||||
|
||||
SECTION("One quoted item with spaces") {
|
||||
REQUIRE(StringUtil::SplitWithQuote(" \" x y \" ") == duckdb::vector<string> {" x y "});
|
||||
}
|
||||
|
||||
SECTION("One quoted item with a delimiter") {
|
||||
REQUIRE(StringUtil::SplitWithQuote("\"x,y\"") == duckdb::vector<string> {"x,y"});
|
||||
}
|
||||
|
||||
SECTION("Three items") {
|
||||
REQUIRE(StringUtil::SplitWithQuote("x,y,z") == duckdb::vector<string> {"x", "y", "z"});
|
||||
}
|
||||
|
||||
SECTION("Three items, with and without quote") {
|
||||
REQUIRE(StringUtil::SplitWithQuote("x,\"y\",z") == duckdb::vector<string> {"x", "y", "z"});
|
||||
}
|
||||
|
||||
SECTION("Even more items, with and without quote") {
|
||||
REQUIRE(StringUtil::SplitWithQuote("a,b,c,d,e,f,g") ==
|
||||
duckdb::vector<string> {"a", "b", "c", "d", "e", "f", "g"});
|
||||
}
|
||||
|
||||
SECTION("Three empty items") {
|
||||
REQUIRE(StringUtil::SplitWithQuote(",,") == duckdb::vector<string> {"", "", ""});
|
||||
}
|
||||
|
||||
SECTION("Three empty quoted items") {
|
||||
REQUIRE(StringUtil::SplitWithQuote("\"\",\"\",\"\"") == duckdb::vector<string> {"", "", ""});
|
||||
}
|
||||
|
||||
SECTION("Unclosed quote") {
|
||||
REQUIRE_THROWS_AS(StringUtil::SplitWithQuote("\""), ParserException);
|
||||
REQUIRE_THROWS_AS(StringUtil::SplitWithQuote("\"x"), ParserException);
|
||||
REQUIRE_THROWS_AS(StringUtil::SplitWithQuote("\"x "), ParserException);
|
||||
REQUIRE_THROWS_AS(StringUtil::SplitWithQuote("\","), ParserException);
|
||||
REQUIRE_THROWS_AS(StringUtil::SplitWithQuote("\"x,"), ParserException);
|
||||
}
|
||||
|
||||
SECTION("Unexpected quote") {
|
||||
REQUIRE_THROWS_AS(StringUtil::SplitWithQuote("abc\"def"), ParserException);
|
||||
}
|
||||
|
||||
SECTION("Missing delimiter") {
|
||||
REQUIRE_THROWS_AS(StringUtil::SplitWithQuote("\"x\"\"y\""), ParserException);
|
||||
REQUIRE_THROWS_AS(StringUtil::SplitWithQuote("\"x\" \"y\""), ParserException);
|
||||
REQUIRE_THROWS_AS(StringUtil::SplitWithQuote("x y"), ParserException);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test path utilities", "[string_util]") {
|
||||
SECTION("File name") {
|
||||
REQUIRE("bin" == StringUtil::GetFileName("/usr/bin/"));
|
||||
REQUIRE("foo.txt" == StringUtil::GetFileName("foo.txt"));
|
||||
REQUIRE("foo.txt" == StringUtil::GetFileName("tmp/foo.txt"));
|
||||
REQUIRE("foo.txt" == StringUtil::GetFileName("tmp\\foo.txt"));
|
||||
REQUIRE("foo.txt" == StringUtil::GetFileName("/tmp/foo.txt"));
|
||||
REQUIRE("foo.txt" == StringUtil::GetFileName("\\tmp\\foo.txt"));
|
||||
REQUIRE("foo.txt" == StringUtil::GetFileName("foo.txt/."));
|
||||
REQUIRE("foo.txt" == StringUtil::GetFileName("foo.txt/./"));
|
||||
REQUIRE("foo.txt" == StringUtil::GetFileName("foo.txt/.//"));
|
||||
REQUIRE("foo.txt" == StringUtil::GetFileName("foo.txt\\."));
|
||||
REQUIRE("foo.txt" == StringUtil::GetFileName("foo.txt\\.\\"));
|
||||
REQUIRE("foo.txt" == StringUtil::GetFileName("foo.txt\\.\\\\"));
|
||||
REQUIRE(".." == StringUtil::GetFileName(".."));
|
||||
REQUIRE("" == StringUtil::GetFileName("/"));
|
||||
}
|
||||
|
||||
SECTION("File extension") {
|
||||
REQUIRE("cpp" == StringUtil::GetFileExtension("test.cpp"));
|
||||
REQUIRE("gz" == StringUtil::GetFileExtension("test.cpp.gz"));
|
||||
REQUIRE("" == StringUtil::GetFileExtension("test"));
|
||||
REQUIRE("" == StringUtil::GetFileExtension(".gitignore"));
|
||||
}
|
||||
|
||||
SECTION("File stem (base name)") {
|
||||
REQUIRE("test" == StringUtil::GetFileStem("test.cpp"));
|
||||
REQUIRE("test.cpp" == StringUtil::GetFileStem("test.cpp.gz"));
|
||||
REQUIRE("test" == StringUtil::GetFileStem("test"));
|
||||
REQUIRE(".gitignore" == StringUtil::GetFileStem(".gitignore"));
|
||||
|
||||
REQUIRE("test" == StringUtil::GetFileStem("test.cpp/"));
|
||||
REQUIRE("test" == StringUtil::GetFileStem("test.cpp/."));
|
||||
REQUIRE("test" == StringUtil::GetFileStem("test.cpp/./"));
|
||||
REQUIRE("test" == StringUtil::GetFileStem("test.cpp/.//"));
|
||||
REQUIRE("test" == StringUtil::GetFileStem("test.cpp\\"));
|
||||
REQUIRE("test" == StringUtil::GetFileStem("test.cpp\\."));
|
||||
REQUIRE("test" == StringUtil::GetFileStem("test.cpp\\.\\"));
|
||||
REQUIRE("test" == StringUtil::GetFileStem("test.cpp\\.\\\\"));
|
||||
REQUIRE(".." == StringUtil::GetFileStem(".."));
|
||||
REQUIRE("" == StringUtil::GetFileStem("/"));
|
||||
REQUIRE("test" == StringUtil::GetFileStem("tmp/test.txt"));
|
||||
REQUIRE("test" == StringUtil::GetFileStem("tmp\\test.txt"));
|
||||
REQUIRE("test" == StringUtil::GetFileStem("/tmp/test.txt"));
|
||||
REQUIRE("test" == StringUtil::GetFileStem("\\tmp\\test.txt"));
|
||||
}
|
||||
|
||||
SECTION("File path") {
|
||||
REQUIRE("/usr/local/bin" == StringUtil::GetFilePath("/usr/local/bin/test.cpp"));
|
||||
REQUIRE("\\usr\\local\\bin" == StringUtil::GetFilePath("\\usr\\local\\bin\\test.cpp"));
|
||||
REQUIRE("tmp" == StringUtil::GetFilePath("tmp/test.txt"));
|
||||
REQUIRE("tmp" == StringUtil::GetFilePath("tmp\\test.txt"));
|
||||
REQUIRE("/tmp" == StringUtil::GetFilePath("/tmp/test.txt"));
|
||||
REQUIRE("\\tmp" == StringUtil::GetFilePath("\\tmp\\test.txt"));
|
||||
REQUIRE("/tmp" == StringUtil::GetFilePath("/tmp/test.txt/"));
|
||||
REQUIRE("\\tmp" == StringUtil::GetFilePath("\\tmp\\test.txt\\"));
|
||||
REQUIRE("/tmp" == StringUtil::GetFilePath("/tmp//test.txt"));
|
||||
REQUIRE("\\tmp" == StringUtil::GetFilePath("\\tmp\\\\test.txt"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test JSON Parsing", "[string_util]") {
|
||||
auto complex_json = StringUtil::ParseJSONMap(R"JSON_LITERAL(
|
||||
{
|
||||
"crs": {
|
||||
"$schema": "https://proj.org/schemas/v0.7/projjson.schema.json",
|
||||
"type": "GeographicCRS",
|
||||
"name": "WGS 84",
|
||||
"datum_ensemble": {
|
||||
"name": "World Geodetic System 1984 ensemble",
|
||||
"members": [
|
||||
{
|
||||
"name": "World Geodetic System 1984 (Transit)",
|
||||
"id": {
|
||||
"authority": "EPSG",
|
||||
"code": 1166
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "World Geodetic System 1984 (G730)",
|
||||
"id": {
|
||||
"authority": "EPSG",
|
||||
"code": 1152
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "World Geodetic System 1984 (G873)",
|
||||
"id": {
|
||||
"authority": "EPSG",
|
||||
"code": 1153
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "World Geodetic System 1984 (G1150)",
|
||||
"id": {
|
||||
"authority": "EPSG",
|
||||
"code": 1154
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "World Geodetic System 1984 (G1674)",
|
||||
"id": {
|
||||
"authority": "EPSG",
|
||||
"code": 1155
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "World Geodetic System 1984 (G1762)",
|
||||
"id": {
|
||||
"authority": "EPSG",
|
||||
"code": 1156
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "World Geodetic System 1984 (G2139)",
|
||||
"id": {
|
||||
"authority": "EPSG",
|
||||
"code": 1309
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "World Geodetic System 1984 (G2296)",
|
||||
"id": {
|
||||
"authority": "EPSG",
|
||||
"code": 1383
|
||||
}
|
||||
}
|
||||
],
|
||||
"ellipsoid": {
|
||||
"name": "WGS 84",
|
||||
"semi_major_axis": 6378137,
|
||||
"inverse_flattening": 298.257223563
|
||||
},
|
||||
"accuracy": "2.0",
|
||||
"id": {
|
||||
"authority": "EPSG",
|
||||
"code": 6326
|
||||
}
|
||||
},
|
||||
"coordinate_system": {
|
||||
"subtype": "ellipsoidal",
|
||||
"axis": [
|
||||
{
|
||||
"name": "Geodetic latitude",
|
||||
"abbreviation": "Lat",
|
||||
"direction": "north",
|
||||
"unit": "degree"
|
||||
},
|
||||
{
|
||||
"name": "Geodetic longitude",
|
||||
"abbreviation": "Lon",
|
||||
"direction": "east",
|
||||
"unit": "degree"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scope": "Horizontal component of 3D system.",
|
||||
"area": "World.",
|
||||
"bbox": {
|
||||
"south_latitude": -90,
|
||||
"west_longitude": -180,
|
||||
"north_latitude": 90,
|
||||
"east_longitude": 180
|
||||
},
|
||||
"id": {
|
||||
"authority": "EPSG",
|
||||
"code": 4326
|
||||
}
|
||||
},
|
||||
"crs_type": "projjson"
|
||||
} )JSON_LITERAL");
|
||||
|
||||
complex_json = StringUtil::ParseJSONMap(R"JSON_LITERAL(
|
||||
{
|
||||
"int": 42,
|
||||
"signed_int": -42,
|
||||
"real": 1.5,
|
||||
"null_val": null,
|
||||
"arr": [1, 2, 3],
|
||||
"obj": {
|
||||
"str_val": "val"
|
||||
},
|
||||
"empty_arr": [],
|
||||
"bool_t": true,
|
||||
"bool_f": false
|
||||
}
|
||||
)JSON_LITERAL");
|
||||
}
|
||||
33
external/duckdb/test/common/test_utf.cpp
vendored
Normal file
33
external/duckdb/test/common/test_utf.cpp
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "catch.hpp"
|
||||
#include "duckdb/common/types/vector.hpp"
|
||||
#include "duckdb/main/appender.hpp"
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
using namespace duckdb;
|
||||
using namespace std;
|
||||
|
||||
static void test_valid_str(Vector &a, const char *str) {
|
||||
Value s(str);
|
||||
REQUIRE_NOTHROW(a.SetValue(0, s));
|
||||
REQUIRE(a.GetValue(0) == s);
|
||||
}
|
||||
|
||||
TEST_CASE("UTF8 error checking", "[utf8]") {
|
||||
Vector a(LogicalType::VARCHAR);
|
||||
|
||||
test_valid_str(a, "a");
|
||||
test_valid_str(a, "\xc3\xb1");
|
||||
test_valid_str(a, "\xE2\x82\xA1");
|
||||
test_valid_str(a, "\xF0\x9F\xA6\x86"); // a duck!
|
||||
test_valid_str(a, "\xf0\x90\x8c\xbc");
|
||||
|
||||
REQUIRE_THROWS(a.SetValue(0, Value("\xc3\x28")));
|
||||
REQUIRE_THROWS(a.SetValue(0, Value("\xa0\xa1")));
|
||||
REQUIRE_THROWS(a.SetValue(0, Value("\xe2\x28\xa1")));
|
||||
REQUIRE_THROWS(a.SetValue(0, Value("\xe2\x82\x28")));
|
||||
REQUIRE_THROWS(a.SetValue(0, Value("\xf0\x28\x8c\xbc")));
|
||||
REQUIRE_THROWS(a.SetValue(0, Value("\xf0\x90\x28\xbc")));
|
||||
REQUIRE_THROWS(a.SetValue(0, Value("\xf0\x28\x8c\x28")));
|
||||
REQUIRE_THROWS(a.SetValue(0, Value("\xf8\xa1\xa1\xa1\xa1")));
|
||||
REQUIRE_THROWS(a.SetValue(0, Value("\xfc\xa1\xa1\xa1\xa1\xa1")));
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user