Database System 0.1.0
Advanced C++20 Database System with Multi-Backend Support
Loading...
Searching...
No Matches
mongodb_backend.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
5#include "mongodb_backend.h"
6#include "../core/result.h"
7
8#ifdef USE_MONGODB
9#include <mongocxx/client.hpp>
10#include <mongocxx/database.hpp>
11#include <mongocxx/collection.hpp>
12#include <mongocxx/instance.hpp>
13#include <mongocxx/uri.hpp>
14#include <bsoncxx/json.hpp>
15#include <bsoncxx/builder/stream/document.hpp>
16#include <bsoncxx/document/view.hpp>
17#endif
18
19#include <sstream>
20#include <variant>
21#include <iostream>
22#include <vector>
23
25
26namespace
27{
28const database::utils::backend_logger logger_("MongoDB");
29}
30
31namespace database
32{
33namespace backends
34{
35
37 : client_(nullptr), database_(nullptr)
38{
39}
40
41kcenon::common::VoidResult mongodb_backend::do_initialize(const core::connection_config& config)
42{
43 connection_config_ = config;
44 std::string conn_uri = build_connection_uri(config);
45 db_name_ = config.database;
46
47#ifdef USE_MONGODB
48 std::lock_guard<std::mutex> lock(mongo_mutex_);
49 try {
50 // Initialize MongoDB instance (should only be done once per application)
51 static mongocxx::instance instance{};
52
53 // Create MongoDB URI
54 mongocxx::uri uri{conn_uri};
55
56 // Create MongoDB client
57 auto client = std::make_unique<mongocxx::client>(uri);
58 client_ = client.release();
59
60 // Get database reference
61 auto* mongo_client = static_cast<mongocxx::client*>(client_);
62 auto db = std::make_unique<mongocxx::database>((*mongo_client)[db_name_]);
63 database_ = db.release();
64
65 // Test connection by running a simple command
66 auto* mongo_db = static_cast<mongocxx::database*>(database_);
67 auto result = mongo_db->run_command(bsoncxx::builder::stream::document{} << "ping" << 1 << bsoncxx::builder::stream::finalize);
68
69 last_error_.clear();
70 return kcenon::common::ok();
71 } catch (const std::exception& e) {
72 last_error_ = std::string("Connection error: ") + e.what();
73 logger_.error("do_initialize", last_error_);
74 }
75#else
76 logger_.warning("MongoDB support not compiled. Mock mode enabled.");
77 // Mock mode for testing without MongoDB
78 last_error_.clear();
79 return kcenon::common::ok();
80#endif
81
82 if (last_error_.empty()) {
83 last_error_ = "Failed to connect to MongoDB server";
84 }
85 return kcenon::common::error_info{
88 "mongodb_backend"
89 };
90}
91
92kcenon::common::VoidResult mongodb_backend::do_shutdown()
93{
94 // Rollback any active transaction before disconnecting
95 if (in_transaction_) {
97 }
98
99#ifdef USE_MONGODB
100 std::lock_guard<std::mutex> lock(mongo_mutex_);
101 if (database_) {
102 delete static_cast<mongocxx::database*>(database_);
103 database_ = nullptr;
104 }
105 if (client_) {
106 delete static_cast<mongocxx::client*>(client_);
107 client_ = nullptr;
108 }
109#endif
110
111 last_error_.clear();
112 return kcenon::common::ok();
113}
114
115bool mongodb_backend::parse_query_string(const std::string& query_string,
116 std::string& collection,
117 std::string& filter,
118 std::string& update) const
119{
120 // Parse format: "collection_name:filter_json" or "collection_name:filter_json:update_json"
121 std::istringstream ss(query_string);
122 std::string part;
123 std::vector<std::string> parts;
124
125 while (std::getline(ss, part, ':')) {
126 parts.push_back(part);
127 }
128
129 if (parts.size() >= 1) collection = parts[0];
130 if (parts.size() >= 2) filter = parts[1];
131 if (parts.size() >= 3) update = parts[2];
132
133 return !collection.empty();
134}
135
136kcenon::common::Result<core::database_result> mongodb_backend::select_query(const std::string& query_string)
137{
138 if (!is_initialized()) {
139 last_error_ = "Backend not initialized";
140 return kcenon::common::error_info{
141 static_cast<int>(database::error_code::invalid_state),
143 "mongodb_backend"
144 };
145 }
146
148
149#ifdef USE_MONGODB
150 if (!database_) {
151 last_error_ = "No active connection";
152 return kcenon::common::error_info{
155 "mongodb_backend"
156 };
157 }
158 std::lock_guard<std::mutex> lock(mongo_mutex_);
159 try {
160 std::string collection_name, filter_json, unused;
161 if (!parse_query_string(query_string, collection_name, filter_json, unused)) {
162 last_error_ = "Invalid query format";
163 return kcenon::common::error_info{
164 static_cast<int>(database::error_code::query_failed),
166 "mongodb_backend"
167 };
168 }
169
170 auto* mongo_db = static_cast<mongocxx::database*>(database_);
171 auto collection = (*mongo_db)[collection_name];
172
173 // Parse JSON to BSON for filter
174 bsoncxx::document::value filter_doc = filter_json.empty() ?
175 bsoncxx::builder::stream::document{} << bsoncxx::builder::stream::finalize :
176 bsoncxx::from_json(filter_json);
177
178 auto cursor = collection.find(filter_doc.view());
179
180 for (auto&& doc : cursor) {
182
183 // Convert BSON document to core::database_row
184 auto json_string = bsoncxx::to_json(doc);
185 row["_document"] = json_string; // Store full document as JSON string
186
187 // Also extract common fields
188 auto view = doc.view();
189 if (view["_id"]) {
190 row["_id"] = bsoncxx::to_json(view["_id"].get_value());
191 }
192
193 // Extract other fields as strings for compatibility
194 for (auto&& element : view) {
195 std::string key = element.key().to_string();
196 if (key != "_id") { // _id already processed
197 row[key] = bsoncxx::to_json(element.get_value());
198 }
199 }
200
201 result.push_back(std::move(row));
202 }
203 } catch (const std::exception& e) {
204 last_error_ = std::string("Select error: ") + e.what();
205 logger_.error("select_query", last_error_);
206 return kcenon::common::error_info{
207 static_cast<int>(database::error_code::query_failed),
209 "mongodb_backend"
210 };
211 }
212#else
213 logger_.warning("MongoDB support not compiled. Select query: " + query_string.substr(0, 20) + "...");
214 // Return mock data for testing
215 core::database_row mock_row;
216 mock_row["_id"] = std::string("mock_object_id");
217 mock_row["name"] = std::string("mongodb_mock_data");
218 mock_row["_document"] = std::string("{\"_id\":\"mock_object_id\",\"name\":\"mongodb_mock_data\"}");
219 result.push_back(mock_row);
220#endif
221
222 last_error_.clear();
223 return result;
224}
225
226kcenon::common::VoidResult mongodb_backend::execute_query(const std::string& query_string)
227{
228 if (!is_initialized()) {
229 last_error_ = "Backend not initialized";
230 return kcenon::common::error_info{
231 static_cast<int>(database::error_code::invalid_state),
233 "mongodb_backend"
234 };
235 }
236
237#ifdef USE_MONGODB
238 if (!database_) {
239 last_error_ = "No active MongoDB connection";
240 logger_.error("execute_query", last_error_);
241 return kcenon::common::error_info{
244 "mongodb_backend"
245 };
246 }
247
248 std::lock_guard<std::mutex> lock(mongo_mutex_);
249 try {
250 auto* mongo_db = static_cast<mongocxx::database*>(database_);
251
252 // Try to parse as MongoDB command (JSON format)
253 try {
254 auto command_doc = bsoncxx::from_json(query_string);
255 auto result = mongo_db->run_command(command_doc.view());
256 last_error_.clear();
257 return kcenon::common::ok();
258 } catch (const std::exception&) {
259 // If JSON parsing fails, treat as collection creation
260 std::string collection_name = query_string;
261 auto collection = (*mongo_db)[collection_name];
262 auto doc = bsoncxx::builder::stream::document{} << "test" << "creation" << bsoncxx::builder::stream::finalize;
263 collection.insert_one(doc.view());
264 collection.delete_one(doc.view());
265 last_error_.clear();
266 return kcenon::common::ok();
267 }
268 } catch (const std::exception& e) {
269 last_error_ = std::string("Execute error: ") + e.what();
270 logger_.error("execute_query", last_error_);
271 return kcenon::common::error_info{
272 static_cast<int>(database::error_code::query_failed),
274 "mongodb_backend"
275 };
276 }
277#else
278 // Mock execution
279 logger_.info("MongoDB support not compiled. Mock execute: " + query_string);
280 last_error_.clear();
281 return kcenon::common::ok();
282#endif
283}
284
285kcenon::common::VoidResult mongodb_backend::begin_transaction()
286{
287 if (!is_initialized()) {
288 last_error_ = "Backend not initialized";
289 return kcenon::common::error_info{
290 static_cast<int>(database::error_code::invalid_state),
292 "mongodb_backend"
293 };
294 }
295
296 if (in_transaction_) {
297 last_error_ = "Transaction already active";
298 return kcenon::common::error_info{
299 static_cast<int>(database::error_code::invalid_state),
301 "mongodb_backend"
302 };
303 }
304
305 // MongoDB transactions require replica sets or sharded clusters
306 // For simplicity, we'll mark the transaction state
307 in_transaction_ = true;
308 last_error_.clear();
309 return kcenon::common::ok();
310}
311
312kcenon::common::VoidResult mongodb_backend::commit_transaction()
313{
314 if (!is_initialized()) {
315 last_error_ = "Backend not initialized";
316 return kcenon::common::error_info{
317 static_cast<int>(database::error_code::invalid_state),
319 "mongodb_backend"
320 };
321 }
322
323 if (!in_transaction_) {
324 last_error_ = "No active transaction";
325 return kcenon::common::error_info{
326 static_cast<int>(database::error_code::invalid_state),
328 "mongodb_backend"
329 };
330 }
331
332 in_transaction_ = false;
333 last_error_.clear();
334 return kcenon::common::ok();
335}
336
337kcenon::common::VoidResult mongodb_backend::rollback_transaction()
338{
339 if (!is_initialized()) {
340 last_error_ = "Backend not initialized";
341 return kcenon::common::error_info{
342 static_cast<int>(database::error_code::invalid_state),
344 "mongodb_backend"
345 };
346 }
347
348 if (!in_transaction_) {
349 // Not an error - already rolled back or never started
350 return kcenon::common::ok();
351 }
352
353 in_transaction_ = false;
354 last_error_.clear();
355 return kcenon::common::ok();
356}
357
359{
360 return in_transaction_;
361}
362
364{
365 return last_error_;
366}
367
368std::map<std::string, std::string> mongodb_backend::connection_info() const
369{
370 std::map<std::string, std::string> info;
371 info["backend"] = "mongodb";
372 info["host"] = connection_config_.host;
373 info["port"] = std::to_string(connection_config_.port);
374 info["database"] = connection_config_.database;
375 info["username"] = connection_config_.username;
376 info["initialized"] = initialized_ ? "true" : "false";
377 info["in_transaction"] = in_transaction_ ? "true" : "false";
378 return info;
379}
380
382{
383 std::ostringstream oss;
384
385 oss << "mongodb://";
386
387 // Add credentials if provided
388 if (!config.username.empty()) {
389 oss << config.username;
390 if (!config.password.empty()) {
391 oss << ":" << config.password;
392 }
393 oss << "@";
394 }
395
396 // Add host (default to localhost if not specified)
397 if (!config.host.empty()) {
398 oss << config.host;
399 } else {
400 oss << "localhost";
401 }
402
403 // Add port (default to 27017 if not specified)
404 if (config.port > 0) {
405 oss << ":" << config.port;
406 } else {
407 oss << ":27017";
408 }
409
410 // Add database
411 if (!config.database.empty()) {
412 oss << "/" << config.database;
413 }
414
415 // Add additional options
416 if (!config.options.empty()) {
417 oss << "?";
418 bool first = true;
419 for (const auto& [key, value] : config.options) {
420 if (!first) {
421 oss << "&";
422 }
423 oss << key << "=" << value;
424 first = false;
425 }
426 }
427
428 return oss.str();
429}
430
431} // namespace backends
432} // namespace database
433
434// Auto-registration with backend_registry when MongoDB support is compiled in
435#ifdef USE_MONGODB
436namespace {
438}
439#endif
Centralized logging utility for database backends.
void * client_
MongoDB client (mongocxx::client*)
kcenon::common::VoidResult rollback_transaction() override
Rollback the current transaction.
kcenon::common::Result< core::database_result > select_query(const std::string &query_string) override
Execute a SELECT query.
bool parse_query_string(const std::string &query_string, std::string &collection, std::string &filter, std::string &update) const
Parse query string format.
std::mutex mongo_mutex_
Mutex for thread safety.
kcenon::common::VoidResult begin_transaction() override
Begin a transaction.
std::string build_connection_uri(const core::connection_config &config) const
Convert connection_config to MongoDB connection URI.
std::string last_error() const override
Get last error message from backend.
std::string last_error_
Last error message.
void * database_
MongoDB database (mongocxx::database*)
bool in_transaction() const override
Check if backend is currently in a transaction.
std::string db_name_
Database name.
std::map< std::string, std::string > connection_info() const override
Get backend-specific connection information.
kcenon::common::VoidResult do_initialize(const core::connection_config &config)
Database-specific initialization logic.
std::atomic< bool > in_transaction_
Transaction state.
core::connection_config connection_config_
Cached connection config.
kcenon::common::VoidResult commit_transaction() override
Commit the current transaction.
kcenon::common::VoidResult execute_query(const std::string &query_string) override
Execute a general SQL query (DDL, DML)
kcenon::common::VoidResult do_shutdown()
Database-specific shutdown logic.
Helper class for automatic backend registration.
Centralized logging utility for database backends.
MongoDB database backend plugin implementation.
std::vector< database_row > database_result
std::map< std::string, database_value > database_row
Result<T> type for database_system error handling.
Configuration for database connection.
std::map< std::string, std::string > options