diff --git a/LuaSTG/CMakeLists.txt b/LuaSTG/CMakeLists.txt index a2fb006c..6b0c41b0 100644 --- a/LuaSTG/CMakeLists.txt +++ b/LuaSTG/CMakeLists.txt @@ -185,6 +185,7 @@ target_link_libraries(LuaSTG PRIVATE lua_filesystem #lua_xlsx_csv lua_imgui + lua_sqlite3 imgui_impl_win32ex imgui_impl_dx11 lua_steam_api diff --git a/LuaSTG/LuaSTG/AppFrameLua.cpp b/LuaSTG/LuaSTG/AppFrameLua.cpp index 6ab6408b..39a17c70 100644 --- a/LuaSTG/LuaSTG/AppFrameLua.cpp +++ b/LuaSTG/LuaSTG/AppFrameLua.cpp @@ -17,6 +17,7 @@ extern "C" { #endif //#include "lua_xlsx_csv.h" #include "lua_steam.h" +#include "lua_sqlite3.h" #include "LuaBinding/lua_xinput.hpp" #include "LuaBinding/lua_random.hpp" #include "LuaBinding/lua_dwrite.hpp" @@ -309,6 +310,7 @@ namespace LuaSTGPlus //lua_xlsx_open(L); //lua_csv_open(L); lua_steam_open(L); + luaopen_sqlite3(L); lua_xinput_open(L); luaopen_dwrite(L); luaopen_random(L); diff --git a/cmake/import/all.cmake b/cmake/import/all.cmake index 66a721aa..6035eab8 100644 --- a/cmake/import/all.cmake +++ b/cmake/import/all.cmake @@ -17,6 +17,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/tinyobjloader.cmake) include(${CMAKE_CURRENT_LIST_DIR}/pcg.cmake) include(${CMAKE_CURRENT_LIST_DIR}/xxhash.cmake) include(${CMAKE_CURRENT_LIST_DIR}/simdutf.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/sqlite3.cmake) include(${CMAKE_CURRENT_LIST_DIR}/wil.cmake) include(${CMAKE_CURRENT_LIST_DIR}/directx_tk.cmake) diff --git a/cmake/import/sqlite3.cmake b/cmake/import/sqlite3.cmake new file mode 100644 index 00000000..34f7657a --- /dev/null +++ b/cmake/import/sqlite3.cmake @@ -0,0 +1,40 @@ +# sqlite3 + +CPMAddPackage( + NAME sqlite3 + VERSION 3.47.0 + URL https://sqlite.org/2024/sqlite-amalgamation-3470000.zip + URL_HASH SHA256=2842FDDBB1CC33F66C7DA998A57535F14A6BFEE159676A07BB4BF3E59375D93E + DOWNLOAD_ONLY YES +) + +if (sqlite3_ADDED) + set(sqlite3_root ${sqlite3_SOURCE_DIR}) + + add_library(sqlite3 STATIC) + if (CMAKE_C_COMPILER_ID STREQUAL "MSVC") + target_compile_options(sqlite3 PRIVATE "/utf-8") + endif () + target_compile_features(sqlite3 PRIVATE c_std_17) + target_include_directories(sqlite3 PUBLIC ${sqlite3_root}) + target_sources(sqlite3 PRIVATE ${sqlite3_root}/sqlite3.h ${sqlite3_root}/sqlite3.c) + set_target_properties(sqlite3 PROPERTIES OUTPUT_NAME "libsqlite3") + + add_executable(sqlite3_cli) + if (CMAKE_C_COMPILER_ID STREQUAL "MSVC") + target_compile_options(sqlite3_cli PRIVATE "/utf-8") + endif () + target_compile_features(sqlite3_cli PRIVATE c_std_17) + target_sources(sqlite3_cli PRIVATE ${sqlite3_root}/shell.c) + target_link_libraries(sqlite3_cli PRIVATE sqlite3) + set_target_properties(sqlite3_cli PROPERTIES OUTPUT_NAME "sqlite3") + + add_custom_command(TARGET sqlite3_cli POST_BUILD + COMMAND ${CMAKE_COMMAND} ARGS -E make_directory ${CMAKE_BINARY_DIR}/bin + COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different $ ${CMAKE_BINARY_DIR}/bin/$ + VERBATIM + ) + + set_target_properties(sqlite3 PROPERTIES FOLDER external) + set_target_properties(sqlite3_cli PROPERTIES FOLDER external) +endif () diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index bcbdb632..53485ea3 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -82,6 +82,11 @@ set_target_properties(lua_cjson PROPERTIES FOLDER lualib) # #set_target_properties(lua_xlsx_csv PROPERTIES FOLDER external) +# ==================== lua sqlite3 ==================== + +add_subdirectory(lua-sqlite3) +set_target_properties(lua_sqlite3 PROPERTIES FOLDER lualib) + # ==================== xmath ==================== add_library(xmath STATIC) diff --git a/external/lua-sqlite3/CMakeLists.txt b/external/lua-sqlite3/CMakeLists.txt new file mode 100644 index 00000000..5df4c30d --- /dev/null +++ b/external/lua-sqlite3/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(lua_sqlite3 STATIC) +target_compile_features(lua_sqlite3 PRIVATE c_std_17 cxx_std_20) +target_include_directories(lua_sqlite3 PUBLIC .) +target_sources(lua_sqlite3 PRIVATE lua_sqlite3.h lua_sqlite3.cpp) +target_link_libraries(lua_sqlite3 PUBLIC lua51_static sqlite3) diff --git a/external/lua-sqlite3/lua_sqlite3.cpp b/external/lua-sqlite3/lua_sqlite3.cpp new file mode 100644 index 00000000..e9137aa0 --- /dev/null +++ b/external/lua-sqlite3/lua_sqlite3.cpp @@ -0,0 +1,339 @@ +#include "lua_sqlite3.h" +#include +#include +#include +#include +#include + +using namespace std::string_view_literals; + +namespace lua { + class StackIndex { + public: + //inline explicit StackIndex(int32_t index) noexcept : value_(index) {} + inline constexpr explicit StackIndex(int32_t index) noexcept : value_(index) {} + inline int32_t value() const noexcept { return value_; } + private: + int32_t const value_; + }; + + constexpr auto arg1 = StackIndex(1); + constexpr auto arg2 = StackIndex(2); + constexpr auto arg3 = StackIndex(3); + constexpr auto arg4 = StackIndex(4); + constexpr auto arg5 = StackIndex(5); + constexpr auto arg6 = StackIndex(6); + constexpr auto arg7 = StackIndex(7); + constexpr auto arg8 = StackIndex(8); + constexpr auto arg9 = StackIndex(9); + + constexpr auto arg_r1 = StackIndex(-1); + constexpr auto arg_r2 = StackIndex(-2); + constexpr auto arg_r3 = StackIndex(-3); + constexpr auto arg_r4 = StackIndex(-4); + constexpr auto arg_r5 = StackIndex(-5); + constexpr auto arg_r6 = StackIndex(-6); + constexpr auto arg_r7 = StackIndex(-7); + constexpr auto arg_r8 = StackIndex(-8); + constexpr auto arg_r9 = StackIndex(-9); + + class StackBalancer { + public: + inline explicit StackBalancer(lua_State* L_) : L(L_), N(lua_gettop(L_)) {} + inline ~StackBalancer() { lua_settop(L, N); } + private: + lua_State* const L; + int32_t const N; + }; + + class Stack { + public: + template + [[nodiscard]] T getValue(StackIndex const index) const { + if constexpr (std::is_same_v) { + return static_cast(luaL_checkinteger(L, index.value())); + } + else if constexpr (std::is_same_v) { + size_t l{}; + char const* s = luaL_checklstring(L, index.value(), &l); + return { s, l }; + } + else { + static_assert(false, "not implemented"); + } + } + template + void pushValue(T const& value) const { + if constexpr (std::is_same_v) { + lua_pushnil(L); + } + else if constexpr (std::is_same_v) { + lua_pushvalue(L, value.value()); + } + else if constexpr (std::is_same_v) { + lua_pushboolean(L, value ? 1 : 0); + } + else if constexpr (std::is_same_v) { + lua_pushinteger(L, value); + } + else if constexpr (std::is_same_v || std::is_same_v) { + lua_pushstring(L, value); + } + else if constexpr (std::is_same_v) { + lua_pushlstring(L, value.data(), value.size()); + } + else if constexpr (std::is_same_v) { + lua_pushcfunction(L, value); + } + else { + static_assert(false, "not implemented"); + } + } + + [[nodiscard]] StackIndex createList(int32_t cap = 0) const { + lua_createtable(L, cap, 0); + return StackIndex(lua_gettop(L)); + } + [[nodiscard]] StackIndex createList(size_t cap = 0) const { + lua_createtable(L, static_cast(cap), 0); + return StackIndex(lua_gettop(L)); + } + + template + void setListValue(StackIndex const index, size_t key, T const& value) const { + pushValue(static_cast(key + 1)); + pushValue(value); + lua_settable(L, index.value()); + } + + [[nodiscard]] StackIndex createMap(size_t cap = 0) const { + lua_createtable(L, 0, static_cast(cap)); + return StackIndex(lua_gettop(L)); + } + + template + void setMapValue(StackIndex const index, std::string_view const& key, T const& value) const { + pushValue(key); + pushValue(value); + lua_settable(L, index.value()); + } + + [[nodiscard]] StackIndex createMetaTable(std::string_view const& name) const { + luaL_newmetatable(L, name.data()); + return StackIndex(lua_gettop(L)); + } + + [[nodiscard]] StackIndex pushModule(std::string_view const& name) const { + constexpr luaL_Reg empty[] = { {} }; + luaL_register(L, name.data(), empty); + auto const index = lua_gettop(L); + lua_pushnil(L); + lua_setglobal(L, name.data()); + return StackIndex(index); + } + public: + inline explicit Stack(lua_State* L_) : L(L_) {} + private: + lua_State* const L; + }; +} + +namespace { + struct Database { + static const std::string_view class_name; + + static void registerClass(lua_State* L); + static Database* create(lua_State* L); + static Database* as(lua_State* L, int index); + static bool is(lua_State* L, int index); + + sqlite3* database; + }; + + const std::string_view Database::class_name{ "sqlite3.Database"sv }; + + struct DatabaseBinding : public Database { + #define verify if (!self->database) return luaL_error(L, "database is closed"); + + // meta methods + + static int __gc(lua_State* L) { + auto* self = as(L, 1); + if (self->database) { + if (auto const result = sqlite3_close_v2(self->database); result == SQLITE_OK) { + self->database = nullptr; + } + } + return 0; + } + static int __tostring(lua_State* L) { + lua::Stack S(L); + [[maybe_unused]] auto* self = as(L, 1); + S.pushValue(class_name); + return 1; + } + + // instance methods + + static int exec(lua_State* L) { + lua::Stack S(L); + auto* self = as(L, 1); + verify; + auto const sql = S.getValue(lua::arg2); + auto const has_callback = lua_isfunction(L, 3); + + struct CallbackContext { + sqlite3* database{}; + lua_State* L{}; + static int callback(void* userdata, int column_count, char** column_values, char** column_names) { + auto& ctx = *static_cast(userdata); + [[maybe_unused]] lua::StackBalancer SB(ctx.L); + lua::Stack S(ctx.L); + S.pushValue(lua::arg3); + // ^stack: ... callback + S.pushValue(column_count); + // ^stack: ... callback column_count + auto const value_list = S.createList(column_count); + // ^stack: ... callback column_count column_values + for (int i = 0; i < column_count; i += 1) { + S.setListValue(value_list, i, column_values[i]); + } + auto const name_list = S.createList(column_count); + // ^stack: ... callback column_count column_values column_names + for (int i = 0; i < column_count; i += 1) { + S.setListValue(name_list, i, column_names[i]); + } + lua_call(ctx.L, 3, 1); + // ^stack: ... code? + return S.getValue(lua::arg_r1); + } + }; + + char* error_message{}; + int result{}; + if (has_callback) { + CallbackContext callback_context{ self->database, L }; + result = sqlite3_exec(self->database, sql.data(), &CallbackContext::callback, &callback_context, &error_message); + } + else { + result = sqlite3_exec(self->database, sql.data(), nullptr, nullptr, &error_message); + } + + if (result != SQLITE_OK) { + S.pushValue(false); + if (error_message) { + S.pushValue(error_message); + sqlite3_free(error_message); + } + else { + S.pushValue("exec error"sv); + } + S.pushValue(result); + return 3; + } + + S.pushValue(true); + return 1; + } + static int close(lua_State* L) { + lua::Stack S(L); + auto* self = as(L, 1); + if (self->database) { + if (auto const result = sqlite3_close_v2(self->database); result == SQLITE_OK) { + self->database = nullptr; + } + else { + S.pushValue(false); + S.pushValue(sqlite3_errmsg(self->database)); + S.pushValue(result); + return 3; + } + } + S.pushValue(true); + return 1; + } + + // static methods + + static int open(lua_State* L) { + lua::Stack S(L); + auto const file_name = S.getValue(lua::arg1); + auto const flags = S.getValue(lua::arg2); + auto* self = create(L); + if (auto const result = sqlite3_open_v2(file_name.data(), &self->database, flags, nullptr); result != SQLITE_OK) { + S.pushValue(std::nullopt); + S.pushValue(sqlite3_errmsg(self->database)); + S.pushValue(result); + return 3; + } + return 1; + } + + #undef verify + }; + + void Database::registerClass(lua_State* L) { + [[maybe_unused]] lua::StackBalancer SB(L); + lua::Stack S(L); + + auto const class_table = S.pushModule(class_name); + S.setMapValue(class_table, "exec"sv, &DatabaseBinding::exec); + S.setMapValue(class_table, "close"sv, &DatabaseBinding::close); + S.setMapValue(class_table, "open"sv, &DatabaseBinding::open); + + auto const meta_table = S.createMetaTable(class_name); + S.setMapValue(meta_table, "__gc"sv, &DatabaseBinding::__gc); + S.setMapValue(meta_table, "__tostring"sv, &DatabaseBinding::__tostring); + S.setMapValue(meta_table, "__index"sv, class_table); + } + Database* Database::create(lua_State* L) { + auto* self = static_cast(lua_newuserdata(L, sizeof(Database))); + self->database = nullptr; + luaL_setmetatable(L, class_name.data()); + return self; + } + Database* Database::as(lua_State* L, int index) { + return static_cast(luaL_checkudata(L, index, class_name.data())); + } + bool Database::is(lua_State* L, int index) { + return luaL_testudata(L, index, class_name.data()) != nullptr; + } +} + +extern "C" int luaopen_sqlite3(lua_State* L) { + [[maybe_unused]] lua::StackBalancer SB(L); + lua::Stack S(L); + + Database::registerClass(L); + + auto const module_table = S.pushModule("sqlite"sv); + +#define CODE(X) S.setMapValue(module_table, "" #X ""sv, SQLITE_##X) + + CODE(OK); + + CODE(OPEN_READONLY ); + CODE(OPEN_READWRITE ); + CODE(OPEN_CREATE ); + CODE(OPEN_DELETEONCLOSE); + CODE(OPEN_EXCLUSIVE ); + CODE(OPEN_AUTOPROXY ); + CODE(OPEN_URI ); + CODE(OPEN_MEMORY ); + CODE(OPEN_MAIN_DB ); + CODE(OPEN_TEMP_DB ); + CODE(OPEN_TRANSIENT_DB ); + CODE(OPEN_MAIN_JOURNAL ); + CODE(OPEN_TEMP_JOURNAL ); + CODE(OPEN_SUBJOURNAL ); + CODE(OPEN_SUPER_JOURNAL); + CODE(OPEN_NOMUTEX ); + CODE(OPEN_FULLMUTEX ); + CODE(OPEN_SHAREDCACHE ); + CODE(OPEN_PRIVATECACHE ); + CODE(OPEN_WAL ); + CODE(OPEN_NOFOLLOW ); + CODE(OPEN_EXRESCODE ); + + return 1; +} diff --git a/external/lua-sqlite3/lua_sqlite3.h b/external/lua-sqlite3/lua_sqlite3.h new file mode 100644 index 00000000..0ddcc8ec --- /dev/null +++ b/external/lua-sqlite3/lua_sqlite3.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +int luaopen_sqlite3(lua_State* L); + +#ifdef __cplusplus +} +#endif