Loading include/database.h +27 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ #include <cstring> #include <vector> #include <string> #include <memory> #ifdef Windows #include <wtypes.h> Loading Loading @@ -140,4 +141,30 @@ namespace dbpp { #endif }; class ReplicatedDatabase { public: ReplicatedDatabase(const std::string &dbdriver, const std::string &primaryConn); ~ReplicatedDatabase(); void addReplica(const std::string &dbdriver, const std::string &connstr); int exec(const SQL &sql, DBResult &res) const; const char *getDriverName() const; const SQL &autoincrement(SQL &sql) const; const SQL &getUUIDType(SQL &sql) const; bool isConnected() const; void reset(); Database &primary(); const Database &primary() const; size_t replicaCount() const; Database &replica(size_t idx); const Database &replica(size_t idx) const; private: static bool isWriteStatement(const char *sql); std::unique_ptr<Database> _primary; std::vector<std::unique_ptr<Database>> _replicas; }; }; src/database.cpp +101 −0 Original line number Diff line number Diff line Loading @@ -168,6 +168,107 @@ const char *dbpp::DBResult2::operator[](int value2){ return nullptr; } // --- ReplicatedDatabase implementation --- dbpp::ReplicatedDatabase::ReplicatedDatabase(const std::string &dbdriver, const std::string &primaryConn) : _primary(std::make_unique<Database>(dbdriver, primaryConn)) { } dbpp::ReplicatedDatabase::~ReplicatedDatabase() = default; void dbpp::ReplicatedDatabase::addReplica(const std::string &dbdriver, const std::string &connstr) { _replicas.push_back(std::make_unique<Database>(dbdriver, connstr)); } bool dbpp::ReplicatedDatabase::isWriteStatement(const char *sql) { if (!sql) return false; // Skip leading whitespace while (*sql == ' ' || *sql == '\t' || *sql == '\n' || *sql == '\r') ++sql; // Case-insensitive prefix check for write statements auto startsWith = [](const char *s, const char *prefix) -> bool { while (*prefix) { if (std::tolower((unsigned char)*s) != std::tolower((unsigned char)*prefix)) return false; ++s; ++prefix; } return true; }; return startsWith(sql, "insert") || startsWith(sql, "update") || startsWith(sql, "delete") || startsWith(sql, "create") || startsWith(sql, "alter") || startsWith(sql, "drop"); } int dbpp::ReplicatedDatabase::exec(const SQL &sql, DBResult &res) const { if (isWriteStatement(sql.c_str())) { // Write: execute on primary first, then replicate to all replicas int rows = _primary->exec(sql, res); for (const auto &replica : _replicas) { try { DBResult rres; replica->exec(sql, rres); } catch (const std::exception &e) { std::fprintf(stderr, "ReplicatedDatabase: replica write failed: %s\n", e.what()); } } return rows; } else { // Read: only from primary return _primary->exec(sql, res); } } const char *dbpp::ReplicatedDatabase::getDriverName() const { return _primary->getDriverName(); } const dbpp::SQL &dbpp::ReplicatedDatabase::autoincrement(SQL &sql) const { return _primary->autoincrement(sql); } const dbpp::SQL &dbpp::ReplicatedDatabase::getUUIDType(SQL &sql) const { return _primary->getUUIDType(sql); } bool dbpp::ReplicatedDatabase::isConnected() const { return _primary->isConnected(); } void dbpp::ReplicatedDatabase::reset() { _primary->reset(); for (auto &replica : _replicas) { try { replica->reset(); } catch (...) {} } } dbpp::Database &dbpp::ReplicatedDatabase::primary() { return *_primary; } const dbpp::Database &dbpp::ReplicatedDatabase::primary() const { return *_primary; } size_t dbpp::ReplicatedDatabase::replicaCount() const { return _replicas.size(); } dbpp::Database &dbpp::ReplicatedDatabase::replica(size_t idx) { return *_replicas.at(idx); } const dbpp::Database &dbpp::ReplicatedDatabase::replica(size_t idx) const { return *_replicas.at(idx); } Loading
include/database.h +27 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ #include <cstring> #include <vector> #include <string> #include <memory> #ifdef Windows #include <wtypes.h> Loading Loading @@ -140,4 +141,30 @@ namespace dbpp { #endif }; class ReplicatedDatabase { public: ReplicatedDatabase(const std::string &dbdriver, const std::string &primaryConn); ~ReplicatedDatabase(); void addReplica(const std::string &dbdriver, const std::string &connstr); int exec(const SQL &sql, DBResult &res) const; const char *getDriverName() const; const SQL &autoincrement(SQL &sql) const; const SQL &getUUIDType(SQL &sql) const; bool isConnected() const; void reset(); Database &primary(); const Database &primary() const; size_t replicaCount() const; Database &replica(size_t idx); const Database &replica(size_t idx) const; private: static bool isWriteStatement(const char *sql); std::unique_ptr<Database> _primary; std::vector<std::unique_ptr<Database>> _replicas; }; };
src/database.cpp +101 −0 Original line number Diff line number Diff line Loading @@ -168,6 +168,107 @@ const char *dbpp::DBResult2::operator[](int value2){ return nullptr; } // --- ReplicatedDatabase implementation --- dbpp::ReplicatedDatabase::ReplicatedDatabase(const std::string &dbdriver, const std::string &primaryConn) : _primary(std::make_unique<Database>(dbdriver, primaryConn)) { } dbpp::ReplicatedDatabase::~ReplicatedDatabase() = default; void dbpp::ReplicatedDatabase::addReplica(const std::string &dbdriver, const std::string &connstr) { _replicas.push_back(std::make_unique<Database>(dbdriver, connstr)); } bool dbpp::ReplicatedDatabase::isWriteStatement(const char *sql) { if (!sql) return false; // Skip leading whitespace while (*sql == ' ' || *sql == '\t' || *sql == '\n' || *sql == '\r') ++sql; // Case-insensitive prefix check for write statements auto startsWith = [](const char *s, const char *prefix) -> bool { while (*prefix) { if (std::tolower((unsigned char)*s) != std::tolower((unsigned char)*prefix)) return false; ++s; ++prefix; } return true; }; return startsWith(sql, "insert") || startsWith(sql, "update") || startsWith(sql, "delete") || startsWith(sql, "create") || startsWith(sql, "alter") || startsWith(sql, "drop"); } int dbpp::ReplicatedDatabase::exec(const SQL &sql, DBResult &res) const { if (isWriteStatement(sql.c_str())) { // Write: execute on primary first, then replicate to all replicas int rows = _primary->exec(sql, res); for (const auto &replica : _replicas) { try { DBResult rres; replica->exec(sql, rres); } catch (const std::exception &e) { std::fprintf(stderr, "ReplicatedDatabase: replica write failed: %s\n", e.what()); } } return rows; } else { // Read: only from primary return _primary->exec(sql, res); } } const char *dbpp::ReplicatedDatabase::getDriverName() const { return _primary->getDriverName(); } const dbpp::SQL &dbpp::ReplicatedDatabase::autoincrement(SQL &sql) const { return _primary->autoincrement(sql); } const dbpp::SQL &dbpp::ReplicatedDatabase::getUUIDType(SQL &sql) const { return _primary->getUUIDType(sql); } bool dbpp::ReplicatedDatabase::isConnected() const { return _primary->isConnected(); } void dbpp::ReplicatedDatabase::reset() { _primary->reset(); for (auto &replica : _replicas) { try { replica->reset(); } catch (...) {} } } dbpp::Database &dbpp::ReplicatedDatabase::primary() { return *_primary; } const dbpp::Database &dbpp::ReplicatedDatabase::primary() const { return *_primary; } size_t dbpp::ReplicatedDatabase::replicaCount() const { return _replicas.size(); } dbpp::Database &dbpp::ReplicatedDatabase::replica(size_t idx) { return *_replicas.at(idx); } const dbpp::Database &dbpp::ReplicatedDatabase::replica(size_t idx) const { return *_replicas.at(idx); }