should be it

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

View File

@@ -0,0 +1,46 @@
include_directories(include)
add_subdirectory(sqlite3)
include_directories(sqlite3_udf_api/include)
add_subdirectory(sqlite3_udf_api)
add_extension_definitions()
add_definitions(-DSQLITE_SHELL_IS_UTF8)
add_definitions(-DUSE_DUCKDB_SHELL_WRAPPER)
include_directories(../../third_party/utf8proc/include)
if(DUCKDB_EXTENSION_AUTOCOMPLETE_SHOULD_LINK)
include_directories(../../extension/autocomplete/include)
set(ALL_OBJECT_FILES ${ALL_OBJECT_FILES}
../../extension/autocomplete/autocomplete_extension.cpp)
add_definitions(-DSHELL_INLINE_AUTOCOMPLETE)
endif()
set(SQLITE_API_WRAPPER_FILES shell_extension.cpp sqlite3_api_wrapper.cpp
${ALL_OBJECT_FILES})
add_library(sqlite3_api_wrapper_static STATIC ${SQLITE_API_WRAPPER_FILES})
target_link_libraries(sqlite3_api_wrapper_static duckdb_static)
if(NOT AMALGAMATION_BUILD)
target_link_libraries(sqlite3_api_wrapper_static duckdb_utf8proc)
endif()
link_threads(sqlite3_api_wrapper_static "")
if(NOT WIN32 AND NOT ZOS)
add_library(sqlite3_api_wrapper SHARED ${SQLITE_API_WRAPPER_FILES})
target_link_libraries(sqlite3_api_wrapper duckdb_static
${DUCKDB_EXTRA_LINK_FLAGS})
link_threads(sqlite3_api_wrapper "")
endif()
include_directories(../../third_party/catch)
include_directories(test/include)
add_subdirectory(test)
add_executable(test_sqlite3_api_wrapper ${SQLITE_TEST_FILES})
if(WIN32 OR ZOS)
target_link_libraries(test_sqlite3_api_wrapper sqlite3_api_wrapper_static)
else()
target_link_libraries(test_sqlite3_api_wrapper sqlite3_api_wrapper)
endif()

View File

@@ -0,0 +1,173 @@
#define sqlite3AppendChar duckdb_shell_sqlite3AppendChar
#define sqlite3StrAccumAppend duckdb_shell_sqlite3StrAccumAppend
#define sqlite3StrAccumAppendAll duckdb_shell_sqlite3StrAccumAppendAll
#define sqlite3StrAccumFinish duckdb_shell_sqlite3StrAccumFinish
#define sqlite3StrAccumInit duckdb_shell_sqlite3StrAccumInit
#define sqlite3StrAccumReset duckdb_shell_sqlite3StrAccumReset
#define sqlite3Utf8Read duckdb_shell_sqlite3Utf8Read
#define sqlite3VXPrintf duckdb_shell_sqlite3VXPrintf
#define sqlite3_aggregate_context duckdb_shell_sqlite3_aggregate_context
#define sqlite3_backup_finish duckdb_shell_sqlite3_backup_finish
#define sqlite3_backup_init duckdb_shell_sqlite3_backup_init
#define sqlite3_backup_step duckdb_shell_sqlite3_backup_step
#define sqlite3_bind_blob duckdb_shell_sqlite3_bind_blob
#define sqlite3_bind_double duckdb_shell_sqlite3_bind_double
#define sqlite3_bind_int duckdb_shell_sqlite3_bind_int
#define sqlite3_bind_int64 duckdb_shell_sqlite3_bind_int64
#define sqlite3_bind_null duckdb_shell_sqlite3_bind_null
#define sqlite3_bind_parameter_count duckdb_shell_sqlite3_bind_parameter_count
#define sqlite3_bind_parameter_index duckdb_shell_sqlite3_bind_parameter_index
#define sqlite3_bind_parameter_name duckdb_shell_sqlite3_bind_parameter_name
#define sqlite3_bind_pointer duckdb_shell_sqlite3_bind_pointer
#define sqlite3_bind_text duckdb_shell_sqlite3_bind_text
#define sqlite3_bind_value duckdb_shell_sqlite3_bind_value
#define sqlite3_bind_zeroblob duckdb_shell_sqlite3_bind_zeroblob
#define sqlite3_blob_open duckdb_shell_sqlite3_blob_open
#define sqlite3_blob_write duckdb_shell_sqlite3_blob_write
#define sqlite3_busy_handler duckdb_shell_sqlite3_busy_handler
#define sqlite3_busy_timeout duckdb_shell_sqlite3_busy_timeout
#define sqlite3_changes duckdb_shell_sqlite3_changes
#define sqlite3_changes64 duckdb_shell_sqlite3_changes64
#define sqlite3_clear_bindings duckdb_shell_sqlite3_clear_bindings
#define sqlite3_close duckdb_shell_sqlite3_close
#define sqlite3_collation_needed duckdb_shell_sqlite3_collation_needed
#define sqlite3_column_blob duckdb_shell_sqlite3_column_blob
#define sqlite3_column_bytes duckdb_shell_sqlite3_column_bytes
#define sqlite3_column_count duckdb_shell_sqlite3_column_count
#define sqlite3_column_decltype duckdb_shell_sqlite3_column_decltype
#define sqlite3_column_double duckdb_shell_sqlite3_column_double
#define sqlite3_column_int duckdb_shell_sqlite3_column_int
#define sqlite3_column_int64 duckdb_shell_sqlite3_column_int64
#define sqlite3_column_name duckdb_shell_sqlite3_column_name
#define sqlite3_column_table_name duckdb_shell_sqlite3_column_table_name
#define sqlite3_column_text duckdb_shell_sqlite3_column_text
#define sqlite3_column_type duckdb_shell_sqlite3_column_type
#define sqlite3_column_value duckdb_shell_sqlite3_column_value
#define sqlite3_commit_hook duckdb_shell_sqlite3_commit_hook
#define sqlite3_complete duckdb_shell_sqlite3_complete
#define sqlite3_config duckdb_shell_sqlite3_config
#define sqlite3_context_db_handle duckdb_shell_sqlite3_context_db_handle
#define sqlite3_create_collation duckdb_shell_sqlite3_create_collation
#define sqlite3_create_collation_v2 duckdb_shell_sqlite3_create_collation_v2
#define sqlite3_create_function duckdb_shell_sqlite3_create_function
#define sqlite3_create_function_v2 duckdb_shell_sqlite3_create_function_v2
#define sqlite3_create_module duckdb_shell_sqlite3_create_module
#define sqlite3_create_module_v2 duckdb_shell_sqlite3_create_module_v2
#define sqlite3_create_window_function duckdb_shell_sqlite3_create_window_function
#define sqlite3_db_config duckdb_shell_sqlite3_db_config
#define sqlite3_db_filename duckdb_shell_sqlite3_db_filename
#define sqlite3_db_handle duckdb_shell_sqlite3_db_handle
#define sqlite3_db_status duckdb_shell_sqlite3_db_status
#define sqlite3_declare_vtab duckdb_shell_sqlite3_declare_vtab
#define sqlite3_enable_load_extension duckdb_shell_sqlite3_enable_load_extension
#define sqlite3_errcode duckdb_shell_sqlite3_errcode
#define sqlite3_errmsg duckdb_shell_sqlite3_errmsg
#define sqlite3_exec duckdb_shell_sqlite3_exec
#define sqlite3_expanded_sql duckdb_shell_sqlite3_expanded_sql
#define sqlite3_extended_errcode duckdb_shell_sqlite3_extended_errcode
#define sqlite3_extended_result_codes duckdb_shell_sqlite3_extended_result_codes
#define sqlite3_file_control duckdb_shell_sqlite3_file_control
#define sqlite3_finalize duckdb_shell_sqlite3_finalize
#define sqlite3_free duckdb_shell_sqlite3_free
#define sqlite3_free_table duckdb_shell_sqlite3_free_table
#define sqlite3_get_autocommit duckdb_shell_sqlite3_get_autocommit
#define sqlite3_get_auxdata duckdb_shell_sqlite3_get_auxdata
#define sqlite3_get_table duckdb_shell_sqlite3_get_table
#define sqlite3_initialize duckdb_shell_sqlite3_initialize
#define sqlite3_interrupt duckdb_shell_sqlite3_interrupt
#define sqlite3_keyword_check duckdb_shell_sqlite3_keyword_check
#define sqlite3_keyword_count duckdb_shell_sqlite3_keyword_count
#define sqlite3_keyword_name duckdb_shell_sqlite3_keyword_name
#define sqlite3_last_insert_rowid duckdb_shell_sqlite3_last_insert_rowid
#define sqlite3_libversion duckdb_shell_sqlite3_libversion
#define sqlite3_libversion_number duckdb_shell_sqlite3_libversion_number
#define sqlite3_limit duckdb_shell_sqlite3_limit
#define sqlite3_load_extension duckdb_shell_sqlite3_load_extension
#define sqlite3_log duckdb_shell_sqlite3_log
#define sqlite3_malloc duckdb_shell_sqlite3_malloc
#define sqlite3_malloc64 duckdb_shell_sqlite3_malloc64
#define sqlite3_mprintf duckdb_shell_sqlite3_mprintf
#define sqlite3_mutex_alloc duckdb_shell_sqlite3_mutex_alloc
#define sqlite3_mutex_free duckdb_shell_sqlite3_mutex_free
#define sqlite3_next_stmt duckdb_shell_sqlite3_next_stmt
#define sqlite3_open duckdb_shell_sqlite3_open
#define sqlite3_open_v2 duckdb_shell_sqlite3_open_v2
#define sqlite3_prepare duckdb_shell_sqlite3_prepare
#define sqlite3_prepare_v2 duckdb_shell_sqlite3_prepare_v2
#define sqlite3_print_duckbox duckdb_shell_sqlite3_print_duckbox
#define sqlite3_profile duckdb_shell_sqlite3_profile
#define sqlite3_progress_handler duckdb_shell_sqlite3_progress_handler
#define sqlite3_randomness duckdb_shell_sqlite3_randomness
#define sqlite3_realloc duckdb_shell_sqlite3_realloc
#define sqlite3_realloc64 duckdb_shell_sqlite3_realloc64
#define sqlite3_reset duckdb_shell_sqlite3_reset
#define sqlite3_result_blob duckdb_shell_sqlite3_result_blob
#define sqlite3_result_blob64 duckdb_shell_sqlite3_result_blob64
#define sqlite3_result_double duckdb_shell_sqlite3_result_double
#define sqlite3_result_error duckdb_shell_sqlite3_result_error
#define sqlite3_result_error16 duckdb_shell_sqlite3_result_error16
#define sqlite3_result_error_code duckdb_shell_sqlite3_result_error_code
#define sqlite3_result_error_nomem duckdb_shell_sqlite3_result_error_nomem
#define sqlite3_result_error_toobig duckdb_shell_sqlite3_result_error_toobig
#define sqlite3_result_int duckdb_shell_sqlite3_result_int
#define sqlite3_result_int64 duckdb_shell_sqlite3_result_int64
#define sqlite3_result_null duckdb_shell_sqlite3_result_null
#define sqlite3_result_pointer duckdb_shell_sqlite3_result_pointer
#define sqlite3_result_text duckdb_shell_sqlite3_result_text
#define sqlite3_result_text16 duckdb_shell_sqlite3_result_text16
#define sqlite3_result_text16be duckdb_shell_sqlite3_result_text16be
#define sqlite3_result_text16le duckdb_shell_sqlite3_result_text16le
#define sqlite3_result_text64 duckdb_shell_sqlite3_result_text64
#define sqlite3_result_value duckdb_shell_sqlite3_result_value
#define sqlite3_result_zeroblob duckdb_shell_sqlite3_result_zeroblob
#define sqlite3_result_zeroblob64 duckdb_shell_sqlite3_result_zeroblob64
#define sqlite3_rollback_hook duckdb_shell_sqlite3_rollback_hook
#define sqlite3_set_authorizer duckdb_shell_sqlite3_set_authorizer
#define sqlite3_set_auxdata duckdb_shell_sqlite3_set_auxdata
#define sqlite3_shutdown duckdb_shell_sqlite3_shutdown
#define sqlite3_sleep duckdb_shell_sqlite3_sleep
#define sqlite3_snprintf duckdb_shell_sqlite3_snprintf
#define sqlite3_sourceid duckdb_shell_sqlite3_sourceid
#define sqlite3_sql duckdb_shell_sqlite3_sql
#define sqlite3_status64 duckdb_shell_sqlite3_status64
#define sqlite3_step duckdb_shell_sqlite3_step
#define sqlite3_stmt_busy duckdb_shell_sqlite3_stmt_busy
#define sqlite3_stmt_isexplain duckdb_shell_sqlite3_stmt_isexplain
#define sqlite3_stmt_readonly duckdb_shell_sqlite3_stmt_readonly
#define sqlite3_stmt_status duckdb_shell_sqlite3_stmt_status
#define sqlite3_strglob duckdb_shell_sqlite3_strglob
#define sqlite3_stricmp duckdb_shell_sqlite3_stricmp
#define sqlite3_strlike duckdb_shell_sqlite3_strlike
#define sqlite3_strnicmp duckdb_shell_sqlite3_strnicmp
#define sqlite3_table_column_metadata duckdb_shell_sqlite3_table_column_metadata
#define sqlite3_test_control duckdb_shell_sqlite3_test_control
#define sqlite3_threadsafe duckdb_shell_sqlite3_threadsafe
#define sqlite3_total_changes duckdb_shell_sqlite3_total_changes
#define sqlite3_total_changes64 duckdb_shell_sqlite3_total_changes64
#define sqlite3_trace duckdb_shell_sqlite3_trace
#define sqlite3_trace_v2 duckdb_shell_sqlite3_trace_v2
#define sqlite3_unlock_notify duckdb_shell_sqlite3_unlock_notify
#define sqlite3_update_hook duckdb_shell_sqlite3_update_hook
#define sqlite3_user_data duckdb_shell_sqlite3_user_data
#define sqlite3_value_blob duckdb_shell_sqlite3_value_blob
#define sqlite3_value_bytes duckdb_shell_sqlite3_value_bytes
#define sqlite3_value_bytes16 duckdb_shell_sqlite3_value_bytes16
#define sqlite3_value_double duckdb_shell_sqlite3_value_double
#define sqlite3_value_dup duckdb_shell_sqlite3_value_dup
#define sqlite3_value_free duckdb_shell_sqlite3_value_free
#define sqlite3_value_int duckdb_shell_sqlite3_value_int
#define sqlite3_value_int64 duckdb_shell_sqlite3_value_int64
#define sqlite3_value_nochange duckdb_shell_sqlite3_value_nochange
#define sqlite3_value_numeric_type duckdb_shell_sqlite3_value_numeric_type
#define sqlite3_value_pointer duckdb_shell_sqlite3_value_pointer
#define sqlite3_value_text duckdb_shell_sqlite3_value_text
#define sqlite3_value_text16 duckdb_shell_sqlite3_value_text16
#define sqlite3_value_text16be duckdb_shell_sqlite3_value_text16be
#define sqlite3_value_text16le duckdb_shell_sqlite3_value_text16le
#define sqlite3_value_type duckdb_shell_sqlite3_value_type
#define sqlite3_vfs_find duckdb_shell_sqlite3_vfs_find
#define sqlite3_vfs_register duckdb_shell_sqlite3_vfs_register
#define sqlite3_vmprintf duckdb_shell_sqlite3_vmprintf
#define sqlite3_vsnprintf duckdb_shell_sqlite3_vsnprintf
#define sqlite3_vtab_collation duckdb_shell_sqlite3_vtab_collation
#define sqlite3_vtab_config duckdb_shell_sqlite3_vtab_config

View File

@@ -0,0 +1,22 @@
//===----------------------------------------------------------------------===//
// DuckDB
//
// shell_extension.hpp
//
//
//===----------------------------------------------------------------------===//
#pragma once
#include "duckdb.hpp"
namespace duckdb {
class ShellExtension : public Extension {
public:
void Load(ExtensionLoader &loader) override;
std::string Name() override;
std::string Version() const override;
};
} // namespace duckdb

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
#include "shell_extension.hpp"
#include "duckdb/main/extension/extension_loader.hpp"
#include "duckdb/common/vector_operations/unary_executor.hpp"
#include "duckdb/main/config.hpp"
#include <stdio.h>
#include <stdlib.h>
namespace duckdb {
static void GetEnvFunction(DataChunk &args, ExpressionState &state, Vector &result) {
UnaryExecutor::Execute<string_t, string_t>(args.data[0], result, args.size(), [&](string_t input) {
string env_name = input.GetString();
auto env_value = getenv(env_name.c_str());
if (!env_value) {
return StringVector::AddString(result, string());
}
return StringVector::AddString(result, env_value);
});
}
static unique_ptr<FunctionData> GetEnvBind(ClientContext &context, ScalarFunction &bound_function,
vector<unique_ptr<Expression>> &arguments) {
auto &config = DBConfig::GetConfig(context);
if (!config.options.enable_external_access) {
throw PermissionException("getenv is disabled through configuration");
}
return nullptr;
}
void ShellExtension::Load(ExtensionLoader &loader) {
loader.SetDescription("Adds CLI-specific support and functionalities");
loader.RegisterFunction(
ScalarFunction("getenv", {LogicalType::VARCHAR}, LogicalType::VARCHAR, GetEnvFunction, GetEnvBind));
}
std::string ShellExtension::Name() {
return "shell";
}
std::string ShellExtension::Version() const {
return DefaultVersion();
}
} // namespace duckdb

View File

@@ -0,0 +1,13 @@
# this directory contains some of the original code from SQLite mainly helper
# functions that we don't really care about replacing ourselves
add_definitions(-DUSE_DUCKDB_SHELL_WRAPPER)
set(SQLITE_FILES printf.c strglob.c)
if(WIN32)
set(SQLITE_FILES ${SQLITE_FILES} os_win.c)
endif()
add_library(sqlite3_api_wrapper_sqlite3 OBJECT ${SQLITE_FILES})
set(ALL_OBJECT_FILES
${ALL_OBJECT_FILES} $<TARGET_OBJECTS:sqlite3_api_wrapper_sqlite3>
PARENT_SCOPE)

View File

@@ -0,0 +1,245 @@
/*
** 2004 May 22
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
******************************************************************************
**
** This file contains code that is specific to Windows.
*/
#if defined(_WIN32) || defined(WIN32)
#include "stripped_sqlite_int.h"
#include <windows.h>
#define SQLITE_OMIT_AUTOINIT
/*
** Allocate and zero memory.
*/
void *sqlite3MallocZero(u64 n) {
void *p = sqlite3Malloc(n);
if (p) {
memset(p, 0, (size_t)n);
}
return p;
}
#define osMultiByteToWideChar MultiByteToWideChar
#define osWideCharToMultiByte WideCharToMultiByte
/*
** Convert a UTF-8 string to Microsoft Unicode.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static LPWSTR winUtf8ToUnicode(const char *zText){
int nChar;
LPWSTR zWideText;
nChar = osMultiByteToWideChar(CP_UTF8, 0, zText, -1, NULL, 0);
if( nChar==0 ){
return 0;
}
zWideText = (LPWSTR) sqlite3MallocZero( nChar*sizeof(WCHAR) );
if( zWideText==0 ){
return 0;
}
nChar = osMultiByteToWideChar(CP_UTF8, 0, zText, -1, zWideText,
nChar);
if( nChar==0 ){
sqlite3_free(zWideText);
zWideText = 0;
}
return zWideText;
}
/*
** Convert a Microsoft Unicode string to UTF-8.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static char *winUnicodeToUtf8(LPCWSTR zWideText){
int nByte;
char *zText;
nByte = osWideCharToMultiByte(CP_UTF8, 0, zWideText, -1, 0, 0, 0, 0);
if( nByte == 0 ){
return 0;
}
zText = (char*) sqlite3MallocZero( nByte );
if( zText==0 ){
return 0;
}
nByte = osWideCharToMultiByte(CP_UTF8, 0, zWideText, -1, zText, nByte,
0, 0);
if( nByte == 0 ){
sqlite3_free(zText);
zText = 0;
}
return zText;
}
/*
** Convert an ANSI string to Microsoft Unicode, using the ANSI or OEM
** code page.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){
int nByte;
LPWSTR zMbcsText;
int codepage = useAnsi ? CP_ACP : CP_OEMCP;
nByte = osMultiByteToWideChar(codepage, 0, zText, -1, NULL,
0)*sizeof(WCHAR);
if( nByte==0 ){
return 0;
}
zMbcsText = (LPWSTR) sqlite3MallocZero( nByte*sizeof(WCHAR) );
if( zMbcsText==0 ){
return 0;
}
nByte = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText,
nByte);
if( nByte==0 ){
sqlite3_free(zMbcsText);
zMbcsText = 0;
}
return zMbcsText;
}
/*
** Convert a Microsoft Unicode string to a multi-byte character string,
** using the ANSI or OEM code page.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static char *winUnicodeToMbcs(LPCWSTR zWideText, int useAnsi){
int nByte;
char *zText;
int codepage = useAnsi ? CP_ACP : CP_OEMCP;
nByte = osWideCharToMultiByte(codepage, 0, zWideText, -1, 0, 0, 0, 0);
if( nByte == 0 ){
return 0;
}
zText = (char*) sqlite3MallocZero( nByte );
if( zText==0 ){
return 0;
}
nByte = osWideCharToMultiByte(codepage, 0, zWideText, -1, zText,
nByte, 0, 0);
if( nByte == 0 ){
sqlite3_free(zText);
zText = 0;
}
return zText;
}
/*
** Convert a multi-byte character string to UTF-8.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static char *winMbcsToUtf8(const char *zText, int useAnsi){
char *zTextUtf8;
LPWSTR zTmpWide;
zTmpWide = winMbcsToUnicode(zText, useAnsi);
if( zTmpWide==0 ){
return 0;
}
zTextUtf8 = winUnicodeToUtf8(zTmpWide);
sqlite3_free(zTmpWide);
return zTextUtf8;
}
/*
** Convert a UTF-8 string to a multi-byte character string.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static char *winUtf8ToMbcs(const char *zText, int useAnsi){
char *zTextMbcs;
LPWSTR zTmpWide;
zTmpWide = winUtf8ToUnicode(zText);
if( zTmpWide==0 ){
return 0;
}
zTextMbcs = winUnicodeToMbcs(zTmpWide, useAnsi);
sqlite3_free(zTmpWide);
return zTextMbcs;
}
/*
** This is a public wrapper for the winUtf8ToUnicode() function.
*/
LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText){
#ifdef SQLITE_ENABLE_API_ARMOR
if( !zText ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
if( sqlite3_initialize() ) return 0;
#endif
return winUtf8ToUnicode(zText);
}
/*
** This is a public wrapper for the winUnicodeToUtf8() function.
*/
char *sqlite3_win32_unicode_to_utf8(LPCWSTR zWideText){
#ifdef SQLITE_ENABLE_API_ARMOR
if( !zWideText ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
if( sqlite3_initialize() ) return 0;
#endif
return winUnicodeToUtf8(zWideText);
}
/*
** This is a public wrapper for the winMbcsToUtf8() function.
*/
char *sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){
#ifdef SQLITE_ENABLE_API_ARMOR
if( !zText ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
if( sqlite3_initialize() ) return 0;
#endif
return winMbcsToUtf8(zText, useAnsi);
}
/*
** This is a public wrapper for the winUtf8ToMbcs() function.
*/
char *sqlite3_win32_utf8_to_mbcs_v2(const char *zText, int useAnsi){
#ifdef SQLITE_ENABLE_API_ARMOR
if( !zText ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
if( sqlite3_initialize() ) return 0;
#endif
return winUtf8ToMbcs(zText, useAnsi);
}
#endif /* SQLITE_OS_WIN */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,262 @@
#include "stripped_sqlite_int.h"
#include <ctype.h>
#define sqlite3Toupper(x) toupper((unsigned char)(x))
#define sqlite3Tolower(x) tolower((unsigned char)(x))
/*
** This lookup table is used to help decode the first byte of
** a multi-byte UTF8 character.
*/
static const unsigned char sqlite3Utf8Trans1[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00,
};
u32 sqlite3Utf8Read(const unsigned char **pz /* Pointer to string from which to read char */
) {
unsigned int c;
/* Same as READ_UTF8() above but without the zTerm parameter.
** For this routine, we assume the UTF8 string is always zero-terminated.
*/
c = *((*pz)++);
if (c >= 0xc0) {
c = sqlite3Utf8Trans1[c - 0xc0];
while ((*(*pz) & 0xc0) == 0x80) {
c = (c << 6) + (0x3f & *((*pz)++));
}
if (c < 0x80 || (c & 0xFFFFF800) == 0xD800 || (c & 0xFFFFFFFE) == 0xFFFE) {
c = 0xFFFD;
}
}
return c;
}
/*
** A structure defining how to do GLOB-style comparisons.
*/
struct compareInfo {
u8 matchAll; /* "*" or "%" */
u8 matchOne; /* "?" or "_" */
u8 matchSet; /* "[" or 0 */
u8 noCase; /* true to ignore case differences */
};
/*
** For LIKE and GLOB matching on EBCDIC machines, assume that every
** character is exactly one byte in size. Also, provde the Utf8Read()
** macro for fast reading of the next character in the common case where
** the next character is ASCII.
*/
#define Utf8Read(A) (A[0] < 0x80 ? *(A++) : sqlite3Utf8Read(&A))
static const struct compareInfo globInfo = {'*', '?', '[', 0};
/* The correct SQL-92 behavior is for the LIKE operator to ignore
** case. Thus 'a' LIKE 'A' would be true. */
static const struct compareInfo likeInfoNorm = {'%', '_', 0, 1};
/* If SQLITE_CASE_SENSITIVE_LIKE is defined, then the LIKE operator
** is case sensitive causing 'a' LIKE 'A' to be false */
// static const struct compareInfo likeInfoAlt = { '%', '_', 0, 0 };
/*
** Possible error returns from patternMatch()
*/
#define SQLITE_MATCH 0
#define SQLITE_NOMATCH 1
#define SQLITE_NOWILDCARDMATCH 2
/*
** Compare two UTF-8 strings for equality where the first string is
** a GLOB or LIKE expression. Return values:
**
** SQLITE_MATCH: Match
** SQLITE_NOMATCH: No match
** SQLITE_NOWILDCARDMATCH: No match in spite of having * or % wildcards.
**
** Globbing rules:
**
** '*' Matches any sequence of zero or more characters.
**
** '?' Matches exactly one character.
**
** [...] Matches one character from the enclosed list of
** characters.
**
** [^...] Matches one character not in the enclosed list.
**
** With the [...] and [^...] matching, a ']' character can be included
** in the list by making it the first character after '[' or '^'. A
** range of characters can be specified using '-'. Example:
** "[a-z]" matches any single lower-case letter. To match a '-', make
** it the last character in the list.
**
** Like matching rules:
**
** '%' Matches any sequence of zero or more characters
**
*** '_' Matches any one character
**
** Ec Where E is the "esc" character and c is any other
** character, including '%', '_', and esc, match exactly c.
**
** The comments within this routine usually assume glob matching.
**
** This routine is usually quick, but can be N**2 in the worst case.
*/
static int patternCompare(const u8 *zPattern, /* The glob pattern */
const u8 *zString, /* The string to compare against the glob */
const struct compareInfo *pInfo, /* Information about how to do the compare */
uint32_t matchOther /* The escape char (LIKE) or '[' (GLOB) */
) {
uint32_t c, c2; /* Next pattern and input string chars */
uint32_t matchOne = pInfo->matchOne; /* "?" or "_" */
uint32_t matchAll = pInfo->matchAll; /* "*" or "%" */
u8 noCase = pInfo->noCase; /* True if uppercase==lowercase */
const u8 *zEscaped = 0; /* One past the last escaped input char */
while ((c = Utf8Read(zPattern)) != 0) {
if (c == matchAll) { /* Match "*" */
/* Skip over multiple "*" characters in the pattern. If there
** are also "?" characters, skip those as well, but consume a
** single character of the input string for each "?" skipped */
while ((c = Utf8Read(zPattern)) == matchAll || c == matchOne) {
if (c == matchOne && sqlite3Utf8Read(&zString) == 0) {
return SQLITE_NOWILDCARDMATCH;
}
}
if (c == 0) {
return SQLITE_MATCH; /* "*" at the end of the pattern matches */
} else if (c == matchOther) {
if (pInfo->matchSet == 0) {
c = sqlite3Utf8Read(&zPattern);
if (c == 0)
return SQLITE_NOWILDCARDMATCH;
} else {
/* "[...]" immediately follows the "*". We have to do a slow
** recursive search in this case, but it is an unusual case. */
assert(matchOther < 0x80); /* '[' is a single-byte character */
while (*zString) {
int bMatch = patternCompare(&zPattern[-1], zString, pInfo, matchOther);
if (bMatch != SQLITE_NOMATCH)
return bMatch;
SQLITE_SKIP_UTF8(zString);
}
return SQLITE_NOWILDCARDMATCH;
}
}
/* At this point variable c contains the first character of the
** pattern string past the "*". Search in the input string for the
** first matching character and recursively continue the match from
** that point.
**
** For a case-insensitive search, set variable cx to be the same as
** c but in the other case and search the input string for either
** c or cx.
*/
if (c <= 0x80) {
char zStop[3];
int bMatch;
if (noCase) {
zStop[0] = sqlite3Toupper(c);
zStop[1] = sqlite3Tolower(c);
zStop[2] = 0;
} else {
zStop[0] = c;
zStop[1] = 0;
}
while (1) {
zString += strcspn((const char *)zString, zStop);
if (zString[0] == 0)
break;
zString++;
bMatch = patternCompare(zPattern, zString, pInfo, matchOther);
if (bMatch != SQLITE_NOMATCH)
return bMatch;
}
} else {
int bMatch;
while ((c2 = Utf8Read(zString)) != 0) {
if (c2 != c)
continue;
bMatch = patternCompare(zPattern, zString, pInfo, matchOther);
if (bMatch != SQLITE_NOMATCH)
return bMatch;
}
}
return SQLITE_NOWILDCARDMATCH;
}
if (c == matchOther) {
if (pInfo->matchSet == 0) {
c = sqlite3Utf8Read(&zPattern);
if (c == 0)
return SQLITE_NOMATCH;
zEscaped = zPattern;
} else {
uint32_t prior_c = 0;
int seen = 0;
int invert = 0;
c = sqlite3Utf8Read(&zString);
if (c == 0)
return SQLITE_NOMATCH;
c2 = sqlite3Utf8Read(&zPattern);
if (c2 == '^') {
invert = 1;
c2 = sqlite3Utf8Read(&zPattern);
}
if (c2 == ']') {
if (c == ']')
seen = 1;
c2 = sqlite3Utf8Read(&zPattern);
}
while (c2 && c2 != ']') {
if (c2 == '-' && zPattern[0] != ']' && zPattern[0] != 0 && prior_c > 0) {
c2 = sqlite3Utf8Read(&zPattern);
if (c >= prior_c && c <= c2)
seen = 1;
prior_c = 0;
} else {
if (c == c2) {
seen = 1;
}
prior_c = c2;
}
c2 = sqlite3Utf8Read(&zPattern);
}
if (c2 == 0 || (seen ^ invert) == 0) {
return SQLITE_NOMATCH;
}
continue;
}
}
c2 = Utf8Read(zString);
if (c == c2)
continue;
if (noCase && sqlite3Tolower(c) == sqlite3Tolower(c2) && c < 0x80 && c2 < 0x80) {
continue;
}
if (c == matchOne && zPattern != zEscaped && c2 != 0)
continue;
return SQLITE_NOMATCH;
}
return *zString == 0 ? SQLITE_MATCH : SQLITE_NOMATCH;
}
/*
** The sqlite3_strglob() interface. Return 0 on a match (like strcmp()) and
** non-zero if there is no match.
*/
int sqlite3_strglob(const char *zGlobPattern, const char *zString) {
return patternCompare((u8 *)zGlobPattern, (u8 *)zString, &globInfo, '[');
}
/*
** The sqlite3_strlike() interface. Return 0 on a match and non-zero for
** a miss - like strcmp().
*/
int sqlite3_strlike(const char *zPattern, const char *zStr, unsigned int esc) {
return patternCompare((u8 *)zPattern, (u8 *)zStr, &likeInfoNorm, esc);
}

View File

@@ -0,0 +1,58 @@
#ifndef __STRIPPED_SQLITE_INT__
#define __STRIPPED_SQLITE_INT__
#define LONGDOUBLE_TYPE long double
#include <stdint.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef int64_t i64;
typedef uint64_t u64;
typedef int64_t sqlite3_int64;
typedef uint64_t sqlite_uint64;
typedef uint64_t sqlite3_uint64;
#ifdef USE_DUCKDB_SHELL_WRAPPER
#include "duckdb_shell_wrapper.h"
void *sqlite3_realloc64(void *ptr, sqlite3_uint64 n);
void *sqlite3_free(void *ptr);
#else
#define sqlite3_realloc64 realloc
#define sqlite3_free free
#endif
#define sqlite3Malloc malloc
#define sqlite3IsNaN isnan
#define ArraySize(X) ((int)(sizeof(X) / sizeof(X[0])))
#define LARGEST_INT64 (0xffffffff | (((i64)0x7fffffff) << 32))
#define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64)
#include <assert.h>
#define SQLITE_SKIP_UTF8(zIn) \
{ \
if ((*(zIn++)) >= 0xc0) { \
while ((*zIn & 0xc0) == 0x80) { \
zIn++; \
} \
} \
}
#ifndef MAX
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#endif
#ifndef MIN
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#endif
#ifndef SQLITE_MAX_LENGTH
#define SQLITE_MAX_LENGTH 1000000000
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
# set(UDF_WRAPPER_FILES cast_sqlite.cpp)
include_directories(include)
add_library(sqlite3_udf_api OBJECT sqlite3_udf_wrapper.cpp cast_sqlite.cpp)
set(ALL_OBJECT_FILES
${ALL_OBJECT_FILES} $<TARGET_OBJECTS:sqlite3_udf_api>
PARENT_SCOPE)

View File

@@ -0,0 +1,155 @@
#include "cast_sqlite.hpp"
#include "duckdb/common/vector_operations/vector_operations.hpp"
#include "duckdb/common/types/validity_mask.hpp"
namespace duckdb {
bool CastSQLite::RequiresCastToVarchar(LogicalType type) {
LogicalTypeId type_id = type.id();
switch (type_id) {
case LogicalTypeId::TINYINT:
case LogicalTypeId::SMALLINT:
case LogicalTypeId::INTEGER:
case LogicalTypeId::BIGINT:
case LogicalTypeId::FLOAT:
case LogicalTypeId::DOUBLE:
case LogicalTypeId::CHAR:
case LogicalTypeId::VARCHAR:
case LogicalTypeId::BLOB:
case LogicalTypeId::SQLNULL:
return false; // supported types
default:
return true; // types need casting to varchar
}
}
void CastSQLite::InputVectorsToVarchar(DataChunk &data_chunk, DataChunk &new_chunk) {
new_chunk.SetCardinality(data_chunk.size());
if (data_chunk.ColumnCount() == 0) {
return;
}
auto new_types = data_chunk.GetTypes();
for (auto &type : new_types) {
if (CastSQLite::RequiresCastToVarchar(type)) {
type = LogicalType::VARCHAR;
}
}
new_chunk.Initialize(Allocator::DefaultAllocator(), new_types);
for (idx_t i = 0; i < data_chunk.ColumnCount(); ++i) {
if (CastSQLite::RequiresCastToVarchar(data_chunk.data[i].GetType())) {
VectorOperations::DefaultCast(data_chunk.data[i], new_chunk.data[i], data_chunk.size(), true);
} else {
new_chunk.data[i].Reference(data_chunk.data[i]);
}
}
}
VectorType CastSQLite::ToVectorsSQLiteValue(DataChunk &data_chunk, Vector &result,
vector<unique_ptr<vector<sqlite3_value>>> &vec_sqlite_values,
duckdb::unsafe_unique_array<UnifiedVectorFormat> vec_data) {
VectorType result_vec_type = VectorType::CONSTANT_VECTOR;
// Casting input data to sqlite_value
for (idx_t i = 0; i < data_chunk.ColumnCount(); ++i) {
auto &input_data = vec_data[i];
auto sqlite_values = CastSQLite::ToVector(data_chunk.data[i].GetType(), input_data, data_chunk.size(), result);
vec_sqlite_values[i] = std::move(sqlite_values);
// case there is a non-constant input vector, the result must be a FLAT vector
if (data_chunk.data[i].GetVectorType() != VectorType::CONSTANT_VECTOR) {
result_vec_type = VectorType::FLAT_VECTOR;
}
}
return result_vec_type;
}
//*** Cast to vectors ***********************************/
unique_ptr<vector<sqlite3_value>> CastSQLite::ToVector(LogicalType type, UnifiedVectorFormat &vec_data, idx_t size,
Vector &result) {
LogicalTypeId type_id = type.id();
switch (type_id) {
case LogicalTypeId::TINYINT: {
return CastToVectorSQLiteValue::Operation<int8_t, CastToSQLiteValue>(vec_data, size);
}
case LogicalTypeId::SMALLINT: {
return CastToVectorSQLiteValue::Operation<int16_t, CastToSQLiteValue>(vec_data, size);
}
case LogicalTypeId::INTEGER: {
return CastToVectorSQLiteValue::Operation<int32_t, CastToSQLiteValue>(vec_data, size);
}
case LogicalTypeId::BIGINT: {
return CastToVectorSQLiteValue::Operation<int64_t, CastToSQLiteValue>(vec_data, size);
}
case LogicalTypeId::FLOAT: {
return CastToVectorSQLiteValue::Operation<float, CastToSQLiteValue>(vec_data, size);
}
case LogicalTypeId::DOUBLE: {
return CastToVectorSQLiteValue::Operation<double, CastToSQLiteValue>(vec_data, size);
}
case LogicalTypeId::BLOB: {
return CastToVectorSQLiteValue::Operation<string_t, CastToSQLiteValue::Blob>(vec_data, size);
}
case LogicalTypeId::SQLNULL: {
return CastToVectorSQLiteValue::FromNull(size);
}
case LogicalTypeId::CHAR:
case LogicalTypeId::VARCHAR:
default:
return CastToVectorSQLiteValue::Operation<string_t, CastToSQLiteValue>(vec_data, size);
}
}
/*** Cast Single Value Operations *****************************/
// INT casts
template <>
sqlite3_value CastToSQLiteValue::Operation(int8_t input) {
return OperationInt<int8_t>(input);
}
template <>
sqlite3_value CastToSQLiteValue::Operation(int16_t input) {
return OperationInt<int16_t>(input);
}
template <>
sqlite3_value CastToSQLiteValue::Operation(int32_t input) {
return OperationInt<int32_t>(input);
}
template <>
sqlite3_value CastToSQLiteValue::Operation(int64_t input) {
return OperationInt<int64_t>(input);
}
// FLOAT casts
template <>
sqlite3_value CastToSQLiteValue::Operation(float input) {
return OperationFloat<float>(input);
}
template <>
sqlite3_value CastToSQLiteValue::Operation(double input) {
return OperationFloat<double>(input);
}
// STRING cast
template <>
sqlite3_value CastToSQLiteValue::Operation(string_t input) {
sqlite3_value sqlite_str;
sqlite_str.type = SQLiteTypeValue::TEXT;
sqlite_str.str = input.GetString();
return sqlite_str;
}
sqlite3_value CastToSQLiteValue::OperationNull() {
sqlite3_value sqlite_null;
sqlite_null.type = SQLiteTypeValue::NULL_VALUE;
sqlite_null.u.i = 0;
sqlite_null.u.r = 0.0;
return sqlite_null;
}
} // namespace duckdb

View File

@@ -0,0 +1,124 @@
#include "udf_struct_sqlite3.h"
#include "sqlite3_value_type.hpp"
#include "duckdb/common/types/vector.hpp"
#include "duckdb/common/constants.hpp"
#include "duckdb/common/operator/cast_operators.hpp"
#include "duckdb/common/types/data_chunk.hpp"
#include "duckdb/common/operator/string_cast.hpp"
namespace duckdb {
struct CastSQLite {
static void InputVectorsToVarchar(DataChunk &data_chunk, DataChunk &new_chunk);
static bool RequiresCastToVarchar(LogicalType type);
static VectorType ToVectorsSQLiteValue(DataChunk &data_chunk, Vector &result,
vector<unique_ptr<vector<sqlite3_value>>> &vec_sqlites,
duckdb::unsafe_unique_array<UnifiedVectorFormat> vec_data);
static unique_ptr<vector<sqlite3_value>> ToVector(LogicalType type, UnifiedVectorFormat &vec_data, idx_t size,
Vector &result);
};
struct CastToSQLiteValue {
template <class SRC>
static inline sqlite3_value Operation(SRC input) {
return (sqlite3_value)input;
}
template <class SRC>
static inline sqlite3_value OperationInt(SRC input) {
sqlite3_value sqlite_int;
sqlite_int.u.i = input;
sqlite_int.type = SQLiteTypeValue::INTEGER;
return sqlite_int;
}
template <class SRC>
static inline sqlite3_value OperationFloat(SRC input) {
sqlite3_value sqlite_float;
sqlite_float.u.r = input;
sqlite_float.type = SQLiteTypeValue::FLOAT;
return sqlite_float;
}
struct Blob {
template <class SRC = string_t>
static sqlite3_value Operation(SRC blob) {
sqlite3_value sqlite_blob;
sqlite_blob.type = SQLiteTypeValue::BLOB;
sqlite_blob.str = blob.GetString();
return sqlite_blob;
}
};
static sqlite3_value OperationNull();
};
struct CastToVectorSQLiteValue {
template <class INPUT_TYPE, class OPCAST>
static inline unique_ptr<vector<sqlite3_value>> Operation(UnifiedVectorFormat &vec_data, idx_t count) {
unique_ptr<vector<sqlite3_value>> result = make_uniq<vector<sqlite3_value>>(count);
auto res_data = (*result).data();
auto input_data = UnifiedVectorFormat::GetData<INPUT_TYPE>(vec_data);
if (vec_data.validity.AllValid()) {
for (idx_t i = 0; i < count; ++i) {
auto idx = vec_data.sel->get_index(i);
res_data[i] = OPCAST::template Operation<INPUT_TYPE>(input_data[idx]);
}
return result;
}
for (idx_t i = 0; i < count; ++i) {
auto idx = vec_data.sel->get_index(i);
if (vec_data.validity.RowIsValidUnsafe(idx)) {
res_data[i] = OPCAST::template Operation<INPUT_TYPE>(input_data[idx]);
} else {
res_data[i] = CastToSQLiteValue::OperationNull();
}
}
return result;
}
static inline unique_ptr<vector<sqlite3_value>> FromNull(idx_t count) {
unique_ptr<vector<sqlite3_value>> result = make_uniq<vector<sqlite3_value>>(count);
auto res_data = (*result).data();
for (idx_t i = 0; i < count; ++i) {
res_data[i] = CastToSQLiteValue::OperationNull();
}
return result;
}
};
// CAST to sqlite int ****************************/
template <>
sqlite3_value CastToSQLiteValue::Operation(int8_t input);
template <>
sqlite3_value CastToSQLiteValue::Operation(int16_t input);
template <>
sqlite3_value CastToSQLiteValue::Operation(int32_t input);
template <>
sqlite3_value CastToSQLiteValue::Operation(int64_t input);
// CAST to sqlite float **************************/
template <>
sqlite3_value CastToSQLiteValue::Operation(float input);
template <>
sqlite3_value CastToSQLiteValue::Operation(double input);
// CAST string **********************************/
template <>
sqlite3_value CastToSQLiteValue::Operation(char *input);
template <>
sqlite3_value CastToSQLiteValue::Operation(string_t input);
} // namespace duckdb

View File

@@ -0,0 +1,14 @@
#include "udf_struct_sqlite3.h"
#include "duckdb/function/scalar_function.hpp"
namespace duckdb {
typedef void (*scalar_sqlite_udf_t)(sqlite3_context *, int, sqlite3_value **);
struct SQLiteUDFWrapper {
public:
static duckdb::scalar_function_t CreateSQLiteScalarFunction(duckdb::scalar_sqlite_udf_t sqlite_udf, sqlite3 *db,
void *pApp);
};
} // namespace duckdb

View File

@@ -0,0 +1,5 @@
#pragma once
namespace duckdb {
enum class SQLiteTypeValue : uint8_t { INTEGER = 1, FLOAT = 2, TEXT = 3, BLOB = 4, NULL_VALUE = 5 };
}

View File

@@ -0,0 +1,63 @@
#pragma once
#include "duckdb/common/types.hpp"
#include "duckdb/common/types/string_type.hpp"
#include "duckdb/common/types/string_heap.hpp"
#include "duckdb/common/error_data.hpp"
#include "duckdb.hpp"
#include <memory>
#include <string>
#include "sqlite3.h"
#include "sqlite3_value_type.hpp"
// it was moved to here because the UDF API must know the structure members
struct sqlite3 {
duckdb::unique_ptr<duckdb::DuckDB> db;
duckdb::unique_ptr<duckdb::Connection> con;
duckdb::ErrorData last_error;
int64_t last_changes = 0;
int64_t total_changes = 0;
int errCode; /* Most recent error code (SQLITE_*) */
};
struct sqlite3_value {
union MemValue {
double r; /* Real value used when MEM_Real is set in flags */
int64_t i; /* Integer value used when MEM_Int is set in flags */
// int nZero; /* Extra zero bytes when MEM_Zero and MEM_Blob set */
} u;
duckdb::SQLiteTypeValue type;
std::string str;
sqlite3 *db; /* The associated database connection */
};
struct FuncDef {
// i8 nArg; /* Number of arguments. -1 means unlimited */
// u32 funcFlags; /* Some combination of SQLITE_FUNC_* */
void *pUserData; /* User data parameter */
// FuncDef *pNext; /* Next function with same name */
// void (*xSFunc)(sqlite3_context*,int,sqlite3_value**); /* func or agg-step */
// void (*xFinalize)(sqlite3_context*); /* Agg finalizer */
// void (*xValue)(sqlite3_context*); /* Current agg value */
// void (*xInverse)(sqlite3_context*,int,sqlite3_value**); /* inverse agg-step */
// const char *zName; /* SQL name of the function. */
// union {
// FuncDef *pHash; /* Next with a different name but the same hash */
// FuncDestructor *pDestructor; /* Reference counted destructor function */
// } u;
};
struct sqlite3_context {
sqlite3_value result; // Mem *pOut; /* The return value is stored here */
FuncDef pFunc; /* Pointer to function information */
// Mem *pMem; /* Memory cell used to store aggregate context */
// Vdbe *pVdbe; /* The VM that owns this context */
// int iOp; /* Instruction number of OP_Function */
int isError; /* Error code returned by the function. */
// u8 skipFlag; /* Skip accumulator loading if true */
// u8 argc; /* Number of arguments */
// sqlite3_value *argv[1]; /* Argument set */
};

View File

@@ -0,0 +1,81 @@
#include "sqlite3_udf_wrapper.hpp"
#include "cast_sqlite.hpp"
#include <cassert>
#include "sqlite3.h"
#include "duckdb/common/types/blob.hpp"
#include "duckdb/common/operator/string_cast.hpp"
duckdb::scalar_function_t duckdb::SQLiteUDFWrapper::CreateSQLiteScalarFunction(duckdb::scalar_sqlite_udf_t sqlite_udf,
sqlite3 *db_sqlite3, void *pApp) {
duckdb::scalar_function_t udf_function = [=](DataChunk &args, ExpressionState &state, Vector &result) -> void {
DataChunk cast_chunk;
CastSQLite::InputVectorsToVarchar(args, cast_chunk);
// ToUnifiedFormat all input columns
auto vec_data = cast_chunk.ToUnifiedFormat();
// Vector of sqlite3_value for all input columns
vector<unique_ptr<vector<sqlite3_value>>> vec_sqlite(cast_chunk.ColumnCount());
// Casting input data to vectors of sqlite_value
VectorType result_vec_type =
CastSQLite::ToVectorsSQLiteValue(cast_chunk, result, vec_sqlite, std::move(vec_data));
sqlite3_context context;
context.pFunc.pUserData = pApp; // set the function data
size_t argc = cast_chunk.ColumnCount(); // num of args for the UDF
duckdb::unique_ptr<sqlite3_value *[]> argv = duckdb::unique_ptr<sqlite3_value *[]>(new sqlite3_value *[argc]);
// traversing the vector of sqlite values
auto result_data = FlatVector::GetData<string_t>(result);
for (idx_t row_idx = 0; row_idx < cast_chunk.size(); ++row_idx) {
// create a tuple from sqlite_values
for (idx_t col_idx = 0; col_idx < argc; ++col_idx) {
argv[col_idx] = &(*(vec_sqlite[col_idx]))[row_idx];
argv[col_idx]->db = db_sqlite3;
db_sqlite3->errCode = SQLITE_OK;
}
// call the UDF on that tuple
context.isError = SQLITE_OK;
context.result.type = SQLiteTypeValue::NULL_VALUE;
sqlite_udf(&context, argc, argv.get());
// check memory allocatated by the sqlite_values
// error set by the UDF
if (context.isError != SQLITE_OK) {
string str_msg;
if (context.result.type == SQLiteTypeValue::TEXT) {
str_msg = context.result.str;
} else {
str_msg = "Error in SQLite UDF, but no error message was provided";
}
throw std::runtime_error(str_msg.c_str());
}
// getting the result type set by the UDF
switch (context.result.type) {
case SQLiteTypeValue::INTEGER:
result_data[row_idx] = StringCast::Operation(context.result.u.i, result);
break;
case SQLiteTypeValue::FLOAT:
result_data[row_idx] = StringCast::Operation(context.result.u.r, result);
break;
case SQLiteTypeValue::TEXT:
result_data[row_idx] = StringVector::AddString(result, context.result.str);
break;
case SQLiteTypeValue::BLOB:
result_data[row_idx] = StringVector::AddString(result, Blob::ToString(context.result.str));
break;
case SQLiteTypeValue::NULL_VALUE:
// NULL value set by the UDF by calling sqlite3_result_null()
FlatVector::SetNull(result, row_idx, true);
break;
default:
throw InternalException("Unrecognized SQLiteTypeValue in type conversion");
}
}
result.SetVectorType(result_vec_type);
};
return udf_function;
}

View File

@@ -0,0 +1,8 @@
set(CUR_DIR ${CMAKE_CURRENT_SOURCE_DIR})
include_directories(../sqlite3_udf_api/include)
set(SQLITE_TEST_FILES
${CUR_DIR}/test_sqlite3_api_wrapper.cpp
${CUR_DIR}/test_sqlite3_udf_api_wrapper.cpp
PARENT_SCOPE)

View File

@@ -0,0 +1,64 @@
#include "sqlite3.h"
#include <string>
#include <vector>
#include "util_functions.hpp"
// C++ wrapper class for the C wrapper API that wraps our C++ API, because why not
class SQLiteDBWrapper {
public:
SQLiteDBWrapper() : db(nullptr) {
}
~SQLiteDBWrapper() {
if (db) {
sqlite3_close(db);
}
}
sqlite3 *db;
std::vector<std::vector<std::string>> results;
public:
int Open(std::string filename) {
return sqlite3_open(filename.c_str(), &db) == SQLITE_OK;
}
std::string GetErrorMessage() {
auto err = sqlite3_errmsg(db);
return err ? std::string(err) : std::string();
}
bool Execute(std::string query) {
results.clear();
char *errmsg = nullptr;
int rc = sqlite3_exec(db, query.c_str(), concatenate_results, &results, &errmsg);
if (errmsg) {
sqlite3_free(errmsg);
}
return rc == SQLITE_OK;
}
void PrintResult() {
print_result(results);
}
bool CheckColumn(size_t column, std::vector<std::string> expected_data) {
if (column >= results.size()) {
fprintf(stderr, "Column index is out of range!\n");
PrintResult();
return false;
}
if (results[column].size() != expected_data.size()) {
fprintf(stderr, "Row counts do not match!\n");
PrintResult();
return false;
}
for (size_t i = 0; i < expected_data.size(); i++) {
if (expected_data[i] != results[column][i]) {
fprintf(stderr, "Value does not match: expected \"%s\" but got \"%s\"\n", expected_data[i].c_str(),
results[column][i].c_str());
return false;
}
}
return true;
}
};

View File

@@ -0,0 +1,80 @@
#include "sqlite3.h"
#include "util_functions.hpp"
class SQLiteStmtWrapper {
public:
SQLiteStmtWrapper() : stmt(nullptr) {
}
~SQLiteStmtWrapper() {
Finalize();
}
sqlite3_stmt *stmt;
std::string error_message;
int Prepare(sqlite3 *db, const char *zSql, int nByte, const char **pzTail) {
Finalize();
return sqlite3_prepare_v2(db, zSql, nByte, &stmt, pzTail);
}
/**
* Execute a prepared statement previously "groomed" and print the result
*/
int ExecutePreparedStmt() {
if (!stmt) {
fprintf(stderr, "There is no a prepared statement: Prepare(...) must be invoked firstly.\n");
return SQLITE_MISUSE;
}
int rc = SQLITE_ROW;
std::vector<std::vector<std::string>> results; /* To print the result */
size_t nCol = sqlite3_column_count(stmt);
char **azCols = (char **)malloc(nCol * sizeof(const char *)); /* Names of result columns */
char **azVals = (char **)malloc(nCol * sizeof(const char *)); /* Result values */
if (!azCols || !azVals) {
rc = SQLITE_NOMEM;
}
for (duckdb::idx_t i = 0; i < nCol; i++) {
azCols[i] = (char *)sqlite3_column_name(stmt, i);
}
while (rc == SQLITE_ROW) {
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
for (duckdb::idx_t i = 0; i < nCol; i++) {
azVals[i] = (char *)sqlite3_column_text(stmt, i);
if (!azVals[i] && sqlite3_column_type(stmt, i) != SQLITE_NULL) {
rc = SQLITE_NOMEM;
fprintf(stderr, "sqlite3_exec: out of memory.\n");
break;
}
}
if (concatenate_results(&results, nCol, azVals, azCols)) {
/* EVIDENCE-OF: R-38229-40159 If the callback function to
** sqlite3_exec() returns non-zero, then sqlite3_exec() will
** return SQLITE_ABORT. */
rc = SQLITE_ABORT;
fprintf(stderr, "sqlite3_exec: callback returned non-zero. "
"Aborting.\n");
break;
}
}
}
if (rc == SQLITE_DONE) {
print_result(results);
}
Finalize();
sqlite3_free(azCols);
sqlite3_free(azVals);
return (rc == SQLITE_DONE) ? SQLITE_OK : rc;
}
void Finalize() {
if (stmt) {
sqlite3_finalize(stmt);
stmt = nullptr;
}
}
};

View File

@@ -0,0 +1,232 @@
#include "sqlite3.h"
#include "udf_struct_sqlite3.h"
#include <string>
// SQLite UDF to be register on DuckDB
static void multiply10(sqlite3_context *context, int argc, sqlite3_value **argv) {
REQUIRE(argc == 1);
int v = sqlite3_value_int(argv[0]);
v *= 10;
sqlite3_result_int(context, v);
}
static void sum_cols_int(sqlite3_context *context, int argc, sqlite3_value **argv) {
REQUIRE(argc > 0);
auto sum = sqlite3_value_int(argv[0]);
for (int i = 1; i < argc; ++i) {
sum += sqlite3_value_int(argv[i]);
}
sqlite3_result_int(context, sum);
}
static void sum_cols_int_check_nulls(sqlite3_context *context, int argc, sqlite3_value **argv) {
REQUIRE(argc > 0);
auto sum = 0;
int res_type = SQLITE_INTEGER;
for (int i = 0; i < argc; ++i) {
int type = sqlite3_value_type(argv[i]);
if (type == SQLITE_NULL) {
res_type = SQLITE_NULL;
} else {
sum += sqlite3_value_int(argv[i]);
}
}
if (res_type == SQLITE_NULL) {
sqlite3_result_null(context);
} else {
sqlite3_result_int(context, sum);
}
}
static void sum_cols_double(sqlite3_context *context, int argc, sqlite3_value **argv) {
REQUIRE(argc > 0);
auto sum = sqlite3_value_double(argv[0]);
for (int i = 1; i < argc; ++i) {
sum += sqlite3_value_double(argv[i]);
}
sqlite3_result_double(context, sum);
}
static void check_text(sqlite3_context *context, int argc, sqlite3_value **argv) {
REQUIRE(argc == 1);
char *str = (char *)sqlite3_value_text(argv[0]);
int len = sqlite3_value_bytes(argv[0]);
for (int i = 0; i < len; ++i) {
str[i] = 'T';
}
sqlite3_result_text(context, str, len, nullptr);
}
static void check_null_terminated_string(sqlite3_context *context, int argc, sqlite3_value **argv) {
REQUIRE(argc == 1);
char *str = (char *)sqlite3_value_text(argv[0]);
// strlen expects a null-terminated string
// otherwise, the result is undefined, it's likely to happen a 'heap-buffer-overflow'
size_t str_len = strlen(str);
// both length must be equal
REQUIRE(str_len == (size_t)sqlite3_value_bytes(argv[0]));
sqlite3_result_text(context, str, str_len, nullptr);
}
static void check_blob(sqlite3_context *context, int argc, sqlite3_value **argv) {
REQUIRE(argc == 1);
auto blob = (char *)sqlite3_value_blob(argv[0]);
int len = sqlite3_value_bytes(argv[0]);
for (int i = 0; i < len; ++i) {
blob[i] = 'B';
}
sqlite3_result_blob(context, blob, len, nullptr);
}
static void check_type(sqlite3_context *context, int argc, sqlite3_value **argv) {
REQUIRE(argc == 1);
int type_id = sqlite3_value_type(argv[0]);
switch (type_id) {
case SQLITE_INTEGER: {
auto value = sqlite3_value_int(argv[0]);
sqlite3_result_int(context, value * 10); // value x 10
break;
}
case SQLITE_FLOAT: {
auto value = sqlite3_value_double(argv[0]);
sqlite3_result_double(context, value * 100); // value x 100
break;
}
case SQLITE_TEXT: {
auto value = (char *)sqlite3_value_text(argv[0]);
value[0] = 'T';
value[1] = 'E';
value[2] = 'X';
value[3] = 'T';
auto len = sqlite3_value_bytes(argv[0]);
sqlite3_result_text(context, value, len, nullptr);
break;
}
case SQLITE_BLOB: {
auto value = sqlite3_value_blob(argv[0]);
((char *)value)[0] = 'B';
((char *)value)[1] = 'L';
((char *)value)[2] = 'O';
((char *)value)[3] = 'B';
auto len = sqlite3_value_bytes(argv[0]);
sqlite3_result_blob(context, value, len, nullptr);
break;
}
default:
break;
}
}
static void set_null(sqlite3_context *context, int argc, sqlite3_value **argv) {
REQUIRE(argc == 1);
sqlite3_result_null(context);
}
// get user data and replace the input value
static void get_user_data(sqlite3_context *context, int argc, sqlite3_value **argv) {
REQUIRE(argc == 1);
auto *pData = sqlite3_user_data(context);
if (pData) {
char *str = (char *)sqlite3_value_text(argv[0]);
int str_len = sqlite3_value_bytes(argv[0]);
int undescore_idx = -1;
// find first '_' underscore
for (int i = 0; i < str_len; ++i) {
if (str[i] == '_') {
undescore_idx = i;
break;
}
}
if (undescore_idx > -1) {
char *userData = (char *)pData;
int u_len = strlen(userData);
// replace from the first undescore, case there is memory space in the string
if (u_len + undescore_idx <= str_len) {
memcpy(str + undescore_idx, userData, u_len);
}
}
sqlite3_result_text(context, str, str_len, nullptr);
} else {
sqlite3_result_null(context);
}
}
// get text value from integer or float types
static void cast_numbers_to_text(sqlite3_context *context, int argc, sqlite3_value **argv) {
REQUIRE((sqlite3_value_type(argv[0]) == SQLITE_INTEGER || sqlite3_value_type(argv[0]) == SQLITE_FLOAT));
char *str = (char *)sqlite3_value_text(argv[0]); // argv[0] is a an integer
REQUIRE(sqlite3_value_type(argv[0]) == SQLITE_TEXT);
size_t len = sqlite3_value_bytes(argv[0]);
sqlite3_result_text(context, str, len, nullptr);
}
static void cast_to_int32(sqlite3_context *context, int argc, sqlite3_value **argv) {
// argv[0] is not a 32-bit integer, internal casting must occur
int value = sqlite3_value_int(argv[0]);
if (sqlite3_errcode(argv[0]->db) == SQLITE_MISMATCH) {
sqlite3_result_null(context);
} else {
sqlite3_result_int(context, value);
}
}
static void cast_to_int64(sqlite3_context *context, int argc, sqlite3_value **argv) {
// argv[0] is not a 64-bit integer, internal casting must occur
int64_t value = sqlite3_value_int64(argv[0]);
if (sqlite3_errcode(argv[0]->db) == SQLITE_MISMATCH) {
sqlite3_result_null(context);
} else {
sqlite3_result_int64(context, value);
}
}
static void cast_to_float(sqlite3_context *context, int argc, sqlite3_value **argv) {
REQUIRE(sqlite3_value_type(argv[0]) != SQLITE_FLOAT);
// argv[0] is not a float, internal casting must occur
double value = sqlite3_value_double(argv[0]);
if (sqlite3_errcode(argv[0]->db) == SQLITE_MISMATCH) {
sqlite3_result_null(context);
} else {
sqlite3_result_double(context, value);
}
}
static void sum_overload_function(sqlite3_context *context, int argc, sqlite3_value **argv) {
REQUIRE(argc > 0);
int value1, value2, value3;
value2 = 0;
value3 = 0;
value1 = sqlite3_value_int(argv[0]);
if (argc == 2) {
value2 = sqlite3_value_int(argv[1]);
} else if (argc == 3) {
value2 = sqlite3_value_int(argv[1]);
value3 = sqlite3_value_int(argv[2]);
}
sqlite3_result_int(context, value1 + value2 + value3);
}
// calling sqlite3_value_text() multiple times
static void calling_value_text_multiple_times(sqlite3_context *context, int argc, sqlite3_value **argv) {
REQUIRE((sqlite3_value_type(argv[0]) == SQLITE_INTEGER || sqlite3_value_type(argv[0]) == SQLITE_FLOAT ||
sqlite3_value_type(argv[0]) == SQLITE_TEXT));
char *str = (char *)sqlite3_value_text(argv[0]);
REQUIRE(sqlite3_value_type(argv[0]) == SQLITE_TEXT);
auto len = strlen(str);
size_t len2;
char *str2;
// calling sqlite3_value_text multiple times, i.e., 10x
for (size_t i = 0; i < 10; ++i) {
str2 = (char *)sqlite3_value_text(argv[0]);
REQUIRE(str == str2);
len2 = strlen(str2);
REQUIRE(len == len2);
}
len = sqlite3_value_bytes(argv[0]);
sqlite3_result_text(context, str, len, nullptr);
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include "sqlite3.h"
#include <string>
#include <vector>
#include "duckdb/common/constants.hpp"
static int concatenate_results(void *arg, int ncols, char **vals, char **colnames) {
auto &results = *((std::vector<std::vector<std::string>> *)arg);
if (results.size() == 0) {
results.resize(ncols);
}
for (int i = 0; i < ncols; i++) {
results[i].push_back(vals[i] ? vals[i] : "NULL");
}
return SQLITE_OK;
}
static void print_result(std::vector<std::vector<std::string>> &results) {
if (results.empty()) {
return;
}
for (duckdb::idx_t row_idx = 0; row_idx < results[0].size(); row_idx++) {
for (duckdb::idx_t col_idx = 0; col_idx < results.size(); col_idx++) {
printf("%s|", results[col_idx][row_idx].c_str());
}
printf("\n");
}
}

View File

@@ -0,0 +1,377 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#ifdef USE_DUCKDB_SHELL_WRAPPER
#include "duckdb_shell_wrapper.h"
#endif
#include "sqlite3.h"
#include <string>
#include <thread>
#include "sqlite_db_wrapper.hpp"
#include "sqlite_stmt_wrapper.hpp"
using namespace std;
TEST_CASE("Basic sqlite wrapper usage", "[sqlite3wrapper]") {
SQLiteDBWrapper db;
// open an in-memory db
REQUIRE(db.Open(":memory:"));
// standard selection
REQUIRE(db.Execute("SELECT 42;"));
REQUIRE(db.CheckColumn(0, {"42"}));
// simple statements
REQUIRE(db.Execute("CREATE TABLE test(i INTEGER)"));
REQUIRE(db.Execute("INSERT INTO test VALUES (1), (2), (3)"));
REQUIRE(db.Execute("SELECT SUM(t1.i)::BIGINT FROM test t1, test t2, test t3;"));
REQUIRE(db.CheckColumn(0, {"54"}));
REQUIRE(db.Execute("DELETE FROM test WHERE i=2"));
REQUIRE(db.Execute("UPDATE test SET i=i+1"));
REQUIRE(db.Execute("SELECT * FROM test ORDER BY 1;"));
REQUIRE(db.CheckColumn(0, {"2", "4"}));
// test different types
#ifndef SQLITE_TEST
REQUIRE(
db.Execute("SELECT CAST('1992-01-01' AS DATE), 3, 'hello world', CAST('1992-01-01 00:00:00' AS TIMESTAMP);"));
REQUIRE(db.CheckColumn(0, {"1992-01-01"}));
REQUIRE(db.CheckColumn(1, {"3"}));
REQUIRE(db.CheckColumn(2, {"hello world"}));
REQUIRE(db.CheckColumn(3, {"1992-01-01 00:00:00"}));
#endif
// handle errors
// syntax error
REQUIRE(!db.Execute("SELEC 42"));
// catalog error
REQUIRE(!db.Execute("SELECT * FROM nonexistant_tbl"));
}
TEST_CASE("Basic prepared statement usage", "[sqlite3wrapper]") {
SQLiteDBWrapper db;
SQLiteStmtWrapper stmt;
// open an in-memory db
REQUIRE(db.Open(":memory:"));
REQUIRE(db.Execute("CREATE TABLE test(i INTEGER, j BIGINT, k DATE, l VARCHAR, b BLOB)"));
#ifndef SQLITE_TEST
// sqlite3_prepare_v2 errors
// nullptr for db/stmt, note: normal sqlite segfaults here
REQUIRE(sqlite3_prepare_v2(nullptr, "INSERT INTO test VALUES ($1, $2, $3, $4, $5)", -1, nullptr, nullptr) ==
SQLITE_MISUSE);
REQUIRE(sqlite3_prepare_v2(db.db, "INSERT INTO test VALUES ($1, $2, $3, $4, $5)", -1, nullptr, nullptr) ==
SQLITE_MISUSE);
#endif
// prepared statement
REQUIRE(stmt.Prepare(db.db, "INSERT INTO test VALUES ($1, $2, $3, $4, $5)", -1, nullptr) == SQLITE_OK);
// test for parameter count, names and indexes
REQUIRE(sqlite3_bind_parameter_count(nullptr) == 0);
REQUIRE(sqlite3_bind_parameter_count(stmt.stmt) == 5);
for (int i = 1; i < 6; i++) {
REQUIRE(sqlite3_bind_parameter_name(nullptr, i) == nullptr);
REQUIRE(sqlite3_bind_parameter_index(nullptr, nullptr) == 0);
REQUIRE(sqlite3_bind_parameter_index(stmt.stmt, nullptr) == 0);
REQUIRE(sqlite3_bind_parameter_name(stmt.stmt, i) != nullptr);
REQUIRE(sqlite3_bind_parameter_name(stmt.stmt, i) == string("$") + to_string(i));
REQUIRE(sqlite3_bind_parameter_index(stmt.stmt, sqlite3_bind_parameter_name(stmt.stmt, i)) == i);
}
REQUIRE(sqlite3_bind_parameter_name(stmt.stmt, 0) == nullptr);
REQUIRE(sqlite3_bind_parameter_name(stmt.stmt, 6) == nullptr);
#ifndef SQLITE_TEST
// this segfaults in SQLITE
REQUIRE(sqlite3_clear_bindings(nullptr) == SQLITE_MISUSE);
#endif
REQUIRE(sqlite3_clear_bindings(stmt.stmt) == SQLITE_OK);
REQUIRE(sqlite3_clear_bindings(stmt.stmt) == SQLITE_OK);
// test for binding parameters
// incorrect bindings: nullptr as statement, wrong type and out of range binding
REQUIRE(sqlite3_bind_int(nullptr, 1, 1) == SQLITE_MISUSE);
REQUIRE(sqlite3_bind_int(stmt.stmt, 0, 1) == SQLITE_RANGE);
REQUIRE(sqlite3_bind_int(stmt.stmt, 6, 1) == SQLITE_RANGE);
// we can bind the incorrect type just fine
// error will only be thrown on execution
REQUIRE(sqlite3_bind_text(stmt.stmt, 1, "hello world", -1, nullptr) == SQLITE_OK);
REQUIRE(sqlite3_bind_int(stmt.stmt, 1, 1) == SQLITE_OK);
// we can rebind the same parameter
REQUIRE(sqlite3_bind_int(stmt.stmt, 1, 2) == SQLITE_OK);
REQUIRE(sqlite3_bind_int64(stmt.stmt, 2, 1000) == SQLITE_OK);
REQUIRE(sqlite3_bind_text(stmt.stmt, 3, "1992-01-01", -1, nullptr) == SQLITE_OK);
REQUIRE(sqlite3_bind_text(stmt.stmt, 4, nullptr, -1, &free) == SQLITE_MISUSE);
char *buffer = (char *)malloc(12);
strcpy(buffer, "hello world");
REQUIRE(sqlite3_bind_text(stmt.stmt, 4, buffer, -1, &free) == SQLITE_OK);
REQUIRE(sqlite3_bind_text(stmt.stmt, 4, "hello world", -1, nullptr) == SQLITE_OK);
// test for bind blob
REQUIRE(sqlite3_bind_blob(stmt.stmt, 5, "hello world", -1, nullptr) == SQLITE_OK);
REQUIRE(sqlite3_bind_blob(stmt.stmt, 5, "hello world", 11, nullptr) == SQLITE_OK);
REQUIRE(sqlite3_bind_blob(stmt.stmt, 5, NULL, 10, &free) == SQLITE_MISUSE);
buffer = (char *)malloc(6);
strcpy(buffer, "hello");
REQUIRE(sqlite3_bind_blob(stmt.stmt, 5, buffer, 5, &free) == SQLITE_OK);
REQUIRE(sqlite3_step(nullptr) == SQLITE_MISUSE);
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_DONE);
// reset the statement
REQUIRE(sqlite3_reset(nullptr) == SQLITE_OK);
REQUIRE(sqlite3_reset(stmt.stmt) == SQLITE_OK);
// we can reset multiple times
REQUIRE(sqlite3_reset(stmt.stmt) == SQLITE_OK);
REQUIRE(sqlite3_bind_null(stmt.stmt, 1) == SQLITE_OK);
REQUIRE(sqlite3_bind_null(stmt.stmt, 2) == SQLITE_OK);
REQUIRE(sqlite3_bind_null(stmt.stmt, 3) == SQLITE_OK);
REQUIRE(sqlite3_bind_null(stmt.stmt, 4) == SQLITE_OK);
REQUIRE(sqlite3_bind_null(stmt.stmt, 5) == SQLITE_OK);
// we can step multiple times
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_DONE);
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_DONE);
REQUIRE(sqlite3_reset(stmt.stmt) == SQLITE_OK);
// after a reset we still have our bound values
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_DONE);
// clearing the bindings results in us not having any values though
REQUIRE(sqlite3_clear_bindings(stmt.stmt) == SQLITE_OK);
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_DONE);
REQUIRE(db.Execute("SELECT * FROM test ORDER BY 1 NULLS FIRST"));
REQUIRE(db.CheckColumn(0, {"NULL", "NULL", "NULL", "NULL", "2"}));
REQUIRE(db.CheckColumn(1, {"NULL", "NULL", "NULL", "NULL", "1000"}));
REQUIRE(db.CheckColumn(2, {"NULL", "NULL", "NULL", "NULL", "1992-01-01"}));
REQUIRE(db.CheckColumn(3, {"NULL", "NULL", "NULL", "NULL", "hello world"}));
REQUIRE(db.CheckColumn(4, {"NULL", "NULL", "NULL", "NULL", "hello"}));
REQUIRE(sqlite3_finalize(nullptr) == SQLITE_OK);
// first prepare the statement again
REQUIRE(stmt.Prepare(db.db, "SELECT CAST($1 AS INTEGER) FROM test", -1, nullptr) == SQLITE_OK);
// bind a non-integer here
REQUIRE(sqlite3_bind_text(stmt.stmt, 1, "hello", -1, nullptr) == SQLITE_OK);
#ifndef SQLITE_TEST
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_ERROR);
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_ERROR);
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_ERROR);
// need to be prepare aggain
REQUIRE(stmt.Prepare(db.db, "SELECT * FROM test WHERE i=CAST($1 AS INTEGER)", -1, nullptr) == SQLITE_OK);
REQUIRE(sqlite3_bind_text(stmt.stmt, 1, "2", -1, nullptr) == SQLITE_OK);
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_ROW);
#else
// sqlite allows string to int casts ("hello" becomes 0)
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_DONE);
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_DONE);
#endif
// rebind and call again
// need to reset first
REQUIRE(sqlite3_bind_text(stmt.stmt, 1, "1", -1, nullptr) == SQLITE_MISUSE);
REQUIRE(sqlite3_reset(stmt.stmt) == SQLITE_OK);
REQUIRE(sqlite3_bind_text(stmt.stmt, 1, "2", -1, nullptr) == SQLITE_OK);
// repeatedly call sqlite3_step on a SELECT statement
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_ROW);
// verify the results
REQUIRE(string((char *)sqlite3_column_text(stmt.stmt, 0)) == string("2"));
REQUIRE(sqlite3_column_int(stmt.stmt, 0) == 2);
REQUIRE(sqlite3_column_int64(stmt.stmt, 0) == 2);
REQUIRE(sqlite3_column_double(stmt.stmt, 0) == 2);
const std::string test_string_col1 {"1000"};
const std::string test_string_col2 {"1992-01-01"};
const std::string test_string_col3 {"hello world"};
const std::string test_string_col4 {"hello"};
REQUIRE(sqlite3_column_bytes(stmt.stmt, 1) == static_cast<int>(test_string_col1.size()));
REQUIRE(sqlite3_column_bytes(stmt.stmt, 2) == static_cast<int>(test_string_col2.size()));
REQUIRE(sqlite3_column_bytes(stmt.stmt, 3) == static_cast<int>(test_string_col3.size()));
REQUIRE(sqlite3_column_bytes(stmt.stmt, 4) == static_cast<int>(test_string_col4.size()));
REQUIRE(string((char *)sqlite3_column_text(stmt.stmt, 1)) == test_string_col1);
REQUIRE(string((char *)sqlite3_column_text(stmt.stmt, 2)) == test_string_col2);
REQUIRE(string((char *)sqlite3_column_text(stmt.stmt, 3)) == test_string_col3);
REQUIRE(string((char *)sqlite3_column_blob(stmt.stmt, 4)) == test_string_col4);
REQUIRE(sqlite3_column_bytes(stmt.stmt, 1) == static_cast<int>(test_string_col1.size()));
REQUIRE(sqlite3_column_bytes(stmt.stmt, 2) == static_cast<int>(test_string_col2.size()));
REQUIRE(sqlite3_column_bytes(stmt.stmt, 3) == static_cast<int>(test_string_col3.size()));
REQUIRE(sqlite3_column_bytes(stmt.stmt, 4) == static_cast<int>(test_string_col4.size()));
REQUIRE(sqlite3_column_bytes(stmt.stmt, 5) == 0);
REQUIRE(sqlite3_column_bytes(stmt.stmt, -1) == 0);
REQUIRE(sqlite3_column_int(stmt.stmt, 3) == 0);
REQUIRE(sqlite3_column_int64(stmt.stmt, 3) == 0);
REQUIRE(sqlite3_column_double(stmt.stmt, 3) == 0);
REQUIRE(sqlite3_column_text(stmt.stmt, -1) == nullptr);
REQUIRE(sqlite3_column_text(stmt.stmt, 10) == nullptr);
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_DONE);
// no data in the current row
REQUIRE(sqlite3_column_int(stmt.stmt, 0) == 0);
REQUIRE(sqlite3_column_int(nullptr, 0) == 0);
// the query resets again after SQLITE_DONE
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_ROW);
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_DONE);
// sqlite bind and errors
REQUIRE(stmt.Prepare(db.db, "SELECT * FROM non_existant_table", -1, nullptr) == SQLITE_ERROR);
REQUIRE(stmt.stmt == nullptr);
// sqlite3 prepare leftovers
// empty statement
const char *leftover;
REQUIRE(stmt.Prepare(db.db, "", -1, &leftover) == SQLITE_OK);
REQUIRE(leftover != nullptr);
REQUIRE(string(leftover) == "");
// leftover comment
REQUIRE(stmt.Prepare(db.db, "SELECT 42; --hello\nSELECT 3", -1, &leftover) == SQLITE_OK);
REQUIRE(leftover != nullptr);
REQUIRE(string(leftover) == " --hello\nSELECT 3");
// leftover extra statement
REQUIRE(stmt.Prepare(db.db, "SELECT 42--hello;\n, 3; SELECT 17", -1, &leftover) == SQLITE_OK);
REQUIRE(leftover != nullptr);
REQUIRE(string(leftover) == " SELECT 17");
// no query
REQUIRE(stmt.Prepare(db.db, nullptr, -1, &leftover) == SQLITE_MISUSE);
// sqlite3 prepare nByte
// any negative value can be used, not just -1
REQUIRE(stmt.Prepare(db.db, "SELECT 42", -1000, &leftover) == SQLITE_OK);
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_ROW);
REQUIRE(sqlite3_column_int(stmt.stmt, 0) == 42);
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_DONE);
// we can use nByte to skip reading part of string (in this case, skip WHERE 1=0)
REQUIRE(stmt.Prepare(db.db, "SELECT 42 WHERE 1=0", 9, &leftover) == SQLITE_OK);
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_ROW);
REQUIRE(sqlite3_column_int(stmt.stmt, 0) == 42);
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_DONE);
// using too large nByte?
REQUIRE(stmt.Prepare(db.db, "SELECT 42 WHERE 1=0", 19, &leftover) == SQLITE_OK);
REQUIRE(sqlite3_step(stmt.stmt) == SQLITE_DONE);
}
static void sqlite3_interrupt_fast(SQLiteDBWrapper *db, bool *success) {
*success = db->Execute("SELECT SUM(i1.i) FROM integers i1, integers i2, integers i3, integers i4, integers i5");
}
TEST_CASE("Test sqlite3_interrupt", "[sqlite3wrapper]") {
SQLiteDBWrapper db;
bool success;
// open an in-memory db
REQUIRE(db.Open(":memory:"));
REQUIRE(db.Execute("CREATE TABLE integers(i INTEGER)"));
// create a database with 5 values
REQUIRE(db.Execute("INSERT INTO integers VALUES (1), (2), (3), (4), (5)"));
// 5 + 5 * 5 = 30 values
REQUIRE(db.Execute("INSERT INTO integers SELECT i1.i FROM integers i1, integers i2"));
// 30 + 30 * 30 = 930 values
REQUIRE(db.Execute("INSERT INTO integers SELECT i1.i FROM integers i1, integers i2"));
// run a thread that will run a big cross product
thread t1(sqlite3_interrupt_fast, &db, &success);
// wait a second and interrupt the db
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
sqlite3_interrupt(db.db);
// join the thread again
t1.join();
// the execution should have been cancelled
REQUIRE(!success);
}
TEST_CASE("Test different statement types", "[sqlite3wrapper]") {
SQLiteDBWrapper db;
// open an in-memory db
REQUIRE(db.Open(":memory:"));
// create
REQUIRE(db.Execute("CREATE TABLE integers(i INTEGER)"));
// prepare
REQUIRE(db.Execute("PREPARE v1 AS INSERT INTO integers VALUES (?)"));
// execute
REQUIRE(db.Execute("EXECUTE v1(1)"));
REQUIRE(db.Execute("EXECUTE v1(2)"));
REQUIRE(db.Execute("EXECUTE v1(3)"));
// select
REQUIRE(db.Execute("SELECT * FROM integers ORDER BY 1"));
REQUIRE(db.CheckColumn(0, {"1", "2", "3"}));
// update
REQUIRE(db.Execute("UPDATE integers SET i=i+1"));
// delete
REQUIRE(db.Execute("DELETE FROM integers WHERE i=4"));
// verify
REQUIRE(db.Execute("SELECT * FROM integers ORDER BY 1"));
REQUIRE(db.CheckColumn(0, {"2", "3"}));
// transactions
REQUIRE(db.Execute("BEGIN TRANSACTION"));
REQUIRE(db.Execute("UPDATE integers SET i=i+1"));
REQUIRE(db.Execute("ROLLBACK"));
// verify
REQUIRE(db.Execute("SELECT * FROM integers ORDER BY 1"));
REQUIRE(db.CheckColumn(0, {"2", "3"}));
// commit
REQUIRE(db.Execute("BEGIN TRANSACTION"));
REQUIRE(db.Execute("UPDATE integers SET i=i+1"));
REQUIRE(db.Execute("COMMIT"));
// verify
REQUIRE(db.Execute("SELECT * FROM integers ORDER BY 1"));
REQUIRE(db.CheckColumn(0, {"3", "4"}));
}
TEST_CASE("Test rollback of aborted transaction", "[sqlite3wrapper]") {
SQLiteDBWrapper db;
// open an in-memory db
REQUIRE(db.Open(":memory:"));
// can start a transaction
REQUIRE(db.Execute("START TRANSACTION"));
// cannot start a transaction within a transaction
REQUIRE(!db.Execute("START TRANSACTION"));
// now we need to rollback!
REQUIRE(db.Execute("ROLLBACK"));
// can start a transaction again after a rollback
REQUIRE(db.Execute("START TRANSACTION"));
}
TEST_CASE("Test PIVOT", "[sqlite3wrapper]") {
SQLiteDBWrapper db;
// open an in-memory db
REQUIRE(db.Open(":memory:"));
REQUIRE(db.Execute("PIVOT (SELECT 'a' AS col) ON col using first(col);SELECT 42;"));
// Results are concatenated
REQUIRE(db.CheckColumn(0, {"a", "42"}));
}
TEST_CASE("Test sqlite3_complete", "[sqlite3wrapper]") {
REQUIRE(sqlite3_complete("SELECT $$ this is a dollar quoted string without a marker $$;") == 1);
REQUIRE(sqlite3_complete("SELECT $this$is a dollar quoted string$this$;") == 1);
REQUIRE(sqlite3_complete("SELECT $this$this$;") == 0);
REQUIRE(sqlite3_complete("SELECT $this$is a non-terminated dollar quoted string;") == 0);
REQUIRE(sqlite3_complete("SELECT $this$is a non-terminated dollar quoted string;$") == 0);
REQUIRE(sqlite3_complete("SELECT $this$is a non-terminated dollar quoted string;$this") == 0);
REQUIRE(sqlite3_complete("SELECT $this$is a non-terminated dollar quoted string;$xxx$") == 0);
REQUIRE(sqlite3_complete("SELECT $this$is a terminated dollar quoted string;$xxx$$this$") == 0);
REQUIRE(sqlite3_complete("SELECT $this$is a terminated dollar quoted string;$xxx$$this$;") == 1);
REQUIRE(sqlite3_complete("SELECT $this$$$is a nested $x$ what what $x$ dollar quoted string;$$$this$;") == 1);
REQUIRE(sqlite3_complete("") == 0);
REQUIRE(sqlite3_complete("S") == 0);
REQUIRE(sqlite3_complete("SELECT 42") == 0);
REQUIRE(sqlite3_complete("SELECT 42;") == 1);
REQUIRE(sqlite3_complete("--comment on first line\nselect 42;") == 1);
REQUIRE(sqlite3_complete("SELECT 42; \n\n\t\t\n\f\t ") == 1);
REQUIRE(sqlite3_complete("SELECT 42;--this is a comment") == 1);
REQUIRE(sqlite3_complete("SELECT 42; --this is a comment") == 1);
REQUIRE(sqlite3_complete("SELECT 'quoted semicolon;") == 0);
REQUIRE(sqlite3_complete("SELECT 'quoted semicolon\nwith newline\n;") == 0);
REQUIRE(sqlite3_complete("SELECT 'quoted semicolon ;\nwith ;; newline\nnow terminated';") == 1);
REQUIRE(sqlite3_complete("SELECT \"double-quoted semicolon ;;") == 0);
REQUIRE(sqlite3_complete("SELECT 42;\n\t\n--this is a comment") == 1);
REQUIRE(sqlite3_complete("SELECT 42;\n\t\n--this is a comment\nS") == 0);
REQUIRE(sqlite3_complete("SELECT 42; /* c-style comment *//*followed by another one */ --and this one") == 1);
REQUIRE(sqlite3_complete("SELECT 'thisis a string with '';") == 0);
REQUIRE(sqlite3_complete("SELECT 'thisis a string with '';;'' escapes';") == 1);
}

View File

@@ -0,0 +1,384 @@
#include "catch.hpp"
#ifdef USE_DUCKDB_SHELL_WRAPPER
#include "duckdb_shell_wrapper.h"
#endif
#include "sqlite3.h"
#include <string>
#include <thread>
#include <string.h>
#include "sqlite_db_wrapper.hpp"
#include "sqlite_stmt_wrapper.hpp"
// All UDFs are implemented in "udf_scalar_functions.hpp"
#include "udf_scalar_functions.hpp"
TEST_CASE("SQLite UDF wrapper: basic usage", "[sqlite3wrapper]") {
SQLiteDBWrapper db_w;
// open an in-memory db
REQUIRE(db_w.Open(":memory:"));
// create and populate table
REQUIRE(db_w.Execute("CREATE TABLE integers(i INTEGER)"));
for (int i = -5; i <= 5; ++i) {
// Insert values: -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5
REQUIRE(db_w.Execute("INSERT INTO integers VALUES (" + std::to_string(i) + ")"));
}
// create sqlite udf
REQUIRE(sqlite3_create_function(db_w.db, "multiply10", 1, 0, nullptr, &multiply10, nullptr, nullptr) == SQLITE_OK);
REQUIRE(db_w.Execute("SELECT multiply10(i) FROM integers"));
REQUIRE(db_w.CheckColumn(0, {"-50", "-40", "-30", "-20", "-10", "0", "10", "20", "30", "40", "50"}));
}
TEST_CASE("SQLite UDF wrapper: testing NULL values", "[sqlite3wrapper]") {
SQLiteDBWrapper db_w;
// open an in-memory db
REQUIRE(db_w.Open(":memory:"));
// testing null values
REQUIRE(db_w.Execute("SELECT NULL"));
REQUIRE(db_w.CheckColumn(0, {"NULL"}));
// insert NULL value and test
REQUIRE(db_w.Execute("CREATE TABLE integers(i INTEGER, j INTEGER, k INTEGER, l INTEGER)"));
REQUIRE(db_w.Execute("INSERT INTO integers VALUES (NULL, NULL, NULL, NULL), (NULL, NULL, NULL, NULL)"));
REQUIRE(sqlite3_create_function(db_w.db, "sum_cols_int_check_nulls", 4, 0, nullptr, &sum_cols_int_check_nulls,
nullptr, nullptr) == SQLITE_OK);
REQUIRE(db_w.Execute("SELECT sum_cols_int_check_nulls(i, j, k, l) FROM integers"));
REQUIRE(db_w.CheckColumn(0, {"NULL", "NULL"}));
// insert valid values
REQUIRE(db_w.Execute("INSERT INTO integers VALUES (1, 1, 1, 1), (2, 2, 2, 2)"));
REQUIRE(db_w.Execute("SELECT sum_cols_int_check_nulls(i, j, k, l) FROM integers"));
REQUIRE(db_w.CheckColumn(0, {"NULL", "NULL", "4", "8"}));
// insert valid values with NULL ones
REQUIRE(db_w.Execute("INSERT INTO integers VALUES (NULL, 1, 1, 1), (2, 2, 2, NULL)"));
REQUIRE(db_w.Execute("SELECT sum_cols_int_check_nulls(i, j, k, l) FROM integers"));
REQUIRE(db_w.CheckColumn(0, {"NULL", "NULL", "4", "8", "NULL", "NULL"}));
// UDF that threats NULL entries as zero
REQUIRE(sqlite3_create_function(db_w.db, "sum_cols_int", 4, 0, nullptr, &sum_cols_int, nullptr, nullptr) ==
SQLITE_OK);
REQUIRE(db_w.Execute("DELETE FROM integers"));
REQUIRE(db_w.Execute("INSERT INTO integers VALUES (NULL, NULL, 1, 1), (2, 2, 2, NULL)"));
REQUIRE(db_w.Execute("SELECT sum_cols_int(i, j, k, l) FROM integers"));
REQUIRE(db_w.CheckColumn(0, {"2", "6"}));
}
TEST_CASE("SQLite UDF wrapper: multiple arguments", "[sqlite3wrapper]") {
SQLiteDBWrapper db_w;
// open an in-memory db
REQUIRE(db_w.Open(":memory:"));
// create and populate table "integers"
REQUIRE(db_w.Execute("CREATE TABLE integers(t_int TINYINT, s_int SMALLINT, i_int INTEGER, b_int BIGINT)"));
for (int i = -5; i <= 5; ++i) {
// Insert values: -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5
REQUIRE(db_w.Execute("INSERT INTO integers VALUES (" + std::to_string(i) + "," + std::to_string(i) + "," +
std::to_string(i) + "," + std::to_string(i) + ")"));
}
// One argument: TINYINT
int argc = 1;
REQUIRE(sqlite3_create_function(db_w.db, "sum_cols_int", argc, 0, nullptr, &sum_cols_int, nullptr, nullptr) ==
SQLITE_OK);
REQUIRE(db_w.Execute("SELECT sum_cols_int(t_int) FROM integers"));
REQUIRE(db_w.CheckColumn(0, {"-5", "-4", "-3", "-2", "-1", "0", "1", "2", "3", "4", "5"}));
// Two arguments: TINYINT + SMALLINT
argc = 2;
REQUIRE(sqlite3_create_function(db_w.db, "sum_cols_int2", argc, 0, nullptr, &sum_cols_int, nullptr, nullptr) ==
SQLITE_OK);
REQUIRE(db_w.Execute("SELECT sum_cols_int2(t_int, s_int) FROM integers"));
REQUIRE(db_w.CheckColumn(0, {"-10", "-8", "-6", "-4", "-2", "0", "2", "4", "6", "8", "10"}));
// Three arguments: TINYINT + SMALLINT + INTEGER
argc = 3;
REQUIRE(sqlite3_create_function(db_w.db, "sum_cols_int3", argc, 0, nullptr, &sum_cols_int, nullptr, nullptr) ==
SQLITE_OK);
REQUIRE(db_w.Execute("SELECT sum_cols_int3(t_int, s_int, i_int) FROM integers"));
REQUIRE(db_w.CheckColumn(0, {"-15", "-12", "-9", "-6", "-3", "0", "3", "6", "9", "12", "15"}));
// Four arguments: TINYINT + SMALLINT + INTEGER + BITINT
argc = 4;
REQUIRE(sqlite3_create_function(db_w.db, "sum_cols_int4", argc, 0, nullptr, &sum_cols_int, nullptr, nullptr) ==
SQLITE_OK);
REQUIRE(db_w.Execute("SELECT sum_cols_int4(t_int, s_int, i_int, b_int) FROM integers"));
REQUIRE(db_w.CheckColumn(0, {"-20", "-16", "-12", "-8", "-4", "0", "4", "8", "12", "16", "20"}));
}
TEST_CASE("SQLite UDF wrapper: double values", "[sqlite3wrapper]") {
SQLiteDBWrapper db_w;
// open an in-memory db
REQUIRE(db_w.Open(":memory:"));
// create and populate table "floats"
REQUIRE(db_w.Execute("CREATE TABLE floats(f FLOAT, d DOUBLE)"));
for (int i = -5; i <= 5; ++i) {
// Insert values: -5.0, -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0
REQUIRE(db_w.Execute("INSERT INTO floats VALUES (" + std::to_string(i) + "," + std::to_string(i) + ")"));
}
// create function
REQUIRE(sqlite3_create_function(db_w.db, "sum_cols_double", 2, 0, nullptr, &sum_cols_double, nullptr, nullptr) ==
SQLITE_OK);
// FLOAT + DOUBLE
REQUIRE(db_w.Execute("SELECT sum_cols_double(f, d) FROM floats"));
REQUIRE(db_w.CheckColumn(0, {"-10.0", "-8.0", "-6.0", "-4.0", "-2.0", "0.0", "2.0", "4.0", "6.0", "8.0", "10.0"}));
}
TEST_CASE("SQLite UDF wrapper: text and blob values", "[sqlite3wrapper]") {
SQLiteDBWrapper db_w;
// open an in-memory db
REQUIRE(db_w.Open(":memory:"));
// create function check_text
REQUIRE(sqlite3_create_function(db_w.db, "check_text", 1, 0, nullptr, &check_text, nullptr, nullptr) == SQLITE_OK);
// create function check_blob
REQUIRE(sqlite3_create_function(db_w.db, "check_blob", 1, 0, nullptr, &check_blob, nullptr, nullptr) == SQLITE_OK);
// create function check_null-terminated_string
REQUIRE(sqlite3_create_function(db_w.db, "check_null_terminated_string", 1, 0, nullptr,
&check_null_terminated_string, nullptr, nullptr) == SQLITE_OK);
// TEXT
REQUIRE(db_w.Execute("SELECT check_text('XXXX'::VARCHAR)"));
REQUIRE(db_w.CheckColumn(0, {"TTTT"}));
// BLOB
REQUIRE(db_w.Execute("SELECT check_blob('XXXX'::BLOB)"));
REQUIRE(db_w.CheckColumn(0, {"BBBB"}));
// check_null_terminated_string
REQUIRE(db_w.Execute("SELECT check_null_terminated_string('Hello world')"));
REQUIRE(db_w.CheckColumn(0, {"Hello world"}));
}
TEST_CASE("SQLite UDF wrapper: check type", "[sqlite3wrapper]") {
SQLiteDBWrapper db_w;
// open an in-memory db
REQUIRE(db_w.Open(":memory:"));
// create function
REQUIRE(sqlite3_create_function(db_w.db, "check_type", 1, 0, nullptr, &check_type, nullptr, nullptr) == SQLITE_OK);
// INT
REQUIRE(db_w.Execute("SELECT check_type('4'::INTEGER)"));
REQUIRE(db_w.CheckColumn(0, {"40"}));
// FLOAT
REQUIRE(db_w.Execute("SELECT check_type('4.0'::DOUBLE)"));
REQUIRE(db_w.CheckColumn(0, {"400.0"}));
// TEXT
REQUIRE(db_w.Execute("SELECT check_type('aaaa'::VARCHAR)"));
REQUIRE(db_w.CheckColumn(0, {"TEXT"}));
// BLOB
REQUIRE(db_w.Execute("SELECT check_type('aaaa'::BLOB)"));
REQUIRE(db_w.CheckColumn(0, {"BLOB"}));
}
TEST_CASE("SQLite UDF wrapper: set null", "[sqlite3wrapper]") {
SQLiteDBWrapper db_w;
// open an in-memory db
REQUIRE(db_w.Open(":memory:"));
// create function
REQUIRE(sqlite3_create_function(db_w.db, "set_null", 1, 0, nullptr, &set_null, nullptr, nullptr) == SQLITE_OK);
// INT
REQUIRE(db_w.Execute("SELECT set_null('4'::INTEGER)"));
REQUIRE(db_w.CheckColumn(0, {"NULL"}));
// FLOAT
REQUIRE(db_w.Execute("SELECT set_null('4.0'::DOUBLE)"));
REQUIRE(db_w.CheckColumn(0, {"NULL"}));
// TEXT
REQUIRE(db_w.Execute("SELECT set_null('aaaa'::VARCHAR)"));
REQUIRE(db_w.CheckColumn(0, {"NULL"}));
// BLOB
REQUIRE(db_w.Execute("SELECT set_null('aaaa'::BLOB)"));
REQUIRE(db_w.CheckColumn(0, {"NULL"}));
}
TEST_CASE("SQLite UDF wrapper: get user data", "[sqlite3wrapper]") {
SQLiteDBWrapper db_w;
// open an in-memory db
REQUIRE(db_w.Open(":memory:"));
char user_data[] = {"TEST"}; // user data to be used along the UDF
// create function that gets user data (string) and replace the input value
REQUIRE(sqlite3_create_function(db_w.db, "get_user_data", 1, 0, user_data, &get_user_data, nullptr, nullptr) ==
SQLITE_OK);
REQUIRE(db_w.Execute("SELECT get_user_data('DUCKDB ____'::VARCHAR)"));
REQUIRE(db_w.CheckColumn(0, {"DUCKDB TEST"}));
}
TEST_CASE("SQLite UDF wrapper: testing sqlite cast numbers to text", "[sqlite3wrapper]") {
SQLiteDBWrapper db_w;
// open an in-memory db
REQUIRE(db_w.Open(":memory:"));
REQUIRE(sqlite3_create_function(db_w.db, "cast_numbers_to_text", 1, 0, nullptr, &cast_numbers_to_text, nullptr,
nullptr) == SQLITE_OK);
// testing conversion of integers to text
REQUIRE(db_w.Execute("CREATE TABLE integers(t_int TINYINT, s_int SMALLINT, i_int INTEGER, b_int BIGINT)"));
REQUIRE(db_w.Execute(
"INSERT INTO integers VALUES (99, 9999, 99999999, 999999999999), (88, 8888, 88888888, 888888888888)"));
REQUIRE(db_w.Execute("SELECT cast_numbers_to_text(t_int) FROM integers"));
REQUIRE(db_w.CheckColumn(0, {"99", "88"}));
REQUIRE(db_w.Execute("SELECT cast_numbers_to_text(s_int) FROM integers"));
REQUIRE(db_w.CheckColumn(0, {"9999", "8888"}));
REQUIRE(db_w.Execute("SELECT cast_numbers_to_text(i_int) FROM integers"));
REQUIRE(db_w.CheckColumn(0, {"99999999", "88888888"}));
REQUIRE(db_w.Execute("SELECT cast_numbers_to_text(b_int) FROM integers"));
REQUIRE(db_w.CheckColumn(0, {"999999999999", "888888888888"}));
// testing conversion of floats to text
REQUIRE(db_w.Execute("CREATE TABLE floats(f FLOAT, d DOUBLE)"));
REQUIRE(db_w.Execute("INSERT INTO floats VALUES (11111.0, 11111.0), (22222.0, 22222.0)"));
REQUIRE(db_w.Execute("SELECT cast_numbers_to_text(f) FROM floats"));
REQUIRE(db_w.CheckColumn(0, {"11111.0", "22222.0"}));
REQUIRE(db_w.Execute("SELECT cast_numbers_to_text(d) FROM floats"));
REQUIRE(db_w.CheckColumn(0, {"11111.0", "22222.0"}));
}
TEST_CASE("SQLite UDF wrapper: testing more casts", "[sqlite3wrapper]") {
SQLiteDBWrapper db_w;
// open an in-memory db
REQUIRE(db_w.Open(":memory:"));
REQUIRE(db_w.Execute("CREATE TABLE tbl(str VARCHAR, blob BLOB, big BIGINT, f_real FLOAT)"));
REQUIRE(db_w.Execute("INSERT INTO tbl VALUES('DuckDB string', 'DuckDB blob', 999999999999999999, 55.0)"));
REQUIRE(sqlite3_create_function(db_w.db, "cast_to_int32", 1, 0, nullptr, &cast_to_int32, nullptr, nullptr) ==
SQLITE_OK);
REQUIRE(db_w.Execute("SELECT cast_to_int32(str) FROM tbl")); // invalid string
REQUIRE(db_w.CheckColumn(0, {"NULL"}));
REQUIRE(db_w.Execute("SELECT cast_to_int32(blob) FROM tbl")); // invalid blob
REQUIRE(db_w.CheckColumn(0, {"NULL"}));
REQUIRE(db_w.Execute("SELECT cast_to_int32(big) FROM tbl")); // big int out of int-32 range
REQUIRE(db_w.CheckColumn(0, {"NULL"}));
REQUIRE(db_w.Execute("SELECT cast_to_int32(f_real) FROM tbl")); // float to int-32
REQUIRE(db_w.CheckColumn(0, {"55"}));
REQUIRE(sqlite3_create_function(db_w.db, "cast_to_int64", 1, 0, nullptr, &cast_to_int64, nullptr, nullptr) ==
SQLITE_OK);
REQUIRE(db_w.Execute("SELECT cast_to_int64(str) FROM tbl"));
REQUIRE(db_w.CheckColumn(0, {"NULL"}));
REQUIRE(db_w.Execute("SELECT cast_to_int64(blob) FROM tbl"));
REQUIRE(db_w.CheckColumn(0, {"NULL"}));
REQUIRE(db_w.Execute("SELECT cast_to_int64(big) FROM tbl"));
REQUIRE(db_w.CheckColumn(0, {"999999999999999999"}));
REQUIRE(db_w.Execute("SELECT cast_to_int64(f_real) FROM tbl")); // float to int-64
REQUIRE(db_w.CheckColumn(0, {"55"}));
REQUIRE(sqlite3_create_function(db_w.db, "cast_to_float", 1, 0, nullptr, &cast_to_float, nullptr, nullptr) ==
SQLITE_OK);
REQUIRE(db_w.Execute("SELECT cast_to_float(str) FROM tbl"));
REQUIRE(db_w.CheckColumn(0, {"NULL"}));
REQUIRE(db_w.Execute("SELECT cast_to_float(blob) FROM tbl"));
REQUIRE(db_w.CheckColumn(0, {"NULL"}));
REQUIRE(db_w.Execute("SELECT cast_to_float(big) FROM tbl"));
REQUIRE(db_w.CheckColumn(0, {"1e+18"}));
}
TEST_CASE("SQLite UDF wrapper: overload function", "[sqlite3wrapper]") {
SQLiteDBWrapper db_w;
// open an in-memory db
REQUIRE(db_w.Open(":memory:"));
int argc = 1;
REQUIRE(sqlite3_create_function(db_w.db, "sum_overload_function", argc, 0, nullptr, &sum_overload_function, nullptr,
nullptr) == SQLITE_OK);
argc = 2;
REQUIRE(sqlite3_create_function(db_w.db, "sum_overload_function", argc, 0, nullptr, &sum_overload_function, nullptr,
nullptr) == SQLITE_OK);
argc = 3;
REQUIRE(sqlite3_create_function(db_w.db, "sum_overload_function", argc, 0, nullptr, &sum_overload_function, nullptr,
nullptr) == SQLITE_OK);
// testing with constant
REQUIRE(db_w.Execute("SELECT sum_overload_function(100)"));
REQUIRE(db_w.CheckColumn(0, {"100"}));
REQUIRE(db_w.Execute("SELECT sum_overload_function(100, 100)"));
REQUIRE(db_w.CheckColumn(0, {"200"}));
REQUIRE(db_w.Execute("SELECT sum_overload_function(100, 100, 100)"));
REQUIRE(db_w.CheckColumn(0, {"300"}));
REQUIRE(db_w.Execute("CREATE TABLE tbl(i INTEGER, j INTEGER, k INTEGER)"));
REQUIRE(db_w.Execute("INSERT INTO tbl VALUES(1, 2, 3)"));
REQUIRE(db_w.Execute("INSERT INTO tbl VALUES(1, 2, 3)"));
// testing with flat vectors
REQUIRE(db_w.Execute("SELECT sum_overload_function(i) FROM tbl"));
REQUIRE(db_w.CheckColumn(0, {"1", "1"}));
REQUIRE(db_w.Execute("SELECT sum_overload_function(i, j) FROM tbl"));
REQUIRE(db_w.CheckColumn(0, {"3", "3"}));
REQUIRE(db_w.Execute("SELECT sum_overload_function(i, j, k) FROM tbl"));
REQUIRE(db_w.CheckColumn(0, {"6", "6"}));
}
TEST_CASE("SQLite UDF wrapper: calling sqlite3_value_text() multiple times", "[sqlite3wrapper]") {
SQLiteDBWrapper db_w;
// open an in-memory db
REQUIRE(db_w.Open(":memory:"));
int argc = 1;
REQUIRE(sqlite3_create_function(db_w.db, "calling_value_text_multiple_times", argc, 0, nullptr,
&calling_value_text_multiple_times, nullptr, nullptr) == SQLITE_OK);
// testing with integer
REQUIRE(db_w.Execute("SELECT calling_value_text_multiple_times(9999::INTEGER)"));
REQUIRE(db_w.CheckColumn(0, {"9999"}));
// testing with float
REQUIRE(db_w.Execute("SELECT calling_value_text_multiple_times(9999.0::FLOAT)"));
REQUIRE(db_w.CheckColumn(0, {"9999.0"}));
// testing with string
REQUIRE(db_w.Execute("SELECT calling_value_text_multiple_times('Hello world'::TEXT)"));
REQUIRE(db_w.CheckColumn(0, {"Hello world"}));
}