commit 16d44cf730477d359d1f9ab707f018b8aeb5bac1 Author: Timerix Date: Sat Dec 13 03:35:34 2025 +0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..498924c --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# build results +bin/ +obj/ + +# IDE files +.vs/ +.vshistory/ +.editorconfig +*.user +*.vcxproj.filters + +# other files +.old*/ +old/ +tmp/ +temp/ +*.tmp +*.temp +logs/ +log/ +*.log diff --git a/.vscode/.gitignore b/.vscode/.gitignore new file mode 100644 index 0000000..e38da20 --- /dev/null +++ b/.vscode/.gitignore @@ -0,0 +1 @@ +settings.json diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..969f96d --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "name": "all", + "defines": [], + "includePath": [ + "include", + "src", + "../tlibc/include", + "${default}" + ], + "cStandard": "c99" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..77216a1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "gdb_debug", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/bin/tsqlite", + "windows": { "program": "${workspaceFolder}/bin/tsqlite.exe" }, + "preLaunchTask": "build_exec_dbg", + "stopAtEntry": false, + "cwd": "${workspaceFolder}/bin", + "externalConsole": false, + "internalConsoleOptions": "neverOpen", + "MIMode": "gdb", + "miDebuggerPath": "gdb", + "setupCommands": [ + { + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..17a453c --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,31 @@ + +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build_exec_dbg", + "detail": "build project with debug symbols", + "type": "cppbuild", + "command": "bash", + "args": [ + "-c", + "cbuild build_exec_dbg" + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": ["$gcc"], + "group": { + "kind": "build" + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "shared", + "showReuseMessage": false, + "clear": true + } + } + ] + } \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d431c1 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# tsqlite +A wrapper for [sqlite](https://sqlite.org) that uses [tlibc](https://timerix.ddns.net/git/Timerix/tlibc) for error handling. + +## Build +1. Clone this repository. + ``` + git clone https://timerix.ddns.net/git/Timerix/tsqlite.git + ``` + +2. Install [cbuild](https://timerix.ddns.net/git/Timerix/cbuild/releases). + Select latest version compatible with the one in `project.config`. + Example: For `2.3.0` download latest `2.3.x`. + +3. Clone [tlibc](https://timerix.ddns.net/git/Timerix/tlibc). + By default `dependencies/tlibc.config` expects that `tlibc/` is present in the same directory as `tsqlite/`. + If you cloned it to another directory, change `DEPENDENCIES_DIR` in `tsqlite/project.user.config`. + ``` + git clone https://timerix.ddns.net/git/Timerix/tlibc.git + ``` + +4. Install **sqlite** library. +MinGW: `pacman -S mingw-w64-x86_64-sqlite3` + +5. Build and run tests + ``` + cd tsqlite + cbuild build_exec exec + ``` + +6. To build library use tasks `build_static_lib[_dbg]` or `build_shared_lib[_dbg]` + + +## Usage +```c +#include "tsqlite.h" + +int main(){ + Deferral(32); // reserve memory for 32 defers + // init tlibc global variables + try_fatal_void(tlibc_init()); + // init tsqlite global variables + try_fatal_void(tsqlite_init()); + Defer(tlibc_deinit()); + Defer(tsqlite_deinit()); + + Return 0; // call defers +} +``` diff --git a/dependencies/tlibc.config b/dependencies/tlibc.config new file mode 100644 index 0000000..3db058f --- /dev/null +++ b/dependencies/tlibc.config @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# This is a dependency config. +# You can copy it to another project to add tlibc dependency. + +DEP_WORKING_DIR="$DEPENDENCIES_DIR/tlibc" +if [[ "$TASK" = *_dbg ]]; then + dep_build_target="build_static_lib_dbg" +else + dep_build_target="build_static_lib" +fi +DEP_PRE_BUILD_COMMAND="" +DEP_BUILD_COMMAND="cbuild $dep_build_target" +DEP_POST_BUILD_COMMAND="" +DEP_CLEAN_COMMAND="cbuild clean" +DEP_DYNAMIC_OUT_FILES="" +DEP_STATIC_OUT_FILES="bin/tlibc.a" +DEP_OTHER_OUT_FILES="" +PRESERVE_OUT_DIRECTORY_STRUCTURE=false diff --git a/include/tsqlite.h b/include/tsqlite.h new file mode 100644 index 0000000..17f9190 --- /dev/null +++ b/include/tsqlite.h @@ -0,0 +1,79 @@ +#pragma once +#include "tlibc/tlibc.h" +#include "tlibc/errors.h" +#include "tlibc/string/str.h" +#include "tlibc/collections/Array.h" +#include "tlibc/collections/Array_impl/Array_u8.h" +#include + +Result(void) tsqlite_init(); +void tsqlite_deinit(); + +/// @param code primary : 8 bits | extended : 24 bits +/// @return heap-allocated string +str tsqlite_ResultCode_toStr(int code); + +ErrorCodePage_declare(SQLITE); + +#define RESULT_ERROR_SQLITE_CODE(CODE) RESULT_ERROR_CODE(SQLITE, CODE & 0xff, tsqlite_ResultCode_toStr(CODE), true) + +#define _try_sqlite3(CALL, N) do {\ + int _rname(N) = CALL;\ + if((_rname(N) & 0xff) != SQLITE_OK){\ + return RESULT_ERROR_SQLITE_CODE(_rname(N));\ + }\ +} while(0) + +#define try_sqlite3(CALL) _try_sqlite3(CALL, __LINE__) + +typedef void(*sqlite3_error_log_func_t)(void* ctx, int code, cstr msg); + + +typedef sqlite3 tsqlite_connection; + +/// @param file_path sqlite file +/// @param logger +/// @param log_func +/// @return new sqlite connection +Result(tsqlite_connection*) tsqlite_connection_open(cstr file_path, + NULLABLE(void*) logger, NULLABLE(sqlite3_error_log_func_t) log_func); + +/// all statements and blobs must be destroyed before calling this +Result(void) tsqlite_connection_close(tsqlite_connection* db); + +typedef struct tsqlite_statement { + sqlite3_stmt* st; + i32 bind_arg_pos; + i32 result_row; + i32 result_col; +} tsqlite_statement; + +/// @brief compile SQL statement to bytecode +/// @param db +/// @param sql_code SQL statement code. May contain placeholders +/// @return compiled statement +Result(tsqlite_statement*) tsqlite_statement_prepare(tsqlite_connection* db, str sql_code); + +/// Bind value to placeholder at `self->bind_arg_pos`. Increases `self->bind_arg_pos` on success. +Result(void) tsqlite_statement_bind_null(tsqlite_statement* self); +Result(void) tsqlite_statement_bind_i32(tsqlite_statement* self, i32 v); +Result(void) tsqlite_statement_bind_i64(tsqlite_statement* self, i64 v); +Result(void) tsqlite_statement_bind_f64(tsqlite_statement* self, f64 v); +Result(void) tsqlite_statement_bind_str(tsqlite_statement* self, str v, NULLABLE(Destructor_t) d); +Result(void) tsqlite_statement_bind_blob(tsqlite_statement* self, Array(u8) v, NULLABLE(Destructor_t) d); +Result(void) tsqlite_statement_bind_zeroblob(tsqlite_statement* self, i32 size); + +/// @brief execute statement or move to next result row +/// @return is next result row avaliable +Result(bool) sqlite3_statement_moveNext(tsqlite_statement* self); + +/// Get value at `self->result_col`. Increases `self->result_col` on success. +Result(i64) tsqlite_statement_getResult_i64(tsqlite_statement* self); +Result(i64) tsqlite_statement_getResult_f64(tsqlite_statement* self); +Result(void) tsqlite_statement_getResult_str(tsqlite_statement* self, str* out_v); +Result(void) tsqlite_statement_getResult_blob(tsqlite_statement* self, Array(u8)* out_v); + +/// call this after executing prepared statement to use it again +Result(void) tsqlite_statement_reset(tsqlite_statement* st); + +void tsqlite_statement_free(tsqlite_statement* st); diff --git a/project.config b/project.config new file mode 100644 index 0000000..fb681ce --- /dev/null +++ b/project.config @@ -0,0 +1,208 @@ +#!/usr/bin/env bash +CBUILD_VERSION=2.3.2 + +PROJECT="tsqlite" +CMP_C="gcc" +CMP_CPP="g++" +STD_C="c99" +STD_CPP="c++11" +WARN_C="-Wall -Wextra + -Wduplicated-branches + -Wduplicated-cond + -Wformat=2 + -Wmissing-include-dirs + -Wshadow + -Werror=return-type + -Werror=pointer-arith + -Werror=init-self + -Werror=incompatible-pointer-types" +WARN_CPP="$WARN_C" +SRC_C="$(find src -name '*.c')" +SRC_CPP="$(find src -name '*.cpp')" +TESTS_C="$(find tests -name '*.c')" +TESTS_CPP="$(find tests -name '*.cpp')" + +# Directory with dependency configs. +# See cbuild/example_dependency_configs +DEPENDENCY_CONFIGS_DIR='dependencies' +# List of dependency config files in DEPENDENCY_CONFIGS_DIR separated by space. +ENABLED_DEPENDENCIES='tlibc' + +# OBJDIR structure: +# ├── objects/ - Compiled object files. Cleans on each call of build task +# ├── static_libs/ - Symbolic links to static libraries used by linker. Cleans on each call of build task. +# ├── static_libs/ - Symbolic links to dynamic libraries used by linker. Cleans on each call of build task. +# └── profile/ - gcc *.gcda profiling info files +OBJDIR="obj" +OUTDIR="bin" +STATIC_LIB_FILE="$PROJECT.a" + +INCLUDE="-Isrc -Iinclude -I../tlibc/include" + +# OS-specific options +case "$OS" in + WINDOWS) + EXEC_FILE="test.exe" + SHARED_LIB_FILE="$PROJECT.dll" + INCLUDE="$INCLUDE " + # example: "-lSDL2 -lSDL2_image" + LINKER_LIBS="-lsqlite3" + ;; + LINUX) + EXEC_FILE="test" + SHARED_LIB_FILE="$PROJECT.so" + INCLUDE="$INCLUDE " + LINKER_LIBS="-lsqlite3" + ;; + *) + error "operating system $OS has no configuration variants" + ;; +esac + +# TASKS +case "$TASK" in + # creates executable using profiling info if it exists + build_exec) + SRC_C="$SRC_C $TESTS_C" + SRC_CPP="$SRC_CPP $TESTS_CPP" + # -flto applies more optimizations across object files + # -flto=auto is needed to multithreaded copilation + # -fuse-linker-plugin is required to use static libs with lto + # -fprofile-use enables compiler to use profiling info files to optimize executable + # -fprofile-prefix-path sets path where profiling info about objects are be saved + # -fdata-sections -ffunction-sections -Wl,--gc-sections removes unused code + C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-use -fprofile-prefix-path=$(realpath $OBJDIR)/objects -fdata-sections -ffunction-sections -Wl,--gc-sections" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="" + TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + POST_TASK_SCRIPT="" + ;; + # creates executable with debug info and no optimizations + build_exec_dbg) + SRC_C="$SRC_C $TESTS_C" + SRC_CPP="$SRC_CPP $TESTS_CPP" + C_ARGS="-O0 -g3" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="" + TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + POST_TASK_SCRIPT="" + ;; + # creates shared library + build_shared_lib) + C_ARGS="-O2 -fpic -flto -shared" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS -Wl,-soname,$SHARED_LIB_FILE" + PRE_TASK_SCRIPT="" + TASK_SCRIPT="@cbuild/default_tasks/build_shared_lib.sh" + POST_TASK_SCRIPT="" + ;; + # creates shared library with debug symbols and no optimizations + build_shared_lib_dbg) + C_ARGS="-O0 -g3 -fpic -shared" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS -Wl,-soname,$SHARED_LIB_FILE" + PRE_TASK_SCRIPT="" + TASK_SCRIPT="@cbuild/default_tasks/build_shared_lib.sh" + POST_TASK_SCRIPT="" + ;; + # creates static library + build_static_lib) + C_ARGS="-O2 -fpic -fdata-sections -ffunction-sections" + CPP_ARGS="$C_ARGS" + PRE_TASK_SCRIPT="" + TASK_SCRIPT="@cbuild/default_tasks/build_static_lib.sh" + POST_TASK_SCRIPT="" + ;; + # creates static library with debug symbols and no optimizations + build_static_lib_dbg) + C_ARGS="-O0 -g3" + CPP_ARGS="$C_ARGS" + PRE_TASK_SCRIPT="" + TASK_SCRIPT="@cbuild/default_tasks/build_static_lib.sh" + POST_TASK_SCRIPT="" + ;; + # executes $EXEC_FILE + exec) + TASK_SCRIPT="@cbuild/default_tasks/exec.sh" + ;; + # executes $EXEC_FILE with valgrind memory checker + valgrind) + VALGRIND_ARGS="-s --read-var-info=yes --track-origins=yes --fullpath-after=$(pwd)/ --leak-check=full --show-leak-kinds=all" + TASK_SCRIPT="@cbuild/default_tasks/valgrind.sh" + ;; + # generates profiling info + profile) + OUTDIR="$OUTDIR/profile" + # -flto applies more optimizations across object files + # -flto=auto is needed to multithreaded copilation + # -fuse-linker-plugin is required to use static libs with lto + # -pg adds code to executable, that generates file containing function call info (gmon.out) + # -fprofile-generate generates executable with profiling code + # -fprofile-prefix-path sets path where profiling info about objects will be saved + C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-generate -fprofile-prefix-path=$(realpath $OBJDIR)/objects" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + TASK_SCRIPT="@cbuild/default_tasks/profile.sh" + POST_TASK_SCRIPT="" + ;; + # compiles program with -pg and runs it with gprof + # uses gprof2dot python script to generate function call tree (pip install gprof2dot) + # requires graphviz (https://www.graphviz.org/download/source/) + gprof) + OUTDIR="$OUTDIR/gprof" + # arguments that emit some call counter code and disable optimizations to see function names + # https://github.com/msys2/MINGW-packages/issues/8503#issuecomment-1365475205 + C_ARGS="-O0 -g -pg -no-pie -fno-omit-frame-pointer + -fno-inline-functions -fno-inline-functions-called-once + -fno-optimize-sibling-calls -fopenmp" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + TASK_SCRIPT="@cbuild/default_tasks/gprof.sh" + POST_TASK_SCRIPT="" + ;; + # compiles program and runs it with callgrind (part of valgrind) + # uses gprof2dot python script to generate function call tree (pip install gprof2dot) + # requires graphviz (https://www.graphviz.org/download/source/) + # P.S. detailed results can be viewed in KCacheGrind + callgrind) + OUTDIR="$OUTDIR/callgrind" + # -pg adds code to executable, that generates file containing function call info (gmon.out) + C_ARGS="-O2 -flto=auto -fuse-linker-plugin" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + TASK_SCRIPT="@cbuild/default_tasks/callgrind.sh" + POST_TASK_SCRIPT="" + ;; + # compiles executable with sanitizers and executes it to find errors and warnings + sanitize) + OUTDIR="$OUTDIR/sanitize" + C_ARGS="-O0 -g3 -fsanitize=undefined,address" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + TASK_SCRIPT="@cbuild/default_tasks/exec.sh" + POST_TASK_SCRIPT="" + ;; + # rebuilds specified dependencies + # EXAMPLE: `cbuild rebuild_dependencies=libexample1,fonts` + # 'all' can be specified to rebuild all dependencies + rebuild_dependencies) + TASK_SCRIPT="@cbuild/default_tasks/rebuild_dependencies.sh" + ;; + # deletes generated files + clean) + TASK_SCRIPT="@cbuild/default_tasks/clean.sh" + ;; + # nothing to do + "" | no_task) + ;; + # unknown task + *) + error "task <$PROJECT/$TASK> not found" + ;; +esac diff --git a/project.config.user.default b/project.config.user.default new file mode 100644 index 0000000..901aa1d --- /dev/null +++ b/project.config.user.default @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Project user config is ignored by git. +# Here you can add variables that users might want to change +# on their local machine, without commiting to the repository. + +# Directory where you install dependencies. +# Do not confuse with DEPENDENCY_CONFIGS_DIR +# Example: +# libexample source code is at `../libexample`, and dependency config +# that specifies how to build this lib is at `dependencies/libexample.config` +DEPENDENCIES_DIR=".." diff --git a/src/connection.c b/src/connection.c new file mode 100644 index 0000000..f4225e5 --- /dev/null +++ b/src/connection.c @@ -0,0 +1,16 @@ +#include "tsqlite.h" + +Result(tsqlite_connection*) tsqlite_connection_open(cstr file_path, + NULLABLE(void*) logger, NULLABLE(sqlite3_error_log_func_t) log_func) +{ + tsqlite_connection* conn = NULL; + try_sqlite3(sqlite3_open(file_path, &conn)); + try_sqlite3(sqlite3_db_config(conn, SQLITE_CONFIG_LOG, log_func, logger)); + try_sqlite3(sqlite3_extended_result_codes(conn, true)); + return RESULT_VALUE(p, conn); +} + +Result(void) tsqlite_connection_close(tsqlite_connection* conn){ + try_sqlite3(sqlite3_close(conn)); + return RESULT_VOID; +} diff --git a/src/errors.c b/src/errors.c new file mode 100644 index 0000000..9a883b0 --- /dev/null +++ b/src/errors.c @@ -0,0 +1,151 @@ +#include "tsqlite.h" +#include "tlibc/string/StringBuilder.h" + +#define PRIMARY_CODE_X(X)\ + X(SQLITE_OK)\ + X(SQLITE_ERROR)\ + X(SQLITE_INTERNAL)\ + X(SQLITE_PERM)\ + X(SQLITE_ABORT)\ + X(SQLITE_BUSY)\ + X(SQLITE_LOCKED)\ + X(SQLITE_NOMEM)\ + X(SQLITE_READONLY)\ + X(SQLITE_INTERRUPT)\ + X(SQLITE_IOERR)\ + X(SQLITE_CORRUPT)\ + X(SQLITE_NOTFOUND)\ + X(SQLITE_FULL)\ + X(SQLITE_CANTOPEN)\ + X(SQLITE_PROTOCOL)\ + X(SQLITE_EMPTY)\ + X(SQLITE_SCHEMA)\ + X(SQLITE_TOOBIG)\ + X(SQLITE_CONSTRAINT)\ + X(SQLITE_MISMATCH)\ + X(SQLITE_MISUSE)\ + X(SQLITE_NOLFS)\ + X(SQLITE_AUTH)\ + X(SQLITE_FORMAT)\ + X(SQLITE_RANGE)\ + X(SQLITE_NOTADB)\ + X(SQLITE_NOTICE)\ + X(SQLITE_WARNING)\ + X(SQLITE_ROW)\ + X(SQLITE_DONE)\ + +#define EXTENDED_CODE_X(X)\ + X(SQLITE_ERROR_MISSING_COLLSEQ)\ + X(SQLITE_ERROR_RETRY)\ + X(SQLITE_ERROR_SNAPSHOT)\ + X(SQLITE_ERROR_RESERVESIZE)\ + X(SQLITE_ERROR_KEY)\ + X(SQLITE_ERROR_UNABLE)\ + X(SQLITE_IOERR_READ)\ + X(SQLITE_IOERR_SHORT_READ)\ + X(SQLITE_IOERR_WRITE)\ + X(SQLITE_IOERR_FSYNC)\ + X(SQLITE_IOERR_DIR_FSYNC)\ + X(SQLITE_IOERR_TRUNCATE)\ + X(SQLITE_IOERR_FSTAT)\ + X(SQLITE_IOERR_UNLOCK)\ + X(SQLITE_IOERR_RDLOCK)\ + X(SQLITE_IOERR_DELETE)\ + X(SQLITE_IOERR_BLOCKED)\ + X(SQLITE_IOERR_NOMEM)\ + X(SQLITE_IOERR_ACCESS)\ + X(SQLITE_IOERR_CHECKRESERVEDLOCK)\ + X(SQLITE_IOERR_LOCK)\ + X(SQLITE_IOERR_CLOSE)\ + X(SQLITE_IOERR_DIR_CLOSE)\ + X(SQLITE_IOERR_SHMOPEN)\ + X(SQLITE_IOERR_SHMSIZE)\ + X(SQLITE_IOERR_SHMLOCK)\ + X(SQLITE_IOERR_SHMMAP)\ + X(SQLITE_IOERR_SEEK)\ + X(SQLITE_IOERR_DELETE_NOENT)\ + X(SQLITE_IOERR_MMAP)\ + X(SQLITE_IOERR_GETTEMPPATH)\ + X(SQLITE_IOERR_CONVPATH)\ + X(SQLITE_IOERR_VNODE)\ + X(SQLITE_IOERR_AUTH)\ + X(SQLITE_IOERR_BEGIN_ATOMIC)\ + X(SQLITE_IOERR_COMMIT_ATOMIC)\ + X(SQLITE_IOERR_ROLLBACK_ATOMIC)\ + X(SQLITE_IOERR_DATA)\ + X(SQLITE_IOERR_CORRUPTFS)\ + X(SQLITE_IOERR_IN_PAGE)\ + X(SQLITE_IOERR_BADKEY)\ + X(SQLITE_IOERR_CODEC)\ + X(SQLITE_LOCKED_SHAREDCACHE)\ + X(SQLITE_LOCKED_VTAB)\ + X(SQLITE_BUSY_RECOVERY)\ + X(SQLITE_BUSY_SNAPSHOT)\ + X(SQLITE_BUSY_TIMEOUT)\ + X(SQLITE_CANTOPEN_NOTEMPDIR)\ + X(SQLITE_CANTOPEN_ISDIR)\ + X(SQLITE_CANTOPEN_FULLPATH)\ + X(SQLITE_CANTOPEN_CONVPATH)\ + X(SQLITE_CANTOPEN_DIRTYWAL)\ + X(SQLITE_CANTOPEN_SYMLINK)\ + X(SQLITE_CORRUPT_VTAB)\ + X(SQLITE_CORRUPT_SEQUENCE)\ + X(SQLITE_CORRUPT_INDEX)\ + X(SQLITE_READONLY_RECOVERY)\ + X(SQLITE_READONLY_CANTLOCK)\ + X(SQLITE_READONLY_ROLLBACK)\ + X(SQLITE_READONLY_DBMOVED)\ + X(SQLITE_READONLY_CANTINIT)\ + X(SQLITE_READONLY_DIRECTORY)\ + X(SQLITE_ABORT_ROLLBACK)\ + X(SQLITE_CONSTRAINT_CHECK)\ + X(SQLITE_CONSTRAINT_COMMITHOOK)\ + X(SQLITE_CONSTRAINT_FOREIGNKEY)\ + X(SQLITE_CONSTRAINT_FUNCTION)\ + X(SQLITE_CONSTRAINT_NOTNULL)\ + X(SQLITE_CONSTRAINT_PRIMARYKEY)\ + X(SQLITE_CONSTRAINT_TRIGGER)\ + X(SQLITE_CONSTRAINT_UNIQUE)\ + X(SQLITE_CONSTRAINT_VTAB)\ + X(SQLITE_CONSTRAINT_ROWID)\ + X(SQLITE_CONSTRAINT_PINNED)\ + X(SQLITE_CONSTRAINT_DATATYPE)\ + X(SQLITE_NOTICE_RECOVER_WAL)\ + X(SQLITE_NOTICE_RECOVER_ROLLBACK)\ + X(SQLITE_NOTICE_RBU)\ + X(SQLITE_WARNING_AUTOINDEX)\ + X(SQLITE_AUTH_USER)\ + X(SQLITE_OK_LOAD_PERMANENTLY)\ + X(SQLITE_OK_SYMLINK)\ + +#define PRIMARY_CODE_CASE(C) case C: return STR(#C); + +static str PrimaryCode_toStr(int code){ + switch(code & 0xff){ + default: return STR("!! ERROR: INVALID SQLITE_CODE !!"); + PRIMARY_CODE_X(PRIMARY_CODE_CASE) + } +} + +#define EXTENDED_CODE_IF(C) \ + if(C & code) {\ + if(multiple_extended_codes){\ + StringBuilder_append_char(&sb, ',');\ + StringBuilder_append_char(&sb, ' ');\ + }\ + else multiple_extended_codes = true;\ + StringBuilder_append_str(&sb, STR(#C));\ + }\ + +str tsqlite_ResultCode_toStr(int code){ + StringBuilder sb = StringBuilder_alloc(128); + bool multiple_extended_codes = false; + + StringBuilder_append_str(&sb, PrimaryCode_toStr(code)); + StringBuilder_append_char(&sb, ':'); + StringBuilder_append_char(&sb, ' '); + + EXTENDED_CODE_X(EXTENDED_CODE_IF); + + return StringBuilder_getStr(&sb); +} diff --git a/src/statement.c b/src/statement.c new file mode 100644 index 0000000..e86d20d --- /dev/null +++ b/src/statement.c @@ -0,0 +1,109 @@ +#include "tsqlite.h" + +Result(tsqlite_statement*) tsqlite_statement_prepare(sqlite3* conn, str sql_code){ + sqlite3_stmt* st = NULL; + i32 flags = SQLITE_PREPARE_PERSISTENT; + try_sqlite3(sqlite3_prepare_v3(conn, sql_code.data, sql_code.len, flags, &st, NULL)); + tsqlite_statement* self = malloc(sizeof(*self)); + zeroStruct(self); + self->st = st; + return RESULT_VALUE(p, self); +} + +void tsqlite_statement_free(tsqlite_statement* self){ + if(!self) + return; + sqlite3_finalize(self->st); + free(self); +} + +Result(void) tsqlite_statement_reset(tsqlite_statement* self){ + try_sqlite3(sqlite3_reset(self->st)); + self->bind_arg_pos = 0; + self->result_row = 0; + self->result_col = 0; + return RESULT_VOID; +} + + +Result(void) tsqlite_statement_bind_null(tsqlite_statement* self){ + try_sqlite3(sqlite3_bind_null(self->st, self->bind_arg_pos)); + self->bind_arg_pos++; + return RESULT_VOID; +} + +Result(void) tsqlite_statement_bind_i32(tsqlite_statement* self, i32 v){ + try_sqlite3(sqlite3_bind_int(self->st, self->bind_arg_pos, v)); + self->bind_arg_pos++; + return RESULT_VOID; +} + +Result(void) tsqlite_statement_bind_i64(tsqlite_statement* self, i64 v){ + try_sqlite3(sqlite3_bind_int64(self->st, self->bind_arg_pos, v)); + self->bind_arg_pos++; + return RESULT_VOID; +} + +Result(void) tsqlite_statement_bind_f64(tsqlite_statement* self, f64 v){ + try_sqlite3(sqlite3_bind_double(self->st, self->bind_arg_pos, v)); + self->bind_arg_pos++; + return RESULT_VOID; +} + +Result(void) tsqlite_statement_bind_str(tsqlite_statement* self, str v, NULLABLE(Destructor_t) d){ + try_sqlite3(sqlite3_bind_text(self->st, self->bind_arg_pos, v.data, v.len, d)); + self->bind_arg_pos++; + return RESULT_VOID; +} + +Result(void) tsqlite_statement_bind_blob(tsqlite_statement* self, Array(u8) v, NULLABLE(Destructor_t) d){ + try_sqlite3(sqlite3_bind_blob(self->st, self->bind_arg_pos, v.data, v.len, d)); + self->bind_arg_pos++; + return RESULT_VOID; +} + +Result(void) tsqlite_statement_bind_zeroblob(tsqlite_statement* self, i32 size){ + try_sqlite3(sqlite3_bind_zeroblob(self->st, self->bind_arg_pos, size)); + self->bind_arg_pos++; + return RESULT_VOID; +} + +Result(bool) sqlite3_statement_moveNext(tsqlite_statement* self){ + int r = sqlite3_step(self->st); + if(r == SQLITE_ROW){ + return RESULT_VALUE(i, true); + } + if(r == SQLITE_DONE){ + return RESULT_VALUE(i, false); + } + + return RESULT_ERROR_SQLITE_CODE(r); +} + +Result(i64) tsqlite_statement_getResult_i64(tsqlite_statement* self){ + i64 r = sqlite3_column_int64(self->st, self->result_col); + // TODO: error checking in sqlite3_column + self->result_col++; + return RESULT_VALUE(i, r); +} + +Result(i64) tsqlite_statement_getResult_f64(tsqlite_statement* self){ + f64 r = sqlite3_column_double(self->st, self->result_col); + // TODO: error checking in sqlite3_column + self->result_col++; + return RESULT_VALUE(f, r); +} + +Result(void) tsqlite_statement_getResult_str(tsqlite_statement* self, str* out_v){ + (void)self; + (void)out_v; + // TODO: tsqlite_statement_getResult_str + return RESULT_VOID; +} + +Result(void) tsqlite_statement_getResult_blob(tsqlite_statement* self, Array(u8)* out_v){ + (void)self; + (void)out_v; + // TODO: tsqlite_statement_getResult_blob + return RESULT_VOID; +} diff --git a/src/tsqlite.c b/src/tsqlite.c new file mode 100644 index 0000000..44d85c6 --- /dev/null +++ b/src/tsqlite.c @@ -0,0 +1,15 @@ +#include "tsqlite.h" + +ErrorCodePage_define(SQLITE); + +Result(void) tsqlite_init(){ + Deferral(4); + + ErrorCodePage_register(SQLITE); + + Return RESULT_VOID; +} + +void tsqlite_deinit(){ + +} diff --git a/tests/main.c b/tests/main.c new file mode 100644 index 0000000..61ce0dd --- /dev/null +++ b/tests/main.c @@ -0,0 +1,12 @@ +#include "tsqlite.h" + +int main(){ + Deferral(32); + + try_fatal_void(tlibc_init()); + try_fatal_void(tsqlite_init()); + Defer(tlibc_deinit()); + Defer(tsqlite_deinit()); + + Return 0; +}