Database System 0.1.0
Advanced C++20 Database System with Multi-Backend Support
Loading...
Searching...
No Matches
redis_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 "redis_backend.h"
6#include "../core/result.h"
7
8#ifdef USE_REDIS
9#include <hiredis/hiredis.h>
10#endif
11
12#include <sstream>
13#include <variant>
14#include <iostream>
15#include <vector>
16
18
19namespace
20{
21const database::utils::backend_logger logger_("Redis");
22}
23
24namespace database
25{
26namespace backends
27{
28
30 : context_(nullptr), host_("localhost"), port_(6379)
31{
32}
33
34kcenon::common::VoidResult redis_backend::do_initialize(const core::connection_config& config)
35{
36 connection_config_ = config;
37 host_ = config.host.empty() ? "localhost" : config.host;
38 port_ = config.port > 0 ? config.port : 6379;
39
40#ifdef USE_REDIS
41 std::lock_guard<std::mutex> lock(redis_mutex_);
42 try {
43 // Create Redis connection
44 redisContext* ctx = redisConnect(host_.c_str(), port_);
45 if (ctx == nullptr || ctx->err) {
46 if (ctx) {
47 last_error_ = std::string("Connection error: ") + ctx->errstr;
48 logger_.error("do_initialize", last_error_);
49 redisFree(ctx);
50 } else {
51 last_error_ = "Connection allocation error";
52 logger_.error("do_initialize", last_error_);
53 }
54 return kcenon::common::error_info{
57 "redis_backend"
58 };
59 }
60
61 context_ = ctx;
62
63 // Authenticate if password provided
64 if (!config.password.empty()) {
65 redisReply* reply = static_cast<redisReply*>(
66 redisCommand(ctx, "AUTH %s", config.password.c_str()));
67 if (reply == nullptr || reply->type == REDIS_REPLY_ERROR) {
68 last_error_ = "Authentication failed";
69 logger_.error("do_initialize", last_error_);
70 if (reply) freeReplyObject(reply);
71 redisFree(ctx);
72 context_ = nullptr;
73 return kcenon::common::error_info{
76 "redis_backend"
77 };
78 }
79 freeReplyObject(reply);
80 }
81
82 // Test connection with PING
83 redisReply* ping_reply = static_cast<redisReply*>(redisCommand(ctx, "PING"));
84 if (ping_reply == nullptr || ping_reply->type == REDIS_REPLY_ERROR) {
85 last_error_ = "PING failed";
86 logger_.error("do_initialize", last_error_);
87 if (ping_reply) freeReplyObject(ping_reply);
88 redisFree(ctx);
89 context_ = nullptr;
90 return kcenon::common::error_info{
93 "redis_backend"
94 };
95 }
96 freeReplyObject(ping_reply);
97
98 last_error_.clear();
99 return kcenon::common::ok();
100 } catch (const std::exception& e) {
101 last_error_ = std::string("Connection error: ") + e.what();
102 logger_.error("do_initialize", last_error_);
103 }
104#else
105 logger_.warning("Redis support not compiled. Mock mode enabled.");
106 // Mock mode for testing without Redis
107 last_error_.clear();
108 return kcenon::common::ok();
109#endif
110
111 if (last_error_.empty()) {
112 last_error_ = "Failed to connect to Redis server";
113 }
114 return kcenon::common::error_info{
117 "redis_backend"
118 };
119}
120
121kcenon::common::VoidResult redis_backend::do_shutdown()
122{
123 // Discard any active transaction before disconnecting
124 if (in_transaction_) {
126 }
127
128#ifdef USE_REDIS
129 std::lock_guard<std::mutex> lock(redis_mutex_);
130 if (context_) {
131 redisFree(static_cast<redisContext*>(context_));
132 context_ = nullptr;
133 }
134#endif
135
136 last_error_.clear();
137 return kcenon::common::ok();
138}
139
140bool redis_backend::parse_redis_query(const std::string& query_string,
141 std::string& key,
142 std::string& value) const
143{
144 // Parse format: "key:value"
145 std::istringstream ss(query_string);
146 std::string part;
147 std::vector<std::string> parts;
148
149 while (std::getline(ss, part, ':')) {
150 parts.push_back(part);
151 }
152
153 if (parts.size() >= 1) key = parts[0];
154 if (parts.size() >= 2) value = parts[1];
155
156 return !key.empty();
157}
158
159kcenon::common::Result<core::database_result> redis_backend::select_query(const std::string& query_string)
160{
161 if (!is_initialized()) {
162 last_error_ = "Backend not initialized";
163 return kcenon::common::error_info{
164 static_cast<int>(database::error_code::invalid_state),
166 "redis_backend"
167 };
168 }
169
171
172#ifdef USE_REDIS
173 if (!context_) {
174 last_error_ = "No active connection";
175 return kcenon::common::error_info{
178 "redis_backend"
179 };
180 }
181 std::lock_guard<std::mutex> lock(redis_mutex_);
182 try {
183 // For select, query_string is the key
184 std::string key = query_string;
185
186 redisContext* ctx = static_cast<redisContext*>(context_);
187 redisReply* reply = static_cast<redisReply*>(
188 redisCommand(ctx, "GET %s", key.c_str()));
189
190 if (reply != nullptr && reply->type != REDIS_REPLY_ERROR &&
191 reply->type != REDIS_REPLY_NIL) {
193 row["key"] = key;
194
195 if (reply->type == REDIS_REPLY_STRING) {
196 row["value"] = std::string(reply->str, reply->len);
197 } else if (reply->type == REDIS_REPLY_INTEGER) {
198 row["value"] = static_cast<int64_t>(reply->integer);
199 } else {
200 row["value"] = std::string("");
201 }
202
203 result.push_back(std::move(row));
204 }
205
206 if (reply) freeReplyObject(reply);
207 } catch (const std::exception& e) {
208 last_error_ = std::string("Select error: ") + e.what();
209 logger_.error("select_query", last_error_);
210 return kcenon::common::error_info{
211 static_cast<int>(database::error_code::query_failed),
213 "redis_backend"
214 };
215 }
216#else
217 logger_.warning("Redis support not compiled. Select query: " + query_string.substr(0, 20) + "...");
218 // Return mock data for testing
219 if (!query_string.empty()) {
220 core::database_row mock_row;
221 mock_row["key"] = query_string;
222 mock_row["value"] = std::string("redis_mock_value");
223 result.push_back(mock_row);
224 }
225#endif
226
227 last_error_.clear();
228 return result;
229}
230
231kcenon::common::VoidResult redis_backend::execute_query(const std::string& query_string)
232{
233 if (!is_initialized()) {
234 last_error_ = "Backend not initialized";
235 return kcenon::common::error_info{
236 static_cast<int>(database::error_code::invalid_state),
238 "redis_backend"
239 };
240 }
241
242#ifdef USE_REDIS
243 if (!context_) {
244 last_error_ = "No active Redis connection";
245 logger_.error("execute_query", last_error_);
246 return kcenon::common::error_info{
249 "redis_backend"
250 };
251 }
252
253 std::lock_guard<std::mutex> lock(redis_mutex_);
254 try {
255 redisContext* ctx = static_cast<redisContext*>(context_);
256 redisReply* reply = static_cast<redisReply*>(
257 redisCommand(ctx, "%s", query_string.c_str()));
258
259 if (reply == nullptr) {
260 last_error_ = std::string("Command failed: ") + ctx->errstr;
261 logger_.error("execute_query", last_error_);
262 return kcenon::common::error_info{
263 static_cast<int>(database::error_code::query_failed),
265 "redis_backend"
266 };
267 }
268
269 bool success = true;
270 if (reply->type == REDIS_REPLY_ERROR) {
271 last_error_ = std::string("Execute error: ") + reply->str;
272 logger_.error("execute_query", last_error_);
273 success = false;
274 }
275
276 freeReplyObject(reply);
277
278 if (!success) {
279 return kcenon::common::error_info{
280 static_cast<int>(database::error_code::query_failed),
282 "redis_backend"
283 };
284 }
285
286 last_error_.clear();
287 return kcenon::common::ok();
288 } catch (const std::exception& e) {
289 last_error_ = std::string("Execute error: ") + e.what();
290 logger_.error("execute_query", last_error_);
291 return kcenon::common::error_info{
292 static_cast<int>(database::error_code::query_failed),
294 "redis_backend"
295 };
296 }
297#else
298 // Mock execution
299 logger_.info("Redis support not compiled. Mock execute: " + query_string);
300 last_error_.clear();
301 return kcenon::common::ok();
302#endif
303}
304
305kcenon::common::VoidResult redis_backend::begin_transaction()
306{
307 if (!is_initialized()) {
308 last_error_ = "Backend not initialized";
309 return kcenon::common::error_info{
310 static_cast<int>(database::error_code::invalid_state),
312 "redis_backend"
313 };
314 }
315
316 if (in_transaction_) {
317 last_error_ = "Transaction already active";
318 return kcenon::common::error_info{
319 static_cast<int>(database::error_code::invalid_state),
321 "redis_backend"
322 };
323 }
324
325 // Redis uses MULTI to begin a transaction
326 auto result = execute_query("MULTI");
327 if (result.is_err()) {
328 return result;
329 }
330
331 in_transaction_ = true;
332 last_error_.clear();
333 return kcenon::common::ok();
334}
335
336kcenon::common::VoidResult redis_backend::commit_transaction()
337{
338 if (!is_initialized()) {
339 last_error_ = "Backend not initialized";
340 return kcenon::common::error_info{
341 static_cast<int>(database::error_code::invalid_state),
343 "redis_backend"
344 };
345 }
346
347 if (!in_transaction_) {
348 last_error_ = "No active transaction";
349 return kcenon::common::error_info{
350 static_cast<int>(database::error_code::invalid_state),
352 "redis_backend"
353 };
354 }
355
356 // Redis uses EXEC to commit a transaction
357 auto result = execute_query("EXEC");
358 if (result.is_err()) {
359 return result;
360 }
361
362 in_transaction_ = false;
363 last_error_.clear();
364 return kcenon::common::ok();
365}
366
367kcenon::common::VoidResult redis_backend::rollback_transaction()
368{
369 if (!is_initialized()) {
370 last_error_ = "Backend not initialized";
371 return kcenon::common::error_info{
372 static_cast<int>(database::error_code::invalid_state),
374 "redis_backend"
375 };
376 }
377
378 if (!in_transaction_) {
379 // Not an error - already discarded or never started
380 return kcenon::common::ok();
381 }
382
383 // Redis uses DISCARD to rollback a transaction
384 auto result = execute_query("DISCARD");
385 in_transaction_ = false; // Force state reset even on error
386
387 if (result.is_err()) {
388 return result;
389 }
390
391 last_error_.clear();
392 return kcenon::common::ok();
393}
394
396{
397 return in_transaction_;
398}
399
400std::string redis_backend::last_error() const
401{
402 return last_error_;
403}
404
405std::map<std::string, std::string> redis_backend::connection_info() const
406{
407 std::map<std::string, std::string> info;
408 info["backend"] = "redis";
409 info["host"] = host_;
410 info["port"] = std::to_string(port_);
411 info["initialized"] = initialized_ ? "true" : "false";
412 info["in_transaction"] = in_transaction_ ? "true" : "false";
413 return info;
414}
415
416} // namespace backends
417} // namespace database
418
419// Auto-registration with backend_registry when Redis support is compiled in
420#ifdef USE_REDIS
421namespace {
423}
424#endif
Centralized logging utility for database backends.
std::string last_error() const override
Get last error message from backend.
kcenon::common::VoidResult rollback_transaction() override
Rollback 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.
kcenon::common::VoidResult commit_transaction() override
Commit the current transaction.
void * context_
Redis context (redisContext*)
kcenon::common::VoidResult begin_transaction() override
Begin a transaction.
std::atomic< bool > in_transaction_
Transaction state (MULTI/EXEC)
redis_backend()
Default constructor.
kcenon::common::Result< core::database_result > select_query(const std::string &query_string) override
Execute a SELECT query.
bool parse_redis_query(const std::string &query_string, std::string &key, std::string &value) const
Parse Redis query string format.
std::mutex redis_mutex_
Mutex for thread safety.
std::string last_error_
Last error message.
bool in_transaction() const override
Check if backend is currently in a transaction.
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.
core::connection_config connection_config_
Cached connection config.
Helper class for automatic backend registration.
Centralized logging utility for database backends.
std::vector< database_row > database_result
std::map< std::string, database_value > database_row
Redis database backend plugin implementation.
Result<T> type for database_system error handling.
Configuration for database connection.