diff --git a/src/fb-cpp/DatabaseManager.cpp b/src/fb-cpp/DatabaseManager.cpp index 24a8792..6bda793 100644 --- a/src/fb-cpp/DatabaseManager.cpp +++ b/src/fb-cpp/DatabaseManager.cpp @@ -41,6 +41,7 @@ void DatabaseManager::setProperties(const DatabasePropertiesOptions& options) if (const auto replicaMode = options.getReplicaMode()) { std::uint8_t modeVal = 0; + switch (*replicaMode) { case ReplicaMode::NONE: @@ -56,9 +57,66 @@ void DatabaseManager::setProperties(const DatabasePropertiesOptions& options) assert(false); break; } + builder->insertBytes(&statusWrapper, isc_spb_prp_replica_mode, &modeVal, 1u); } + if (const auto shutdownMode = options.getShutdownMode()) + { + std::uint8_t stateVal = 0; + + switch (*shutdownMode) + { + case ShutdownMode::NORMAL: + stateVal = isc_spb_prp_sm_normal; + break; + case ShutdownMode::MULTI: + stateVal = isc_spb_prp_sm_multi; + break; + case ShutdownMode::SINGLE: + stateVal = isc_spb_prp_sm_single; + break; + case ShutdownMode::FULL: + stateVal = isc_spb_prp_sm_full; + break; + default: + assert(false); + break; + } + + builder->insertBytes(&statusWrapper, isc_spb_prp_shutdown_mode, &stateVal, 1u); + } + + if (const auto shutdownType = options.getShutdownType()) + { + const auto timeout = options.getShutdownTimeout().value_or(0); + + switch (*shutdownType) + { + case ShutdownType::DENY_TRANSACTIONS: + builder->insertInt(&statusWrapper, isc_spb_prp_deny_new_transactions, timeout); + break; + case ShutdownType::DENY_ATTACHMENTS: + builder->insertInt(&statusWrapper, isc_spb_prp_deny_new_attachments, timeout); + break; + case ShutdownType::FORCED: + builder->insertInt(&statusWrapper, isc_spb_prp_force_shutdown, timeout); + break; + default: + assert(false); + break; + } + } + + if (const auto online = options.getOnline()) + { + if (*online) + { + std::uint8_t stateVal = isc_spb_prp_sm_normal; + builder->insertBytes(&statusWrapper, isc_spb_prp_online_mode, &stateVal, 1u); + } + } + const auto buffer = builder->getBuffer(&statusWrapper); const auto length = builder->getBufferLength(&statusWrapper); diff --git a/src/fb-cpp/DatabaseManager.h b/src/fb-cpp/DatabaseManager.h index 29173bb..5d55089 100644 --- a/src/fb-cpp/DatabaseManager.h +++ b/src/fb-cpp/DatabaseManager.h @@ -36,6 +36,53 @@ /// namespace fbcpp { + /// + /// Shutdown mode for a Firebird database. + /// + enum class ShutdownMode + { + /// + /// Normal online state. + /// + NORMAL, + + /// + /// Multi-user shutdown. + /// + MULTI, + + /// + /// Single-user shutdown. + /// + SINGLE, + + /// + /// Full exclusive shutdown. + /// + FULL + }; + + /// + /// Shutdown type for a Firebird database. + /// + enum class ShutdownType + { + /// + /// Forced shutdown: disconnects all users immediately after the timeout. + /// + FORCED, + + /// + /// Deny new transactions: waits for existing transactions to finish. + /// + DENY_TRANSACTIONS, + + /// + /// Deny new attachments: waits for existing connections to finish. + /// + DENY_ATTACHMENTS + }; + /// /// Represents options used to configure database properties through the service manager. /// @@ -76,9 +123,81 @@ namespace fbcpp return *this; } + /// + /// Returns the shutdown mode. + /// + const std::optional& getShutdownMode() const + { + return shutdownMode; + } + + /// + /// Sets the shutdown mode. + /// + DatabasePropertiesOptions& setShutdownMode(ShutdownMode value) + { + shutdownMode = value; + return *this; + } + + /// + /// Returns the shutdown type. + /// + const std::optional& getShutdownType() const + { + return shutdownType; + } + + /// + /// Sets the shutdown type. + /// + DatabasePropertiesOptions& setShutdownType(ShutdownType value) + { + shutdownType = value; + return *this; + } + + /// + /// Returns the shutdown timeout in seconds. + /// + const std::optional& getShutdownTimeout() const + { + return shutdownTimeout; + } + + /// + /// Sets the shutdown timeout in seconds. + /// + DatabasePropertiesOptions& setShutdownTimeout(int value) + { + shutdownTimeout = value; + return *this; + } + + /// + /// Returns whether the database should be brought online. + /// + const std::optional& getOnline() const + { + return online; + } + + /// + /// Sets whether the database should be brought online. + /// + DatabasePropertiesOptions& setOnline(bool value) + { + online = value; + return *this; + } + private: std::string database; std::optional replicaMode; + std::optional shutdownMode; + std::optional shutdownType; + std::optional shutdownTimeout; + std::optional online; }; /// diff --git a/src/test/DatabaseManager.cpp b/src/test/DatabaseManager.cpp index 85cd176..13cb5d7 100644 --- a/src/test/DatabaseManager.cpp +++ b/src/test/DatabaseManager.cpp @@ -194,4 +194,47 @@ BOOST_AUTO_TEST_CASE(databaseSweepAndValidate) cleanup.dropDatabase(); } +BOOST_AUTO_TEST_CASE(databaseShutdownAndOnline) +{ + const auto databasePath = getTempFile("DatabaseManager-shutdownAndOnline.fdb", false); + const auto databaseUri = getTempFile("DatabaseManager-shutdownAndOnline.fdb"); + const auto attachmentOptions = AttachmentOptions().setConnectionCharSet("UTF8"); + + { // scope + Attachment attachment{ + CLIENT, databaseUri, AttachmentOptions().setCreateDatabase(true).setConnectionCharSet("UTF8")}; + Transaction transaction{attachment}; + Statement createTable{attachment, transaction, "create table test (id integer)"}; + BOOST_REQUIRE(createTable.execute(transaction)); + transaction.commit(); + } + + DatabaseManager manager{CLIENT, makeServiceManagerOptions()}; + + // Shutdown the database + manager.setProperties(DatabasePropertiesOptions() + .setDatabase(databasePath) + .setShutdownMode(ShutdownMode::FULL) + .setShutdownType(ShutdownType::FORCED) + .setShutdownTimeout(0)); + + // Attachment should fail when the database is shutdown + BOOST_CHECK_THROW(Attachment(CLIENT, databaseUri, attachmentOptions), DatabaseException); + + // Bring the database online + manager.setProperties(DatabasePropertiesOptions().setDatabase(databasePath).setOnline(true)); + + // Attachment should succeed when online + { // scope + Attachment attachment{CLIENT, databaseUri, attachmentOptions}; + Transaction transaction{attachment}; + Statement query{attachment, transaction, "select count(*) from test"}; + BOOST_REQUIRE(query.execute(transaction)); + BOOST_CHECK_EQUAL(query.getInt32(0).value(), 0); + } + + Attachment cleanup{CLIENT, databaseUri, attachmentOptions}; + cleanup.dropDatabase(); +} + BOOST_AUTO_TEST_SUITE_END()