Database System 0.1.0
Advanced C++20 Database System with Multi-Backend Support
Loading...
Searching...
No Matches
postgres_manager.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 "postgres_manager.h"
6
7#ifdef USE_POSTGRESQL
8#include <pqxx/pqxx>
9#elif defined(HAVE_LIBPQ)
10#include "libpq-fe.h"
11#endif
12
13#include <iostream>
14#include <sstream>
15#include <stdexcept>
16
17// Logging helper macros - using std::cerr/cout for consistent behavior
18#define POSTGRES_LOG_ERROR(context, message) \
19 std::cerr << "[PostgreSQL:" << context << "] Error: " << message << std::endl
20#define POSTGRES_LOG_WARNING(message) \
21 std::cerr << "[PostgreSQL] Warning: " << message << std::endl
22#define POSTGRES_LOG_INFO(message) \
23 std::cout << "[PostgreSQL] Info: " << message << std::endl
24
25namespace
26{
27// PostgreSQL type OIDs (from pg_type.h)
28constexpr unsigned int PG_INT4OID = 23;
29constexpr unsigned int PG_INT8OID = 20;
30constexpr unsigned int PG_FLOAT4OID = 700;
31constexpr unsigned int PG_FLOAT8OID = 701;
32constexpr unsigned int PG_BOOLOID = 16;
33}
34
35namespace database
36{
38 : connection_(nullptr)
39 , initialized_(false)
40 , in_transaction_(false)
41 {
42 }
43
48
53
54 kcenon::common::VoidResult postgres_manager::initialize(const core::connection_config& config)
55 {
56 if (initialized_) {
57 return kcenon::common::error_info{-1, "Already initialized", "postgres_manager"};
58 }
59
60 // Build connection string from config
61 std::ostringstream conn_str;
62 if (!config.host.empty()) {
63 conn_str << "host=" << config.host << " ";
64 }
65 if (config.port > 0) {
66 conn_str << "port=" << config.port << " ";
67 }
68 if (!config.database.empty()) {
69 conn_str << "dbname=" << config.database << " ";
70 }
71 if (!config.username.empty()) {
72 conn_str << "user=" << config.username << " ";
73 }
74 if (!config.password.empty()) {
75 conn_str << "password=" << config.password << " ";
76 }
77 for (const auto& [key, value] : config.options) {
78 conn_str << key << "=" << value << " ";
79 }
80
81 connection_string_ = conn_str.str();
82
83#ifdef USE_POSTGRESQL
84 try {
85 auto conn = std::make_unique<pqxx::connection>(connection_string_);
86 if (conn->is_open()) {
87 connection_ = conn.release();
88 initialized_ = true;
89 return kcenon::common::ok();
90 }
91 last_error_ = "Connection failed to open";
92 } catch (const std::exception& e) {
93 last_error_ = std::string("Connection error: ") + e.what();
94 POSTGRES_LOG_ERROR("initialize", last_error_);
95 }
96#elif defined(HAVE_LIBPQ)
97 try {
98 connection_ = PQconnectdb(connection_string_.c_str());
99 if (PQstatus(static_cast<PGconn*>(connection_)) == CONNECTION_OK) {
100 initialized_ = true;
101 return kcenon::common::ok();
102 }
103 last_error_ = PQerrorMessage(static_cast<PGconn*>(connection_));
104 PQfinish(static_cast<PGconn*>(connection_));
105 connection_ = nullptr;
106 } catch (const std::exception& e) {
107 last_error_ = std::string("Connection error: ") + e.what();
108 POSTGRES_LOG_ERROR("initialize", last_error_);
109 }
110#else
111 POSTGRES_LOG_WARNING("PostgreSQL support not compiled. Mock connection established.");
112 initialized_ = true;
113 return kcenon::common::ok();
114#endif
115 return kcenon::common::error_info{-2, last_error_, "postgres_manager"};
116 }
117
118 kcenon::common::VoidResult postgres_manager::shutdown()
119 {
120 if (!initialized_) {
121 return kcenon::common::ok();
122 }
123
124 if (in_transaction_) {
126 }
127
128#ifdef USE_POSTGRESQL
129 try {
130 delete static_cast<pqxx::connection*>(connection_);
131 connection_ = nullptr;
132 initialized_ = false;
133 return kcenon::common::ok();
134 } catch (const std::exception& e) {
135 last_error_ = std::string("Disconnect error: ") + e.what();
136 POSTGRES_LOG_ERROR("shutdown", last_error_);
137 }
138#elif defined(HAVE_LIBPQ)
139 try {
140 PQfinish(static_cast<PGconn*>(connection_));
141 connection_ = nullptr;
142 initialized_ = false;
143 return kcenon::common::ok();
144 } catch (const std::exception& e) {
145 last_error_ = std::string("Disconnect error: ") + e.what();
146 POSTGRES_LOG_ERROR("shutdown", last_error_);
147 }
148#else
149 connection_ = nullptr;
150 initialized_ = false;
151 POSTGRES_LOG_WARNING("PostgreSQL support not compiled. Mock disconnect.");
152 return kcenon::common::ok();
153#endif
154 return kcenon::common::error_info{-3, last_error_, "postgres_manager"};
155 }
156
158 {
159 return initialized_;
160 }
161
162 kcenon::common::Result<uint64_t> postgres_manager::execute_modification_query(const std::string& query_string)
163 {
164 if (!initialized_) {
165 return kcenon::common::error_info{-1, "Not initialized", "postgres_manager"};
166 }
167
168#ifdef USE_POSTGRESQL
169 try {
170 pqxx::connection* conn = static_cast<pqxx::connection*>(connection_);
171 pqxx::work txn(*conn);
172 pqxx::result result = txn.exec(query_string);
173 txn.commit();
174 return static_cast<uint64_t>(result.affected_rows());
175 } catch (const std::exception& e) {
176 last_error_ = std::string("Modification query error: ") + e.what();
177 POSTGRES_LOG_ERROR("execute_modification_query", last_error_);
178 return kcenon::common::error_info{-2, last_error_, "postgres_manager"};
179 }
180#elif defined(HAVE_LIBPQ)
181 try {
182 PGresult* result = PQexec(static_cast<PGconn*>(connection_), query_string.c_str());
183 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
184 last_error_ = PQerrorMessage(static_cast<PGconn*>(connection_));
185 PQclear(result);
186 return kcenon::common::error_info{-2, last_error_, "postgres_manager"};
187 }
188 const char* affected_rows = PQcmdTuples(result);
189 uint64_t count = 0;
190 if (affected_rows && *affected_rows) {
191 count = static_cast<uint64_t>(std::stoull(affected_rows));
192 }
193 PQclear(result);
194 return count;
195 } catch (const std::exception& e) {
196 last_error_ = std::string("Modification query error: ") + e.what();
197 POSTGRES_LOG_ERROR("execute_modification_query", last_error_);
198 return kcenon::common::error_info{-2, last_error_, "postgres_manager"};
199 }
200#else
201 POSTGRES_LOG_WARNING("PostgreSQL support not compiled. Mock modification: " + query_string.substr(0, 20) + "...");
202 return uint64_t{1};
203#endif
204 }
205
206 kcenon::common::Result<core::database_result> postgres_manager::select_query(const std::string& query_string)
207 {
208 if (!initialized_) {
209 return kcenon::common::error_info{-1, "Not initialized", "postgres_manager"};
210 }
211
213
214#ifdef USE_POSTGRESQL
215 try {
216 pqxx::connection* conn = static_cast<pqxx::connection*>(connection_);
217 pqxx::work txn(*conn);
218 pqxx::result pqxx_result = txn.exec(query_string);
219 txn.commit();
220
221 for (const auto& row : pqxx_result) {
222 core::database_row db_row;
223 for (size_t i = 0; i < row.size(); ++i) {
224 std::string column_name = pqxx_result.column_name(i);
225 if (row[i].is_null()) {
226 db_row[column_name] = nullptr;
227 } else {
228 if (row[i].type() == PG_INT8OID ||
229 row[i].type() == PG_INT4OID) {
230 db_row[column_name] = row[i].as<int64_t>();
231 } else if (row[i].type() == PG_FLOAT8OID ||
232 row[i].type() == PG_FLOAT4OID) {
233 db_row[column_name] = row[i].as<double>();
234 } else if (row[i].type() == PG_BOOLOID) {
235 db_row[column_name] = row[i].as<bool>();
236 } else {
237 db_row[column_name] = row[i].as<std::string>();
238 }
239 }
240 }
241 result.push_back(std::move(db_row));
242 }
243 return result;
244 } catch (const std::exception& e) {
245 last_error_ = std::string("Select query error: ") + e.what();
246 POSTGRES_LOG_ERROR("select_query", last_error_);
247 return kcenon::common::error_info{-2, last_error_, "postgres_manager"};
248 }
249#elif defined(HAVE_LIBPQ)
250 try {
251 PGresult* pg_result = PQexec(static_cast<PGconn*>(connection_), query_string.c_str());
252 if (PQresultStatus(pg_result) != PGRES_TUPLES_OK) {
253 last_error_ = PQerrorMessage(static_cast<PGconn*>(connection_));
254 PQclear(pg_result);
255 return kcenon::common::error_info{-2, last_error_, "postgres_manager"};
256 }
257
258 int rows = PQntuples(pg_result);
259 int cols = PQnfields(pg_result);
260
261 for (int row = 0; row < rows; ++row) {
262 core::database_row db_row;
263 for (int col = 0; col < cols; ++col) {
264 std::string column_name = PQfname(pg_result, col);
265 if (PQgetisnull(pg_result, row, col)) {
266 db_row[column_name] = nullptr;
267 } else {
268 const char* value = PQgetvalue(pg_result, row, col);
269 Oid type_oid = PQftype(pg_result, col);
270
271 if (type_oid == 20 || type_oid == 21 || type_oid == 23) { // int8, int2, int4
272 db_row[column_name] = static_cast<int64_t>(std::stoll(value));
273 } else if (type_oid == 700 || type_oid == 701) { // float4, float8
274 db_row[column_name] = std::stod(value);
275 } else if (type_oid == 16) { // bool
276 db_row[column_name] = (*value == 't' || *value == '1');
277 } else {
278 db_row[column_name] = std::string(value);
279 }
280 }
281 }
282 result.push_back(std::move(db_row));
283 }
284 PQclear(pg_result);
285 return result;
286 } catch (const std::exception& e) {
287 last_error_ = std::string("Select query error: ") + e.what();
288 POSTGRES_LOG_ERROR("select_query", last_error_);
289 return kcenon::common::error_info{-2, last_error_, "postgres_manager"};
290 }
291#else
292 POSTGRES_LOG_WARNING("PostgreSQL support not compiled. Mock select: " + query_string.substr(0, 20) + "...");
293 if (query_string.find("SELECT") != std::string::npos) {
294 core::database_row mock_row;
295 mock_row["id"] = int64_t(1);
296 mock_row["name"] = std::string("mock_data");
297 mock_row["active"] = true;
298 result.push_back(mock_row);
299 }
300 return result;
301#endif
302 }
303
304 kcenon::common::VoidResult postgres_manager::execute_query(const std::string& query_string)
305 {
306 if (!initialized_) {
307 return kcenon::common::error_info{-1, "Not initialized", "postgres_manager"};
308 }
309
310#ifdef USE_POSTGRESQL
311 try {
312 pqxx::work txn{*static_cast<pqxx::connection*>(connection_)};
313 txn.exec(query_string);
314 txn.commit();
315 return kcenon::common::ok();
316 } catch (const std::exception& e) {
317 last_error_ = std::string("Execute error: ") + e.what();
318 POSTGRES_LOG_ERROR("execute_query", last_error_);
319 return kcenon::common::error_info{-2, last_error_, "postgres_manager"};
320 }
321#elif defined(HAVE_LIBPQ)
322 PGresult* result = PQexec(static_cast<PGconn*>(connection_), query_string.c_str());
323 if (result == nullptr) {
324 last_error_ = "PostgreSQL execute failed";
325 POSTGRES_LOG_ERROR("execute_query", last_error_);
326 return kcenon::common::error_info{-2, last_error_, "postgres_manager"};
327 }
328
329 ExecStatusType status = PQresultStatus(result);
330 bool success = (status == PGRES_COMMAND_OK) || (status == PGRES_TUPLES_OK);
331
332 if (!success) {
333 last_error_ = PQerrorMessage(static_cast<PGconn*>(connection_));
334 POSTGRES_LOG_ERROR("execute_query", last_error_);
335 PQclear(result);
336 return kcenon::common::error_info{-2, last_error_, "postgres_manager"};
337 }
338
339 PQclear(result);
340 return kcenon::common::ok();
341#else
342 POSTGRES_LOG_INFO("PostgreSQL support not compiled. Mock execute: " + query_string);
343 return kcenon::common::ok();
344#endif
345 }
346
347 kcenon::common::VoidResult postgres_manager::begin_transaction()
348 {
349 if (!initialized_) {
350 return kcenon::common::error_info{-1, "Not initialized", "postgres_manager"};
351 }
352
353 if (in_transaction_) {
354 return kcenon::common::error_info{-2, "Transaction already active", "postgres_manager"};
355 }
356
357 auto result = execute_query("BEGIN");
358 if (result.is_ok()) {
359 in_transaction_ = true;
360 }
361 return result;
362 }
363
364 kcenon::common::VoidResult postgres_manager::commit_transaction()
365 {
366 if (!initialized_) {
367 return kcenon::common::error_info{-1, "Not initialized", "postgres_manager"};
368 }
369
370 if (!in_transaction_) {
371 return kcenon::common::error_info{-2, "No active transaction", "postgres_manager"};
372 }
373
374 auto result = execute_query("COMMIT");
375 if (result.is_ok()) {
376 in_transaction_ = false;
377 }
378 return result;
379 }
380
381 kcenon::common::VoidResult postgres_manager::rollback_transaction()
382 {
383 if (!initialized_) {
384 return kcenon::common::error_info{-1, "Not initialized", "postgres_manager"};
385 }
386
387 if (!in_transaction_) {
388 return kcenon::common::error_info{-2, "No active transaction", "postgres_manager"};
389 }
390
391 auto result = execute_query("ROLLBACK");
392 if (result.is_ok()) {
393 in_transaction_ = false;
394 }
395 return result;
396 }
397
399 {
400 return in_transaction_;
401 }
402
404 {
405 return last_error_;
406 }
407
408 std::map<std::string, std::string> postgres_manager::connection_info() const
409 {
410 std::map<std::string, std::string> info;
411 info["backend"] = "postgresql";
412 info["initialized"] = initialized_ ? "true" : "false";
413 info["in_transaction"] = in_transaction_ ? "true" : "false";
414
415#ifdef USE_POSTGRESQL
416 info["driver"] = "libpqxx";
417 if (initialized_ && connection_) {
418 auto* conn = static_cast<pqxx::connection*>(connection_);
419 info["server_version"] = std::to_string(conn->server_version());
420 info["protocol_version"] = std::to_string(conn->protocol_version());
421 }
422#elif defined(HAVE_LIBPQ)
423 info["driver"] = "libpq";
424 if (initialized_ && connection_) {
425 auto* conn = static_cast<PGconn*>(connection_);
426 info["server_version"] = std::to_string(PQserverVersion(conn));
427 info["protocol_version"] = std::to_string(PQprotocolVersion(conn));
428 }
429#else
430 info["driver"] = "mock";
431#endif
432
433 return info;
434 }
435} // namespace database
std::string last_error_
Last error message.
kcenon::common::VoidResult begin_transaction() override
Begin a transaction.
postgres_manager(void)
Default constructor.
std::string last_error() const override
Get last error message from backend.
bool in_transaction_
Whether a transaction is active.
kcenon::common::VoidResult shutdown() override
Shutdown the database backend gracefully.
kcenon::common::Result< uint64_t > execute_modification_query(const std::string &query_string)
Common implementation for INSERT, UPDATE, and DELETE queries.
virtual ~postgres_manager(void)
Destructor.
bool initialized_
Whether the backend is initialized.
std::map< std::string, std::string > connection_info() const override
Get backend-specific connection information.
kcenon::common::VoidResult initialize(const core::connection_config &config) override
Initialize the database backend.
kcenon::common::VoidResult execute_query(const std::string &query_string) override
Executes a general SQL query (DDL, DML) on PostgreSQL.
bool is_initialized() const override
Check if backend is initialized and ready.
kcenon::common::VoidResult commit_transaction() override
Commit the current transaction.
std::string connection_string_
Connection string for connection_info.
kcenon::common::VoidResult rollback_transaction() override
Rollback the current transaction.
database_types type() const override
Returns the specific type of the database.
bool in_transaction() const override
Check if backend is currently in a transaction.
void * connection_
Pointer to the underlying PostgreSQL connection object.
kcenon::common::Result< core::database_result > select_query(const std::string &query_string) override
Executes a SELECT query on the connected PostgreSQL database and returns the resulting data.
std::vector< database_row > database_result
std::map< std::string, database_value > database_row
database_types
Represents various database backends or modes.
@ postgres
Indicates a PostgreSQL database.
#define POSTGRES_LOG_INFO(message)
#define POSTGRES_LOG_ERROR(context, message)
#define POSTGRES_LOG_WARNING(message)
Configuration for database connection.
std::map< std::string, std::string > options