diff --git a/.gitmodules b/.gitmodules index afd88d2..76375dc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -47,3 +47,7 @@ path = modules/cache url = https://github.com/vixcpp/cache.git branch = dev +[submodule "modules/db"] + path = modules/db + url = git@github.com:vixcpp/db.git + branch = dev diff --git a/CHANGELOG.md b/CHANGELOG.md index 5708868..9222517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- ## [Unreleased] +# Vix.cpp v1.21.0 + +This release stabilizes the new DB core module, improves CLI runtime output, and fixes several build/link issues across modules. + +## Highlights +- DB/ORM separation is now fully aligned: `vix::db` is the low-level core layer, `vix::orm` remains optional sugar on top. +- CLI runtime output and error UX were refined to be clearer and less noisy. +- Improved reliability for MySQL detection/linking in diverse environments. + +## CLI (modules/cli) +- Fix: prevent duplicate runtime logs in some failure paths. +- Improve: runtime error detectors and diagnostics formatting. +- Improve: UX cleanup for run/dev flows (clearer output, less noise). + +## DB Core (modules/db) +- Fix: CMake/source/linkage issues across DB drivers. +- Fix: MySQL Connector/C++ discovery via fallback alias target (more robust CI/local setups). +- Improve: driver linkage consistency and feature flag reporting. + +## Umbrella / Modules +- Introduced `vix::db` as a core module and decoupled ORM tooling/drivers accordingly. +- Synced submodules after DB/ORM compatibility fixes. + +## Upgrade notes +- If you enable `ORM`, it automatically implies `DB`. +- If MySQL is enabled, ensure Connector/C++ is available (the fallback alias helps when CMake configs are missing). + ## v1.20.1 — Improved CLI Error UX & Build Feedback ### ✨ CLI — Error reporting & diagnostics diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a4a03d..e4180e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,41 @@ cmake_minimum_required(VERSION 3.20) # cmake --build build -j # ==================================================================== -project(vix VERSION 1.17.1 LANGUAGES CXX) +cmake_minimum_required(VERSION 3.20) + +# Resolve umbrella version from git (preferred) +set(_VIX_VERSION_FALLBACK "0.0.0") + +find_package(Git QUIET) +set(_VIX_GIT_DESCRIBE "") +if(Git_FOUND) + execute_process( + COMMAND "${GIT_EXECUTABLE}" describe --tags --always --dirty + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE _VIX_GIT_DESCRIBE + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) +endif() + +# Compute PROJECT_VERSION (must be numeric for CMake packaging) +# If describe returns: v1.20.1-4-gXXXX -> numeric is 1.20.1 +set(_VIX_PROJECT_VERSION "${_VIX_VERSION_FALLBACK}") +if(_VIX_GIT_DESCRIBE MATCHES "^v([0-9]+\\.[0-9]+\\.[0-9]+)") + set(_VIX_PROJECT_VERSION "${CMAKE_MATCH_1}") +endif() + +project(vix VERSION ${_VIX_PROJECT_VERSION} LANGUAGES CXX) + +# Human-friendly version string (can include -N-gHASH[-dirty]) +if(_VIX_GIT_DESCRIBE STREQUAL "") + set(VIX_UMBRELLA_VERSION "v${PROJECT_VERSION}") +else() + set(VIX_UMBRELLA_VERSION "${_VIX_GIT_DESCRIBE}") +endif() +set(VIX_UMBRELLA_VERSION "${VIX_UMBRELLA_VERSION}" CACHE STRING "Umbrella version" FORCE) + +set(VIX_UMBRELLA_BUILD ON CACHE BOOL "Building Vix from umbrella" FORCE) # Make find_package honor *_ROOT hints (e.g. MYSQLCPPCONN_ROOT) if (POLICY CMP0144) @@ -72,6 +106,15 @@ option(VIX_BENCH_MODE "Disable heavy security/logging checks for benchmarks" OFF option(VIX_ENABLE_MIDDLEWARE "Build Vix middleware module" ON) option(VIX_ENABLE_HTTP_COMPRESSION "Enable HTTP compression middleware deps (zlib/brotli)" ON) +option(VIX_ENABLE_DB "Build Vix DB module (core anti-ORM)" ON) + +# DB backends (forwarded to modules/db) +option(VIX_DB_USE_MYSQL "Enable MySQL backend in vix_db" ON) +option(VIX_DB_USE_SQLITE "Enable SQLite backend in vix_db" OFF) +option(VIX_DB_USE_POSTGRES "Enable PostgreSQL backend in vix_db" OFF) +option(VIX_DB_USE_REDIS "Enable Redis backend in vix_db" OFF) + + # ---------------------------------------------------- # Tooling / Static analysis # ---------------------------------------------------- @@ -341,6 +384,23 @@ if (VIX_BENCH_MODE) endif() endif() +# --- DB (optional, required by ORM) --- +set(VIX_HAS_DB OFF) +if (VIX_ENABLE_DB AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/db/CMakeLists.txt") + message(STATUS "Adding 'modules/db'...") + add_subdirectory(modules/db db_build) + + if (TARGET vix::db OR TARGET vix_db) + if (TARGET vix_db AND NOT TARGET vix::db) + add_library(vix::db ALIAS vix_db) + endif() + set(VIX_HAS_DB ON) + else() + message(WARNING "DB module added but no vix::db target was exported.") + endif() +else() + message(STATUS "DB: disabled or not present.") +endif() # --- WebSocket (optional) --- set(VIX_HAS_WEBSOCKET OFF) @@ -358,21 +418,24 @@ endif() # --- CLI (optional) --- if (VIX_ENABLE_CLI AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/cli/CMakeLists.txt") message(STATUS "Adding 'modules/cli'...") + set(VIX_UMBRELLA_VERSION "${PROJECT_VERSION}" CACHE STRING "Umbrella version" FORCE) add_subdirectory(modules/cli cli_build) else() message(STATUS "CLI: disabled or not present.") endif() # --- ORM (optional) --- -# Forwarded to modules/orm (users can override via -D flags) -option(VIX_ORM_USE_MYSQL "Enable MySQL backend in vix_orm" ON) -set(VIX_ORM_BUILD_EXAMPLES OFF CACHE BOOL "Build vix_orm examples") -set(VIX_ORM_BUILD_TESTS OFF CACHE BOOL "Build vix_orm tests") - -set(VIX_UMBRELLA_BUILD ON CACHE BOOL "Building Vix from umbrella" FORCE) set(VIX_HAS_ORM OFF) if (VIX_ENABLE_ORM AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/orm/CMakeLists.txt") + + if (NOT VIX_HAS_DB) + message(FATAL_ERROR "ORM requires DB module. Enable it with -DVIX_ENABLE_DB=ON and ensure modules/db exists.") + endif() + message(STATUS "Adding 'modules/orm'...") + set(VIX_ORM_BUILD_EXAMPLES OFF CACHE BOOL "Build vix_orm examples") + set(VIX_ORM_BUILD_TESTS OFF CACHE BOOL "Build vix_orm tests") + add_subdirectory(modules/orm orm_build) if (TARGET vix::orm OR TARGET vix_orm) @@ -396,6 +459,10 @@ target_link_libraries(vix INTERFACE ${JSON_TARGET} ) +if (TARGET vix::db) + target_link_libraries(vix INTERFACE vix::db) +endif() + # Link websocket only if it exists if (TARGET vix::websocket) target_link_libraries(vix INTERFACE vix::websocket) @@ -463,7 +530,7 @@ if (VIX_BUILD_EXAMPLES) endif() # 2) If ORM is ON but MySQL backend is OFF → remove MySQL-based ORM examples - if (VIX_HAS_ORM AND NOT VIX_ORM_USE_MYSQL) + if (VIX_HAS_ORM AND NOT VIX_DB_USE_MYSQL) foreach(_ex IN LISTS _ORM_MYSQL_EXAMPLES) list(FILTER VIX_UMBRELLA_EXAMPLES EXCLUDE REGEX ".*/${_ex}\\.cpp$") list(FILTER VIX_UMBRELLA_EXAMPLES EXCLUDE REGEX ".*\\\\${_ex}\\.cpp$") @@ -629,6 +696,11 @@ if (VIX_ENABLE_INSTALL) FILES_MATCHING PATTERN "*.hpp" PATTERN "*.h") endif() + if (VIX_HAS_DB AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/db/include") + install(DIRECTORY modules/db/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING PATTERN "*.hpp" PATTERN "*.h") + endif() + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/middleware/include") install(DIRECTORY modules/middleware/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.hpp" PATTERN "*.h") @@ -654,10 +726,10 @@ if (VIX_ENABLE_INSTALL) COMPATIBILITY SameMajorVersion ) - set(VIX_HAS_ORM ${VIX_HAS_ORM}) - set(VIX_ORM_WITH_MYSQL OFF) - if (VIX_HAS_ORM AND VIX_ORM_USE_MYSQL) - set(VIX_ORM_WITH_MYSQL ON) + set(VIX_HAS_ORM ${VIX_HAS_ORM}) + set(VIX_DB_WITH_MYSQL OFF) + if (VIX_HAS_DB AND VIX_DB_USE_MYSQL) + set(VIX_DB_WITH_MYSQL ON) endif() configure_package_config_file( @@ -696,6 +768,7 @@ message(STATUS "Project version : ${PROJECT_VERSION}") message(STATUS "JSON backend : ${_VIX_JSON_BACKEND}") message(STATUS "WebSocket built : ${VIX_HAS_WEBSOCKET}") message(STATUS "ORM packaged : ${VIX_HAS_ORM}") +message(STATUS "DB built : ${VIX_HAS_DB}") message(STATUS "Middleware built : ${VIX_HAS_MIDDLEWARE}") message(STATUS "Examples : ${VIX_BUILD_EXAMPLES}") message(STATUS "Tests : ${VIX_BUILD_TESTS}") diff --git a/examples/http_crud/batch_insert_tx.cpp b/examples/http_crud/batch_insert_tx.cpp index 7cdaeae..23044c3 100644 --- a/examples/http_crud/batch_insert_tx.cpp +++ b/examples/http_crud/batch_insert_tx.cpp @@ -1,6 +1,6 @@ /** * - * @file examples/http_crud/batch_insert_tx.cpp + * @file batch_insert_tx.hpp * @author Gaspard Kirira * * Copyright 2025, Gaspard Kirira. All rights reserved. @@ -9,28 +9,26 @@ * that can be found in the License file. * * Vix.cpp - * */ - #include -#include -#include +#include #include -#include #include +#include using namespace vix::orm; int main(int argc, char **argv) { - std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); - std::string user = (argc > 2 ? argv[2] : "root"); - std::string pass = (argc > 3 ? argv[3] : ""); - std::string db = (argc > 4 ? argv[4] : "vixdb"); + const std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); + const std::string user = (argc > 2 ? argv[2] : "root"); + const std::string pass = (argc > 3 ? argv[3] : ""); + const std::string db = (argc > 4 ? argv[4] : "vixdb"); try { + // DB factory (MySQL driver) auto factory = make_mysql_factory(host, user, pass, db); PoolConfig cfg; @@ -40,6 +38,7 @@ int main(int argc, char **argv) ConnectionPool pool{factory, cfg}; pool.warmup(); + // Transaction (RAII rollback if not committed) Transaction tx(pool); auto &c = tx.conn(); @@ -52,7 +51,7 @@ int main(int argc, char **argv) int age; }; - std::vector rows = { + const std::vector rows = { {"Zoe", "zoe@example.com", 23}, {"Mina", "mina@example.com", 31}, {"Omar", "omar@example.com", 35}, @@ -71,6 +70,11 @@ int main(int argc, char **argv) std::cout << "[OK] inserted rows = " << total << "\n"; return 0; } + catch (const DBError &e) + { + std::cerr << "[DBError] " << e.what() << "\n"; + return 1; + } catch (const std::exception &e) { std::cerr << "[ERR] " << e.what() << "\n"; diff --git a/examples/http_crud/error_handling.cpp b/examples/http_crud/error_handling.cpp index 0b320d1..5155f87 100644 --- a/examples/http_crud/error_handling.cpp +++ b/examples/http_crud/error_handling.cpp @@ -1,6 +1,6 @@ /** * - * @file examples/http_crud/error_handling.cpp + * @file error_handling.hpp * @author Gaspard Kirira * * Copyright 2025, Gaspard Kirira. All rights reserved. @@ -9,10 +9,11 @@ * that can be found in the License file. * * Vix.cpp - * */ #include + #include +#include using namespace vix::orm; @@ -20,20 +21,44 @@ int main(int argc, char **argv) { (void)argc; (void)argv; + try { - // Intentionally wrong DB name to show error - auto raw = make_mysql_factory("tcp://127.0.0.1:3306", "root", "", "db_does_not_exist"); - //..... + // Intentionally wrong DB name to show error handling + const std::string host = "tcp://127.0.0.1:3306"; + const std::string user = "root"; + const std::string pass = ""; + const std::string db = "db_does_not_exist"; + + auto factory = make_mysql_factory(host, user, pass, db); + + PoolConfig cfg; + cfg.min = 1; + cfg.max = 8; + + ConnectionPool pool{factory, cfg}; + + // will throw if factory returns invalid connection (recommended after our warmup fix), + // or later when first query fails. + pool.warmup(); + + UnitOfWork uow{pool}; + auto &con = uow.conn(); + + auto st = con.prepare("SELECT 1"); + (void)st->exec(); + std::cout << "[INFO] This message may not be reached if connection fails.\n"; + return 0; } catch (const DBError &e) { std::cerr << "[DBError] " << e.what() << "\n"; + return 1; } catch (const std::exception &e) { std::cerr << "[std::exception] " << e.what() << "\n"; + return 1; } - return 0; } diff --git a/examples/http_crud/querybuilder_update.cpp b/examples/http_crud/querybuilder_update.cpp index 604ee61..e942105 100644 --- a/examples/http_crud/querybuilder_update.cpp +++ b/examples/http_crud/querybuilder_update.cpp @@ -1,6 +1,6 @@ /** * - * @file examples/http_crud/querybuilder_update.cpp + * @file querybuilder_update.hpp * @author Gaspard Kirira * * Copyright 2025, Gaspard Kirira. All rights reserved. @@ -9,12 +9,10 @@ * that can be found in the License file. * * Vix.cpp - * */ #include -#include -#include +#include #include #include @@ -22,10 +20,10 @@ using namespace vix::orm; int main(int argc, char **argv) { - std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); - std::string user = (argc > 2 ? argv[2] : "root"); - std::string pass = (argc > 3 ? argv[3] : ""); - std::string db = (argc > 4 ? argv[4] : "vixdb"); + const std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); + const std::string user = (argc > 2 ? argv[2] : "root"); + const std::string pass = (argc > 3 ? argv[3] : ""); + const std::string db = (argc > 4 ? argv[4] : "vixdb"); try { @@ -48,12 +46,17 @@ int main(int argc, char **argv) const auto &ps = qb.params(); for (std::size_t i = 0; i < ps.size(); ++i) - st->bind(i + 1, ps[i]); + st->bind(i + 1, any_to_dbvalue_or_throw(ps[i])); - auto affected = st->exec(); + const auto affected = st->exec(); std::cout << "[OK] affected rows = " << affected << "\n"; return 0; } + catch (const DBError &e) + { + std::cerr << "[DBError] " << e.what() << "\n"; + return 1; + } catch (const std::exception &e) { std::cerr << "[ERR] " << e.what() << "\n"; diff --git a/examples/http_crud/repository_crud_full.cpp b/examples/http_crud/repository_crud_full.cpp index 8db701c..3072700 100644 --- a/examples/http_crud/repository_crud_full.cpp +++ b/examples/http_crud/repository_crud_full.cpp @@ -1,6 +1,6 @@ /** * - * @file examples/http_crud/repository_crud_full.cpp + * @file repository_crud_full.hpp * @author Gaspard Kirira * * Copyright 2025, Gaspard Kirira. All rights reserved. @@ -9,15 +9,16 @@ * that can be found in the License file. * * Vix.cpp - * */ + #include -#include -#include -#include +#include #include +#include #include +#include +#include struct User { @@ -32,7 +33,15 @@ namespace vix::orm template <> struct Mapper { - static User fromRow(const ResultRow &) { return {}; } // pending + static User fromRow(const ResultRow &row) + { + User u{}; + u.id = row.getInt64Or(0, 0); + u.name = row.getStringOr(1, ""); + u.email = row.getStringOr(2, ""); + u.age = static_cast(row.getInt64Or(3, 0)); + return u; + } static std::vector> toInsertParams(const User &u) @@ -60,10 +69,10 @@ int main(int argc, char **argv) { using namespace vix::orm; - std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); - std::string user = (argc > 2 ? argv[2] : "root"); - std::string pass = (argc > 3 ? argv[3] : ""); - std::string db = (argc > 4 ? argv[4] : "vixdb"); + const std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); + const std::string user = (argc > 2 ? argv[2] : "root"); + const std::string pass = (argc > 3 ? argv[3] : ""); + const std::string db = (argc > 4 ? argv[4] : "vixdb"); try { @@ -79,20 +88,31 @@ int main(int argc, char **argv) BaseRepository repo{pool, "users"}; // Create - std::int64_t id = static_cast( + const std::int64_t id = static_cast( repo.create(User{0, "Bob", "gaspardkirira@example.com", 30})); - std::cout << "[OK] create → id=" << id << "\n"; + std::cout << "[OK] create -> id=" << id << "\n"; // Update - repo.updateById(id, User{id, "Adastra", "adastra@example.com", 31}); - std::cout << "[OK] update → id=" << id << "\n"; + (void)repo.updateById(id, User{id, "Adastra", "adastra@example.com", 31}); + std::cout << "[OK] update -> id=" << id << "\n"; + + // (Optional) Read back + if (auto u = repo.findById(id)) + { + std::cout << "[OK] findById -> name=" << u->name << " email=" << u->email << " age=" << u->age << "\n"; + } // Delete - repo.removeById(id); - std::cout << "[OK] delete → id=" << id << "\n"; + (void)repo.removeById(id); + std::cout << "[OK] delete -> id=" << id << "\n"; return 0; } + catch (const DBError &e) + { + std::cerr << "[DBError] " << e.what() << "\n"; + return 1; + } catch (const std::exception &e) { std::cerr << "[ERR] " << e.what() << "\n"; diff --git a/examples/http_crud/tx_unit_of_work.cpp b/examples/http_crud/tx_unit_of_work.cpp index 9196bcb..b413873 100644 --- a/examples/http_crud/tx_unit_of_work.cpp +++ b/examples/http_crud/tx_unit_of_work.cpp @@ -1,6 +1,6 @@ /** * - * @file examples/http_crud/tx_unit_of_work.cpp + * @file tx_unit_of_work.hpp * @author Gaspard Kirira * * Copyright 2025, Gaspard Kirira. All rights reserved. @@ -9,13 +9,11 @@ * that can be found in the License file. * * Vix.cpp - * */ #include -#include -#include +#include #include #include @@ -23,10 +21,10 @@ using namespace vix::orm; int main(int argc, char **argv) { - std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); - std::string user = (argc > 2 ? argv[2] : "root"); - std::string pass = (argc > 3 ? argv[3] : ""); - std::string db = (argc > 4 ? argv[4] : "vixdb"); + const std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); + const std::string user = (argc > 2 ? argv[2] : "root"); + const std::string pass = (argc > 3 ? argv[3] : ""); + const std::string db = (argc > 4 ? argv[4] : "vixdb"); try { @@ -44,17 +42,17 @@ int main(int argc, char **argv) { auto st = c.prepare("INSERT INTO users(name,email,age) VALUES(?,?,?)"); - st->bind(1, std::string("Alice")); - st->bind(2, std::string("alice@example.com")); + st->bind(1, "Alice"); + st->bind(2, "alice@example.com"); st->bind(3, 27); st->exec(); } - const auto userId = c.lastInsertId(); + const std::uint64_t userId = c.lastInsertId(); { auto st = c.prepare("INSERT INTO orders(user_id,total) VALUES(?,?)"); - st->bind(1, static_cast(userId)); + st->bind(1, userId); st->bind(2, 199.99); st->exec(); } diff --git a/examples/http_crud/users_crud.cpp b/examples/http_crud/users_crud.cpp index 964b560..09f33ca 100644 --- a/examples/http_crud/users_crud.cpp +++ b/examples/http_crud/users_crud.cpp @@ -1,6 +1,6 @@ /** * - * @file examples/http_crud/users_crud.cpp + * @file users_crud.hpp * @author Gaspard Kirira * * Copyright 2025, Gaspard Kirira. All rights reserved. @@ -9,22 +9,22 @@ * that can be found in the License file. * * Vix.cpp - * */ + #include -#include -#include + #include +#include #include using namespace vix::orm; int main(int argc, char **argv) { - std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); - std::string user = (argc > 2 ? argv[2] : "root"); - std::string pass = (argc > 3 ? argv[3] : ""); - std::string db = (argc > 4 ? argv[4] : "vixdb"); + const std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); + const std::string user = (argc > 2 ? argv[2] : "root"); + const std::string pass = (argc > 3 ? argv[3] : ""); + const std::string db = (argc > 4 ? argv[4] : "vixdb"); try { @@ -48,8 +48,9 @@ int main(int argc, char **argv) const char *email; int age; }; - std::vector rows = { - {"Zoe", "zoe@example.com", 23}, + + const std::vector rows = { + {"Gaspard", "gaspardkirira@outlook.com", 23}, {"Mina", "mina@example.com", 31}, {"Omar", "omar@example.com", 35}, }; diff --git a/modules/cli b/modules/cli index 88b72fe..d655ebf 160000 --- a/modules/cli +++ b/modules/cli @@ -1 +1 @@ -Subproject commit 88b72fe6d1c50ea9e6ab36856c8d02c8b403cc88 +Subproject commit d655ebf3f56eb206c2460b77d2e4b0268466fa67 diff --git a/modules/db b/modules/db new file mode 160000 index 0000000..1962378 --- /dev/null +++ b/modules/db @@ -0,0 +1 @@ +Subproject commit 1962378ffb62deaadaf8b3e4d42b4f544772a80d diff --git a/modules/orm b/modules/orm index e63da9b..cc90727 160000 --- a/modules/orm +++ b/modules/orm @@ -1 +1 @@ -Subproject commit e63da9b21138a87620dd80c0074b066717083c4b +Subproject commit cc90727785840e2d03378bcca923a439fe812612