Bladeren bron

feat(db): DbGateway with project namespacing (qualify/listProjects/ensureProject/dropProject)

Fszontagh 2 maanden geleden
bovenliggende
commit
ad4e7a1721
6 gewijzigde bestanden met toevoegingen van 145 en 0 verwijderingen
  1. 1 0
      src/CMakeLists.txt
  2. 39 0
      src/db_gateway.cpp
  3. 44 0
      src/db_gateway.hpp
  4. 4 0
      tests/CMakeLists.txt
  5. 24 0
      tests/db_test_util.hpp
  6. 33 0
      tests/test_db_gateway.cpp

+ 1 - 0
src/CMakeLists.txt

@@ -3,6 +3,7 @@ add_library(vectorapi_core STATIC
     errors.cpp
     json_http.cpp
     filters.cpp
+    db_gateway.cpp
 )
 target_include_directories(vectorapi_core PUBLIC
     ${CMAKE_CURRENT_SOURCE_DIR}

+ 39 - 0
src/db_gateway.cpp

@@ -0,0 +1,39 @@
+#include "db_gateway.hpp"
+
+namespace svapi {
+
+std::string qualify(const std::string& project, const std::string& collection) {
+    if (project.empty() || project == "default") return collection;
+    return project + ":" + collection;
+}
+
+namespace {
+smartbotic::database::Client::Config makeConfig(std::string address, uint32_t timeoutMs) {
+    smartbotic::database::Client::Config cfg;
+    cfg.address   = std::move(address);
+    cfg.timeoutMs = timeoutMs;
+    return cfg;   // leave cfg.project = "default"; we qualify per-call
+}
+} // namespace
+
+DbGateway::DbGateway(std::string address, uint32_t timeoutMs)
+    : client_(makeConfig(std::move(address), timeoutMs)) {}
+
+bool DbGateway::connect()  { return client_.connect(); }
+bool DbGateway::healthy()  { return client_.healthCheck(); }
+smartbotic::database::Client& DbGateway::client() { return client_; }
+
+std::vector<std::string> DbGateway::listProjects() { return client_.listProjects(); }
+
+bool DbGateway::ensureProject(const std::string& name) {
+    try { client_.createProject(name); return true; }   // idempotent server-side
+    catch (const std::exception&) { return false; }
+}
+
+bool DbGateway::dropProject(const std::string& name) {
+    if (name == "default") return false;
+    try { client_.dropProject(name); return true; }
+    catch (const std::exception&) { return false; }
+}
+
+} // namespace svapi

+ 44 - 0
src/db_gateway.hpp

@@ -0,0 +1,44 @@
+#pragma once
+#include <smartbotic/database/client.hpp>
+#include <string>
+#include <vector>
+
+namespace svapi {
+
+/// Returns `project + ":" + collection` for named projects; returns bare
+/// `collection` when project is "" or "default" (back-compat default project).
+std::string qualify(const std::string& project, const std::string& collection);
+
+/// Thin wrapper over `smartbotic::database::Client` that adds project-level
+/// helpers (listProjects / ensureProject / dropProject) and a uniform
+/// connect/healthy lifecycle.
+class DbGateway {
+public:
+    /// @param address  host:port of the smartbotic-database gRPC endpoint.
+    /// @param timeoutMs  per-call timeout in milliseconds (default 5 s).
+    explicit DbGateway(std::string address, uint32_t timeoutMs = 5000);
+
+    /// Establish the gRPC connection. Returns true on success.
+    bool connect();
+
+    /// Returns true if the server responds to a health-check ping.
+    bool healthy();
+
+    /// Direct access to the underlying client for collection/document ops.
+    smartbotic::database::Client& client();
+
+    /// List all projects on the server (always includes "default").
+    std::vector<std::string> listProjects();
+
+    /// Idempotent createProject. Returns true on success (new or already existed).
+    bool ensureProject(const std::string& name);
+
+    /// Drop a project. Returns false (without throwing) when name == "default".
+    /// Returns false on any transport or server-side failure.
+    bool dropProject(const std::string& name);
+
+private:
+    smartbotic::database::Client client_;
+};
+
+} // namespace svapi

+ 4 - 0
tests/CMakeLists.txt

@@ -16,3 +16,7 @@ gtest_discover_tests(test_errors)
 add_executable(test_filters test_filters.cpp)
 target_link_libraries(test_filters PRIVATE vectorapi_core GTest::gtest_main)
 gtest_discover_tests(test_filters)
+
+add_executable(test_db_gateway test_db_gateway.cpp)
+target_link_libraries(test_db_gateway PRIVATE vectorapi_core GTest::gtest_main)
+gtest_discover_tests(test_db_gateway)

+ 24 - 0
tests/db_test_util.hpp

@@ -0,0 +1,24 @@
+#pragma once
+#include "db_gateway.hpp"
+#include <gtest/gtest.h>
+#include <cstdlib>
+#include <memory>
+#include <string>
+
+namespace svapi::testutil {
+inline std::string testDbAddress() {
+    const char* env = std::getenv("SVAPI_TEST_DB");
+    return env ? env : "localhost:9004";
+}
+inline std::unique_ptr<DbGateway> connectOrNull() {
+    auto g = std::make_unique<DbGateway>(testDbAddress());
+    if (!g->connect() || !g->healthy()) return nullptr;
+    return g;
+}
+inline std::string tmpName(const std::string& base) { return "svapitest_" + base; }  // project/coll test name (no leading _, valid project chars)
+} // namespace svapi::testutil
+
+#define SVAPI_REQUIRE_DB(var)                                            \
+    auto var = ::svapi::testutil::connectOrNull();                       \
+    if (!var) GTEST_SKIP() << "smartbotic-database not reachable at "     \
+                           << ::svapi::testutil::testDbAddress()

+ 33 - 0
tests/test_db_gateway.cpp

@@ -0,0 +1,33 @@
+#include "db_test_util.hpp"
+#include <algorithm>
+using namespace svapi;
+
+TEST(Qualify, Rules) {
+    EXPECT_EQ(qualify("", "docs"), "docs");
+    EXPECT_EQ(qualify("default", "docs"), "docs");
+    EXPECT_EQ(qualify("acme", "docs"), "acme:docs");
+}
+
+TEST(DbGateway, HealthyAndProjects) {
+    SVAPI_REQUIRE_DB(db);
+    EXPECT_TRUE(db->healthy());
+    auto before = db->listProjects();
+    EXPECT_NE(std::find(before.begin(), before.end(), "default"), before.end());
+
+    const std::string proj = testutil::tmpName("proj_gw");
+    EXPECT_TRUE(db->ensureProject(proj));
+    auto after = db->listProjects();
+    EXPECT_NE(std::find(after.begin(), after.end(), proj), after.end());
+
+    // a doc lands in the project namespace
+    auto id = db->client().insert(qualify(proj, "things"), nlohmann::json{{"a", 1}});
+    ASSERT_FALSE(id.empty());
+    EXPECT_TRUE(db->client().get(qualify(proj, "things"), id).has_value());
+
+    db->dropProject(proj);
+}
+
+TEST(DbGateway, CannotDropDefault) {
+    SVAPI_REQUIRE_DB(db);
+    EXPECT_FALSE(db->dropProject("default"));
+}