diff --git a/src/fb-cpp/Attachment.cpp b/src/fb-cpp/Attachment.cpp index 3b0b8cd..02527e4 100644 --- a/src/fb-cpp/Attachment.cpp +++ b/src/fb-cpp/Attachment.cpp @@ -35,10 +35,9 @@ Attachment::Attachment(Client& client, const std::string& uri, const AttachmentO { const auto master = client.getMaster(); - const auto status = client.newStatus(); - StatusWrapper statusWrapper{client, status.get()}; + StatusWrapper statusWrapper{client}; - auto dpbBuilder = fbUnique(master->getUtilInterface()->getXpbBuilder(&statusWrapper, fb::IXpbBuilder::DPB, + auto dpbBuilder = fbUnique(client.getUtil()->getXpbBuilder(&statusWrapper, fb::IXpbBuilder::DPB, reinterpret_cast(options.getDpb().data()), static_cast(options.getDpb().size()))); @@ -74,8 +73,7 @@ void Attachment::disconnectOrDrop(bool drop) { assert(isValid()); - const auto status = client->newStatus(); - StatusWrapper statusWrapper{*client, status.get()}; + StatusWrapper statusWrapper{*client}; if (drop) handle->dropDatabase(&statusWrapper); diff --git a/src/fb-cpp/Batch.cpp b/src/fb-cpp/Batch.cpp index 0d10df4..eae405f 100644 --- a/src/fb-cpp/Batch.cpp +++ b/src/fb-cpp/Batch.cpp @@ -36,15 +36,13 @@ using namespace fbcpp::impl; BatchCompletionState::BatchCompletionState(Client& client, FbUniquePtr handle) noexcept : client{&client}, - status{client.newStatus()}, - statusWrapper{client, status.get()}, + statusWrapper{client}, handle{std::move(handle)} { } BatchCompletionState::BatchCompletionState(BatchCompletionState&& o) noexcept : client{o.client}, - status{std::move(o.status)}, statusWrapper{std::move(o.statusWrapper)}, handle{std::move(o.handle)} { @@ -102,8 +100,7 @@ Batch::Batch(Statement& statement, Transaction& transaction, const BatchOptions& : client{&statement.getAttachment().getClient()}, transaction{&transaction}, statement{&statement}, - status{client->newStatus()}, - statusWrapper{*client, status.get()} + statusWrapper{*client} { assert(statement.isValid()); assert(transaction.isValid()); @@ -118,8 +115,7 @@ Batch::Batch(Attachment& attachment, Transaction& transaction, std::string_view const BatchOptions& options) : client{&attachment.getClient()}, transaction{&transaction}, - status{client->newStatus()}, - statusWrapper{*client, status.get()} + statusWrapper{*client} { assert(attachment.isValid()); assert(transaction.isValid()); @@ -135,7 +131,6 @@ Batch::Batch(Batch&& o) noexcept : client{o.client}, transaction{o.transaction}, statement{o.statement}, - status{std::move(o.status)}, statusWrapper{std::move(o.statusWrapper)}, handle{std::move(o.handle)} { diff --git a/src/fb-cpp/Batch.h b/src/fb-cpp/Batch.h index 16ad119..792bc1d 100644 --- a/src/fb-cpp/Batch.h +++ b/src/fb-cpp/Batch.h @@ -250,7 +250,6 @@ namespace fbcpp private: Client* client; - FbUniquePtr status; impl::StatusWrapper statusWrapper; FbUniquePtr handle; }; @@ -447,7 +446,6 @@ namespace fbcpp Client* client; Transaction* transaction; Statement* statement = nullptr; - FbUniquePtr status; impl::StatusWrapper statusWrapper; FbRef handle; std::vector inputDescriptors; diff --git a/src/fb-cpp/Blob.cpp b/src/fb-cpp/Blob.cpp index 26954df..4ef7e5c 100644 --- a/src/fb-cpp/Blob.cpp +++ b/src/fb-cpp/Blob.cpp @@ -38,8 +38,7 @@ using namespace fbcpp::impl; Blob::Blob(Attachment& attachment, Transaction& transaction, const BlobOptions& options) : attachment{attachment}, transaction{transaction}, - status{attachment.getClient().newStatus()}, - statusWrapper{attachment.getClient(), status.get()} + statusWrapper{attachment.getClient()} { assert(attachment.isValid()); assert(transaction.isValid()); @@ -54,8 +53,7 @@ Blob::Blob(Attachment& attachment, Transaction& transaction, const BlobId& blobI : attachment{attachment}, transaction{transaction}, id{blobId}, - status{attachment.getClient().newStatus()}, - statusWrapper{attachment.getClient(), status.get()} + statusWrapper{attachment.getClient()} { assert(attachment.isValid()); assert(transaction.isValid()); diff --git a/src/fb-cpp/Blob.h b/src/fb-cpp/Blob.h index 0368358..3464819 100644 --- a/src/fb-cpp/Blob.h +++ b/src/fb-cpp/Blob.h @@ -284,7 +284,6 @@ namespace fbcpp : attachment{o.attachment}, transaction{o.transaction}, id{o.id}, - status{std::move(o.status)}, statusWrapper{std::move(o.statusWrapper)}, handle{std::move(o.handle)} { @@ -427,7 +426,6 @@ namespace fbcpp Attachment& attachment; Transaction& transaction; BlobId id; - FbUniquePtr status; impl::StatusWrapper statusWrapper; FbRef handle; }; diff --git a/src/fb-cpp/CalendarConverter.h b/src/fb-cpp/CalendarConverter.h index 32cb71f..d73df02 100644 --- a/src/fb-cpp/CalendarConverter.h +++ b/src/fb-cpp/CalendarConverter.h @@ -48,9 +48,8 @@ namespace fbcpp::impl class CalendarConverter final { public: - explicit CalendarConverter(Client& client, StatusWrapper* statusWrapper) - : client{&client}, - statusWrapper{statusWrapper} + explicit CalendarConverter(Client& client) + : client{&client} { } @@ -236,7 +235,7 @@ namespace fbcpp::impl subseconds); } - OpaqueTimeTz timeTzToOpaqueTimeTz(const TimeTz& timeTz) + OpaqueTimeTz timeTzToOpaqueTimeTz(StatusWrapper* statusWrapper, const TimeTz& timeTz) { const auto duration = timeTz.utcTime.to_duration(); @@ -259,7 +258,8 @@ namespace fbcpp::impl return opaque; } - TimeTz opaqueTimeTzToTimeTz(const OpaqueTimeTz& opaqueTime, std::string* decodedTimeZoneName = nullptr) + TimeTz opaqueTimeTzToTimeTz( + StatusWrapper* statusWrapper, const OpaqueTimeTz& opaqueTime, std::string* decodedTimeZoneName = nullptr) { const auto ticks = static_cast(opaqueTime.value.utc_time) * 100; @@ -282,12 +282,12 @@ namespace fbcpp::impl return timeTz; } - OpaqueTimeTz stringToOpaqueTimeTz(std::string_view value) + OpaqueTimeTz stringToOpaqueTimeTz(StatusWrapper* statusWrapper, std::string_view value) { - return timeTzToOpaqueTimeTz(stringToTimeTz(value)); + return timeTzToOpaqueTimeTz(statusWrapper, stringToTimeTz(statusWrapper, value)); } - std::string opaqueTimeTzToString(const OpaqueTimeTz& time) + std::string opaqueTimeTzToString(StatusWrapper* statusWrapper, const OpaqueTimeTz& time) { unsigned hours; unsigned minutes; @@ -301,7 +301,7 @@ namespace fbcpp::impl return std::format("{:02}:{:02}:{:02}.{:04} {}", hours, minutes, seconds, fractions, timeZoneBuffer.data()); } - TimeTz stringToTimeTz(std::string_view value) + TimeTz stringToTimeTz(StatusWrapper* statusWrapper, std::string_view value) { static const std::regex pattern( R"(^\s*([0-9]{2})\s*:\s*([0-9]{2})\s*:\s*([0-9]{2})(?:\s*\.\s*([0-9]{1,4}))?\s+([^\s]+)\s*$)"); @@ -363,7 +363,7 @@ namespace fbcpp::impl client->getUtil()->encodeTimeTz( statusWrapper, &encoded.value, hours, minutes, seconds, fractions, timeZoneString.c_str()); - return opaqueTimeTzToTimeTz(encoded); + return opaqueTimeTzToTimeTz(statusWrapper, encoded); } // FIXME: review @@ -509,7 +509,7 @@ namespace fbcpp::impl return std::format("{} {}", dateString, timeString); } - OpaqueTimestampTz timestampTzToOpaqueTimestampTz(const TimestampTz& timestampTz) + OpaqueTimestampTz timestampTzToOpaqueTimestampTz(StatusWrapper* statusWrapper, const TimestampTz& timestampTz) { OpaqueTimestampTz opaque; @@ -522,7 +522,7 @@ namespace fbcpp::impl return opaque; } - TimestampTz opaqueTimestampTzToTimestampTz( + TimestampTz opaqueTimestampTzToTimestampTz(StatusWrapper* statusWrapper, const OpaqueTimestampTz& opaqueTimestamp, std::string* decodedTimeZoneName = nullptr) { const auto ticks = @@ -553,12 +553,12 @@ namespace fbcpp::impl return timestampTz; } - OpaqueTimestampTz stringToOpaqueTimestampTz(std::string_view value) + OpaqueTimestampTz stringToOpaqueTimestampTz(StatusWrapper* statusWrapper, std::string_view value) { - return timestampTzToOpaqueTimestampTz(stringToTimestampTz(value)); + return timestampTzToOpaqueTimestampTz(statusWrapper, stringToTimestampTz(statusWrapper, value)); } - std::string opaqueTimestampTzToString(const OpaqueTimestampTz& timestamp) + std::string opaqueTimestampTzToString(StatusWrapper* statusWrapper, const OpaqueTimestampTz& timestamp) { unsigned year; unsigned month; @@ -576,7 +576,7 @@ namespace fbcpp::impl seconds, subseconds, timeZoneBuffer.data()); } - TimestampTz stringToTimestampTz(std::string_view value) + TimestampTz stringToTimestampTz(StatusWrapper* statusWrapper, std::string_view value) { static const std::regex pattern( R"(^\s*([0-9]{4})\s*-\s*([0-9]{2})\s*-\s*([0-9]{2})\s+([0-9]{2})\s*:\s*([0-9]{2})\s*:\s*([0-9]{2})(?:\s*\.\s*([0-9]{1,4}))?\s+([^\s]+)\s*$)"); @@ -660,7 +660,7 @@ namespace fbcpp::impl throwInvalidTimestampValue(); std::string resolvedTimeZoneName; - opaqueTimestampTzToTimestampTz(encoded, &resolvedTimeZoneName); + opaqueTimestampTzToTimestampTz(statusWrapper, encoded, &resolvedTimeZoneName); return TimestampTz{utcTimestamp, resolvedTimeZoneName}; } @@ -713,7 +713,6 @@ namespace fbcpp::impl std::chrono::year{1858} / std::chrono::November / 17, }; Client* client; - StatusWrapper* statusWrapper; }; } // namespace fbcpp::impl diff --git a/src/fb-cpp/Client.cpp b/src/fb-cpp/Client.cpp index 0d18d73..bed4830 100644 --- a/src/fb-cpp/Client.cpp +++ b/src/fb-cpp/Client.cpp @@ -35,8 +35,7 @@ void Client::shutdown() assert(isValid()); auto dispatcher = fbRef(master->getDispatcher()); - const auto status = newStatus(); - StatusWrapper statusWrapper{*this, status.get()}; + StatusWrapper statusWrapper{*this}; dispatcher->shutdown(&statusWrapper, 0, fb_shutrsn_app_stopped); diff --git a/src/fb-cpp/EventListener.cpp b/src/fb-cpp/EventListener.cpp index ffd40e1..13c1cb1 100644 --- a/src/fb-cpp/EventListener.cpp +++ b/src/fb-cpp/EventListener.cpp @@ -104,8 +104,7 @@ EventListener::EventListener(Attachment& attachment, const std::vectorqueEvents( &statusWrapper, &firebirdCallback, static_cast(eventBuffer.size()), eventBuffer.data())); @@ -249,8 +248,7 @@ void EventListener::handleEvent(unsigned length, const std::uint8_t* events) return; } - const auto status = client.newStatus(); - StatusWrapper statusWrapper{client, status.get()}; + StatusWrapper statusWrapper{client}; FbRef newHandle; try @@ -351,8 +349,7 @@ void EventListener::cancelEventsHandle() if (!handle) return; - const auto status = client.newStatus(); - StatusWrapper statusWrapper{client, status.get()}; + StatusWrapper statusWrapper{client}; handle->cancel(&statusWrapper); } diff --git a/src/fb-cpp/Exception.cpp b/src/fb-cpp/Exception.cpp index 02e128a..c9eb25e 100644 --- a/src/fb-cpp/Exception.cpp +++ b/src/fb-cpp/Exception.cpp @@ -32,6 +32,17 @@ using namespace fbcpp; using namespace fbcpp::impl; +fb::IStatus* StatusWrapper::getStatus() const +{ + if (!status) + { + status = client->newStatus().release(); + statusOwner = true; + } + + return status; +} + void StatusWrapper::checkException(StatusWrapper* status) { if (status->dirty && (status->getState() & fb::IStatus::STATE_ERRORS)) diff --git a/src/fb-cpp/Exception.h b/src/fb-cpp/Exception.h index 67acc5c..758ee05 100644 --- a/src/fb-cpp/Exception.h +++ b/src/fb-cpp/Exception.h @@ -45,12 +45,52 @@ namespace fbcpp::impl class StatusWrapper : public fb::IStatusImpl { public: - explicit StatusWrapper(Client& client, IStatus* status) + explicit StatusWrapper(Client& client, IStatus* status = nullptr) : client{&client}, status{status} { } + StatusWrapper(StatusWrapper&& o) noexcept + : client{o.client}, + status{o.status}, + statusOwner{o.statusOwner}, + dirty{o.dirty} + { + o.status = nullptr; + o.statusOwner = false; + o.dirty = false; + } + + StatusWrapper& operator=(StatusWrapper&& o) noexcept + { + if (this != &o) + { + if (statusOwner && status) + status->dispose(); + + client = o.client; + status = o.status; + statusOwner = o.statusOwner; + dirty = o.dirty; + + o.status = nullptr; + o.statusOwner = false; + o.dirty = false; + } + + return *this; + } + + StatusWrapper(const StatusWrapper&) = delete; + StatusWrapper& operator=(const StatusWrapper&) = delete; + + ~StatusWrapper() + { + if (statusOwner && status) + status->dispose(); + } + public: static void checkException(StatusWrapper* status); @@ -66,7 +106,7 @@ namespace fbcpp::impl if (dirty) { dirty = false; - status->init(); + getStatus()->init(); } } @@ -105,8 +145,11 @@ namespace fbcpp::impl void dispose() noexcept override { // Disposes only the delegated status. Let the user destroy this instance. - status->dispose(); + if (status) + status->dispose(); + status = nullptr; + statusOwner = false; } void init() noexcept override @@ -116,53 +159,56 @@ namespace fbcpp::impl unsigned getState() const noexcept override { - return dirty ? status->getState() : 0; + return dirty ? getStatus()->getState() : 0; } void setErrors2(unsigned length, const intptr_t* value) noexcept override { dirty = true; - status->setErrors2(length, value); + getStatus()->setErrors2(length, value); } void setWarnings2(unsigned length, const intptr_t* value) noexcept override { dirty = true; - status->setWarnings2(length, value); + getStatus()->setWarnings2(length, value); } void setErrors(const intptr_t* value) noexcept override { dirty = true; - status->setErrors(value); + getStatus()->setErrors(value); } void setWarnings(const intptr_t* value) noexcept override { dirty = true; - status->setWarnings(value); + getStatus()->setWarnings(value); } const intptr_t* getErrors() const noexcept override { - return dirty ? status->getErrors() : cleanStatus(); + return dirty ? getStatus()->getErrors() : cleanStatus(); } const intptr_t* getWarnings() const noexcept override { - return dirty ? status->getWarnings() : cleanStatus(); + return dirty ? getStatus()->getWarnings() : cleanStatus(); } IStatus* clone() const noexcept override { - return status->clone(); + return getStatus()->clone(); } protected: Client* client; - IStatus* status; + mutable IStatus* status; + mutable bool statusOwner = false; bool dirty = false; + IStatus* getStatus() const; + static const intptr_t* cleanStatus() noexcept { static intptr_t clean[3] = {1, 0, 0}; diff --git a/src/fb-cpp/NumericConverter.h b/src/fb-cpp/NumericConverter.h index 5cad647..ca0a627 100644 --- a/src/fb-cpp/NumericConverter.h +++ b/src/fb-cpp/NumericConverter.h @@ -147,9 +147,8 @@ namespace fbcpp::impl class NumericConverter final { public: - explicit NumericConverter(Client& client, StatusWrapper* statusWrapper) - : client{&client}, - statusWrapper{statusWrapper} + explicit NumericConverter(Client& client) + : client{&client} { } @@ -354,15 +353,15 @@ namespace fbcpp::impl return from.str(); } - std::string opaqueInt128ToString(const OpaqueInt128& opaqueInt128, int scale) + std::string opaqueInt128ToString(StatusWrapper* statusWrapper, const OpaqueInt128& opaqueInt128, int scale) { - const auto int128Util = client->getUtil()->getInt128(statusWrapper); + const auto int128Util = client->getInt128Util(statusWrapper); char buffer[fb::IInt128::STRING_SIZE + 1]; int128Util->toString(statusWrapper, &opaqueInt128, scale, static_cast(sizeof(buffer)), buffer); return buffer; } - std::string opaqueDecFloat16ToString(const OpaqueDecFloat16& opaqueDecFloat16) + std::string opaqueDecFloat16ToString(StatusWrapper* statusWrapper, const OpaqueDecFloat16& opaqueDecFloat16) { const auto decFloat16Util = client->getDecFloat16Util(statusWrapper); char buffer[fb::IDecFloat16::STRING_SIZE + 1]; @@ -370,7 +369,7 @@ namespace fbcpp::impl return buffer; } - std::string opaqueDecFloat34ToString(const OpaqueDecFloat34& opaqueDecFloat34) + std::string opaqueDecFloat34ToString(StatusWrapper* statusWrapper, const OpaqueDecFloat34& opaqueDecFloat34) { const auto decFloat34Util = client->getDecFloat34Util(statusWrapper); char buffer[fb::IDecFloat34::STRING_SIZE + 1]; @@ -399,7 +398,8 @@ namespace fbcpp::impl return boostInt128; } - OpaqueDecFloat16 boostDecFloat16ToOpaqueDecFloat16(const BoostDecFloat16& boostDecFloat16) + OpaqueDecFloat16 boostDecFloat16ToOpaqueDecFloat16( + StatusWrapper* statusWrapper, const BoostDecFloat16& boostDecFloat16) { const auto decFloat16Util = client->getDecFloat16Util(statusWrapper); OpaqueDecFloat16 opaqueDecFloat16; @@ -407,12 +407,14 @@ namespace fbcpp::impl return opaqueDecFloat16; } - BoostDecFloat16 opaqueDecFloat16ToBoostDecFloat16(const OpaqueDecFloat16& opaqueDecFloat16) + BoostDecFloat16 opaqueDecFloat16ToBoostDecFloat16( + StatusWrapper* statusWrapper, const OpaqueDecFloat16& opaqueDecFloat16) { - return BoostDecFloat16{opaqueDecFloat16ToString(opaqueDecFloat16)}; + return BoostDecFloat16{opaqueDecFloat16ToString(statusWrapper, opaqueDecFloat16)}; } - OpaqueDecFloat34 boostDecFloat34ToOpaqueDecFloat34(const BoostDecFloat34& boostDecFloat34) + OpaqueDecFloat34 boostDecFloat34ToOpaqueDecFloat34( + StatusWrapper* statusWrapper, const BoostDecFloat34& boostDecFloat34) { const auto decFloat34Util = client->getDecFloat34Util(statusWrapper); OpaqueDecFloat34 opaqueDecFloat34; @@ -420,9 +422,10 @@ namespace fbcpp::impl return opaqueDecFloat34; } - BoostDecFloat34 opaqueDecFloat34ToBoostDecFloat34(const OpaqueDecFloat34& opaqueDecFloat34) + BoostDecFloat34 opaqueDecFloat34ToBoostDecFloat34( + StatusWrapper* statusWrapper, const OpaqueDecFloat34& opaqueDecFloat34) { - return BoostDecFloat34{opaqueDecFloat34ToString(opaqueDecFloat34)}; + return BoostDecFloat34{opaqueDecFloat34ToString(statusWrapper, opaqueDecFloat34)}; } #endif @@ -574,7 +577,6 @@ namespace fbcpp::impl private: Client* client; - StatusWrapper* statusWrapper; }; } // namespace fbcpp::impl diff --git a/src/fb-cpp/Row.h b/src/fb-cpp/Row.h new file mode 100644 index 0000000..adb5d92 --- /dev/null +++ b/src/fb-cpp/Row.h @@ -0,0 +1,1213 @@ +/* + * MIT License + * + * Copyright (c) 2026 F.D.Castel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef FBCPP_ROW_H +#define FBCPP_ROW_H + +#include "config.h" +#include "fb-api.h" +#include "types.h" +#include "Blob.h" +#include "NumericConverter.h" +#include "CalendarConverter.h" +#include "Descriptor.h" +#include "Exception.h" +#include "StructBinding.h" +#include "VariantTypeTraits.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if FB_CPP_USE_BOOST_MULTIPRECISION != 0 +#include +#include +#endif + + +/// +/// fb-cpp namespace. +/// +namespace fbcpp +{ + /// + /// @brief A lightweight, non-owning view of a single row's data with typed accessors. + /// + /// Row provides typed access to column values in a message buffer, using + /// descriptors to interpret the raw bytes. It is produced by RowSet for any + /// fetched row. + /// + class Row final + { + public: + /// + /// @brief Constructs a Row view over the given message buffer. + /// + /// @param client Client used to build conversion helpers. + /// @param descriptors Column descriptors. + /// @param message Span over the raw row data. + /// + Row(Client& client, const std::vector& descriptors, std::span message) + : client{&client}, + descriptors{&descriptors}, + message{message}, + statusWrapper{client}, + numericConverter{client}, + calendarConverter{client} + { + } + + public: + /// + /// @name Result reading + /// @{ + + /// + /// @brief Reports whether the row has a null at the given column. + /// + bool isNull(unsigned index) + { + const auto& descriptor = getDescriptor(index); + return *reinterpret_cast(&message[descriptor.nullOffset]) != FB_FALSE; + } + + /// + /// @brief Reads a boolean column. + /// + std::optional getBool(unsigned index) + { + const auto& descriptor = getDescriptor(index); + + if (*reinterpret_cast(&message[descriptor.nullOffset]) != FB_FALSE) + return std::nullopt; + + switch (descriptor.adjustedType) + { + case DescriptorAdjustedType::BOOLEAN: + return message[descriptor.offset] != std::byte{0}; + + default: + throwInvalidType("bool", descriptor.adjustedType); + } + } + + /// + /// @brief Reads a 16-bit signed integer column. + /// + std::optional getInt16(unsigned index) + { + std::optional scale{0}; + return getNumber(index, scale, "std::int16_t"); + } + + /// + /// @brief Reads a scaled 16-bit signed integer column. + /// + std::optional getScaledInt16(unsigned index) + { + std::optional scale; + const auto value = getNumber(index, scale, "ScaledInt16"); + return value.has_value() ? std::optional{ScaledInt16{value.value(), scale.value()}} : std::nullopt; + } + + /// + /// @brief Reads a 32-bit signed integer column. + /// + std::optional getInt32(unsigned index) + { + std::optional scale{0}; + return getNumber(index, scale, "std::int32_t"); + } + + /// + /// @brief Reads a scaled 32-bit signed integer column. + /// + std::optional getScaledInt32(unsigned index) + { + std::optional scale; + const auto value = getNumber(index, scale, "ScaledInt32"); + return value.has_value() ? std::optional{ScaledInt32{value.value(), scale.value()}} : std::nullopt; + } + + /// + /// @brief Reads a 64-bit signed integer column. + /// + std::optional getInt64(unsigned index) + { + std::optional scale{0}; + return getNumber(index, scale, "std::int64_t"); + } + + /// + /// @brief Reads a scaled 64-bit signed integer column. + /// + std::optional getScaledInt64(unsigned index) + { + std::optional scale; + const auto value = getNumber(index, scale, "ScaledInt64"); + return value.has_value() ? std::optional{ScaledInt64{value.value(), scale.value()}} : std::nullopt; + } + + /// + /// @brief Reads a Firebird scaled 128-bit integer column. + /// + std::optional getScaledOpaqueInt128(unsigned index) + { + const auto& descriptor = getDescriptor(index); + + if (*reinterpret_cast(&message[descriptor.nullOffset]) != FB_FALSE) + return std::nullopt; + + switch (descriptor.adjustedType) + { + case DescriptorAdjustedType::INT128: + return ScaledOpaqueInt128{ + OpaqueInt128{*reinterpret_cast(&message[descriptor.offset])}, + descriptor.scale}; + + default: + throwInvalidType("ScaledOpaqueInt128", descriptor.adjustedType); + } + } + +#if FB_CPP_USE_BOOST_MULTIPRECISION != 0 + /// + /// @brief Reads a Boost 128-bit integer column. + /// + std::optional getBoostInt128(unsigned index) + { + std::optional scale{0}; + const auto value = getNumber(index, scale, "BoostInt128"); + return value.has_value() ? std::optional{value.value()} : std::nullopt; + } + + /// + /// @brief Reads a scaled Boost 128-bit integer column. + /// + std::optional getScaledBoostInt128(unsigned index) + { + std::optional scale; + const auto value = getNumber(index, scale, "ScaledBoostInt128"); + return value.has_value() ? std::optional{ScaledBoostInt128{value.value(), scale.value()}} : std::nullopt; + } +#endif + + /// + /// @brief Reads a single precision floating-point column. + /// + std::optional getFloat(unsigned index) + { + std::optional scale{0}; + return getNumber(index, scale, "float"); + } + + /// + /// @brief Reads a double precision floating-point column. + /// + std::optional getDouble(unsigned index) + { + std::optional scale{0}; + return getNumber(index, scale, "double"); + } + + /// + /// @brief Reads a Firebird 16-digit decimal floating-point column. + /// + std::optional getOpaqueDecFloat16(unsigned index) + { + const auto& descriptor = getDescriptor(index); + + if (*reinterpret_cast(&message[descriptor.nullOffset]) != FB_FALSE) + return std::nullopt; + + switch (descriptor.adjustedType) + { + case DescriptorAdjustedType::DECFLOAT16: + return OpaqueDecFloat16{*reinterpret_cast(&message[descriptor.offset])}; + + default: + throwInvalidType("OpaqueDecFloat16", descriptor.adjustedType); + } + } + +#if FB_CPP_USE_BOOST_MULTIPRECISION != 0 + /// + /// @brief Reads a Boost-based 16-digit decimal floating-point column. + /// + std::optional getBoostDecFloat16(unsigned index) + { + std::optional scale{0}; + return getNumber(index, scale, "BoostDecFloat16"); + } +#endif + + /// + /// @brief Reads a Firebird 34-digit decimal floating-point column. + /// + std::optional getOpaqueDecFloat34(unsigned index) + { + const auto& descriptor = getDescriptor(index); + + if (*reinterpret_cast(&message[descriptor.nullOffset]) != FB_FALSE) + return std::nullopt; + + switch (descriptor.adjustedType) + { + case DescriptorAdjustedType::DECFLOAT34: + return OpaqueDecFloat34{*reinterpret_cast(&message[descriptor.offset])}; + + default: + throwInvalidType("OpaqueDecFloat34", descriptor.adjustedType); + } + } + +#if FB_CPP_USE_BOOST_MULTIPRECISION != 0 + /// + /// @brief Reads a Boost-based 34-digit decimal floating-point column. + /// + std::optional getBoostDecFloat34(unsigned index) + { + std::optional scale{0}; + return getNumber(index, scale, "BoostDecFloat34"); + } +#endif + + /// + /// @brief Reads a date column. + /// + std::optional getDate(unsigned index) + { + const auto& descriptor = getDescriptor(index); + + if (*reinterpret_cast(&message[descriptor.nullOffset]) != FB_FALSE) + return std::nullopt; + + switch (descriptor.adjustedType) + { + case DescriptorAdjustedType::DATE: + return calendarConverter.opaqueDateToDate( + *reinterpret_cast(&message[descriptor.offset])); + + default: + throwInvalidType("Date", descriptor.adjustedType); + } + } + + /// + /// @brief Reads a raw date column in Firebird's representation. + /// + std::optional getOpaqueDate(unsigned index) + { + const auto& descriptor = getDescriptor(index); + + if (*reinterpret_cast(&message[descriptor.nullOffset]) != FB_FALSE) + return std::nullopt; + + switch (descriptor.adjustedType) + { + case DescriptorAdjustedType::DATE: + return OpaqueDate{*reinterpret_cast(&message[descriptor.offset])}; + + default: + throwInvalidType("OpaqueDate", descriptor.adjustedType); + } + } + + /// + /// @brief Reads a time-of-day column without timezone. + /// + std::optional