should be it
This commit is contained in:
8
external/duckdb/tools/sqlite3_api_wrapper/test/CMakeLists.txt
vendored
Normal file
8
external/duckdb/tools/sqlite3_api_wrapper/test/CMakeLists.txt
vendored
Normal 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)
|
||||
64
external/duckdb/tools/sqlite3_api_wrapper/test/include/sqlite_db_wrapper.hpp
vendored
Normal file
64
external/duckdb/tools/sqlite3_api_wrapper/test/include/sqlite_db_wrapper.hpp
vendored
Normal 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;
|
||||
}
|
||||
};
|
||||
80
external/duckdb/tools/sqlite3_api_wrapper/test/include/sqlite_stmt_wrapper.hpp
vendored
Normal file
80
external/duckdb/tools/sqlite3_api_wrapper/test/include/sqlite_stmt_wrapper.hpp
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
232
external/duckdb/tools/sqlite3_api_wrapper/test/include/udf_scalar_functions.hpp
vendored
Normal file
232
external/duckdb/tools/sqlite3_api_wrapper/test/include/udf_scalar_functions.hpp
vendored
Normal 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);
|
||||
}
|
||||
30
external/duckdb/tools/sqlite3_api_wrapper/test/include/util_functions.hpp
vendored
Normal file
30
external/duckdb/tools/sqlite3_api_wrapper/test/include/util_functions.hpp
vendored
Normal 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");
|
||||
}
|
||||
}
|
||||
377
external/duckdb/tools/sqlite3_api_wrapper/test/test_sqlite3_api_wrapper.cpp
vendored
Normal file
377
external/duckdb/tools/sqlite3_api_wrapper/test/test_sqlite3_api_wrapper.cpp
vendored
Normal 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);
|
||||
}
|
||||
384
external/duckdb/tools/sqlite3_api_wrapper/test/test_sqlite3_udf_api_wrapper.cpp
vendored
Normal file
384
external/duckdb/tools/sqlite3_api_wrapper/test/test_sqlite3_udf_api_wrapper.cpp
vendored
Normal 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"}));
|
||||
}
|
||||
Reference in New Issue
Block a user