diff --git a/src/fb-cpp/Attachment.cpp b/src/fb-cpp/Attachment.cpp index fafd876..df57b3f 100644 --- a/src/fb-cpp/Attachment.cpp +++ b/src/fb-cpp/Attachment.cpp @@ -31,7 +31,7 @@ using namespace fbcpp::impl; Attachment::Attachment(Client& client, const std::string& uri, const AttachmentOptions& options) - : client{client} + : client{&client} { const auto master = client.getMaster(); @@ -68,8 +68,8 @@ void Attachment::disconnectOrDrop(bool drop) { assert(isValid()); - const auto status = client.newStatus(); - StatusWrapper statusWrapper{client, status.get()}; + const auto status = client->newStatus(); + StatusWrapper statusWrapper{*client, status.get()}; if (drop) handle->dropDatabase(&statusWrapper); diff --git a/src/fb-cpp/Attachment.h b/src/fb-cpp/Attachment.h index ba8d0e3..e325db2 100644 --- a/src/fb-cpp/Attachment.h +++ b/src/fb-cpp/Attachment.h @@ -192,7 +192,22 @@ namespace fbcpp { } - Attachment& operator=(Attachment&&) = delete; + /// + /// @brief Transfers ownership of another Attachment into this one. + /// + /// The old handle is released via `FbRef::operator=(FbRef&&)`. + /// After the assignment, `this` is valid (with `o`'s handle) and `o` is invalid. + /// + Attachment& operator=(Attachment&& o) noexcept + { + if (this != &o) + { + client = o.client; + handle = std::move(o.handle); + } + + return *this; + } Attachment(const Attachment&) = delete; Attachment& operator=(const Attachment&) = delete; @@ -229,7 +244,7 @@ namespace fbcpp /// Client& getClient() noexcept { - return client; + return *client; } /// @@ -254,7 +269,7 @@ namespace fbcpp void disconnectOrDrop(bool drop); private: - Client& client; + Client* client; FbRef handle; }; } // namespace fbcpp diff --git a/src/fb-cpp/CalendarConverter.h b/src/fb-cpp/CalendarConverter.h index 5261bb4..32cb71f 100644 --- a/src/fb-cpp/CalendarConverter.h +++ b/src/fb-cpp/CalendarConverter.h @@ -49,7 +49,7 @@ namespace fbcpp::impl { public: explicit CalendarConverter(Client& client, StatusWrapper* statusWrapper) - : client{client}, + : client{&client}, statusWrapper{statusWrapper} { } @@ -67,7 +67,7 @@ namespace fbcpp::impl if (yearValue <= 0) throwInvalidDateValue(); - return OpaqueDate{client.getUtil()->encodeDate(static_cast(yearValue), monthValue, dayValue)}; + return OpaqueDate{client->getUtil()->encodeDate(static_cast(yearValue), monthValue, dayValue)}; } Date opaqueDateToDate(OpaqueDate date) @@ -76,7 +76,7 @@ namespace fbcpp::impl unsigned month; unsigned day; - client.getUtil()->decodeDate(date.value, &year, &month, &day); + client->getUtil()->decodeDate(date.value, &year, &month, &day); return Date{std::chrono::year{static_cast(year)}, std::chrono::month{month}, std::chrono::day{day}}; } @@ -140,7 +140,7 @@ namespace fbcpp::impl const auto subseconds = static_cast(time.subseconds().count() / 100); OpaqueTime opaqueTime; - opaqueTime.value = client.getUtil()->encodeTime(hours, minutes, seconds, subseconds); + opaqueTime.value = client->getUtil()->encodeTime(hours, minutes, seconds, subseconds); return opaqueTime; } @@ -152,7 +152,7 @@ namespace fbcpp::impl unsigned seconds; unsigned subseconds; - const auto util = client.getUtil(); + const auto util = client->getUtil(); util->decodeTime(time.value, &hours, &minutes, &seconds, &subseconds); const auto timeOfDay = std::chrono::hours{hours} + std::chrono::minutes{minutes} + @@ -252,7 +252,7 @@ namespace fbcpp::impl OpaqueTimeTz opaque{}; - client.getUtil()->encodeTimeTz(statusWrapper, &opaque.value, 0u, 0u, 0u, 0u, timeTz.zone.c_str()); + client->getUtil()->encodeTimeTz(statusWrapper, &opaque.value, 0u, 0u, 0u, 0u, timeTz.zone.c_str()); opaque.value.utc_time = static_cast(duration.count() / 100); @@ -269,7 +269,7 @@ namespace fbcpp::impl unsigned fractions; std::array timeZoneBuffer; - client.getUtil()->decodeTimeTz(statusWrapper, &opaqueTime.value, &hours, &minutes, &seconds, &fractions, + client->getUtil()->decodeTimeTz(statusWrapper, &opaqueTime.value, &hours, &minutes, &seconds, &fractions, static_cast(timeZoneBuffer.size()), timeZoneBuffer.data()); TimeTz timeTz; @@ -295,7 +295,7 @@ namespace fbcpp::impl unsigned fractions; std::array timeZoneBuffer; - client.getUtil()->decodeTimeTz(statusWrapper, &time.value, &hours, &minutes, &seconds, &fractions, + client->getUtil()->decodeTimeTz(statusWrapper, &time.value, &hours, &minutes, &seconds, &fractions, static_cast(timeZoneBuffer.size()), timeZoneBuffer.data()); return std::format("{:02}:{:02}:{:02}.{:04} {}", hours, minutes, seconds, fractions, timeZoneBuffer.data()); @@ -360,7 +360,7 @@ namespace fbcpp::impl OpaqueTimeTz encoded; const std::string timeZoneString{makeComponentView(5)}; - client.getUtil()->encodeTimeTz( + client->getUtil()->encodeTimeTz( statusWrapper, &encoded.value, hours, minutes, seconds, fractions, timeZoneString.c_str()); return opaqueTimeTzToTimeTz(encoded); @@ -389,7 +389,7 @@ namespace fbcpp::impl OpaqueTimestamp opaqueTimestamp; opaqueTimestamp.value.timestamp_date = opaqueDate.value; opaqueTimestamp.value.timestamp_time = - client.getUtil()->encodeTime(static_cast(timestamp.time.hours().count()), + client->getUtil()->encodeTime(static_cast(timestamp.time.hours().count()), static_cast(timestamp.time.minutes().count()), static_cast(timestamp.time.seconds().count()), static_cast(subseconds / 100)); @@ -406,7 +406,7 @@ namespace fbcpp::impl unsigned seconds; unsigned subseconds; - const auto util = client.getUtil(); + const auto util = client->getUtil(); util->decodeDate(timestamp.value.timestamp_date, &year, &month, &day); util->decodeTime(timestamp.value.timestamp_time, &hours, &minutes, &seconds, &subseconds); @@ -513,7 +513,7 @@ namespace fbcpp::impl { OpaqueTimestampTz opaque; - client.getUtil()->encodeTimeStampTz( + client->getUtil()->encodeTimeStampTz( statusWrapper, &opaque.value, 1u, 1u, 1u, 0u, 0u, 0u, 0u, timestampTz.zone.c_str()); const auto utcOpaque = timestampToOpaqueTimestamp(timestampTz.utcTimestamp); @@ -539,7 +539,7 @@ namespace fbcpp::impl unsigned subseconds; std::array timeZoneBuffer; - client.getUtil()->decodeTimeStampTz(statusWrapper, &opaqueTimestamp.value, &year, &month, &day, &hours, + client->getUtil()->decodeTimeStampTz(statusWrapper, &opaqueTimestamp.value, &year, &month, &day, &hours, &minutes, &seconds, &subseconds, static_cast(timeZoneBuffer.size()), timeZoneBuffer.data()); TimestampTz timestampTz; @@ -569,7 +569,7 @@ namespace fbcpp::impl unsigned subseconds; std::array timeZoneBuffer; - client.getUtil()->decodeTimeStampTz(statusWrapper, ×tamp.value, &year, &month, &day, &hours, &minutes, + client->getUtil()->decodeTimeStampTz(statusWrapper, ×tamp.value, &year, &month, &day, &hours, &minutes, &seconds, &subseconds, static_cast(timeZoneBuffer.size()), timeZoneBuffer.data()); return std::format("{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:04} {}", year, month, day, hours, minutes, @@ -648,7 +648,7 @@ namespace fbcpp::impl OpaqueTimestampTz encoded; const std::string timeZoneString{makeComponentView(8)}; - client.getUtil()->encodeTimeStampTz(statusWrapper, &encoded.value, + client->getUtil()->encodeTimeStampTz(statusWrapper, &encoded.value, static_cast(static_cast(date.year())), monthValue, dayValue, hours, minutes, seconds, fractions, timeZoneString.c_str()); @@ -674,7 +674,7 @@ namespace fbcpp::impl isc_arg_end, }; - throw DatabaseException(client, STATUS_CONVERSION_ERROR_FROM_STRING); + throw DatabaseException(*client, STATUS_CONVERSION_ERROR_FROM_STRING); } [[noreturn]] void throwInvalidDateValue() @@ -684,7 +684,7 @@ namespace fbcpp::impl isc_arg_end, }; - throw DatabaseException(client, STATUS_INVALID_DATE_VALUE); + throw DatabaseException(*client, STATUS_INVALID_DATE_VALUE); } [[noreturn]] void throwInvalidTimeValue() @@ -694,7 +694,7 @@ namespace fbcpp::impl isc_arg_end, }; - throw DatabaseException(client, STATUS_INVALID_TIME_VALUE); + throw DatabaseException(*client, STATUS_INVALID_TIME_VALUE); } [[noreturn]] void throwInvalidTimestampValue() @@ -704,7 +704,7 @@ namespace fbcpp::impl isc_arg_end, }; - throw DatabaseException(client, STATUS_INVALID_TIMESTAMP_VALUE); + throw DatabaseException(*client, STATUS_INVALID_TIMESTAMP_VALUE); } private: @@ -712,7 +712,7 @@ namespace fbcpp::impl static constexpr auto BASE_EPOCH = std::chrono::local_days{ std::chrono::year{1858} / std::chrono::November / 17, }; - Client& client; + Client* client; StatusWrapper* statusWrapper; }; } // namespace fbcpp::impl diff --git a/src/fb-cpp/Exception.cpp b/src/fb-cpp/Exception.cpp index 6ea022e..2f9f94c 100644 --- a/src/fb-cpp/Exception.cpp +++ b/src/fb-cpp/Exception.cpp @@ -34,7 +34,7 @@ using namespace fbcpp::impl; void StatusWrapper::checkException(StatusWrapper* status) { if (status->dirty && (status->getState() & fb::IStatus::STATE_ERRORS)) - throw DatabaseException{status->client, status->getErrors()}; + throw DatabaseException{*status->client, status->getErrors()}; } void StatusWrapper::catchException(fb::IStatus* status) noexcept diff --git a/src/fb-cpp/Exception.h b/src/fb-cpp/Exception.h index fb62ddb..2203a4d 100644 --- a/src/fb-cpp/Exception.h +++ b/src/fb-cpp/Exception.h @@ -45,7 +45,7 @@ namespace fbcpp::impl { public: explicit StatusWrapper(Client& client, IStatus* status) - : client{client}, + : client{&client}, status{status} { } @@ -158,7 +158,7 @@ namespace fbcpp::impl } protected: - Client& client; + Client* client; IStatus* status; bool dirty = false; diff --git a/src/fb-cpp/NumericConverter.h b/src/fb-cpp/NumericConverter.h index d2eb81b..5cad647 100644 --- a/src/fb-cpp/NumericConverter.h +++ b/src/fb-cpp/NumericConverter.h @@ -148,7 +148,7 @@ namespace fbcpp::impl { public: explicit NumericConverter(Client& client, StatusWrapper* statusWrapper) - : client{client}, + : client{&client}, statusWrapper{statusWrapper} { } @@ -161,7 +161,7 @@ namespace fbcpp::impl isc_arg_end, }; - throw DatabaseException(client, STATUS_NUMERIC_OUT_OF_RANGE); + throw DatabaseException(*client, STATUS_NUMERIC_OUT_OF_RANGE); } [[noreturn]] void throwConversionErrorFromString(const std::string& str) @@ -172,7 +172,7 @@ namespace fbcpp::impl isc_arg_end, }; - throw DatabaseException(client, STATUS_CONVERSION_ERROR_FROM_STRING); + throw DatabaseException(*client, STATUS_CONVERSION_ERROR_FROM_STRING); } public: @@ -356,7 +356,7 @@ namespace fbcpp::impl std::string opaqueInt128ToString(const OpaqueInt128& opaqueInt128, int scale) { - const auto int128Util = client.getUtil()->getInt128(statusWrapper); + const auto int128Util = client->getUtil()->getInt128(statusWrapper); char buffer[fb::IInt128::STRING_SIZE + 1]; int128Util->toString(statusWrapper, &opaqueInt128, scale, static_cast(sizeof(buffer)), buffer); return buffer; @@ -364,7 +364,7 @@ namespace fbcpp::impl std::string opaqueDecFloat16ToString(const OpaqueDecFloat16& opaqueDecFloat16) { - const auto decFloat16Util = client.getDecFloat16Util(statusWrapper); + const auto decFloat16Util = client->getDecFloat16Util(statusWrapper); char buffer[fb::IDecFloat16::STRING_SIZE + 1]; decFloat16Util->toString(statusWrapper, &opaqueDecFloat16, static_cast(sizeof(buffer)), buffer); return buffer; @@ -372,7 +372,7 @@ namespace fbcpp::impl std::string opaqueDecFloat34ToString(const OpaqueDecFloat34& opaqueDecFloat34) { - const auto decFloat34Util = client.getDecFloat34Util(statusWrapper); + const auto decFloat34Util = client->getDecFloat34Util(statusWrapper); char buffer[fb::IDecFloat34::STRING_SIZE + 1]; decFloat34Util->toString(statusWrapper, &opaqueDecFloat34, static_cast(sizeof(buffer)), buffer); return buffer; @@ -401,7 +401,7 @@ namespace fbcpp::impl OpaqueDecFloat16 boostDecFloat16ToOpaqueDecFloat16(const BoostDecFloat16& boostDecFloat16) { - const auto decFloat16Util = client.getDecFloat16Util(statusWrapper); + const auto decFloat16Util = client->getDecFloat16Util(statusWrapper); OpaqueDecFloat16 opaqueDecFloat16; decFloat16Util->fromString(statusWrapper, boostDecFloat16.str().c_str(), &opaqueDecFloat16); return opaqueDecFloat16; @@ -414,7 +414,7 @@ namespace fbcpp::impl OpaqueDecFloat34 boostDecFloat34ToOpaqueDecFloat34(const BoostDecFloat34& boostDecFloat34) { - const auto decFloat34Util = client.getDecFloat34Util(statusWrapper); + const auto decFloat34Util = client->getDecFloat34Util(statusWrapper); OpaqueDecFloat34 opaqueDecFloat34; decFloat34Util->fromString(statusWrapper, boostDecFloat34.str().c_str(), &opaqueDecFloat34); return opaqueDecFloat34; @@ -573,7 +573,7 @@ namespace fbcpp::impl } private: - Client& client; + Client* client; StatusWrapper* statusWrapper; }; } // namespace fbcpp::impl diff --git a/src/fb-cpp/Statement.cpp b/src/fb-cpp/Statement.cpp index a42a5b1..700e74b 100644 --- a/src/fb-cpp/Statement.cpp +++ b/src/fb-cpp/Statement.cpp @@ -33,7 +33,7 @@ using namespace fbcpp::impl; Statement::Statement( Attachment& attachment, Transaction& transaction, std::string_view sql, const StatementOptions& options) - : attachment{attachment}, + : attachment{&attachment}, status{attachment.getClient().newStatus()}, statusWrapper{attachment.getClient(), status.get()}, calendarConverter{attachment.getClient(), &statusWrapper}, diff --git a/src/fb-cpp/Statement.h b/src/fb-cpp/Statement.h index 3d1e30c..e6c37be 100644 --- a/src/fb-cpp/Statement.h +++ b/src/fb-cpp/Statement.h @@ -272,7 +272,36 @@ namespace fbcpp { } - Statement& operator=(Statement&&) = delete; + /// + /// @brief Transfers ownership of another prepared statement into this one. + /// + /// The old handles are released via `FbRef::operator=(FbRef&&)`. + /// After the assignment, `this` is valid (with `o`'s state) and `o` is invalid. + /// + Statement& operator=(Statement&& o) noexcept + { + if (this != &o) + { + attachment = o.attachment; + status = std::move(o.status); + statusWrapper = std::move(o.statusWrapper); + calendarConverter = std::move(o.calendarConverter); + numericConverter = std::move(o.numericConverter); + statementHandle = std::move(o.statementHandle); + resultSetHandle = std::move(o.resultSetHandle); + inMetadata = std::move(o.inMetadata); + inDescriptors = std::move(o.inDescriptors); + inMessage = std::move(o.inMessage); + outMetadata = std::move(o.outMetadata); + outDescriptors = std::move(o.outDescriptors); + outMessage = std::move(o.outMessage); + type = o.type; + cursorFlags = o.cursorFlags; + } + + return *this; + } + Statement(const Statement&) = delete; Statement& operator=(const Statement&) = delete; @@ -1090,7 +1119,7 @@ namespace fbcpp assert(isValid()); - auto& client = attachment.getClient(); + auto& client = attachment->getClient(); const auto value = optValue.value(); const auto& descriptor = getInDescriptor(index); const auto message = inMessage.data(); @@ -2759,7 +2788,7 @@ namespace fbcpp } private: - Attachment& attachment; + Attachment* attachment; FbUniquePtr status; impl::StatusWrapper statusWrapper; impl::CalendarConverter calendarConverter; diff --git a/src/test/Attachment.cpp b/src/test/Attachment.cpp index cec54a1..350c16f 100644 --- a/src/test/Attachment.cpp +++ b/src/test/Attachment.cpp @@ -71,6 +71,31 @@ BOOST_AUTO_TEST_CASE(isNotValidAfterMove) BOOST_CHECK_EQUAL(attachment1.isValid(), false); } +BOOST_AUTO_TEST_CASE(moveAssignmentTransfersOwnership) +{ + const auto database1 = getTempFile("Attachment-moveAssign-1.fdb"); + const auto database2 = getTempFile("Attachment-moveAssign-2.fdb"); + + Attachment attachment1{CLIENT, database1, AttachmentOptions().setCreateDatabase(true)}; + Attachment attachment2{CLIENT, database2, AttachmentOptions().setCreateDatabase(true)}; + BOOST_CHECK(attachment1.isValid()); + BOOST_CHECK(attachment2.isValid()); + + // Move-assign attachment2 into attachment1. + // attachment1's old connection is disconnected; attachment2 becomes invalid. + attachment1 = std::move(attachment2); + BOOST_CHECK(attachment1.isValid()); + BOOST_CHECK(!attachment2.isValid()); + + // The moved-to attachment can still operate on the database. + attachment1.dropDatabase(); + BOOST_CHECK(!attachment1.isValid()); + + // Clean up the first database (its connection was disconnected by the move). + Attachment cleanup{CLIENT, database1}; + cleanup.dropDatabase(); +} + BOOST_AUTO_TEST_CASE(isNotValidAfterDisconnect) { const auto database = getTempFile("Attachment-isNotValidAfterDisconnect.fdb"); diff --git a/src/test/Statement.cpp b/src/test/Statement.cpp index b802065..4eef52a 100644 --- a/src/test/Statement.cpp +++ b/src/test/Statement.cpp @@ -78,6 +78,54 @@ BOOST_AUTO_TEST_CASE(isNotValidAfterMove) BOOST_CHECK_EQUAL(stmt2.isValid(), true); } +BOOST_AUTO_TEST_CASE(moveAssignmentTransfersOwnership) +{ + const auto database = getTempFile("Statement-moveAssignmentTransfersOwnership.fdb"); + + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + Statement stmt1{attachment, transaction, "select 1 from rdb$database"}; + Statement stmt2{attachment, transaction, "select 2 from rdb$database"}; + BOOST_CHECK(stmt1.isValid()); + BOOST_CHECK(stmt2.isValid()); + + // Move-assign stmt2 into stmt1. + // stmt1's old handle is freed; stmt2 becomes invalid. + stmt1 = std::move(stmt2); + BOOST_CHECK(stmt1.isValid()); + BOOST_CHECK(!stmt2.isValid()); + + // The moved-to statement can still execute. + BOOST_CHECK(stmt1.execute(transaction)); + BOOST_CHECK_EQUAL(stmt1.getInt32(0).value(), 2); +} + +BOOST_AUTO_TEST_CASE(moveAssignmentToMovedFromStatement) +{ + const auto database = getTempFile("Statement-moveAssignmentToMovedFrom.fdb"); + + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + Statement stmt1{attachment, transaction, "select 1 from rdb$database"}; + Statement stmt2{attachment, transaction, "select 2 from rdb$database"}; + + // Move-construct stmt3 from stmt1 (leaves stmt1 invalid). + Statement stmt3{std::move(stmt1)}; + BOOST_CHECK(!stmt1.isValid()); + + // Move-assign into the moved-from stmt1. + stmt1 = std::move(stmt2); + BOOST_CHECK(stmt1.isValid()); + BOOST_CHECK(!stmt2.isValid()); + + BOOST_CHECK(stmt1.execute(transaction)); + BOOST_CHECK_EQUAL(stmt1.getInt32(0).value(), 2); +} + BOOST_AUTO_TEST_CASE(freeReleasesHandle) { const auto database = getTempFile("Statement-freeReleasesHandle.fdb");